Compare commits
249 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
465d0d46a3 | ||
|
|
6e858233dc | ||
|
|
522bc4374a | ||
|
|
c2b5eea3fb | ||
|
|
824017fa1f | ||
|
|
545f153bba | ||
|
|
008b50ea67 | ||
|
|
b836d83bc2 | ||
|
|
6fbcfc794a | ||
|
|
aa90c536b3 | ||
|
|
9c32e6e6dc | ||
|
|
7b55f92e11 | ||
|
|
daa4a88447 | ||
|
|
776a251d98 | ||
|
|
613810e43c | ||
|
|
df8997129e | ||
|
|
e2586130e1 | ||
|
|
ea455477a0 | ||
|
|
5b914e8877 | ||
|
|
918d2f0bbd | ||
|
|
91d1d8b0e1 | ||
|
|
3ffe4869f3 | ||
|
|
bde259e38c | ||
|
|
75dd8dc561 | ||
|
|
a6aed1772d | ||
|
|
63a1039fb2 | ||
|
|
9d3a6c51b1 | ||
|
|
8bcdf13d7f | ||
|
|
f9fb0875e0 | ||
|
|
d36d835d08 | ||
|
|
20931e2ce7 | ||
|
|
fb84ae9b25 | ||
|
|
e64e597aa6 | ||
|
|
f9aac4fc9f | ||
|
|
0f276e483b | ||
|
|
888a58fd2a | ||
|
|
89cfc55870 | ||
|
|
4c19084cb1 | ||
|
|
bc572e70dc | ||
|
|
544db1951f | ||
|
|
1b14be868e | ||
|
|
e8236aaf65 | ||
|
|
0260775f30 | ||
|
|
3b79b58f68 | ||
|
|
d9c558107e | ||
|
|
0be6832e6b | ||
|
|
5586bbdff3 | ||
|
|
cbc578998a | ||
|
|
94af770a6d | ||
|
|
d2c22f0353 | ||
|
|
c5a5b7dad7 | ||
|
|
e6ca4e54a7 | ||
|
|
e0d1e5facf | ||
|
|
35c4e93a09 | ||
|
|
567b74ad0a | ||
|
|
f306a61ece | ||
|
|
0aaaf13278 | ||
|
|
e17dbab7c6 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
.eggs
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
@@ -42,6 +43,7 @@ output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
doc/source/reference/api
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
@@ -54,3 +56,6 @@ ChangeLog
|
||||
|
||||
sftp-config.json
|
||||
/.idea/
|
||||
|
||||
# Desktop Service Store
|
||||
*.DS_Store
|
||||
|
||||
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=${OS_TEST_PATH:-./watcherclient/tests/functional}
|
||||
top_dir=./
|
||||
@@ -1,7 +1,7 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-10} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
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
|
||||
test_list_option=--list
|
||||
|
||||
9
.zuul.yaml
Normal file
9
.zuul.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
- project:
|
||||
check:
|
||||
jobs:
|
||||
- watcherclient-tempest-functional
|
||||
- openstack-tox-lower-constraints
|
||||
gate:
|
||||
jobs:
|
||||
- watcherclient-tempest-functional
|
||||
- openstack-tox-lower-constraints
|
||||
@@ -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/
|
||||
|
||||
176
LICENSE
Normal file
176
LICENSE
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
||||
67
README.rst
67
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://git.openstack.org/cgit/openstack/python-watcher
|
||||
* Bugs: https://bugs.launchpad.net/watcher
|
||||
|
||||
Installation
|
||||
============
|
||||
@@ -49,13 +58,21 @@ You can install the Watcher CLI with the following command:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install python-watcherclient
|
||||
sudo pip install python-watcherclient
|
||||
|
||||
|
||||
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:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo pip install python-openstackclient
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Create a **creds** file containing your Openstack credentials:
|
||||
Create a **creds** file containing your OpenStack credentials:
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -73,31 +90,48 @@ Source these credentials into your current shell session:
|
||||
|
||||
# source creds
|
||||
|
||||
You should be able to launch the following command which gets the list of previously created Audit Templates:
|
||||
You should be able to launch the following command which gets the list of
|
||||
previously created Audit Templates:
|
||||
|
||||
.. code::
|
||||
|
||||
# watcher audit-template-list
|
||||
+------+------+
|
||||
| UUID | Name |
|
||||
+------+------+
|
||||
+------+------+
|
||||
# watcher audittemplate list
|
||||
|
||||
You can view the entire list of available Watcher commands and options using this command:
|
||||
or::
|
||||
|
||||
# openstack optimize audittemplate list
|
||||
+--------------------------------+------+----------------------+----------+
|
||||
| UUID | Name | Goal | Strategy |
|
||||
+--------------------------------+------+----------------------+----------+
|
||||
+--------------------------------+------+----------------------+----------+
|
||||
|
||||
|
||||
You can view the entire list of available Watcher commands and options using
|
||||
this command:
|
||||
|
||||
.. code::
|
||||
|
||||
# watcher help
|
||||
|
||||
or::
|
||||
|
||||
# openstack help optimize
|
||||
|
||||
|
||||
Troubleshootings
|
||||
================
|
||||
|
||||
If any watcher command fails, you can obtain more details with the **--debug** option :
|
||||
If any watcher command fails, you can obtain more details with the **--debug**
|
||||
option :
|
||||
|
||||
.. code::
|
||||
|
||||
# watcher --debug audit-template-list
|
||||
# watcher --debug audittemplate list
|
||||
|
||||
or::
|
||||
|
||||
# openstack --debug optimize audittemplate list
|
||||
|
||||
|
||||
Install the openstack CLI :
|
||||
|
||||
@@ -105,7 +139,8 @@ Install the openstack CLI :
|
||||
|
||||
# pip install python-openstackclient
|
||||
|
||||
Make sure that your Openstack credentials are correct. If so, you should be able to verify that the watcher user has been declared in your Openstack keystone :
|
||||
Make sure that your Openstack credentials are correct. If so, you should be able
|
||||
to verify that the watcher user has been declared in your Openstack keystone :
|
||||
|
||||
.. code::
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
watcherclient.client.rst
|
||||
watcherclient.common.apiclient.base.rst
|
||||
watcherclient.common.apiclient.exceptions.rst
|
||||
watcherclient.common.base.rst
|
||||
watcherclient.common.cliutils.rst
|
||||
watcherclient.common.http.rst
|
||||
watcherclient.common.i18n.rst
|
||||
watcherclient.common.utils.rst
|
||||
watcherclient.exceptions.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_shell.rst
|
||||
watcherclient.tests.test_utils.rst
|
||||
watcherclient.tests.utils.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_metric_collector_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.metric_collector_shell.rst
|
||||
watcherclient.v1.resource_fields.rst
|
||||
watcherclient.v1.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:
|
||||
1012
doc/source/cli/details.rst
Normal file
1012
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/v2.0
|
||||
|
||||
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
|
||||
81
doc/source/cli/openstack_cli.rst
Normal file
81
doc/source/cli/openstack_cli.rst
Normal file
@@ -0,0 +1,81 @@
|
||||
=====================================================================
|
||||
:program:`openstack` Command-Line Interface (CLI) with Watcher plugin
|
||||
=====================================================================
|
||||
|
||||
.. program:: openstack
|
||||
.. highlight:: bash
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
:program:`openstack` [options] :program:`optimize` <command> [command-options]
|
||||
|
||||
:program:`openstack help optimize`
|
||||
|
||||
:program:`openstack help optimize` <command>
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The :program:`openstack` command-line interface (CLI) can interact with the
|
||||
OpenStack infra-optim Service (Watcher), by using our additional plugin
|
||||
(included into the python-watcherclient package).
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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::
|
||||
|
||||
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
|
||||
environment variable. (It defaults to the first in the list returned.)
|
||||
::
|
||||
|
||||
$ export OS_REGION_NAME=region
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
To get a list of available (sub)commands and options, run::
|
||||
|
||||
$ openstack help optimize
|
||||
|
||||
To get usage and options of a command, run::
|
||||
|
||||
$ openstack help optimize <command>
|
||||
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
|
||||
Get information about the audit-create command::
|
||||
|
||||
$ openstack help optimize audit create
|
||||
|
||||
|
||||
Get a list of available goal::
|
||||
|
||||
$ openstack optimize goal list
|
||||
|
||||
|
||||
Get a list of audits::
|
||||
|
||||
$ openstack optimize audit list
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
==============================================
|
||||
===============================================
|
||||
:program:`watcher` Command-Line Interface (CLI)
|
||||
==============================================
|
||||
===============================================
|
||||
|
||||
.. program:: watcher
|
||||
.. highlight:: bash
|
||||
@@ -19,35 +19,35 @@ DESCRIPTION
|
||||
===========
|
||||
|
||||
The :program:`watcher` command-line interface (CLI) interacts with the
|
||||
OpenStack TODEFINE Service (Watcher).
|
||||
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_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
|
||||
|
||||
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
|
||||
$ 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.)
|
||||
::
|
||||
|
||||
export OS_REGION_NAME=region
|
||||
$ export OS_REGION_NAME=region
|
||||
|
||||
Watcher CLI supports bash completion. The command-line tool can automatically
|
||||
fill partially typed commands. To use this feature, source the below file
|
||||
@@ -55,7 +55,7 @@ fill partially typed commands. To use this feature, source the below file
|
||||
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/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
|
||||
@@ -66,11 +66,11 @@ OPTIONS
|
||||
|
||||
To get a list of available (sub)commands and options, run::
|
||||
|
||||
watcher help
|
||||
$ watcher help
|
||||
|
||||
To get usage and options of a command, run::
|
||||
|
||||
watcher help <command>
|
||||
$ watcher help <command>
|
||||
|
||||
|
||||
EXAMPLES
|
||||
@@ -78,12 +78,12 @@ EXAMPLES
|
||||
|
||||
Get information about the audit-create command::
|
||||
|
||||
watcher help audit-create
|
||||
$ watcher help audit create
|
||||
|
||||
Get a list of available goal::
|
||||
|
||||
watcher goal-list
|
||||
$ watcher goal list
|
||||
|
||||
Get a list of audits::
|
||||
|
||||
watcher audit-list
|
||||
$ watcher audit list
|
||||
@@ -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,6 +11,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from watcherclient import version as watcherclient_version
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
@@ -20,9 +20,8 @@ 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',
|
||||
]
|
||||
|
||||
'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
|
||||
@@ -70,7 +69,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 +87,13 @@ latex_documents = [
|
||||
u'OpenStack Foundation', 'manual'
|
||||
),
|
||||
]
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/python-watcherclient'
|
||||
bug_project = 'python-watcherclient'
|
||||
bug_tag = ''
|
||||
|
||||
# Must set this variable to include year, month, day, hours, and minutes.
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
#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,7 +41,7 @@ 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
|
||||
|
||||
@@ -1,50 +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
|
||||
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
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _Watcher: https://wiki.openstack.org/wiki/Watcher
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
Or, if you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
|
||||
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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.. include:: ../../README.rst
|
||||
8
doc/source/reference/api/index.rst
Normal file
8
doc/source/reference/api/index.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
======================
|
||||
Python API Reference
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
autoindex
|
||||
@@ -68,7 +68,7 @@ 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
|
||||
@@ -80,12 +80,6 @@ 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
|
||||
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/index
|
||||
api_v1
|
||||
92
lower-constraints.txt
Normal file
92
lower-constraints.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
alabaster==0.7.10
|
||||
appdirs==1.4.3
|
||||
asn1crypto==0.23.0
|
||||
Babel==2.5.3
|
||||
certifi==2018.1.18
|
||||
cffi==1.7.0
|
||||
chardet==3.0.4
|
||||
cliff==2.11.0
|
||||
cmd2==0.8.2
|
||||
coverage==4.0
|
||||
cryptography==2.1
|
||||
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
|
||||
future==0.16.0
|
||||
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
|
||||
mock==2.0.0
|
||||
monotonic==1.4
|
||||
mox3==0.20.0
|
||||
msgpack-python==0.4.0
|
||||
munch==2.2.0
|
||||
netaddr==0.7.19
|
||||
netifaces==0.10.6
|
||||
openstackdocstheme==1.18.1
|
||||
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
|
||||
prettytable==0.7.2
|
||||
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
|
||||
-e git://git.openstack.org/openstack/python-watcherclient@104894958882a4877dad6f469361d2adb41d0b59#egg=python_watcherclient
|
||||
pytz==2018.3
|
||||
PyYAML==3.12
|
||||
requests==2.18.4
|
||||
requestsexceptions==1.4.0
|
||||
rfc3986==0.3.1
|
||||
simplejson==3.13.2
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
Sphinx==1.6.5
|
||||
sphinxcontrib-websupport==1.0.1
|
||||
stestr==1.0.0
|
||||
stevedore==1.28.0
|
||||
tempest==17.1.0
|
||||
testrepository==0.0.18
|
||||
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
|
||||
@@ -2,9 +2,13 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
Babel>=1.3 # BSD
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
pbr>=1.6 # Apache-2.0
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
Babel!=2.4.0,>=2.3.4 # BSD
|
||||
cliff!=2.9.0,>=2.8.0 # Apache-2.0
|
||||
osc-lib>=1.8.0 # Apache-2.0
|
||||
oslo.i18n>=3.15.3 # Apache-2.0
|
||||
oslo.utils>=3.33.0 # Apache-2.0
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7.1 # BSD
|
||||
keystoneauth1>=3.4.0 # Apache-2.0
|
||||
six>=1.10.0 # MIT
|
||||
PyYAML>=3.12 # MIT
|
||||
|
||||
90
setup.cfg
90
setup.cfg
@@ -5,7 +5,7 @@ description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
home-page = https://docs.openstack.org/python-watcherclient/latest/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
@@ -16,7 +16,7 @@ classifier =
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
Programming Language :: Python :: 3.5
|
||||
|
||||
[files]
|
||||
packages =
|
||||
@@ -25,14 +25,98 @@ packages =
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
watcher = watcherclient.shell:main
|
||||
tempest.test_plugins =
|
||||
watcherclient_tests = watcherclient.plugin:WatcherClientTempestPlugin
|
||||
|
||||
openstack.cli.extension =
|
||||
infra_optim = watcherclient.osc.plugin
|
||||
|
||||
# Entry points for the 'openstack' command
|
||||
openstack.infra_optim.v1 =
|
||||
optimize_goal_show = watcherclient.v1.goal_shell:ShowGoal
|
||||
optimize_goal_list = watcherclient.v1.goal_shell:ListGoal
|
||||
|
||||
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
|
||||
optimize_audittemplate_create = watcherclient.v1.audit_template_shell:CreateAuditTemplate
|
||||
optimize_audittemplate_update = watcherclient.v1.audit_template_shell:UpdateAuditTemplate
|
||||
optimize_audittemplate_delete = watcherclient.v1.audit_template_shell:DeleteAuditTemplate
|
||||
|
||||
optimize_audit_show = watcherclient.v1.audit_shell:ShowAudit
|
||||
optimize_audit_list = watcherclient.v1.audit_shell:ListAudit
|
||||
optimize_audit_create = watcherclient.v1.audit_shell:CreateAudit
|
||||
optimize_audit_update = watcherclient.v1.audit_shell:UpdateAudit
|
||||
optimize_audit_delete = watcherclient.v1.audit_shell:DeleteAudit
|
||||
|
||||
optimize_actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# The same as above but used by the 'watcher' command
|
||||
watcherclient.v1 =
|
||||
goal_show = watcherclient.v1.goal_shell:ShowGoal
|
||||
goal_list = watcherclient.v1.goal_shell:ListGoal
|
||||
|
||||
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
|
||||
audittemplate_create = watcherclient.v1.audit_template_shell:CreateAuditTemplate
|
||||
audittemplate_update = watcherclient.v1.audit_template_shell:UpdateAuditTemplate
|
||||
audittemplate_delete = watcherclient.v1.audit_template_shell:DeleteAuditTemplate
|
||||
|
||||
audit_show = watcherclient.v1.audit_shell:ShowAudit
|
||||
audit_list = watcherclient.v1.audit_shell:ListAudit
|
||||
audit_create = watcherclient.v1.audit_shell:CreateAudit
|
||||
audit_update = watcherclient.v1.audit_shell:UpdateAudit
|
||||
audit_delete = watcherclient.v1.audit_shell:DeleteAudit
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
autodoc_exclude_modules =
|
||||
watcherclient.tests.*
|
||||
api_doc_dir = reference/api
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
warning-is-error = 1
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
@@ -49,4 +133,4 @@ 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
|
||||
output_file = watcherclient/locale/watcherclient.pot
|
||||
|
||||
2
setup.py
2
setup.py
@@ -25,5 +25,5 @@ except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=1.8'],
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
# 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
|
||||
discover # BSD
|
||||
hacking<0.11,>=0.10.2
|
||||
mock>=1.2 # 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.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
mock>=2.0.0 # BSD
|
||||
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
python-subunit>=1.0.0 # Apache-2.0/BSD
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
testtools>=2.2.0 # MIT
|
||||
tempest>=17.1.0 # Apache-2.0
|
||||
|
||||
# Needed for pypi packaging
|
||||
wheel # MIT
|
||||
wheel>=0.24.0 # MIT
|
||||
|
||||
45
tox.ini
45
tox.ini
@@ -1,16 +1,19 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py34,py27,pep8
|
||||
minversion = 1.8
|
||||
envlist = py35,py27,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
install_command =
|
||||
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
OS_TEST_PATH=./watcherclient/tests/unit
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = rm -f .testrepository/times.dbm
|
||||
python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
@@ -19,17 +22,35 @@ commands = flake8
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
commands =
|
||||
python setup.py testr --coverage --testr-args='{posargs}'
|
||||
coverage report
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
|
||||
|
||||
[testenv:functional]
|
||||
basepython = python2.7
|
||||
passenv =
|
||||
OS_PROJECT_DOMAIN_NAME
|
||||
OS_USER_DOMAIN_NAME
|
||||
OS_PROJECT_NAME
|
||||
OS_USERNAME
|
||||
OS_PASSWORD
|
||||
OS_AUTH_URL
|
||||
OS_IDENTITY_API_VERSION
|
||||
OS_IMAGE_API_VERSION
|
||||
setenv =
|
||||
OS_TEST_PATH = ./watcherclient/tests/functional
|
||||
commands = python setup.py testr --slowest --testr-args='--concurrency=1 {posargs}'
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
show-source = True
|
||||
enable-extensions = H203,H106
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
||||
@@ -39,3 +60,11 @@ 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.
|
||||
|
||||
@@ -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,176 @@
|
||||
# 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_watcher_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_watcher_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_watcher_api_version': os_watcher_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)
|
||||
return client_class(*args, **kwargs)
|
||||
|
||||
215
watcherclient/common/api_versioning.py
Normal file
215
watcherclient/common/api_versioning.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#
|
||||
# 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())
|
||||
|
||||
|
||||
HEADER_NAME = "OpenStack-API-Version"
|
||||
SERVICE_TYPE = "infra-optim"
|
||||
# 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'")
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -169,15 +169,18 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
data = '-'
|
||||
if field in formatters:
|
||||
row.append(formatters[field](o))
|
||||
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, '')
|
||||
row.append(data)
|
||||
if data is None:
|
||||
data = '-'
|
||||
row.append(data)
|
||||
pt.add_row(row)
|
||||
|
||||
if six.PY3:
|
||||
@@ -195,12 +198,14 @@ def print_dict(dct, dict_property="Property", wrap=0):
|
||||
"""
|
||||
pt = prettytable.PrettyTable([dict_property, 'Value'])
|
||||
pt.align = 'l'
|
||||
for k, v in six.iteritems(dct):
|
||||
for k, v in dct.items():
|
||||
# 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)
|
||||
elif wrap < 0:
|
||||
raise ValueError(_("Wrap argument should be a positive integer"))
|
||||
# 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:
|
||||
@@ -210,6 +215,8 @@ def print_dict(dct, dict_property="Property", wrap=0):
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
else:
|
||||
if v is None:
|
||||
v = '-'
|
||||
pt.add_row([k, v])
|
||||
|
||||
if six.PY3:
|
||||
@@ -267,5 +274,5 @@ def pretty_choice_list(l):
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print (msg, file=sys.stderr)
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
50
watcherclient/common/command.py
Normal file
50
watcherclient/common/command.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Copyright 2016 NEC Corporation
|
||||
#
|
||||
# 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 abc
|
||||
import logging
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
import six
|
||||
|
||||
|
||||
class CommandMeta(abc.ABCMeta):
|
||||
|
||||
def __new__(mcs, name, bases, cls_dict):
|
||||
if 'log' not in cls_dict:
|
||||
cls_dict['log'] = logging.getLogger(
|
||||
cls_dict['__module__'] + '.' + name)
|
||||
return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict)
|
||||
|
||||
|
||||
@six.add_metaclass(CommandMeta)
|
||||
class Command(command.Command):
|
||||
|
||||
def run(self, parsed_args):
|
||||
self.log.debug('run(%s)', parsed_args)
|
||||
return super(Command, self).run(parsed_args)
|
||||
|
||||
|
||||
class Lister(Command, lister.Lister):
|
||||
pass
|
||||
|
||||
|
||||
class ShowOne(Command, show.ShowOne):
|
||||
def get_parser(self, prog_name, formatter_class=None):
|
||||
parser = super(ShowOne, self).get_parser(prog_name)
|
||||
if formatter_class:
|
||||
parser.formatter_class = formatter_class
|
||||
return parser
|
||||
@@ -1,384 +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 = {}
|
||||
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.ConnectionRefused(
|
||||
_("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:
|
||||
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)
|
||||
631
watcherclient/common/httpclient.py
Normal file
631
watcherclient/common/httpclient.py
Normal file
@@ -0,0 +1,631 @@
|
||||
# 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 import exceptions
|
||||
|
||||
|
||||
# NOTE(deva): Record the latest version that this client was tested with.
|
||||
# We still have a lot of work to do in the client to implement
|
||||
# microversion support in the client properly! See
|
||||
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html # noqa
|
||||
# for full details.
|
||||
DEFAULT_VER = '1.0'
|
||||
|
||||
|
||||
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)
|
||||
# NOTE: servers before commit 32fb6e99 did not return version headers
|
||||
# on error, so we need to perform a GET to determine
|
||||
# the supported version range
|
||||
if not max_ver:
|
||||
LOG.debug('No version header in response, requesting from server')
|
||||
if self.os_watcher_api_version:
|
||||
base_version = ("/v%s" %
|
||||
str(self.os_watcher_api_version).split('.')[0])
|
||||
else:
|
||||
base_version = API_VERSION
|
||||
resp = self._make_simple_request(conn, 'GET', base_version)
|
||||
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_watcher_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_watcher_api_version,
|
||||
'min': min_ver, 'max': max_ver}))
|
||||
|
||||
negotiated_ver = str(
|
||||
min(version.StrictVersion(self.os_watcher_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_watcher_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('X-OpenStack-Watcher-API-Minimum-Version',
|
||||
None)
|
||||
max_ver = accessor_func('X-OpenStack-Watcher-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_watcher_api_version = kwargs.get('os_watcher_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 urlparse.urljoin(self.endpoint_trimmed, url)
|
||||
|
||||
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_watcher_api_version:
|
||||
kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
|
||||
self.os_watcher_api_version)
|
||||
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']['X-OpenStack-Watcher-API-Version'] = (
|
||||
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':
|
||||
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 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_watcher_api_version,
|
||||
api_version_select_state,
|
||||
max_retries,
|
||||
retry_interval,
|
||||
endpoint,
|
||||
**kwargs):
|
||||
self.os_watcher_api_version = os_watcher_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_watcher_api_version', None):
|
||||
kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
|
||||
self.os_watcher_api_version)
|
||||
|
||||
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']['X-OpenStack-Watcher-API-Version'] = (
|
||||
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_watcher_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_watcher_api_version=os_watcher_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_watcher_api_version=os_watcher_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.
|
||||
@@ -18,8 +17,11 @@
|
||||
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 _
|
||||
@@ -73,7 +75,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
|
||||
@@ -86,10 +88,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)
|
||||
|
||||
@@ -102,7 +105,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
|
||||
@@ -110,7 +113,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":
|
||||
@@ -157,17 +161,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 = []
|
||||
@@ -177,4 +185,26 @@ 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
|
||||
|
||||
|
||||
def is_uuid_like(val):
|
||||
"""Returns validation of a value as a UUID.
|
||||
|
||||
For our purposes, a UUID is a canonical form string:
|
||||
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
|
||||
"""
|
||||
try:
|
||||
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
|
||||
|
||||
23
watcherclient/config.py
Normal file
23
watcherclient/config.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2018 Servionica
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
service_option = cfg.BoolOpt("watcher_client",
|
||||
default=True,
|
||||
help="Whether or not watcher_client is expected"
|
||||
"to be available")
|
||||
@@ -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,6 +52,20 @@ 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."""
|
||||
@@ -86,11 +99,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 +112,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)
|
||||
|
||||
55
watcherclient/osc/plugin.py
Normal file
55
watcherclient/osc/plugin.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# 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."""
|
||||
infraoptim_client_class = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
|
||||
|
||||
client = infraoptim_client_class(
|
||||
os_watcher_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
|
||||
34
watcherclient/plugin.py
Normal file
34
watcherclient/plugin.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from tempest.test_discover import plugins
|
||||
|
||||
from watcherclient import config as watcher_config
|
||||
|
||||
|
||||
class WatcherClientTempestPlugin(plugins.TempestPlugin):
|
||||
def load_tests(self):
|
||||
base_path = os.path.split(os.path.dirname(
|
||||
os.path.abspath(__file__)))[0]
|
||||
test_dir = "watcherclient/tests/functional/v1"
|
||||
full_test_dir = os.path.join(base_path, test_dir)
|
||||
return full_test_dir, base_path
|
||||
|
||||
def register_opts(self, conf):
|
||||
conf.register_opt(watcher_config.service_option,
|
||||
group='service_available')
|
||||
|
||||
def get_opt_lists(self):
|
||||
return [('service_available', [watcher_config.service_option])]
|
||||
@@ -1,507 +1,210 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2013 Rackspace, 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Command-line interface to the Watcher API.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from keystoneclient.auth.identity import v2 as v2_auth
|
||||
from keystoneclient.auth.identity import v3 as v3_auth
|
||||
from keystoneclient import discover
|
||||
from keystoneclient import exceptions as ks_exc
|
||||
from keystoneclient import session as kssession
|
||||
import six.moves.urllib.parse as urlparse
|
||||
from cliff import app
|
||||
from cliff import command
|
||||
from cliff import commandmanager
|
||||
from cliff import complete
|
||||
from cliff import help as cli_help
|
||||
from keystoneauth1 import loading
|
||||
from osc_lib import logs
|
||||
from osc_lib import utils
|
||||
|
||||
import watcherclient
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient import client as watcher_client
|
||||
from watcherclient.common import cliutils
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient import client as watcherclient
|
||||
from watcherclient import version
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WatcherShell(object):
|
||||
API_NAME = 'infra-optim'
|
||||
API_VERSIONS = {
|
||||
'1': 'watcherclient.v1.client.Client',
|
||||
}
|
||||
_DEFAULT_IDENTITY_API_VERSION = '3'
|
||||
_IDENTITY_API_VERSION_2 = ['2', '2.0']
|
||||
_IDENTITY_API_VERSION_3 = ['3']
|
||||
|
||||
def _append_global_identity_args(self, parser):
|
||||
# FIXME(dhu): these are global identity (Keystone) arguments which
|
||||
# should be consistent and shared by all service clients. Therefore,
|
||||
# they should be provided by python-keystoneclient. We will need to
|
||||
# refactor this code once this functionality is avaible in
|
||||
# python-keystoneclient.
|
||||
|
||||
# Register arguments needed for a Session
|
||||
kssession.Session.register_cli_options(parser)
|
||||
class WatcherShell(app.App):
|
||||
"""Watcher command line interface."""
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.client = None
|
||||
|
||||
# Patch command.Command to add a default auth_required = True
|
||||
command.Command.auth_required = True
|
||||
|
||||
# Some commands do not need authentication
|
||||
cli_help.HelpCommand.auth_required = False
|
||||
complete.CompleteCommand.auth_required = False
|
||||
|
||||
super(WatcherShell, self).__init__(
|
||||
description=__doc__.strip(),
|
||||
version=version.__version__,
|
||||
command_manager=commandmanager.CommandManager(
|
||||
'watcherclient.v1'),
|
||||
deferred_help=True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def create_client(self, args):
|
||||
client = watcherclient.get_client('1', **args.__dict__)
|
||||
return client
|
||||
|
||||
def build_option_parser(self, description, version, argparse_kwargs=None):
|
||||
"""Introduces global arguments for the application.
|
||||
|
||||
This is inherited from the framework.
|
||||
"""
|
||||
parser = super(WatcherShell, self).build_option_parser(
|
||||
description, version, argparse_kwargs)
|
||||
parser.add_argument('--no-auth', '-N', action='store_true',
|
||||
help='Do not use authentication.')
|
||||
parser.add_argument('--os-identity-api-version',
|
||||
metavar='<identity-api-version>',
|
||||
default=utils.env('OS_IDENTITY_API_VERSION'),
|
||||
help='Specify Identity API version to use. '
|
||||
'Defaults to env[OS_IDENTITY_API_VERSION]'
|
||||
' or 3.')
|
||||
parser.add_argument('--os-auth-url', '-A',
|
||||
metavar='<auth-url>',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL].')
|
||||
parser.add_argument('--os-region-name', '-R',
|
||||
metavar='<region-name>',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME].')
|
||||
parser.add_argument('--os-username', '-U',
|
||||
metavar='<auth-user-name>',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME].')
|
||||
parser.add_argument('--os-user-id',
|
||||
metavar='<auth-user-id>',
|
||||
default=utils.env('OS_USER_ID'),
|
||||
help='Defaults to env[OS_USER_ID].')
|
||||
parser.add_argument('--os-password', '-P',
|
||||
metavar='<auth-password>',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD].')
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
default=cliutils.env('OS_USER_DOMAIN_ID'),
|
||||
metavar='<auth-user-domain-id>',
|
||||
default=utils.env('OS_USER_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_ID].')
|
||||
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
default=cliutils.env('OS_USER_DOMAIN_NAME'),
|
||||
metavar='<auth-user-domain-name>',
|
||||
default=utils.env('OS_USER_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_NAME].')
|
||||
|
||||
parser.add_argument('--os-tenant-name', '-T',
|
||||
metavar='<auth-tenant-name>',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
parser.add_argument('--os-tenant-id', '-I',
|
||||
metavar='<tenant-id>',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
parser.add_argument('--os-project-id',
|
||||
default=cliutils.env('OS_PROJECT_ID'),
|
||||
metavar='<auth-project-id>',
|
||||
default=utils.env('OS_PROJECT_ID'),
|
||||
help='Another way to specify tenant ID. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-id. '
|
||||
'Defaults to env[OS_PROJECT_ID].')
|
||||
|
||||
'Defaults to env[OS_PROJECT_ID].')
|
||||
parser.add_argument('--os-project-name',
|
||||
default=cliutils.env('OS_PROJECT_NAME'),
|
||||
metavar='<auth-project-name>',
|
||||
default=utils.env('OS_PROJECT_NAME'),
|
||||
help='Another way to specify tenant name. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-name. '
|
||||
'Defaults to env[OS_PROJECT_NAME].')
|
||||
|
||||
parser.add_argument('--os-project-domain-id',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
metavar='<auth-project-domain-id>',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
|
||||
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
metavar='<auth-project-domain-name>',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='watcher',
|
||||
description=__doc__.strip(),
|
||||
epilog='See "watcher help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter,
|
||||
)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=watcherclient.__version__)
|
||||
|
||||
parser.add_argument('--debug',
|
||||
default=bool(cliutils.env('WATCHERCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help='Defaults to env[WATCHERCLIENT_DEBUG]')
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help="Print more verbose output")
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--cert-file',
|
||||
dest='os_cert',
|
||||
help='DEPRECATED! Use --os-cert.')
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--key-file',
|
||||
dest='os_key',
|
||||
help='DEPRECATED! Use --os-key.')
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--ca-file',
|
||||
dest='os_cacert',
|
||||
help='DEPRECATED! Use --os-cacert.')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=cliutils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME]')
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=cliutils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD]')
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=cliutils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID]')
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=cliutils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME]')
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=cliutils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL]')
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=cliutils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME]')
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=cliutils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN]')
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--watcher-url',
|
||||
default=cliutils.env('WATCHER_URL'),
|
||||
help='Defaults to env[WATCHER_URL]')
|
||||
|
||||
parser.add_argument('--watcher_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--watcher-api-version',
|
||||
default=cliutils.env(
|
||||
'WATCHER_API_VERSION', default='1'),
|
||||
help='Defaults to env[WATCHER_API_VERSION] '
|
||||
'or 1')
|
||||
|
||||
parser.add_argument('--watcher_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
default=cliutils.env('OS_SERVICE_TYPE'),
|
||||
help='Defaults to env[OS_SERVICE_TYPE] or '
|
||||
'"watcher"')
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint',
|
||||
default=cliutils.env('OS_SERVICE_ENDPOINT'),
|
||||
help='Specify an endpoint to use instead of '
|
||||
'retrieving one from the service catalog '
|
||||
'(via authentication). '
|
||||
'Defaults to env[OS_SERVICE_ENDPOINT].')
|
||||
|
||||
parser.add_argument('--os_endpoint',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
metavar='<auth-token>',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
parser.add_argument('--os-watcher-api-version',
|
||||
metavar='<os-watcher-api-version>',
|
||||
default=utils.env('OS_WATCHER_API_VERSION',
|
||||
default='1'),
|
||||
help='Defaults to env[OS_WATCHER_API_VERSION].')
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=cliutils.env('OS_ENDPOINT_TYPE'),
|
||||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE] or '
|
||||
'"publicURL"')
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# FIXME(gyee): this method should come from python-keystoneclient.
|
||||
# Will refactor this code once it is available.
|
||||
# https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
|
||||
|
||||
self._append_global_identity_args(parser)
|
||||
|
||||
parser.add_argument('--os-endpoint-override',
|
||||
metavar='<endpoint-override>',
|
||||
default=utils.env('OS_ENDPOINT_OVERRIDE'),
|
||||
help="Use this API endpoint instead of the "
|
||||
"Service Catalog.")
|
||||
parser.epilog = ('See "watcher help COMMAND" for help '
|
||||
'on a specific command.')
|
||||
loading.register_session_argparse_arguments(parser)
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
def configure_logging(self):
|
||||
"""Configure logging for the app."""
|
||||
self.log_configurator = logs.LogConfigurator(self.options)
|
||||
self.dump_stack_trace = self.log_configurator.dump_trace
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
submodule.enhance_parser(parser, subparsers, self.subcommands)
|
||||
utils.define_commands_from_module(subparsers, self, self.subcommands)
|
||||
return parser
|
||||
def prepare_to_run_command(self, cmd):
|
||||
"""Prepares to run the command
|
||||
|
||||
def _setup_debugging(self, debug):
|
||||
if debug:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s %(message)s",
|
||||
level=logging.CRITICAL)
|
||||
Checks if the minimal parameters are provided and creates the
|
||||
client interface.
|
||||
This is inherited from the framework.
|
||||
"""
|
||||
self.client_manager = namedtuple('ClientManager', 'infra_optim')
|
||||
if cmd.auth_required:
|
||||
client = self.create_client(self.options)
|
||||
setattr(self.client_manager, 'infra-optim', client)
|
||||
|
||||
def do_bash_completion(self):
|
||||
"""Prints all of the commands and options for bash-completion."""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in sc._optionals._option_string_actions.keys():
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
print(' '.join(commands | options))
|
||||
|
||||
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
|
||||
def run(self, argv):
|
||||
ret_val = 1
|
||||
self.command_options = argv
|
||||
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)
|
||||
ret_val = super(WatcherShell, self).run(argv)
|
||||
return ret_val
|
||||
except Exception as e:
|
||||
if not logging.getLogger('').handlers:
|
||||
logging.basicConfig()
|
||||
self.log.error('Exception raised: %s', str(e))
|
||||
|
||||
return (v2_auth_url, v3_auth_url)
|
||||
return ret_val
|
||||
|
||||
def _get_keystone_v3_auth(self, v3_auth_url, **kwargs):
|
||||
auth_token = kwargs.pop('auth_token', None)
|
||||
if auth_token:
|
||||
return v3_auth.Token(v3_auth_url, auth_token)
|
||||
else:
|
||||
return v3_auth.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_auth.Token(
|
||||
v2_auth_url,
|
||||
auth_token,
|
||||
tenant_id=kwargs.pop('project_id', None),
|
||||
tenant_name=kwargs.pop('project_name', None))
|
||||
else:
|
||||
return v2_auth.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 main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self._setup_debugging(options.debug)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.watcher_api_version
|
||||
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line
|
||||
if options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
|
||||
# Short-circuit and deal with these commands right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion()
|
||||
return 0
|
||||
|
||||
if not (args.os_auth_token and (args.watcher_url or args.os_endpoint)):
|
||||
if not args.os_username:
|
||||
raise exc.CommandError(_("You must provide a username via "
|
||||
"either --os-username or via "
|
||||
"env[OS_USERNAME]"))
|
||||
|
||||
if not args.os_password:
|
||||
# No password, If we've got a tty, try prompting for it
|
||||
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
|
||||
# Check for Ctl-D
|
||||
try:
|
||||
args.os_password = getpass.getpass(
|
||||
'OpenStack Password: ')
|
||||
except EOFError:
|
||||
pass
|
||||
# No password because we didn't have a tty or the
|
||||
# user Ctl-D when prompted.
|
||||
if not args.os_password:
|
||||
raise exc.CommandError(_("You must provide a password via "
|
||||
"either --os-password, "
|
||||
"env[OS_PASSWORD], "
|
||||
"or prompted response"))
|
||||
|
||||
if not (args.os_tenant_id or args.os_tenant_name or
|
||||
args.os_project_id or args.os_project_name):
|
||||
raise exc.CommandError(_(
|
||||
"You must provide a project name or"
|
||||
" project id via --os-project-name, --os-project-id,"
|
||||
" env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]. You may"
|
||||
" use os-project and os-tenant interchangeably."))
|
||||
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError(_("You must provide an auth url via "
|
||||
"either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"))
|
||||
|
||||
endpoint = args.watcher_url or args.os_endpoint
|
||||
service_type = args.os_service_type or 'infra-optim'
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
|
||||
if (args.os_auth_token and (args.watcher_url or args.os_endpoint)):
|
||||
kwargs = {
|
||||
'token': args.os_auth_token,
|
||||
'insecure': args.insecure,
|
||||
'timeout': args.timeout,
|
||||
'ca_file': args.os_cacert,
|
||||
'cert_file': args.os_cert,
|
||||
'key_file': args.os_key,
|
||||
'auth_ref': None,
|
||||
}
|
||||
elif (args.os_username and
|
||||
args.os_password and
|
||||
args.os_auth_url and
|
||||
(project_id or project_name)):
|
||||
|
||||
keystone_session = kssession.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)
|
||||
if not endpoint:
|
||||
svc_type = args.os_service_type
|
||||
region_name = args.os_region_name
|
||||
endpoint = keystone_auth.get_endpoint(keystone_session,
|
||||
service_type=svc_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,
|
||||
}
|
||||
client = watcher_client.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
try:
|
||||
args.func(client, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError(_("Invalid OpenStack Identity credentials"))
|
||||
|
||||
@cliutils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
def do_help(self, args):
|
||||
"""Display help about this program or one of its subcommands."""
|
||||
if getattr(args, 'command', None):
|
||||
if args.command in self.subcommands:
|
||||
self.subcommands[args.command].print_help()
|
||||
else:
|
||||
raise exc.CommandError(_("'%s' is not a valid subcommand") %
|
||||
args.command)
|
||||
else:
|
||||
self.parser.print_help()
|
||||
finally:
|
||||
self.log.info("END return value: %s", ret_val)
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
def start_section(self, heading):
|
||||
# Title-case the headings
|
||||
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||
super(HelpFormatter, self).start_section(heading)
|
||||
def main(argv=sys.argv[1:]):
|
||||
watcher_app = WatcherShell()
|
||||
return watcher_app.run(argv)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
WatcherShell().main(sys.argv[1:])
|
||||
except KeyboardInterrupt:
|
||||
print("... terminating watcher client", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
|
||||
61
watcherclient/tests/README.rst
Normal file
61
watcherclient/tests/README.rst
Normal file
@@ -0,0 +1,61 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
=======
|
||||
Testing
|
||||
=======
|
||||
|
||||
.. _functional_tests:
|
||||
|
||||
Functional tests
|
||||
================
|
||||
|
||||
The following procedure gets you started with Tempest testing but you can also
|
||||
refer to the `Tempest documentation`_ for more details.
|
||||
|
||||
.. _Tempest documentation: https://docs.openstack.org/tempest/latest/
|
||||
|
||||
|
||||
Tempest installation
|
||||
--------------------
|
||||
|
||||
You need to install virtualenv, create a virtual environment and activate it::
|
||||
|
||||
$ pip install virtualenv
|
||||
$ virtualenv watcher-env
|
||||
$ . watcher-env/bin/activate
|
||||
|
||||
Then, to install Tempest you can issue the following commands::
|
||||
|
||||
$ git clone https://github.com/openstack/tempest/
|
||||
$ pip install tempest/
|
||||
|
||||
There should be set environment variables using the OpenStack RC file. If
|
||||
you don't have RC file yet, create ``admin-openrc`` file and fill it using
|
||||
the following example::
|
||||
|
||||
export OS_PROJECT_DOMAIN_NAME=default
|
||||
export OS_USER_DOMAIN_NAME=default
|
||||
export OS_PROJECT_NAME=admin
|
||||
export OS_USERNAME=admin
|
||||
export OS_PASSWORD=admin
|
||||
export OS_AUTH_URL=http://controller:35357/v3
|
||||
export OS_IDENTITY_API_VERSION=3
|
||||
export OS_IMAGE_API_VERSION=2
|
||||
|
||||
Then, save file and execute ``source admin-openrc`` to set environment
|
||||
variables.
|
||||
|
||||
To run functional tests you need to go to python-watcherclient folder, install
|
||||
all requirements and execute ``tempest run`` command::
|
||||
|
||||
$ pip install -r requirements.txt -r test-requirements.txt
|
||||
$ pip install .
|
||||
$ tempest run --regex watcherclient.tests.functional
|
||||
|
||||
You can run specified test(s) by using regular expression::
|
||||
|
||||
$ tempest run --regex watcherclient.tests.functional.v1.test_action.ActionTests.test_action_list
|
||||
0
watcherclient/tests/functional/__init__.py
Normal file
0
watcherclient/tests/functional/__init__.py
Normal file
0
watcherclient/tests/functional/v1/__init__.py
Normal file
0
watcherclient/tests/functional/v1/__init__.py
Normal file
144
watcherclient/tests/functional/v1/base.py
Normal file
144
watcherclient/tests/functional/v1/base.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import testtools
|
||||
|
||||
import six
|
||||
from tempest import clients
|
||||
from tempest.common import credentials_factory as creds_factory
|
||||
from tempest.lib.cli import output_parser
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
def credentials():
|
||||
# You can get credentials either from tempest.conf file or
|
||||
# from OS environment.
|
||||
tempest_creds = clients.get_auth_provider(
|
||||
creds_factory.get_configured_admin_credentials())
|
||||
creds = tempest_creds.credentials
|
||||
creds_dict = {
|
||||
'--os-username': os.environ.get('OS_USERNAME', creds.username),
|
||||
'--os-password': os.environ.get('OS_PASSWORD', creds.password),
|
||||
'--os-project-name': os.environ.get('OS_PROJECT_NAME',
|
||||
creds.project_name),
|
||||
'--os-auth-url': os.environ.get('OS_AUTH_URL',
|
||||
tempest_creds.auth_url),
|
||||
'--os-project-domain-name': os.environ.get('OS_PROJECT_DOMAIN_ID',
|
||||
creds.project_domain_name),
|
||||
'--os-user-domain-name': os.environ.get('OS_USER_DOMAIN_ID',
|
||||
creds.user_domain_name),
|
||||
}
|
||||
return [x for sub in creds_dict.items() for x in sub]
|
||||
|
||||
|
||||
def execute(cmd, fail_ok=False, merge_stderr=False):
|
||||
"""Executes specified command for the given action."""
|
||||
cmdlist = shlex.split(cmd)
|
||||
cmdlist.extend(credentials())
|
||||
stdout = subprocess.PIPE
|
||||
stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
|
||||
proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr)
|
||||
result, result_err = proc.communicate()
|
||||
result = result.decode('utf-8')
|
||||
if not fail_ok and proc.returncode != 0:
|
||||
raise exceptions.CommandFailed(proc.returncode, cmd, result,
|
||||
result_err)
|
||||
return result
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
|
||||
|
||||
@classmethod
|
||||
def watcher(cls, cmd, fail_ok=False):
|
||||
"""Executes watcherclient command for the given action."""
|
||||
return execute('watcher {0}'.format(cmd), fail_ok=fail_ok)
|
||||
|
||||
@classmethod
|
||||
def get_opts(cls, fields, format='value'):
|
||||
return ' -f {0} {1}'.format(format,
|
||||
' '.join(['-c ' + it for it in fields]))
|
||||
|
||||
@classmethod
|
||||
def assertOutput(cls, expected, actual):
|
||||
if expected != actual:
|
||||
raise Exception('{0} != {1}'.format(expected, actual))
|
||||
|
||||
@classmethod
|
||||
def assertInOutput(cls, expected, actual):
|
||||
if expected not in actual:
|
||||
raise Exception('{0} not in {1}'.format(expected, actual))
|
||||
|
||||
def assert_table_structure(self, items, field_names):
|
||||
"""Verify that all items have keys listed in field_names."""
|
||||
for item in items:
|
||||
for field in field_names:
|
||||
self.assertIn(field, item)
|
||||
|
||||
def assert_show_fields(self, items, field_names):
|
||||
"""Verify that all items have keys listed in field_names."""
|
||||
for item in items:
|
||||
for key in six.iterkeys(item):
|
||||
self.assertIn(key, field_names)
|
||||
|
||||
def assert_show_structure(self, items, field_names):
|
||||
"""Verify that all field_names listed in keys of all items."""
|
||||
if isinstance(items, list):
|
||||
o = {}
|
||||
for d in items:
|
||||
o.update(d)
|
||||
else:
|
||||
o = items
|
||||
item_keys = o.keys()
|
||||
for field in field_names:
|
||||
self.assertIn(field, item_keys)
|
||||
|
||||
@staticmethod
|
||||
def parse_show_as_object(raw_output):
|
||||
"""Return a dict with values parsed from cli output."""
|
||||
items = TestCase.parse_show(raw_output)
|
||||
o = {}
|
||||
for item in items:
|
||||
o.update(item)
|
||||
return o
|
||||
|
||||
@staticmethod
|
||||
def parse_show(raw_output):
|
||||
"""Return list of dicts with item values parsed from cli output."""
|
||||
|
||||
items = []
|
||||
table_ = output_parser.table(raw_output)
|
||||
for row in table_['values']:
|
||||
item = {}
|
||||
item[row[0]] = row[1]
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
def parse_listing(self, raw_output):
|
||||
"""Return list of dicts with basic item parsed from cli output."""
|
||||
return output_parser.listing(raw_output)
|
||||
|
||||
def has_actionplan_succeeded(self, ap_uuid):
|
||||
return self.parse_show_as_object(
|
||||
self.watcher('actionplan show %s' % ap_uuid)
|
||||
)['State'] == 'SUCCEEDED'
|
||||
|
||||
@classmethod
|
||||
def has_audit_created(cls, audit_uuid):
|
||||
return cls.parse_show_as_object(
|
||||
cls.watcher('audit show %s' % audit_uuid))['State'] == 'SUCCEEDED'
|
||||
81
watcherclient/tests/functional/v1/test_action.py
Normal file
81
watcherclient/tests/functional/v1/test_action.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# Copyright (c) 2016 Servionica
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
import functools
|
||||
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
||||
from watcherclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class ActionTests(base.TestCase):
|
||||
"""Functional tests for action."""
|
||||
|
||||
dummy_name = 'dummy'
|
||||
list_fields = ['UUID', 'Parents', 'State', 'Action Plan', 'Action']
|
||||
detailed_list_fields = list_fields + ['Created At', 'Updated At',
|
||||
'Deleted At', 'Parameters']
|
||||
audit_template_name = 'a' + uuidutils.generate_uuid()
|
||||
audit_uuid = None
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
template_raw_output = cls.watcher(
|
||||
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
|
||||
template_output = cls.parse_show_as_object(template_raw_output)
|
||||
audit_output = cls.parse_show_as_object(cls.watcher(
|
||||
'audit create -a %s' % template_output['Name']))
|
||||
cls.audit_uuid = audit_output['UUID']
|
||||
audit_created = test_utils.call_until_true(
|
||||
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
|
||||
duration=600,
|
||||
sleep_for=2)
|
||||
if not audit_created:
|
||||
raise Exception('Audit has not been succeeded')
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# Delete Action Plan and all related actions.
|
||||
output = cls.parse_show(
|
||||
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
|
||||
action_plan_uuid = list(output[0])[0]
|
||||
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
|
||||
cls.assertOutput('', raw_output)
|
||||
# Delete audit
|
||||
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
|
||||
cls.assertOutput('', raw_output)
|
||||
# Delete Template
|
||||
raw_output = cls.watcher(
|
||||
'audittemplate delete %s' % cls.audit_template_name)
|
||||
cls.assertOutput('', raw_output)
|
||||
|
||||
def test_action_list(self):
|
||||
raw_output = self.watcher('action list')
|
||||
self.assert_table_structure([raw_output], self.list_fields)
|
||||
|
||||
def test_action_detailed_list(self):
|
||||
raw_output = self.watcher('action list --detail')
|
||||
self.assert_table_structure([raw_output], self.detailed_list_fields)
|
||||
|
||||
def test_action_show(self):
|
||||
action_list = self.parse_show(self.watcher('action list --audit %s'
|
||||
% self.audit_uuid))
|
||||
action_uuid = list(action_list[0])[0]
|
||||
action = self.watcher('action show %s' % action_uuid)
|
||||
self.assertIn(action_uuid, action)
|
||||
self.assert_table_structure([action],
|
||||
self.detailed_list_fields)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user