Compare commits
421 Commits
0.20.0
...
unmaintain
| Author | SHA1 | Date | |
|---|---|---|---|
| ffddb84060 | |||
| 5df69a9ad0 | |||
|
|
56e47c38e2 | ||
|
|
04b6cc9a04 | ||
|
|
33bcb8b04f | ||
|
|
9aace80a9b | ||
|
|
63d305ca25 | ||
|
|
a3445ded36 | ||
|
|
6d6deee2d6 | ||
| 3c6ce105b8 | |||
|
|
e227f82fd6 | ||
| 765701da39 | |||
|
|
dc9b5cb347 | ||
|
|
cf18f56c6f | ||
|
|
f2f882e297 | ||
|
|
add4254a3f | ||
|
|
983a347341 | ||
|
|
6a4937d18d | ||
|
|
d0f41996ff | ||
|
|
52a3fd062d | ||
|
|
c97e16fd01 | ||
|
|
6a33b44d7f | ||
|
|
41feafc420 | ||
| e1bcc5f758 | |||
|
|
80f728b14f | ||
|
|
2e97950670 | ||
|
|
21b106dc15 | ||
|
|
7213f7195e | ||
|
|
21f42884f1 | ||
|
|
4c57d61f8d | ||
|
|
e05b498a5f | ||
|
|
8d3ce6c58d | ||
|
|
bb7af48350 | ||
|
|
92c15fa38a | ||
|
|
d3cc035ff7 | ||
|
|
fb22d3e1f8 | ||
| ac0ccb67a6 | |||
|
|
5946fbb5b1 | ||
|
|
2830afdc75 | ||
|
|
1912b9aa82 | ||
|
|
1f9194ced5 | ||
|
|
9387369cd7 | ||
|
|
431319ee4d | ||
|
|
e3c903cfce | ||
|
|
e3f0a3d2b5 | ||
|
|
88351df1c2 | ||
|
|
df3ec7849e | ||
|
|
0c7e306642 | ||
|
|
db70063cee | ||
|
|
1d1f28a256 | ||
|
|
62fdb73dae | ||
|
|
297ca1dfc5 | ||
|
|
a8f919e1f7 | ||
|
|
ea74a22b49 | ||
|
|
3caa385548 | ||
|
|
0e9c827c76 | ||
|
|
dbbe7df1e2 | ||
|
|
be479feef9 | ||
|
|
02583f7469 | ||
|
|
1aebe93477 | ||
|
|
cde518be31 | ||
|
|
573ca6ed59 | ||
|
|
f062e60990 | ||
|
|
c0b2604e9b | ||
|
|
2cab116e30 | ||
|
|
bb5586b477 | ||
|
|
c30f095481 | ||
|
|
ffb71506aa | ||
|
|
32a8799516 | ||
|
|
b52df6899d | ||
|
|
103bb38475 | ||
|
|
76a01b0c7c | ||
|
|
939371d180 | ||
|
|
c62757b093 | ||
|
|
e2082907ff | ||
|
|
16ff74f54c | ||
|
|
de9b8d124b | ||
|
|
a25cd66052 | ||
|
|
9b1e234e36 | ||
|
|
512e43eb32 | ||
|
|
d0f2f10f5d | ||
|
|
8342e64051 | ||
|
|
2c29c49f24 | ||
|
|
a70ebd8a41 | ||
|
|
4db175f342 | ||
|
|
775199cb42 | ||
|
|
9d9c5fe3be | ||
|
|
e4ee4ff03c | ||
|
|
ca6190c3a1 | ||
|
|
74cbf1b17b | ||
|
|
4a1b6eaf81 | ||
|
|
1186c7c34f | ||
|
|
02498f85f6 | ||
|
|
9d1d5e2cae | ||
|
|
b0ac926ee6 | ||
|
|
3e734b177d | ||
|
|
2cfe7f9afc | ||
|
|
df57eb800d | ||
|
|
abea1a8815 | ||
|
|
ab22110f7b | ||
|
|
ec7e8ca803 | ||
|
|
bdd99d60f3 | ||
|
|
25ff2811d5 | ||
|
|
8ce78f8c1f | ||
|
|
ffc50c5001 | ||
|
|
6c66276040 | ||
|
|
c256ee2a21 | ||
|
|
7abd55a823 | ||
|
|
98e09044c1 | ||
|
|
6c8dfd366d | ||
|
|
112e372239 | ||
|
|
c5b4db7fc1 | ||
|
|
7c6b39eeba | ||
|
|
94f6864bf0 | ||
|
|
e391d53c2f | ||
|
|
b7928eb155 | ||
|
|
7b7d07fac1 | ||
|
|
946ef06dc6 | ||
|
|
69f0493968 | ||
|
|
8c89b3327b | ||
|
|
2e9de6868b | ||
|
|
db7a5e4bc9 | ||
|
|
825e5cb081 | ||
|
|
a8185c7067 | ||
|
|
4a0b0e76b4 | ||
|
|
1918d3f26b | ||
|
|
2ca6b6b421 | ||
|
|
1086b23454 | ||
|
|
535b9da7d2 | ||
|
|
34a4ad3041 | ||
|
|
39d4c1e944 | ||
|
|
c1fd569b76 | ||
|
|
eed22ea2bb | ||
|
|
47241e0462 | ||
|
|
88d122a21f | ||
|
|
5e928b80be | ||
|
|
723ad12afa | ||
|
|
3926dcc541 | ||
|
|
c557e45ae2 | ||
|
|
0b8dba2cb9 | ||
|
|
496f4c1365 | ||
|
|
6681724daa | ||
|
|
2551ff0934 | ||
|
|
adc812746c | ||
|
|
8d62995cf4 | ||
|
|
d82be4c8c4 | ||
|
|
152804168d | ||
|
|
4a4add3dcc | ||
|
|
db012f1f8f | ||
|
|
a4c020c19f | ||
|
|
8ef72f18c4 | ||
|
|
673905fead | ||
|
|
d886672fb5 | ||
|
|
83ccfbb177 | ||
|
|
4c47c7364c | ||
|
|
8acf01859c | ||
|
|
d4af8d8834 | ||
|
|
40d8df21c8 | ||
|
|
b87399cb2c | ||
|
|
9bfefbf9ab | ||
|
|
b84039d7d4 | ||
|
|
f8ead12481 | ||
|
|
85e4e364b7 | ||
|
|
ed9ce5b952 | ||
|
|
731e019bc1 | ||
|
|
a22dab5f88 | ||
|
|
0d7233055d | ||
|
|
2777a9bba8 | ||
|
|
e359488cde | ||
|
|
a1b6eed54c | ||
|
|
a907c2d0cd | ||
|
|
d38bb255ed | ||
|
|
30e2aad126 | ||
|
|
bc0fb4f051 | ||
|
|
ef533eb151 | ||
|
|
3c160af376 | ||
|
|
186029ac87 | ||
|
|
b9112a1199 | ||
|
|
9d301fa14a | ||
|
|
15d7d78b8b | ||
|
|
81b84bcff5 | ||
|
|
79880c2dff | ||
|
|
32259fe287 | ||
|
|
d36ba79e77 | ||
|
|
4f5487b5e1 | ||
|
|
7bf192a981 | ||
|
|
f8b341c0f3 | ||
|
|
abd6d8a0cb | ||
|
|
147e4cd383 | ||
|
|
3988e9e7a8 | ||
|
|
88131d19fd | ||
|
|
c1668fff44 | ||
|
|
1a6703266f | ||
|
|
fada471714 | ||
|
|
1cd8c38d95 | ||
|
|
0257aa9116 | ||
|
|
aa9d1a65f6 | ||
|
|
b5f584dafa | ||
|
|
bf706365ea | ||
|
|
7898268426 | ||
|
|
1eecf8fd63 | ||
|
|
e6f361804f | ||
|
|
f5b2e7e4f5 | ||
|
|
0a29b296ae | ||
|
|
4789e22e4a | ||
|
|
5378102cd6 | ||
|
|
42cab67124 | ||
|
|
0fd1b11f6d | ||
|
|
74773a60b2 | ||
|
|
e92dd4db20 | ||
|
|
ad0a32886d | ||
|
|
84f05d8e40 | ||
|
|
b33a444e90 | ||
|
|
27d1e4c85a | ||
|
|
77eb8f677f | ||
|
|
320bb4c14f | ||
|
|
7b5908b390 | ||
|
|
1d69f6a72f | ||
|
|
0e080e7294 | ||
|
|
c09ad28c9a | ||
|
|
08fbccc684 | ||
|
|
2f2d05d657 | ||
|
|
c02f584b33 | ||
|
|
d6b68dc819 | ||
|
|
a70d587803 | ||
|
|
2e47d77f86 | ||
|
|
a9405f5c2d | ||
|
|
4bb678f7a5 | ||
|
|
baadbe3dca | ||
|
|
24b089c057 | ||
|
|
c8acc20e82 | ||
|
|
fa91740b47 | ||
|
|
504ca86b78 | ||
|
|
2b155f9505 | ||
|
|
c8df16da8f | ||
|
|
0de3f627c5 | ||
|
|
9c46ee00bb | ||
|
|
48703e19e4 | ||
|
|
799084d3d1 | ||
|
|
ee087b85d5 | ||
|
|
8524016170 | ||
|
|
ed5e1b86c3 | ||
|
|
42bf82c22c | ||
|
|
0d0192c472 | ||
|
|
641cd44adb | ||
|
|
da715f8e70 | ||
|
|
abe14a14d4 | ||
|
|
984601ecfa | ||
|
|
f32956493b | ||
|
|
1e2a13fe1a | ||
|
|
417b3a2669 | ||
|
|
3f25b6b223 | ||
|
|
5cbce9fb20 | ||
|
|
f379b1544c | ||
|
|
30f49c9f70 | ||
|
|
af13d9cdd1 | ||
|
|
8952b2fbd2 | ||
|
|
66681ef9c9 | ||
|
|
4285b388ca | ||
|
|
723d39cd65 | ||
|
|
156d660cd3 | ||
|
|
07b19ede3f | ||
|
|
94c317250e | ||
|
|
7250886d14 | ||
|
|
03ac32b1be | ||
|
|
89390ac0a9 | ||
|
|
69c10379af | ||
|
|
0ea4020d98 | ||
|
|
cbcc990383 | ||
|
|
15f519f827 | ||
|
|
1ccf0588f9 | ||
|
|
cd267c9fa4 | ||
|
|
ce477baa1f | ||
|
|
cd918fe086 | ||
|
|
1f7df3d53d | ||
|
|
dfe9d31ee4 | ||
|
|
2ea26be247 | ||
|
|
15b5fb791e | ||
|
|
eafb6b21f7 | ||
|
|
08cf7d99d3 | ||
|
|
cec8ee4f15 | ||
|
|
4713a69a18 | ||
|
|
22bc9a97c9 | ||
|
|
11e3568d66 | ||
|
|
96ef016b81 | ||
|
|
c0f6ea54f6 | ||
|
|
b8666d511e | ||
|
|
4a093e168d | ||
|
|
1fa93eeb8f | ||
|
|
d36f2de07e | ||
|
|
d17bc25fef | ||
|
|
075b32efe2 | ||
|
|
a2cbb8d4b2 | ||
|
|
ecfa35ecc2 | ||
|
|
546d03c3aa | ||
|
|
3a04f9fb9a | ||
|
|
17a144e2f8 | ||
|
|
658fd038f3 | ||
|
|
d2aede7151 | ||
|
|
de54d5b8ba | ||
|
|
ae003b7e76 | ||
|
|
d08c0e3ef1 | ||
|
|
c68120edcd | ||
|
|
bf7085fabd | ||
|
|
ef93a57b46 | ||
|
|
6e291f0f13 | ||
|
|
73dcf25b63 | ||
|
|
0ecafcd6a0 | ||
|
|
529f406075 | ||
|
|
b66834506a | ||
|
|
5b8e3d4d6c | ||
|
|
fce4e7e10c | ||
|
|
8004b4ec0b | ||
|
|
0584350663 | ||
|
|
ccef7be62c | ||
|
|
d960977c96 | ||
|
|
51b295c21c | ||
|
|
e3478de41d | ||
|
|
c8bf47d72d | ||
|
|
c83499e85f | ||
|
|
28acfe1535 | ||
|
|
9edd6afa28 | ||
|
|
9923b51944 | ||
|
|
7dfcdba21e | ||
|
|
dfdee212cc | ||
|
|
50af3b6bb8 | ||
|
|
494b2ecc88 | ||
|
|
d109e9d9a0 | ||
|
|
c185f32ebb | ||
|
|
11056302b4 | ||
|
|
466131e788 | ||
|
|
c88551435d | ||
|
|
77d282e452 | ||
|
|
940a7093e6 | ||
|
|
0c0f6cb079 | ||
|
|
9c3440df44 | ||
|
|
f50df0dc74 | ||
|
|
abf3257e1c | ||
|
|
4e86151f81 | ||
|
|
a48a9213f0 | ||
|
|
ad4ee3fcc7 | ||
|
|
4df180fc1f | ||
|
|
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 | ||
|
|
3640fc4a0b | ||
|
|
15511203d8 | ||
|
|
f390f3526f | ||
|
|
0ce454f3b3 | ||
|
|
5b4729dcbe | ||
|
|
a36b42afef | ||
|
|
9699fc5f70 | ||
|
|
51a3b3fe3f | ||
|
|
b30fad9965 | ||
|
|
59c0dc667e | ||
|
|
dee7be63d9 | ||
|
|
3ef4a0cae0 | ||
|
|
670a70ff5a | ||
|
|
926af3273a | ||
|
|
a0f97ee02b | ||
|
|
740dcf7bc2 | ||
|
|
4b2f948e7a | ||
|
|
eb0c70d332 | ||
|
|
03d12fd030 | ||
|
|
1852227505 | ||
|
|
dbcc77bc6c |
11
.coveragerc
Normal file
11
.coveragerc
Normal file
@@ -0,0 +1,11 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = watcherclient
|
||||
omit =
|
||||
watcherclient/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
exclude_lines =
|
||||
@abc.abstract
|
||||
raise NotImplementedError
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
.eggs
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
@@ -22,11 +23,12 @@ lib64
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.coverage*
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
.stestr/
|
||||
.venv
|
||||
.testrepository/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
@@ -42,6 +44,7 @@ output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
doc/source/reference/api
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
@@ -54,3 +57,10 @@ ChangeLog
|
||||
|
||||
sftp-config.json
|
||||
/.idea/
|
||||
/cover/
|
||||
|
||||
# Desktop Service Store
|
||||
*.DS_Store
|
||||
|
||||
# Atom
|
||||
.remote-sync.json
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
host=review.opendev.org
|
||||
port=29418
|
||||
project=openstack/python-watcherclient.git
|
||||
defaultbranch=stable/2024.1
|
||||
|
||||
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=./watcherclient/tests/unit
|
||||
top_dir=./
|
||||
@@ -1,7 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-10} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
||||
10
.zuul.yaml
Normal file
10
.zuul.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
- project:
|
||||
templates:
|
||||
- openstack-cover-jobs
|
||||
- openstack-python3-jobs
|
||||
- publish-openstack-docs-pti
|
||||
- check-requirements
|
||||
- openstackclient-plugin-jobs
|
||||
check:
|
||||
jobs:
|
||||
- watcherclient-tempest-functional
|
||||
@@ -1,13 +1,13 @@
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
python-watcherclient Style Commandments
|
||||
=======================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
||||
|
||||
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
|
||||
81
README.rst
81
README.rst
@@ -1,16 +1,31 @@
|
||||
========================
|
||||
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
|
||||
====================
|
||||
|
||||
Python client library for Watcher API.
|
||||
Client for resource optimization service for OpenStack.
|
||||
|
||||
Watcher takes advantage of CEP and ML algorithms/metaheuristics to improve physical resources usage through better VM placement. Watcher can improve your cloud optimization by reducing energy footprint and increasing profits.
|
||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||
service for multi-tenant OpenStack-based clouds.
|
||||
Watcher provides a complete optimization loop-including everything from a
|
||||
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!
|
||||
|
||||
* Free software: Apache license
|
||||
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||
* Source: http://git.openstack.org/cgit/openstack/python-watcher
|
||||
* Bugs: http://bugs.launchpad.net/watcher
|
||||
|
||||
* Wiki: https://wiki.openstack.org/wiki/Watcher
|
||||
* Source: https://opendev.org/openstack/python-watcherclient
|
||||
* Bugs: https://bugs.launchpad.net/watcher
|
||||
|
||||
Installation
|
||||
============
|
||||
@@ -23,13 +38,13 @@ On Ubuntu (tested on 14.04-64)
|
||||
.. code::
|
||||
|
||||
sudo apt-get install python-dev libssl-dev python-pip git-core libmysqlclient-dev libffi-dev
|
||||
|
||||
|
||||
On Fedora-based distributions e.g., Fedora/RHEL/CentOS/Scientific Linux (tested on CentOS 6.5)
|
||||
|
||||
.. code::
|
||||
|
||||
sudo yum install python-virtualenv openssl-devel python-pip git gcc libffi-devel mysql-devel postgresql-devel
|
||||
|
||||
|
||||
On openSUSE-based distributions (SLES 12, openSUSE 13.1, Factory or Tumbleweed)
|
||||
|
||||
.. code::
|
||||
@@ -43,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::
|
||||
|
||||
@@ -67,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 :
|
||||
|
||||
@@ -99,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::
|
||||
|
||||
|
||||
8
doc/requirements.txt
Normal file
8
doc/requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
openstackdocstheme>=2.2.1 # Apache-2.0
|
||||
sphinx>=2.0.0,!=2.1.0 # BSD
|
||||
sphinxcontrib-apidoc>=0.2.0 # BSD
|
||||
|
||||
1102
doc/source/cli/details.rst
Normal file
1102
doc/source/cli/details.rst
Normal file
File diff suppressed because it is too large
Load Diff
32
doc/source/cli/index.rst
Normal file
32
doc/source/cli/index.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
=============================
|
||||
Command-line Tool Reference
|
||||
=============================
|
||||
|
||||
In order to use the CLI, you must provide your OpenStack username,
|
||||
password, tenant, and auth endpoint. Use the corresponding
|
||||
configuration options (``--os-username``, ``--os-password``,
|
||||
``--os-tenant-id``, and ``--os-auth-url``) or set them in environment
|
||||
variables::
|
||||
|
||||
export OS_USERNAME=user
|
||||
export OS_PASSWORD=pass
|
||||
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
|
||||
export OS_AUTH_URL=http://auth.example.com:5000/v3/
|
||||
|
||||
The command line tool will attempt to reauthenticate using your
|
||||
provided credentials for every request. You can override this behavior
|
||||
by manually supplying an auth token using ``--os-watcher-url`` and
|
||||
``--os-auth-token``. You can alternatively set these environment
|
||||
variables::
|
||||
|
||||
export OS_WATCHER_URL=http://watcher.example.org:9322/
|
||||
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
|
||||
Once you've configured your authentication parameters, you can run
|
||||
``watcher help`` to see a complete listing of available commands.
|
||||
|
||||
.. toctree::
|
||||
|
||||
watcher
|
||||
openstack_cli
|
||||
details
|
||||
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/v3/
|
||||
|
||||
The command-line tool will attempt to reauthenticate using the provided
|
||||
credentials for every request. You can override this behavior by manually
|
||||
supplying an auth token using :option:``--watcher-url`` and
|
||||
:option:``--os-auth-token``, or by setting the corresponding environment variables::
|
||||
|
||||
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,43 +19,43 @@ 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/v3/
|
||||
|
||||
The command-line tool will attempt to reauthenticate using the provided
|
||||
credentials for every request. You can override this behavior by manually
|
||||
supplying an auth token using :option:`--watcher-url` and
|
||||
:option:`--os-auth-token`, or by setting the corresponding environment variables::
|
||||
supplying an auth token using :option:``--watcher-url`` and
|
||||
:option:``--os-auth-token``, or by setting the corresponding environment variables::
|
||||
|
||||
export WATCHER_URL=http://watcher.example.org:9322/
|
||||
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
$ 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
|
||||
(available at
|
||||
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
|
||||
https://opendev.org/openstack/python-watcherclient/src/branch/master/tools/watcher.bash_completion)
|
||||
to your terminal and then bash completion should work::
|
||||
|
||||
source watcher.bash_completion
|
||||
$ . watcher.bash_completion
|
||||
|
||||
To avoid doing this every time, add this to your ``.bashrc`` or copy the
|
||||
watcher.bash_completion file to the default bash completion scripts directory
|
||||
@@ -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,7 +11,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from watcherclient import version as watcherclient_version
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
@@ -20,13 +18,20 @@ from watcherclient import version as watcherclient_version
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'oslosphinx',
|
||||
]
|
||||
|
||||
'sphinxcontrib.apidoc',
|
||||
'openstackdocstheme',
|
||||
]
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# sphinxcontrib.apidoc options
|
||||
apidoc_module_dir = '../../watcherclient'
|
||||
apidoc_output_dir = 'reference/api'
|
||||
apidoc_excluded_paths = [
|
||||
'tests/*']
|
||||
apidoc_separate_modules = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
@@ -37,18 +42,8 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'python-watcherclient'
|
||||
copyright = u'OpenStack Foundation'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = watcherclient_version.version_info.release_string()
|
||||
# The short X.Y version.
|
||||
version = watcherclient_version.version_info.version_string()
|
||||
project = 'python-watcherclient'
|
||||
copyright = 'OpenStack Foundation'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['watcherclient.']
|
||||
@@ -61,7 +56,7 @@ add_function_parentheses = True
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = 'native'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
@@ -70,7 +65,8 @@ pygments_style = 'sphinx'
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['_static']
|
||||
html_theme_options = {'incubating': True}
|
||||
html_theme = 'openstackdocs'
|
||||
# html_theme_path = [openstackdocstheme.get_html_theme_path()]
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
@@ -83,7 +79,27 @@ latex_documents = [
|
||||
(
|
||||
'index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'
|
||||
'%s Documentation' % project,
|
||||
'OpenStack Foundation', 'manual'
|
||||
),
|
||||
]
|
||||
|
||||
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
|
||||
latex_use_xindy = False
|
||||
|
||||
latex_domain_indices = False
|
||||
|
||||
latex_elements = {
|
||||
'makeindex': '',
|
||||
'printindex': '',
|
||||
'preamble': r'\setcounter{tocdepth}{3}',
|
||||
}
|
||||
|
||||
# openstackdocstheme options
|
||||
openstackdocs_repo_name = 'openstack/python-watcherclient'
|
||||
openstackdocs_pdf_link = True
|
||||
openstackdocs_bug_project = 'python-watcherclient'
|
||||
openstackdocs_bug_tag = ''
|
||||
|
||||
|
||||
#html_theme_options = {"show_other_versions": "True"}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.. _contributing:
|
||||
|
||||
===================================
|
||||
====================================
|
||||
Contributing to python-watcherclient
|
||||
===================================
|
||||
====================================
|
||||
|
||||
If you're interested in contributing to the python-watcherclient project,
|
||||
the following will help get you started.
|
||||
@@ -19,8 +19,8 @@ signed OpenStack's contributor's agreement.
|
||||
|
||||
.. seealso::
|
||||
|
||||
* http://docs.openstack.org/infra/manual/developers.html
|
||||
* http://wiki.openstack.org/CLA
|
||||
* https://docs.openstack.org/infra/manual/developers.html
|
||||
* https://wiki.openstack.org/CLA
|
||||
|
||||
LaunchPad Project
|
||||
-----------------
|
||||
@@ -41,14 +41,14 @@ Project Hosting Details
|
||||
-------------------------
|
||||
|
||||
Bug tracker
|
||||
http://launchpad.net/python-watcherclient
|
||||
https://launchpad.net/python-watcherclient
|
||||
|
||||
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
|
||||
|
||||
Code Hosting
|
||||
https://git.openstack.org/cgit/openstack/python-watcherclient
|
||||
https://opendev.org/openstack/python-watcherclient
|
||||
|
||||
Code Review
|
||||
https://review.openstack.org/#/q/status:open+project:openstack/python-watcherclient,n,z
|
||||
https://review.opendev.org/#/q/status:open+project:openstack/python-watcherclient,n,z
|
||||
|
||||
|
||||
@@ -1,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,9 +2,9 @@
|
||||
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
|
||||
$ git clone https://opendev.org/openstack/python-watcherclient
|
||||
$ cd python-watcherclient && python setup.py install
|
||||
$ pip install -r ./requirements.txt
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.. include:: ../../README.rst
|
||||
@@ -68,11 +68,10 @@ Once you have an watcher `Client`_, you can perform various tasks::
|
||||
|
||||
>>> watcher.action.list() # list of actions
|
||||
>>> watcher.action_plan.list() # list of action_plan
|
||||
>>> watcher.audit.get(audit_uuid) # information about a particular audit
|
||||
>>> watcher.audit.get(audit_uuid_or_name) # information about a particular audit
|
||||
|
||||
When the `Client`_ needs to propagate an exception, it will usually
|
||||
raise an instance subclassed from
|
||||
`watcherclient.exc.BaseException`_ or `watcherclient.exc.ClientException`_.
|
||||
raise an instance listed in `watcherclient.exceptions`_.
|
||||
|
||||
Refer to the modules themselves, for more details.
|
||||
|
||||
@@ -80,15 +79,8 @@ Refer to the modules themselves, for more details.
|
||||
watcherclient Modules
|
||||
=====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
modules <api/autoindex>
|
||||
|
||||
|
||||
.. _watcherclient.v1.audit: api/watcherclient.v1.audit.html#watcherclient.v1.audit.Audit
|
||||
.. _watcherclient.v1.client.Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
|
||||
.. _Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
|
||||
.. _watcherclient.client.get_client(): api/watcherclient.client.html#watcherclient.client.get_client
|
||||
.. _watcherclient.exc.BaseException: api/watcherclient.exc.html#watcherclient.exc.BaseException
|
||||
.. _watcherclient.exc.ClientException: api/watcherclient.exc.html#watcherclient.exc.ClientException
|
||||
.. _watcherclient.exceptions: api/watcherclient.exceptions.html
|
||||
14
doc/source/reference/index.rst
Normal file
14
doc/source/reference/index.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
==========================
|
||||
Python Library Reference
|
||||
==========================
|
||||
|
||||
In order to use the python api directly, you must first obtain an auth
|
||||
token and identify which endpoint you wish to speak to. Once you have
|
||||
done so, you can use the API like so.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api/modules
|
||||
api_v1
|
||||
@@ -1,10 +0,0 @@
|
||||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from oslo-incubator.git
|
||||
module=apiclient
|
||||
module=cliutils
|
||||
module=_i18n
|
||||
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=watcherclient
|
||||
6
releasenotes/notes/drop-py-2-7-f7078b44cf99cae1.yaml
Normal file
6
releasenotes/notes/drop-py-2-7-f7078b44cf99cae1.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Python 2.7 support has been dropped. Last release of python-watcherclient
|
||||
to support py2.7 is OpenStack Train. The minimum version of Python now
|
||||
supported by python-watcherclient is Python 3.6.
|
||||
@@ -1,10 +1,8 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
argparse
|
||||
Babel>=1.3
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
pbr>=1.6
|
||||
python-keystoneclient>=1.6.0,!=1.8.0
|
||||
six>=1.9.0
|
||||
cliff!=2.9.0,>=2.11.0 # Apache-2.0
|
||||
osc-lib>=1.10.0 # Apache-2.0
|
||||
oslo.i18n>=3.20.0 # Apache-2.0
|
||||
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||
oslo.utils>=3.36.0 # Apache-2.0
|
||||
pbr!=2.1.0,>=3.1.1 # Apache-2.0
|
||||
keystoneauth1>=3.4.0 # Apache-2.0
|
||||
PyYAML>=3.13 # MIT
|
||||
|
||||
107
setup.cfg
107
setup.cfg
@@ -1,11 +1,12 @@
|
||||
[metadata]
|
||||
name = python-watcherclient
|
||||
summary = Python client library for Watcher API
|
||||
description-file =
|
||||
description_file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
author_email = openstack-discuss@lists.openstack.org
|
||||
home_page = https://docs.openstack.org/python-watcherclient/latest/
|
||||
python_requires = >=3.8
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
@@ -13,11 +14,11 @@ classifier =
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
|
||||
[files]
|
||||
packages =
|
||||
@@ -27,13 +28,89 @@ packages =
|
||||
console_scripts =
|
||||
watcher = watcherclient.shell:main
|
||||
|
||||
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_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
|
||||
optimize_actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
|
||||
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
|
||||
|
||||
optimize_datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
|
||||
|
||||
# The same as above but used by the 'watcher' command
|
||||
watcherclient.v1 =
|
||||
goal_show = watcherclient.v1.goal_shell:ShowGoal
|
||||
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_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
|
||||
|
||||
datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
autodoc_exclude_modules =
|
||||
watcherclient.tests.*
|
||||
api_doc_dir = reference/api
|
||||
|
||||
12
setup.py
Executable file → Normal file
12
setup.py
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -14,17 +13,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
hacking<0.11,>=0.10
|
||||
httpretty>=0.8.4,<0.8.7
|
||||
mock>=1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
python-subunit>=0.0.18
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
|
||||
# Needed for pypi packaging
|
||||
wheel
|
||||
twine
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
hacking>=3.0.1,<3.1.0 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
python-subunit>=1.0.0 # Apache-2.0/BSD
|
||||
stestr>=2.0.0 # Apache-2.0
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
tempest>=17.1.0 # Apache-2.0
|
||||
|
||||
70
tox.ini
70
tox.ini
@@ -1,43 +1,79 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py33,py34,py27,pep8
|
||||
skipsdist = True
|
||||
minversion = 3.18.0
|
||||
envlist = py3,pep8
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
passenv = ZUUL_CACHE_DIR
|
||||
REQUIREMENTS_PIP_LOCATION
|
||||
install_command = pip install {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.1}
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-r{toxinidir}/requirements.txt
|
||||
allowlist_externals =
|
||||
rm
|
||||
commands = rm -f .testrepository/times.dbm
|
||||
# The --test-path is defined in .stestr.conf
|
||||
stestr run {posargs}
|
||||
stestr slowest
|
||||
|
||||
[testenv:pep8]
|
||||
basepython = python3
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
basepython = python3
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
basepython = python3
|
||||
setenv =
|
||||
PYTHON=coverage run --source watcherclient --parallel-mode
|
||||
commands =
|
||||
stestr run {posargs}
|
||||
coverage combine
|
||||
coverage html -d cover
|
||||
coverage xml -o cover/coverage.xml
|
||||
coverage report
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
basepython = python3
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.1}
|
||||
-r{toxinidir}/doc/requirements.txt
|
||||
commands = sphinx-build -W -b html doc/source doc/build/html
|
||||
|
||||
|
||||
[testenv:pdf-docs]
|
||||
basepython = python3
|
||||
envdir = {toxworkdir}/docs
|
||||
deps = {[testenv:docs]deps}
|
||||
allowlist_externals =
|
||||
rm
|
||||
make
|
||||
commands =
|
||||
rm -rf doc/build/pdf
|
||||
sphinx-build -W -b latex doc/source doc/build/pdf
|
||||
make -C doc/build/pdf
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
basepython = python3
|
||||
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
enable-extensions = H203,H106
|
||||
ignore = E123,E125,W504
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
||||
|
||||
[testenv:pypi]
|
||||
commands =
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload --config-file .pypirc {posargs} dist/*
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
||||
|
||||
[testenv:wheel]
|
||||
basepython = python3
|
||||
commands = python setup.py bdist_wheel
|
||||
|
||||
[hacking]
|
||||
import_exceptions = watcherclient._i18n
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
@@ -16,7 +15,9 @@
|
||||
# under the License.
|
||||
|
||||
import pbr.version
|
||||
|
||||
from watcherclient import client
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient import exceptions
|
||||
|
||||
|
||||
@@ -24,3 +25,11 @@ __version__ = pbr.version.VersionInfo(
|
||||
'python-watcherclient').version_string()
|
||||
|
||||
__all__ = ['client', 'exceptions', ]
|
||||
|
||||
API_MIN_VERSION = api_versioning.APIVersion("1.0")
|
||||
# The max version should be the latest version that is supported in the client,
|
||||
# not necessarily the latest that the server can provide. This is only bumped
|
||||
# when client supported the max version, and bumped sequentially, otherwise
|
||||
# the client may break due to server side new version may include some
|
||||
# backward incompatible change.
|
||||
API_MAX_VERSION = api_versioning.APIVersion("1.1")
|
||||
|
||||
35
watcherclient/_i18n.py
Normal file
35
watcherclient/_i18n.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
DOMAIN = "watcherclient"
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# The contextual translation function using the name "_C"
|
||||
# requires oslo.i18n >=2.1.0
|
||||
_C = _translators.contextual_form
|
||||
|
||||
# The plural translation function using the name "_P"
|
||||
# requires oslo.i18n >=2.1.0
|
||||
_P = _translators.plural_form
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages(DOMAIN)
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@@ -12,112 +10,183 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
from keystoneauth1 import loading as kaloading
|
||||
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common import gettextutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
gettextutils.install('watcherclient')
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient import exceptions
|
||||
|
||||
|
||||
def _get_ksclient(**kwargs):
|
||||
"""Get an endpoint and auth token from Keystone.
|
||||
|
||||
:param kwargs: keyword args containing credentials:
|
||||
* username: name of user
|
||||
* password: user's password
|
||||
* auth_url: endpoint to authenticate against
|
||||
* insecure: allow insecure SSL (no cert verification)
|
||||
* tenant_{name|id}: name or ID of tenant
|
||||
"""
|
||||
return ksclient.Client(username=kwargs.get('username'),
|
||||
password=kwargs.get('password'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
tenant_name=kwargs.get('tenant_name'),
|
||||
auth_url=kwargs.get('auth_url'),
|
||||
insecure=kwargs.get('insecure'))
|
||||
|
||||
|
||||
def _get_endpoint(client, **kwargs):
|
||||
"""Get an endpoint using the provided keystone client."""
|
||||
attr = None
|
||||
filter_value = None
|
||||
if kwargs.get('region_name'):
|
||||
attr = 'region'
|
||||
filter_value = kwargs.get('region_name')
|
||||
return client.service_catalog.url_for(
|
||||
service_type=kwargs.get('service_type') or 'infra-optim',
|
||||
attr=attr,
|
||||
filter_value=filter_value,
|
||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
||||
|
||||
|
||||
def get_client(api_version, **kwargs):
|
||||
"""Get an authenticated client, based on the credentials in args.
|
||||
def get_client(api_version, os_auth_token=None, watcher_url=None,
|
||||
os_username=None, os_password=None, os_auth_url=None,
|
||||
os_project_id=None, os_project_name=None, os_tenant_id=None,
|
||||
os_tenant_name=None, os_region_name=None,
|
||||
os_user_domain_id=None, os_user_domain_name=None,
|
||||
os_project_domain_id=None, os_project_domain_name=None,
|
||||
os_service_type=None, os_endpoint_type=None,
|
||||
insecure=None, timeout=None, os_cacert=None, ca_file=None,
|
||||
os_cert=None, cert_file=None, os_key=None, key_file=None,
|
||||
os_infra_optim_api_version=None, max_retries=None,
|
||||
retry_interval=None, session=None, os_endpoint_override=None,
|
||||
**ignored_kwargs):
|
||||
"""Get an authenticated client, based on the credentials.
|
||||
|
||||
:param api_version: the API version to use. Valid value: '1'.
|
||||
:param kwargs: keyword args containing credentials, either:
|
||||
* os_auth_token: pre-existing token to re-use
|
||||
* watcher_url: watcher API endpoint
|
||||
or:
|
||||
* os_username: name of user
|
||||
* os_password: user's password
|
||||
* os_auth_url: endpoint to authenticate against
|
||||
* insecure: allow insecure SSL (no cert verification)
|
||||
* os_tenant_{name|id}: name or ID of tenant
|
||||
:param os_auth_token: pre-existing token to re-use
|
||||
:param watcher_url: watcher API endpoint
|
||||
:param os_username: name of a user
|
||||
:param os_password: user's password
|
||||
:param os_auth_url: endpoint to authenticate against
|
||||
:param os_project_id: ID of a project
|
||||
:param os_project_name: name of a project
|
||||
:param os_tenant_id: ID of a tenant (deprecated in favour of
|
||||
os_project_id)
|
||||
:param os_tenant_name: name of a tenant (deprecated in favour of
|
||||
os_project_name)
|
||||
:param os_region_name: name of a keystone region
|
||||
:param os_user_domain_id: ID of a domain the user belongs to
|
||||
:param os_user_domain_name: name of a domain the user belongs to
|
||||
:param os_project_domain_id: ID of a domain the project belongs to
|
||||
:param os_project_domain_name: name of a domain the project belongs to
|
||||
:param os_service_type: the type of service to lookup the endpoint for
|
||||
:param os_endpoint_type: the type (exposure) of the endpoint
|
||||
:param insecure: allow insecure SSL (no cert verification)
|
||||
:param timeout: allows customization of the timeout for client HTTP
|
||||
requests
|
||||
:param os_cacert: path to cacert file
|
||||
:param ca_file: path to cacert file, deprecated in favour of os_cacert
|
||||
:param os_cert: path to cert file
|
||||
:param cert_file: path to cert file, deprecated in favour of os_cert
|
||||
:param os_key: path to key file
|
||||
:param key_file: path to key file, deprecated in favour of os_key
|
||||
:param os_infra_optim_api_version: watcher API version to use
|
||||
:param max_retries: Maximum number of retries in case of conflict error
|
||||
:param retry_interval: Amount of time (in seconds) between retries in case
|
||||
of conflict error
|
||||
:param session: Keystone session to use
|
||||
:param os_endpoint_override: watcher API endpoint
|
||||
:param ignored_kwargs: all the other params that are passed. Left for
|
||||
backwards compatibility. They are ignored.
|
||||
"""
|
||||
|
||||
if kwargs.get('os_auth_token') and kwargs.get('watcher_url'):
|
||||
token = kwargs.get('os_auth_token')
|
||||
endpoint = kwargs.get('watcher_url')
|
||||
auth_ref = None
|
||||
elif (kwargs.get('os_username') and
|
||||
kwargs.get('os_password') and
|
||||
kwargs.get('os_auth_url') and
|
||||
(kwargs.get('os_tenant_id') or kwargs.get('os_tenant_name'))):
|
||||
|
||||
ks_kwargs = {
|
||||
'username': kwargs.get('os_username'),
|
||||
'password': kwargs.get('os_password'),
|
||||
'tenant_id': kwargs.get('os_tenant_id'),
|
||||
'tenant_name': kwargs.get('os_tenant_name'),
|
||||
'auth_url': kwargs.get('os_auth_url'),
|
||||
'service_type': kwargs.get('os_service_type'),
|
||||
'endpoint_type': kwargs.get('os_endpoint_type'),
|
||||
'insecure': kwargs.get('insecure'),
|
||||
}
|
||||
_ksclient = _get_ksclient(**ks_kwargs)
|
||||
token = (kwargs.get('os_auth_token')
|
||||
if kwargs.get('os_auth_token')
|
||||
else _ksclient.auth_token)
|
||||
|
||||
ks_kwargs['region_name'] = kwargs.get('os_region_name')
|
||||
endpoint = (kwargs.get('watcher_url') or
|
||||
_get_endpoint(_ksclient, **ks_kwargs))
|
||||
|
||||
auth_ref = _ksclient.auth_ref
|
||||
|
||||
else:
|
||||
e = (_('Must provide Keystone credentials or user-defined endpoint '
|
||||
'and token'))
|
||||
raise exc.AmbiguousAuthSystem(e)
|
||||
|
||||
cli_kwargs = {
|
||||
'token': token,
|
||||
'insecure': kwargs.get('insecure'),
|
||||
'timeout': kwargs.get('timeout'),
|
||||
'ca_file': kwargs.get('ca_file'),
|
||||
'cert_file': kwargs.get('cert_file'),
|
||||
'key_file': kwargs.get('key_file'),
|
||||
'auth_ref': auth_ref,
|
||||
os_service_type = os_service_type or 'infra-optim'
|
||||
os_endpoint_type = os_endpoint_type or 'publicURL'
|
||||
project_id = (os_project_id or os_tenant_id)
|
||||
project_name = (os_project_name or os_tenant_name)
|
||||
kwargs = {
|
||||
'os_infra_optim_api_version': os_infra_optim_api_version,
|
||||
'max_retries': max_retries,
|
||||
'retry_interval': retry_interval,
|
||||
}
|
||||
endpoint = watcher_url or os_endpoint_override
|
||||
cacert = os_cacert or ca_file
|
||||
cert = os_cert or cert_file
|
||||
key = os_key or key_file
|
||||
if os_auth_token and endpoint:
|
||||
kwargs.update({
|
||||
'token': os_auth_token,
|
||||
'insecure': insecure,
|
||||
'ca_file': cacert,
|
||||
'cert_file': cert,
|
||||
'key_file': key,
|
||||
'timeout': timeout,
|
||||
})
|
||||
elif os_auth_url:
|
||||
auth_type = 'password'
|
||||
auth_kwargs = {
|
||||
'auth_url': os_auth_url,
|
||||
'project_id': project_id,
|
||||
'project_name': project_name,
|
||||
'user_domain_id': os_user_domain_id,
|
||||
'user_domain_name': os_user_domain_name,
|
||||
'project_domain_id': os_project_domain_id,
|
||||
'project_domain_name': os_project_domain_name,
|
||||
}
|
||||
if os_username and os_password:
|
||||
auth_kwargs.update({
|
||||
'username': os_username,
|
||||
'password': os_password,
|
||||
})
|
||||
elif os_auth_token:
|
||||
auth_type = 'token'
|
||||
auth_kwargs.update({
|
||||
'token': os_auth_token,
|
||||
})
|
||||
# Create new session only if it was not passed in
|
||||
if not session:
|
||||
loader = kaloading.get_plugin_loader(auth_type)
|
||||
auth_plugin = loader.load_from_options(**auth_kwargs)
|
||||
# Let keystoneauth do the necessary parameter conversions
|
||||
session = kaloading.session.Session().load_from_options(
|
||||
auth=auth_plugin, insecure=insecure, cacert=cacert,
|
||||
cert=cert, key=key, timeout=timeout,
|
||||
)
|
||||
|
||||
return Client(api_version, endpoint, **cli_kwargs)
|
||||
exception_msg = _('Must provide Keystone credentials or user-defined '
|
||||
'endpoint and token')
|
||||
if not endpoint:
|
||||
if session:
|
||||
try:
|
||||
# Pass the endpoint, it will be used to get hostname
|
||||
# and port that will be used for API version caching. It will
|
||||
# be also set as endpoint_override.
|
||||
endpoint = session.get_endpoint(
|
||||
service_type=os_service_type,
|
||||
interface=os_endpoint_type,
|
||||
region_name=os_region_name
|
||||
)
|
||||
except Exception as e:
|
||||
raise exceptions.AmbiguousAuthSystem(
|
||||
exception_msg + _(', error was: %s') % e)
|
||||
else:
|
||||
# Neither session, nor valid auth parameters provided
|
||||
raise exceptions.AmbiguousAuthSystem(exception_msg)
|
||||
|
||||
# Always pass the session
|
||||
kwargs['session'] = session
|
||||
|
||||
return Client(api_version, endpoint, **kwargs)
|
||||
|
||||
|
||||
def _get_client_class_and_version(version):
|
||||
if not isinstance(version, api_versioning.APIVersion):
|
||||
version = api_versioning.get_api_version(version)
|
||||
else:
|
||||
api_versioning.check_major_version(version)
|
||||
if version.is_latest():
|
||||
raise exceptions.UnsupportedVersion(
|
||||
_("The version should be explicit, not latest."))
|
||||
return version, importutils.import_class(
|
||||
"watcherclient.v%s.client.Client" % version.ver_major)
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
"""Initialize client object based on given version.
|
||||
|
||||
HOW-TO:
|
||||
The simplest way to create a client instance is initialization with your
|
||||
credentials::
|
||||
|
||||
>>> from watcherclient import client
|
||||
>>> watcher = client.Client(VERSION, USERNAME, PASSWORD,
|
||||
... PROJECT_ID, AUTH_URL)
|
||||
|
||||
Here ``VERSION`` can be a string or
|
||||
``watcherclient.api_versions.APIVersion`` obj. If you prefer string value,
|
||||
you can use ``1`` or ``1.X`` (where X is a microversion).
|
||||
|
||||
|
||||
Alternatively, you can create a client instance using the keystoneauth
|
||||
session API. See "The watcherclient Python API" page at
|
||||
python-watcherclient's doc.
|
||||
"""
|
||||
api_version, client_class = _get_client_class_and_version(version)
|
||||
|
||||
kw_api = kwargs.get('os_infra_optim_api_version')
|
||||
endpoint = kwargs.get('endpoint')
|
||||
# If both os_infra_optim_api_version and endpoint are not provided, get
|
||||
# API version from arg.
|
||||
if not kw_api and not endpoint:
|
||||
kwargs['os_infra_optim_api_version'] = api_version.get_string()
|
||||
return client_class(*args, **kwargs)
|
||||
|
||||
235
watcherclient/common/api_versioning.py
Normal file
235
watcherclient/common/api_versioning.py
Normal file
@@ -0,0 +1,235 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
|
||||
from oslo_utils import strutils
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
if not LOG.handlers:
|
||||
LOG.addHandler(logging.StreamHandler())
|
||||
|
||||
|
||||
MINOR_1_START_END_TIMING = '1.1'
|
||||
MINOR_2_FORCE_AUDIT = '1.2'
|
||||
HEADER_NAME = "OpenStack-API-Version"
|
||||
# key is a deprecated version and value is an alternative version.
|
||||
DEPRECATED_VERSIONS = {}
|
||||
|
||||
_type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'")
|
||||
|
||||
|
||||
def allow_start_end_audit_time(requested_version):
|
||||
"""Check if we should support optional start/end attributes for Audit.
|
||||
|
||||
Version 1.1 of the API added support for start and end time of continuous
|
||||
audits.
|
||||
"""
|
||||
return (APIVersion(requested_version) >=
|
||||
APIVersion(MINOR_1_START_END_TIMING))
|
||||
|
||||
|
||||
def launch_audit_forced(requested_version):
|
||||
"""Check if we should support force option for Audit.
|
||||
|
||||
Version 1.2 of the API added support for force option.
|
||||
"""
|
||||
return (APIVersion(requested_version) >=
|
||||
APIVersion(MINOR_2_FORCE_AUDIT))
|
||||
|
||||
|
||||
class APIVersion(object):
|
||||
"""This class represents an API Version Request.
|
||||
|
||||
This class provides convenience methods for manipulation
|
||||
and comparison of version numbers that we need to do to
|
||||
implement microversions.
|
||||
"""
|
||||
|
||||
def __init__(self, version_str=None):
|
||||
"""Create an API version object.
|
||||
|
||||
:param version_str: String representation of APIVersionRequest.
|
||||
Correct format is 'X.Y', where 'X' and 'Y'
|
||||
are int values. None value should be used
|
||||
to create Null APIVersionRequest, which is
|
||||
equal to 0.0
|
||||
"""
|
||||
self.ver_major = 0
|
||||
self.ver_minor = 0
|
||||
|
||||
if version_str is not None:
|
||||
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str)
|
||||
if match:
|
||||
self.ver_major = int(match.group(1))
|
||||
if match.group(2) == "latest":
|
||||
# NOTE(andreykurilin): Infinity allows to easily determine
|
||||
# latest version and doesn't require any additional checks
|
||||
# in comparison methods.
|
||||
self.ver_minor = float("inf")
|
||||
else:
|
||||
self.ver_minor = int(match.group(2))
|
||||
else:
|
||||
msg = _("Invalid format of client version '%s'. "
|
||||
"Expected format 'X.Y', where X is a major part and Y "
|
||||
"is a minor part of version.") % version_str
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
def __str__(self):
|
||||
"""Debug/Logging representation of object."""
|
||||
if self.is_latest():
|
||||
return "Latest API Version Major: %s" % self.ver_major
|
||||
return ("API Version Major: %s, Minor: %s"
|
||||
% (self.ver_major, self.ver_minor))
|
||||
|
||||
def __repr__(self):
|
||||
if self.is_null():
|
||||
return "<APIVersion: null>"
|
||||
else:
|
||||
return "<APIVersion: %s>" % self.get_string()
|
||||
|
||||
def is_null(self):
|
||||
return self.ver_major == 0 and self.ver_minor == 0
|
||||
|
||||
def is_latest(self):
|
||||
return self.ver_minor == float("inf")
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, APIVersion):
|
||||
raise TypeError(_type_error_msg % {"other": other,
|
||||
"cls": self.__class__})
|
||||
|
||||
return ((self.ver_major, self.ver_minor) <
|
||||
(other.ver_major, other.ver_minor))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, APIVersion):
|
||||
raise TypeError(_type_error_msg % {"other": other,
|
||||
"cls": self.__class__})
|
||||
|
||||
return ((self.ver_major, self.ver_minor) ==
|
||||
(other.ver_major, other.ver_minor))
|
||||
|
||||
def __gt__(self, other):
|
||||
if not isinstance(other, APIVersion):
|
||||
raise TypeError(_type_error_msg % {"other": other,
|
||||
"cls": self.__class__})
|
||||
|
||||
return ((self.ver_major, self.ver_minor) >
|
||||
(other.ver_major, other.ver_minor))
|
||||
|
||||
def __le__(self, other):
|
||||
return self < other or self == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self > other or self == other
|
||||
|
||||
def matches(self, min_version, max_version):
|
||||
"""Matches the version object.
|
||||
|
||||
Returns whether the version object represents a version
|
||||
greater than or equal to the minimum version and less than
|
||||
or equal to the maximum version.
|
||||
|
||||
:param min_version: Minimum acceptable version.
|
||||
:param max_version: Maximum acceptable version.
|
||||
:returns: boolean
|
||||
|
||||
If min_version is null then there is no minimum limit.
|
||||
If max_version is null then there is no maximum limit.
|
||||
If self is null then raise ValueError
|
||||
"""
|
||||
|
||||
if self.is_null():
|
||||
raise ValueError(_("Null APIVersion doesn't support 'matches'."))
|
||||
if max_version.is_null() and min_version.is_null():
|
||||
return True
|
||||
elif max_version.is_null():
|
||||
return min_version <= self
|
||||
elif min_version.is_null():
|
||||
return self <= max_version
|
||||
else:
|
||||
return min_version <= self <= max_version
|
||||
|
||||
def get_string(self):
|
||||
"""Version string representation.
|
||||
|
||||
Converts object to string representation which if used to create
|
||||
an APIVersion object results in the same version.
|
||||
"""
|
||||
if self.is_null():
|
||||
raise ValueError(
|
||||
_("Null APIVersion cannot be converted to string."))
|
||||
elif self.is_latest():
|
||||
return "%s.%s" % (self.ver_major, "latest")
|
||||
return "%s.%s" % (self.ver_major, self.ver_minor)
|
||||
|
||||
|
||||
def get_available_major_versions():
|
||||
# NOTE(andreykurilin): available clients version should not be
|
||||
# hardcoded, so let's discover them.
|
||||
matcher = re.compile(r"v[0-9]*$")
|
||||
submodules = pkgutil.iter_modules(
|
||||
[os.path.dirname(os.path.dirname(__file__))])
|
||||
available_versions = [name[1:] for loader, name, ispkg in submodules
|
||||
if matcher.search(name)]
|
||||
return available_versions
|
||||
|
||||
|
||||
def check_major_version(api_version):
|
||||
"""Checks major part of ``APIVersion`` obj is supported.
|
||||
|
||||
:raises watcherclient.exceptions.UnsupportedVersion: if major part is not
|
||||
supported
|
||||
"""
|
||||
available_versions = get_available_major_versions()
|
||||
if (not api_version.is_null() and
|
||||
str(api_version.ver_major) not in available_versions):
|
||||
if len(available_versions) == 1:
|
||||
msg = _("Invalid client version '%(version)s'. "
|
||||
"Major part should be '%(major)s'") % {
|
||||
"version": api_version.get_string(),
|
||||
"major": available_versions[0]}
|
||||
else:
|
||||
msg = _("Invalid client version '%(version)s'. "
|
||||
"Major part must be one of: '%(major)s'") % {
|
||||
"version": api_version.get_string(),
|
||||
"major": ", ".join(available_versions)}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
|
||||
def get_api_version(version_string):
|
||||
"""Returns checked APIVersion object"""
|
||||
version_string = str(version_string)
|
||||
if version_string in DEPRECATED_VERSIONS:
|
||||
LOG.warning(
|
||||
"Version %(deprecated_version)s is deprecated, using "
|
||||
"alternative version %(alternative)s instead.",
|
||||
{"deprecated_version": version_string,
|
||||
"alternative": DEPRECATED_VERSIONS[version_string]})
|
||||
version_string = DEPRECATED_VERSIONS[version_string]
|
||||
if strutils.is_int_like(version_string):
|
||||
version_string = "%s.0" % version_string
|
||||
|
||||
api_version = APIVersion(version_string)
|
||||
check_major_version(api_version)
|
||||
return api_version
|
||||
@@ -39,13 +39,12 @@ Base utilities to build API operation managers and objects on top of.
|
||||
|
||||
import abc
|
||||
import copy
|
||||
from urllib import parse
|
||||
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
def getid(obj):
|
||||
@@ -224,8 +223,7 @@ class BaseManager(HookableMixin):
|
||||
return self.client.delete(url)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ManagerWithFind(BaseManager):
|
||||
class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta):
|
||||
"""Manager with additional `find()`/`findall()` methods."""
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -317,7 +315,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:
|
||||
@@ -467,8 +465,7 @@ class Resource(object):
|
||||
|
||||
@property
|
||||
def human_id(self):
|
||||
"""Human-readable ID which can be used for bash completion.
|
||||
"""
|
||||
"""Human-readable ID which can be used for bash completion."""
|
||||
if self.HUMAN_ID:
|
||||
name = getattr(self, self.NAME_ATTR, None)
|
||||
if name is not None:
|
||||
@@ -476,7 +473,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
|
||||
@@ -498,7 +495,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.
|
||||
@@ -522,6 +519,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
|
||||
|
||||
@@ -36,14 +36,11 @@ Exception definitions.
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient._i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises.
|
||||
"""
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
pass
|
||||
|
||||
|
||||
@@ -118,8 +115,7 @@ class AmbiguousEndpoints(EndpointException):
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions.
|
||||
"""
|
||||
"""The base exception class for all HTTP exceptions."""
|
||||
http_status = 0
|
||||
message = _("HTTP Error")
|
||||
|
||||
@@ -423,7 +419,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)
|
||||
)
|
||||
|
||||
@@ -435,11 +431,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,
|
||||
@@ -463,7 +455,7 @@ def from_response(response, method, url):
|
||||
kwargs["message"] = (error.get("message") or
|
||||
error.get("faultstring"))
|
||||
kwargs["details"] = (error.get("details") or
|
||||
six.text_type(body))
|
||||
str(body))
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = response.text
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
@@ -20,10 +19,9 @@ Base utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
import copy
|
||||
from urllib import parse as urlparse
|
||||
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from watcherclient.openstack.common.apiclient import base
|
||||
from watcherclient.common.apiclient import base
|
||||
|
||||
|
||||
def getid(obj):
|
||||
@@ -32,10 +30,7 @@ def getid(obj):
|
||||
Abstracts the common pattern of allowing both an object or an
|
||||
object's ID (UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
return getattr(obj, 'id', obj)
|
||||
|
||||
|
||||
class Manager(object):
|
||||
@@ -135,6 +130,11 @@ class Manager(object):
|
||||
def _delete(self, url):
|
||||
self.api.raw_request('DELETE', url)
|
||||
|
||||
def _start(self, url, body=None, method='POST'):
|
||||
resp, body = self.api.json_request(method, url, body={})
|
||||
if body:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
|
||||
class Resource(base.Resource):
|
||||
"""Represents a particular instance of an object (tenant, user, etc).
|
||||
|
||||
48
watcherclient/common/command.py
Normal file
48
watcherclient/common/command.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# 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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class Command(command.Command, metaclass=CommandMeta):
|
||||
|
||||
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 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:
|
||||
msg = 'Unsupported scheme: %s' % parts.scheme
|
||||
raise exc.EndpointException(msg)
|
||||
|
||||
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:
|
||||
message = ("Error finding address for %(url)s: %(e)s"
|
||||
% dict(url=url, e=e))
|
||||
raise exc.EndpointNotFound(message)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
endpoint = self.endpoint
|
||||
message = ("Error communicating with %(endpoint)s %(e)s"
|
||||
% dict(endpoint=endpoint, e=e))
|
||||
raise exc.ConnectionRefused(message)
|
||||
|
||||
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.warn("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('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('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)
|
||||
640
watcherclient/common/httpclient.py
Normal file
640
watcherclient/common/httpclient.py
Normal file
@@ -0,0 +1,640 @@
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
from distutils import version
|
||||
import functools
|
||||
import hashlib
|
||||
import http.client
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import ssl
|
||||
import textwrap
|
||||
import time
|
||||
from urllib import parse as urlparse
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
from keystoneauth1 import exceptions as kexceptions
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import strutils
|
||||
import requests
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient import exceptions
|
||||
|
||||
|
||||
# Record the latest version that this client was tested with.
|
||||
DEFAULT_VER = '1.latest'
|
||||
# Minor version 4 for adding webhook API
|
||||
LAST_KNOWN_API_VERSION = 4
|
||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
USER_AGENT = 'python-watcherclient'
|
||||
CHUNKSIZE = 1024 * 64 # 64kB
|
||||
|
||||
API_VERSION = '/v1'
|
||||
API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'cached', 'default')
|
||||
|
||||
|
||||
DEFAULT_MAX_RETRIES = 5
|
||||
DEFAULT_RETRY_INTERVAL = 2
|
||||
SENSITIVE_HEADERS = ('X-Auth-Token',)
|
||||
|
||||
|
||||
SUPPORTED_ENDPOINT_SCHEME = ('http', 'https')
|
||||
|
||||
|
||||
def _trim_endpoint_api_version(url):
|
||||
"""Trim API version and trailing slash from endpoint."""
|
||||
return url.rstrip('/').rstrip(API_VERSION)
|
||||
|
||||
|
||||
def _extract_error_json(body):
|
||||
"""Return error_message from the HTTP response body."""
|
||||
error_json = {}
|
||||
try:
|
||||
body_json = jsonutils.loads(body)
|
||||
if 'error_message' in body_json:
|
||||
raw_msg = body_json['error_message']
|
||||
error_json = jsonutils.loads(raw_msg)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return error_json
|
||||
|
||||
|
||||
def get_server(endpoint):
|
||||
"""Extract and return the server & port that we're connecting to."""
|
||||
if endpoint is None:
|
||||
return None, None
|
||||
parts = urlparse.urlparse(endpoint)
|
||||
return parts.hostname, str(parts.port)
|
||||
|
||||
|
||||
class VersionNegotiationMixin(object):
|
||||
def negotiate_version(self, conn, resp):
|
||||
"""Negotiate the server version
|
||||
|
||||
Assumption: Called after receiving a 406 error when doing a request.
|
||||
|
||||
param conn: A connection object
|
||||
param resp: The response object from http request
|
||||
"""
|
||||
if self.api_version_select_state not in API_VERSION_SELECTED_STATES:
|
||||
raise RuntimeError(
|
||||
_('Error: self.api_version_select_state should be one of the '
|
||||
'values in: "%(valid)s" but had the value: "%(value)s"') %
|
||||
{'valid': ', '.join(API_VERSION_SELECTED_STATES),
|
||||
'value': self.api_version_select_state})
|
||||
min_ver, max_ver = self._parse_version_headers(resp)
|
||||
# If the user requested an explicit version or we have negotiated a
|
||||
# version and still failing then error now. The server could
|
||||
# support the version requested but the requested operation may not
|
||||
# be supported by the requested version.
|
||||
if self.api_version_select_state == 'user':
|
||||
raise exceptions.UnsupportedVersion(textwrap.fill(
|
||||
_("Requested API version %(req)s is not supported by the "
|
||||
"server or the requested operation is not supported by the "
|
||||
"requested version. Supported version range is %(min)s to "
|
||||
"%(max)s")
|
||||
% {'req': self.os_infra_optim_api_version,
|
||||
'min': min_ver, 'max': max_ver}))
|
||||
if self.api_version_select_state == 'negotiated':
|
||||
raise exceptions.UnsupportedVersion(textwrap.fill(
|
||||
_("No API version was specified and the requested operation "
|
||||
"was not supported by the client's negotiated API version "
|
||||
"%(req)s. Supported version range is: %(min)s to %(max)s")
|
||||
% {'req': self.os_infra_optim_api_version,
|
||||
'min': min_ver, 'max': max_ver}))
|
||||
|
||||
negotiated_ver = str(
|
||||
min(version.StrictVersion(self.os_infra_optim_api_version),
|
||||
version.StrictVersion(max_ver)))
|
||||
if negotiated_ver < min_ver:
|
||||
negotiated_ver = min_ver
|
||||
# server handles microversions, but doesn't support
|
||||
# the requested version, so try a negotiated version
|
||||
self.api_version_select_state = 'negotiated'
|
||||
self.os_infra_optim_api_version = negotiated_ver
|
||||
LOG.debug('Negotiated API version is %s', negotiated_ver)
|
||||
|
||||
return negotiated_ver
|
||||
|
||||
def _generic_parse_version_headers(self, accessor_func):
|
||||
min_ver = accessor_func('OpenStack-API-Minimum-Version',
|
||||
None)
|
||||
max_ver = accessor_func('OpenStack-API-Maximum-Version',
|
||||
None)
|
||||
return min_ver, max_ver
|
||||
|
||||
def _parse_version_headers(self, accessor_func):
|
||||
# NOTE(jlvillal): Declared for unit testing purposes
|
||||
raise NotImplementedError()
|
||||
|
||||
def _make_simple_request(self, conn, method, url):
|
||||
# NOTE(jlvillal): Declared for unit testing purposes
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
_RETRY_EXCEPTIONS = (exceptions.ServiceUnavailable,
|
||||
exceptions.ConnectionRefused,
|
||||
kexceptions.RetriableConnectionFailure)
|
||||
|
||||
|
||||
def with_retries(func):
|
||||
"""Wrapper for _http_request adding support for retries."""
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, url, method, **kwargs):
|
||||
if self.conflict_max_retries is None:
|
||||
self.conflict_max_retries = DEFAULT_MAX_RETRIES
|
||||
if self.conflict_retry_interval is None:
|
||||
self.conflict_retry_interval = DEFAULT_RETRY_INTERVAL
|
||||
|
||||
num_attempts = self.conflict_max_retries + 1
|
||||
for attempt in range(1, num_attempts + 1):
|
||||
try:
|
||||
return func(self, url, method, **kwargs)
|
||||
except _RETRY_EXCEPTIONS as error:
|
||||
msg = ("Error contacting Watcher server: %(error)s. "
|
||||
"Attempt %(attempt)d of %(total)d" %
|
||||
{'attempt': attempt,
|
||||
'total': num_attempts,
|
||||
'error': error})
|
||||
if attempt == num_attempts:
|
||||
LOG.error(msg)
|
||||
raise
|
||||
else:
|
||||
LOG.debug(msg)
|
||||
time.sleep(self.conflict_retry_interval)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class HTTPClient(VersionNegotiationMixin):
|
||||
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
self.endpoint = endpoint
|
||||
self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
|
||||
self.auth_token = kwargs.get('token')
|
||||
self.auth_ref = kwargs.get('auth_ref')
|
||||
self.os_infra_optim_api_version = kwargs.get(
|
||||
'os_infra_optim_api_version', DEFAULT_VER)
|
||||
self.api_version_select_state = kwargs.get(
|
||||
'api_version_select_state', 'default')
|
||||
self.conflict_max_retries = kwargs.pop('max_retries',
|
||||
DEFAULT_MAX_RETRIES)
|
||||
self.conflict_retry_interval = kwargs.pop('retry_interval',
|
||||
DEFAULT_RETRY_INTERVAL)
|
||||
self.session = requests.Session()
|
||||
|
||||
parts = urlparse.urlparse(endpoint)
|
||||
if parts.scheme not in SUPPORTED_ENDPOINT_SCHEME:
|
||||
msg = _('Unsupported scheme: %s') % parts.scheme
|
||||
raise exceptions.EndpointException(msg)
|
||||
|
||||
if parts.scheme == 'https':
|
||||
if kwargs.get('insecure') is True:
|
||||
self.session.verify = False
|
||||
elif kwargs.get('ca_file'):
|
||||
self.session.verify = kwargs['ca_file']
|
||||
self.session.cert = (kwargs.get('cert_file'),
|
||||
kwargs.get('key_file'))
|
||||
|
||||
def _process_header(self, name, value):
|
||||
"""Redacts any sensitive header
|
||||
|
||||
Redact a header that contains sensitive information, by returning an
|
||||
updated header with the sha1 hash of that value. The redacted value is
|
||||
prefixed by '{SHA1}' because that's the convention used within
|
||||
OpenStack.
|
||||
|
||||
:returns: A tuple of (name, value)
|
||||
name: the safe encoding format of name
|
||||
value: the redacted value if name is x-auth-token,
|
||||
or the safe encoding format of name
|
||||
|
||||
"""
|
||||
if name in SENSITIVE_HEADERS:
|
||||
v = value.encode('utf-8')
|
||||
h = hashlib.sha1(v)
|
||||
d = h.hexdigest()
|
||||
return (name, "{SHA1}%s" % d)
|
||||
else:
|
||||
return (name, value)
|
||||
|
||||
def log_curl_request(self, method, url, kwargs):
|
||||
curl = ['curl -i -X %s' % method]
|
||||
|
||||
for (key, value) in kwargs['headers'].items():
|
||||
header = '-H \'%s: %s\'' % self._process_header(key, value)
|
||||
curl.append(header)
|
||||
|
||||
if not self.session.verify:
|
||||
curl.append('-k')
|
||||
elif isinstance(self.session.verify, str):
|
||||
curl.append('--cacert %s' % self.session.verify)
|
||||
|
||||
if self.session.cert:
|
||||
curl.append('--cert %s' % self.session.cert[0])
|
||||
curl.append('--key %s' % self.session.cert[1])
|
||||
|
||||
if 'body' in kwargs:
|
||||
body = strutils.mask_password(kwargs['body'])
|
||||
curl.append('-d \'%s\'' % body)
|
||||
|
||||
curl.append(urlparse.urljoin(self.endpoint_trimmed, url))
|
||||
LOG.debug(' '.join(curl))
|
||||
|
||||
@staticmethod
|
||||
def log_http_response(resp, body=None):
|
||||
# NOTE(aarefiev): resp.raw is urllib3 response object, it's used
|
||||
# only to get 'version', response from request with 'stream = True'
|
||||
# should be used for raw reading.
|
||||
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
|
||||
dump = ['\nHTTP/%.1f %s %s' % status]
|
||||
dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
|
||||
dump.append('')
|
||||
if body:
|
||||
body = strutils.mask_password(body)
|
||||
dump.extend([body, ''])
|
||||
LOG.debug('\n'.join(dump))
|
||||
|
||||
def _make_connection_url(self, url):
|
||||
return '%s/%s' % (self.endpoint_trimmed.rstrip('/'), url.lstrip('/'))
|
||||
|
||||
def _parse_version_headers(self, resp):
|
||||
return self._generic_parse_version_headers(resp.headers.get)
|
||||
|
||||
def _make_simple_request(self, conn, method, url):
|
||||
return conn.request(method, self._make_connection_url(url))
|
||||
|
||||
@with_retries
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around request.Session.request to handle tasks such
|
||||
as setting headers and error handling.
|
||||
"""
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
||||
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
||||
if self.os_infra_optim_api_version:
|
||||
api_version = api_versioning.get_api_version(
|
||||
self.os_infra_optim_api_version)
|
||||
if api_version.is_latest():
|
||||
api_version = api_versioning.get_api_version(
|
||||
LATEST_VERSION)
|
||||
kwargs['headers'].setdefault(
|
||||
'OpenStack-API-Version',
|
||||
' '.join(['infra-optim', api_version.get_string()]))
|
||||
if self.auth_token:
|
||||
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
||||
|
||||
self.log_curl_request(method, url, kwargs)
|
||||
|
||||
# NOTE(aarefiev): This is for backwards compatibility, request
|
||||
# expected body in 'data' field, previously we used httplib,
|
||||
# which expected 'body' field.
|
||||
body = kwargs.pop('body', None)
|
||||
if body:
|
||||
kwargs['data'] = body
|
||||
|
||||
conn_url = self._make_connection_url(url)
|
||||
try:
|
||||
resp = self.session.request(method,
|
||||
conn_url,
|
||||
**kwargs)
|
||||
|
||||
# TODO(deva): implement graceful client downgrade when connecting
|
||||
# to servers that did not support microversions. Details here:
|
||||
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-watcher-user-specified # noqa
|
||||
|
||||
if resp.status_code == http.client.NOT_ACCEPTABLE:
|
||||
negotiated_ver = self.negotiate_version(self.session, resp)
|
||||
kwargs['headers']['OpenStack-API-Version'] = (
|
||||
' '.join(['infra-optim', negotiated_ver]))
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
message = (_("Error has occurred while handling "
|
||||
"request for %(url)s: %(e)s") %
|
||||
dict(url=conn_url, e=e))
|
||||
# NOTE(aarefiev): not valid request(invalid url, missing schema,
|
||||
# and so on), retrying is not needed.
|
||||
if isinstance(e, ValueError):
|
||||
raise exceptions.ValidationError(message)
|
||||
|
||||
raise exceptions.ConnectionRefused(message)
|
||||
|
||||
body_iter = resp.iter_content(chunk_size=CHUNKSIZE)
|
||||
|
||||
# Read body into string if it isn't obviously image data
|
||||
body_str = None
|
||||
if resp.headers.get('Content-Type') != 'application/octet-stream':
|
||||
# decoding byte to string is necessary for Python 3 compatibility
|
||||
# this issues has not been found with Python 3 unit tests
|
||||
# because the test creates a fake http response of type str
|
||||
# the if statement satisfies test (str) and real (bytes) behavior
|
||||
body_list = [
|
||||
chunk.decode("utf-8") if isinstance(chunk, bytes)
|
||||
else chunk for chunk in body_iter
|
||||
]
|
||||
body_str = ''.join(body_list)
|
||||
self.log_http_response(resp, body_str)
|
||||
body_iter = io.StringIO(body_str)
|
||||
else:
|
||||
self.log_http_response(resp)
|
||||
|
||||
if resp.status_code >= http.client.BAD_REQUEST:
|
||||
error_json = _extract_error_json(body_str)
|
||||
raise exceptions.from_response(
|
||||
resp, error_json.get('faultstring'),
|
||||
error_json.get('debuginfo'), method, url)
|
||||
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
|
||||
http.client.FOUND,
|
||||
http.client.USE_PROXY):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
return self._http_request(resp['location'], method, **kwargs)
|
||||
elif resp.status_code == http.client.MULTIPLE_CHOICES:
|
||||
raise exceptions.from_response(resp, method=method, url=url)
|
||||
|
||||
return resp, body_iter
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||
|
||||
if 'body' in kwargs:
|
||||
kwargs['body'] = jsonutils.dumps(kwargs['body'])
|
||||
|
||||
resp, body_iter = self._http_request(url, method, **kwargs)
|
||||
content_type = resp.headers.get('Content-Type')
|
||||
|
||||
if (resp.status_code in (http.client.NO_CONTENT,
|
||||
http.client.RESET_CONTENT) or
|
||||
content_type is None):
|
||||
return resp, list()
|
||||
|
||||
if 'application/json' in content_type:
|
||||
body = ''.join([chunk for chunk in body_iter])
|
||||
try:
|
||||
body = jsonutils.loads(body)
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
|
||||
return resp, body
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type',
|
||||
'application/octet-stream')
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
|
||||
class VerifiedHTTPSConnection(http.client.HTTPSConnection):
|
||||
"""httplib-compatible connection using client-side SSL authentication
|
||||
|
||||
:see http://code.activestate.com/recipes/
|
||||
577548-https-httplib-client-connection-with-certificate-v/
|
||||
"""
|
||||
|
||||
def __init__(self, host, port, key_file=None, cert_file=None,
|
||||
ca_file=None, timeout=None, insecure=False):
|
||||
super(VerifiedHTTPSConnection, self).__init__(
|
||||
self, host, port, key_file=key_file, cert_file=cert_file)
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
if ca_file is not None:
|
||||
self.ca_file = ca_file
|
||||
else:
|
||||
self.ca_file = self.get_system_ca_file()
|
||||
self.timeout = timeout
|
||||
self.insecure = insecure
|
||||
|
||||
def connect(self):
|
||||
"""Connect to a host on a given (SSL) port.
|
||||
|
||||
If ca_file is pointing somewhere, use it to check Server Certificate.
|
||||
|
||||
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
|
||||
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
|
||||
ssl.wrap_socket(), which forces SSL to check server certificate against
|
||||
our client certificate.
|
||||
"""
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
|
||||
if self._tunnel_host:
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
|
||||
if self.insecure is True:
|
||||
kwargs = {'cert_reqs': ssl.CERT_NONE}
|
||||
else:
|
||||
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
|
||||
|
||||
if self.cert_file:
|
||||
kwargs['certfile'] = self.cert_file
|
||||
if self.key_file:
|
||||
kwargs['keyfile'] = self.key_file
|
||||
|
||||
self.sock = ssl.wrap_socket(sock, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_system_ca_file():
|
||||
"""Return path to system default CA file."""
|
||||
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
||||
# Suse, FreeBSD/OpenBSD
|
||||
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
'/etc/ssl/ca-bundle.pem',
|
||||
'/etc/ssl/cert.pem']
|
||||
for ca in ca_path:
|
||||
if os.path.exists(ca):
|
||||
return ca
|
||||
return None
|
||||
|
||||
|
||||
class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
|
||||
"""HTTP client based on Keystone client session."""
|
||||
|
||||
def __init__(self,
|
||||
os_infra_optim_api_version,
|
||||
api_version_select_state,
|
||||
max_retries,
|
||||
retry_interval,
|
||||
endpoint,
|
||||
**kwargs):
|
||||
self.os_infra_optim_api_version = os_infra_optim_api_version
|
||||
self.api_version_select_state = api_version_select_state
|
||||
self.conflict_max_retries = max_retries
|
||||
self.conflict_retry_interval = retry_interval
|
||||
self.endpoint = endpoint
|
||||
|
||||
super(SessionClient, self).__init__(**kwargs)
|
||||
|
||||
def _parse_version_headers(self, resp):
|
||||
return self._generic_parse_version_headers(resp.headers.get)
|
||||
|
||||
def _make_simple_request(self, conn, method, url):
|
||||
# NOTE: conn is self.session for this class
|
||||
return conn.request(url, method, raise_exc=False)
|
||||
|
||||
@with_retries
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('user_agent', USER_AGENT)
|
||||
kwargs.setdefault('auth', self.auth)
|
||||
if isinstance(self.endpoint_override, str):
|
||||
kwargs.setdefault(
|
||||
'endpoint_override',
|
||||
_trim_endpoint_api_version(self.endpoint_override)
|
||||
)
|
||||
|
||||
if getattr(self, 'os_infra_optim_api_version', None):
|
||||
api_version = api_versioning.get_api_version(
|
||||
self.os_infra_optim_api_version)
|
||||
if api_version.is_latest():
|
||||
api_version = api_versioning.get_api_version(
|
||||
LATEST_VERSION)
|
||||
kwargs['headers'].setdefault(
|
||||
'OpenStack-API-Version',
|
||||
' '.join(['infra-optim',
|
||||
api_version.get_string()]))
|
||||
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('interface', self.interface)
|
||||
endpoint_filter.setdefault('service_type', self.service_type)
|
||||
endpoint_filter.setdefault('region_name', self.region_name)
|
||||
|
||||
resp = self.session.request(url, method,
|
||||
raise_exc=False, **kwargs)
|
||||
if resp.status_code == http.client.NOT_ACCEPTABLE:
|
||||
negotiated_ver = self.negotiate_version(self.session, resp)
|
||||
kwargs['headers']['OpenStack-API-Version'] = (
|
||||
' '.join(['infra-optim', negotiated_ver]))
|
||||
return self._http_request(url, method, **kwargs)
|
||||
if resp.status_code >= http.client.BAD_REQUEST:
|
||||
error_json = _extract_error_json(resp.content)
|
||||
raise exceptions.from_response(
|
||||
resp, error_json.get('faultstring'),
|
||||
error_json.get('debuginfo'), method, url)
|
||||
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
|
||||
http.client.FOUND, http.client.USE_PROXY):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
location = resp.headers.get('location')
|
||||
resp = self._http_request(location, method, **kwargs)
|
||||
elif resp.status_code == http.client.MULTIPLE_CHOICES:
|
||||
raise exceptions.from_response(resp, method=method, url=url)
|
||||
return resp
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||
|
||||
if 'body' in kwargs:
|
||||
kwargs['data'] = jsonutils.dumps(kwargs.pop('body'))
|
||||
|
||||
resp = self._http_request(url, method, **kwargs)
|
||||
body = resp.content
|
||||
content_type = resp.headers.get('content-type', None)
|
||||
status = resp.status_code
|
||||
if (status in (http.client.NO_CONTENT, http.client.RESET_CONTENT) or
|
||||
content_type is None):
|
||||
return resp, list()
|
||||
if 'application/json' in content_type:
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
|
||||
return resp, body
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type',
|
||||
'application/octet-stream')
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
|
||||
def _construct_http_client(endpoint=None,
|
||||
session=None,
|
||||
token=None,
|
||||
auth_ref=None,
|
||||
os_infra_optim_api_version=DEFAULT_VER,
|
||||
api_version_select_state='default',
|
||||
max_retries=DEFAULT_MAX_RETRIES,
|
||||
retry_interval=DEFAULT_RETRY_INTERVAL,
|
||||
timeout=600,
|
||||
ca_file=None,
|
||||
cert_file=None,
|
||||
key_file=None,
|
||||
insecure=None,
|
||||
**kwargs):
|
||||
if session:
|
||||
kwargs.setdefault('service_type', 'infra-optim')
|
||||
kwargs.setdefault('user_agent', 'python-watcherclient')
|
||||
kwargs.setdefault('interface', kwargs.pop('endpoint_type', None))
|
||||
kwargs.setdefault('endpoint_override', endpoint)
|
||||
|
||||
ignored = {'token': token,
|
||||
'auth_ref': auth_ref,
|
||||
'timeout': timeout != 600,
|
||||
'ca_file': ca_file,
|
||||
'cert_file': cert_file,
|
||||
'key_file': key_file,
|
||||
'insecure': insecure}
|
||||
|
||||
dvars = [k for k, v in ignored.items() if v]
|
||||
|
||||
if dvars:
|
||||
LOG.warning('The following arguments are ignored when using '
|
||||
'the session to construct a client: %s',
|
||||
', '.join(dvars))
|
||||
|
||||
return SessionClient(
|
||||
session=session,
|
||||
os_infra_optim_api_version=os_infra_optim_api_version,
|
||||
api_version_select_state=api_version_select_state,
|
||||
max_retries=max_retries,
|
||||
retry_interval=retry_interval,
|
||||
endpoint=endpoint,
|
||||
**kwargs)
|
||||
else:
|
||||
if kwargs:
|
||||
LOG.warning('The following arguments are being ignored when '
|
||||
'constructing the client: %s', ', '.join(kwargs))
|
||||
|
||||
return HTTPClient(
|
||||
endpoint=endpoint,
|
||||
token=token,
|
||||
auth_ref=auth_ref,
|
||||
os_infra_optim_api_version=os_infra_optim_api_version,
|
||||
api_version_select_state=api_version_select_state,
|
||||
max_retries=max_retries,
|
||||
retry_interval=retry_interval,
|
||||
timeout=timeout,
|
||||
ca_file=ca_file,
|
||||
cert_file=cert_file,
|
||||
key_file=key_file,
|
||||
insecure=insecure)
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
@@ -15,14 +14,16 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
import yaml
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common import importutils
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
@@ -72,7 +73,7 @@ def import_versioned_module(version, submodule=None):
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
def split_and_deserialize(string):
|
||||
def split_and_deserialize(string, exclude_fields=[]):
|
||||
"""Split and try to JSON deserialize a string.
|
||||
|
||||
Gets a string with the KEY=VALUE format, split it (using '=' as the
|
||||
@@ -85,10 +86,11 @@ def split_and_deserialize(string):
|
||||
except ValueError:
|
||||
raise exc.CommandError(_('Attributes must be a list of '
|
||||
'PATH=VALUE not "%s"') % string)
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
if key not in exclude_fields:
|
||||
try:
|
||||
value = jsonutils.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return (key, value)
|
||||
|
||||
@@ -101,7 +103,7 @@ def args_array_to_dict(kwargs, key_to_convert):
|
||||
return kwargs
|
||||
|
||||
|
||||
def args_array_to_patch(op, attributes):
|
||||
def args_array_to_patch(op, attributes, exclude_fields=[]):
|
||||
patch = []
|
||||
for attr in attributes:
|
||||
# Sanitize
|
||||
@@ -109,7 +111,8 @@ def args_array_to_patch(op, attributes):
|
||||
attr = '/' + attr
|
||||
|
||||
if op in ['add', 'replace']:
|
||||
path, value = split_and_deserialize(attr)
|
||||
path, value = split_and_deserialize(attr,
|
||||
exclude_fields=exclude_fields)
|
||||
patch.append({'op': op, 'path': path, 'value': value})
|
||||
|
||||
elif op == "remove":
|
||||
@@ -156,17 +159,21 @@ def common_params_for_list(args, fields, field_labels):
|
||||
args.sort_dir)
|
||||
params['sort_dir'] = args.sort_dir
|
||||
|
||||
marker = getattr(args, 'marker', None)
|
||||
if marker is not None:
|
||||
params['marker'] = marker
|
||||
params['detail'] = args.detail
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def common_filters(limit=None, sort_key=None, sort_dir=None):
|
||||
def common_filters(limit=None, sort_key=None, sort_dir=None, marker=None):
|
||||
"""Generate common filters for any list request.
|
||||
|
||||
:param limit: maximum number of entities to return.
|
||||
:param sort_key: field to use for sorting.
|
||||
:param sort_dir: direction of sorting: 'asc' or 'desc'.
|
||||
:param marker: The last actionplan UUID of the previous page.
|
||||
:returns: list of string filters.
|
||||
"""
|
||||
filters = []
|
||||
@@ -176,4 +183,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
|
||||
|
||||
@@ -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
|
||||
@@ -12,29 +11,72 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
from watcherclient.openstack.common.apiclient.exceptions import * # noqa
|
||||
from watcherclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
# NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
|
||||
# compatibility.
|
||||
InvalidEndpoint = EndpointException
|
||||
CommunicationError = ConnectionRefused
|
||||
HTTPBadRequest = BadRequest
|
||||
HTTPInternalServerError = InternalServerError
|
||||
HTTPNotFound = NotFound
|
||||
HTTPServiceUnavailable = ServiceUnavailable
|
||||
InvalidEndpoint = exceptions.EndpointException
|
||||
CommunicationError = exceptions.ConnectionRefused
|
||||
HTTPBadRequest = exceptions.BadRequest
|
||||
HTTPInternalServerError = exceptions.InternalServerError
|
||||
HTTPNotFound = exceptions.NotFound
|
||||
HTTPServiceUnavailable = exceptions.ServiceUnavailable
|
||||
|
||||
|
||||
class AmbiguousAuthSystem(ClientException):
|
||||
CommandError = exceptions.CommandError
|
||||
"""Error in CLI tool.
|
||||
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.CommandError`
|
||||
"""
|
||||
|
||||
Unauthorized = exceptions.Unauthorized
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.Unauthorized`
|
||||
"""
|
||||
|
||||
InternalServerError = exceptions.InternalServerError
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.InternalServerError`
|
||||
"""
|
||||
|
||||
ValidationError = exceptions.ValidationError
|
||||
"""Error in validation on API client side.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.ValidationError`
|
||||
"""
|
||||
|
||||
Conflict = exceptions.Conflict
|
||||
ConnectionRefused = exceptions.ConnectionRefused
|
||||
EndpointException = exceptions.EndpointException
|
||||
EndpointNotFound = exceptions.EndpointNotFound
|
||||
ServiceUnavailable = exceptions.ServiceUnavailable
|
||||
|
||||
|
||||
class UnsupportedVersion(Exception):
|
||||
"""Unsupported API Version
|
||||
|
||||
Indicates that the user is trying to use an unsupported version of the API.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousAuthSystem(exceptions.ClientException):
|
||||
"""Could not obtain token and endpoint using provided credentials."""
|
||||
pass
|
||||
|
||||
|
||||
# Alias for backwards compatibility
|
||||
AmbigiousAuthSystem = AmbiguousAuthSystem
|
||||
|
||||
|
||||
class InvalidAttribute(ClientException):
|
||||
class InvalidAttribute(exceptions.ClientException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -58,18 +100,17 @@ 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
|
||||
# openstack.common.apiclient.exceptions module where if the
|
||||
# common.apiclient.exceptions module where if the
|
||||
# content-type of the response is text/* then it expects
|
||||
# the response to have a 'text' attribute, but that
|
||||
# doesn't always seem to necessarily be the case.
|
||||
# This is to work around that problem.
|
||||
response.text = ''
|
||||
|
||||
return exceptions.from_response(response, message, url)
|
||||
return exceptions.from_response(response, method, url)
|
||||
|
||||
279
watcherclient/locale/fr/LC_MESSAGES/watcherclient.po
Normal file
279
watcherclient/locale/fr/LC_MESSAGES/watcherclient.po
Normal file
@@ -0,0 +1,279 @@
|
||||
# French translations for python-watcherclient.
|
||||
# Copyright (C) 2016 ORGANIZATION
|
||||
# This file is distributed under the same license as the
|
||||
# python-watcherclient project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: python-watcherclient 0.20.1.dev4\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-01-14 14:57+0100\n"
|
||||
"PO-Revision-Date: 2016-01-12 02:05+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: fr\n"
|
||||
"Language-Team: fr <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.1.1\n"
|
||||
|
||||
#: watcherclient/client.py:103
|
||||
msgid "Must provide Keystone credentials or user-defined endpoint and token"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:279
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:344
|
||||
msgid ""
|
||||
"Unable to determine the Keystone version to authenticate with using the "
|
||||
"given auth_url."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:380
|
||||
msgid ""
|
||||
"You must provide a username via either --os-username or via "
|
||||
"env[OS_USERNAME]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:396
|
||||
msgid ""
|
||||
"You must provide a password via either --os-password, env[OS_PASSWORD], "
|
||||
"or prompted response"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:403
|
||||
msgid ""
|
||||
"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."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:410
|
||||
msgid ""
|
||||
"You must provide an auth url via either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:473
|
||||
msgid "Invalid OpenStack Identity credentials"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:483
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid subcommand"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:40
|
||||
#, python-format
|
||||
msgid "Missing arguments: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:158
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Field labels list %(labels)s has different number of elements than fields"
|
||||
" list %(fields)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:88
|
||||
#, python-format
|
||||
msgid "Unsupported scheme: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:162
|
||||
#, python-format
|
||||
msgid "Error finding address for %(url)s: %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:167
|
||||
#, python-format
|
||||
msgid "Error communicating with %(endpoint)s %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:181
|
||||
msgid "Request returned failure status."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:213 watcherclient/common/http.py:337
|
||||
msgid "Could not decode response body as JSON"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:87
|
||||
#, python-format
|
||||
msgid "Attributes must be a list of PATH=VALUE not \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:120
|
||||
#, python-format
|
||||
msgid "Unknown PATCH operation: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:136
|
||||
#, python-format
|
||||
msgid "Expected non-negative --limit, got %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:147
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(sort_key)s is an invalid field for sorting, valid values for --sort-key"
|
||||
" are: %(valid)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:155
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s is an invalid value for sort direction, valid values for --sort-dir "
|
||||
"are: 'asc', 'desc'"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/base.py:244
|
||||
#: watcherclient/common/apiclient/base.py:401
|
||||
#, python-format
|
||||
msgid "No %(name)s matching %(args)s."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:83
|
||||
#, python-format
|
||||
msgid "Authentication failed. Missing options: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:92
|
||||
#, python-format
|
||||
msgid "AuthSystemNotFound: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:115
|
||||
#, python-format
|
||||
msgid "AmbiguousEndpoints: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:122
|
||||
msgid "HTTP Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:142
|
||||
msgid "HTTP Redirection"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:150
|
||||
msgid "HTTP Client Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:159
|
||||
msgid "HTTP Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:169
|
||||
msgid "Multiple Choices"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:178
|
||||
msgid "Bad Request"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:188
|
||||
msgid "Unauthorized"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:197
|
||||
msgid "Payment Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:207
|
||||
msgid "Forbidden"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:217
|
||||
msgid "Not Found"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:227
|
||||
msgid "Method Not Allowed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:237
|
||||
msgid "Not Acceptable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:246
|
||||
msgid "Proxy Authentication Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:255
|
||||
msgid "Request Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:265
|
||||
msgid "Conflict"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:275
|
||||
msgid "Gone"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:285
|
||||
msgid "Length Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:295
|
||||
msgid "Precondition Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:304
|
||||
msgid "Request Entity Too Large"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:321
|
||||
msgid "Request-URI Too Long"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:331
|
||||
msgid "Unsupported Media Type"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:341
|
||||
msgid "Requested Range Not Satisfiable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:350
|
||||
msgid "Expectation Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:360
|
||||
msgid "Unprocessable Entity"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:369
|
||||
msgid "Internal Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:380
|
||||
msgid "Not Implemented"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:390
|
||||
msgid "Bad Gateway"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:399
|
||||
msgid "Service Unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:409
|
||||
msgid "Gateway Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:418
|
||||
msgid "HTTP Version Not Supported"
|
||||
msgstr ""
|
||||
|
||||
277
watcherclient/locale/watcherclient.pot
Normal file
277
watcherclient/locale/watcherclient.pot
Normal file
@@ -0,0 +1,277 @@
|
||||
# Translations template for python-watcherclient.
|
||||
# Copyright (C) 2016 ORGANIZATION
|
||||
# This file is distributed under the same license as the
|
||||
# python-watcherclient project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: python-watcherclient 0.20.1.dev5\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-01-14 14:57+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.1.1\n"
|
||||
|
||||
#: watcherclient/client.py:103
|
||||
msgid "Must provide Keystone credentials or user-defined endpoint and token"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:279
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:344
|
||||
msgid ""
|
||||
"Unable to determine the Keystone version to authenticate with using the "
|
||||
"given auth_url."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:380
|
||||
msgid ""
|
||||
"You must provide a username via either --os-username or via "
|
||||
"env[OS_USERNAME]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:396
|
||||
msgid ""
|
||||
"You must provide a password via either --os-password, env[OS_PASSWORD], "
|
||||
"or prompted response"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:403
|
||||
msgid ""
|
||||
"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."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:410
|
||||
msgid ""
|
||||
"You must provide an auth url via either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:473
|
||||
msgid "Invalid OpenStack Identity credentials"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:483
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid subcommand"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:40
|
||||
#, python-format
|
||||
msgid "Missing arguments: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:158
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Field labels list %(labels)s has different number of elements than fields"
|
||||
" list %(fields)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:88
|
||||
#, python-format
|
||||
msgid "Unsupported scheme: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:162
|
||||
#, python-format
|
||||
msgid "Error finding address for %(url)s: %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:167
|
||||
#, python-format
|
||||
msgid "Error communicating with %(endpoint)s %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:181
|
||||
msgid "Request returned failure status."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:213 watcherclient/common/http.py:337
|
||||
msgid "Could not decode response body as JSON"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:87
|
||||
#, python-format
|
||||
msgid "Attributes must be a list of PATH=VALUE not \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:120
|
||||
#, python-format
|
||||
msgid "Unknown PATCH operation: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:136
|
||||
#, python-format
|
||||
msgid "Expected non-negative --limit, got %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:147
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(sort_key)s is an invalid field for sorting, valid values for --sort-key"
|
||||
" are: %(valid)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:155
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s is an invalid value for sort direction, valid values for --sort-dir "
|
||||
"are: 'asc', 'desc'"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/base.py:244
|
||||
#: watcherclient/common/apiclient/base.py:401
|
||||
#, python-format
|
||||
msgid "No %(name)s matching %(args)s."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:83
|
||||
#, python-format
|
||||
msgid "Authentication failed. Missing options: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:92
|
||||
#, python-format
|
||||
msgid "AuthSystemNotFound: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:115
|
||||
#, python-format
|
||||
msgid "AmbiguousEndpoints: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:122
|
||||
msgid "HTTP Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:142
|
||||
msgid "HTTP Redirection"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:150
|
||||
msgid "HTTP Client Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:159
|
||||
msgid "HTTP Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:169
|
||||
msgid "Multiple Choices"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:178
|
||||
msgid "Bad Request"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:188
|
||||
msgid "Unauthorized"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:197
|
||||
msgid "Payment Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:207
|
||||
msgid "Forbidden"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:217
|
||||
msgid "Not Found"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:227
|
||||
msgid "Method Not Allowed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:237
|
||||
msgid "Not Acceptable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:246
|
||||
msgid "Proxy Authentication Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:255
|
||||
msgid "Request Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:265
|
||||
msgid "Conflict"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:275
|
||||
msgid "Gone"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:285
|
||||
msgid "Length Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:295
|
||||
msgid "Precondition Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:304
|
||||
msgid "Request Entity Too Large"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:321
|
||||
msgid "Request-URI Too Long"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:331
|
||||
msgid "Unsupported Media Type"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:341
|
||||
msgid "Requested Range Not Satisfiable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:350
|
||||
msgid "Expectation Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:360
|
||||
msgid "Unprocessable Entity"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:369
|
||||
msgid "Internal Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:380
|
||||
msgid "Not Implemented"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:390
|
||||
msgid "Bad Gateway"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:399
|
||||
msgid "Service Unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:409
|
||||
msgid "Gateway Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:418
|
||||
msgid "HTTP Version Not Supported"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
import oslo_i18n
|
||||
|
||||
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
|
||||
# application name when this module is synced into the separate
|
||||
# repository. It is OK to have more than one translation function
|
||||
# using the same domain, since there will still only be one message
|
||||
# catalog.
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='watcherclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# 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
|
||||
except ImportError:
|
||||
# NOTE(dims): Support for cases where a project wants to use
|
||||
# code from oslo-incubator, but is not ready to be internationalized
|
||||
# (like tempest)
|
||||
_ = _LI = _LW = _LE = _LC = lambda x: x
|
||||
@@ -1,234 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 Spanish National Research Council.
|
||||
# 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.
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
_discovered_plugins = {}
|
||||
|
||||
|
||||
def discover_auth_systems():
|
||||
"""Discover the available auth-systems.
|
||||
|
||||
This won't take into account the old style auth-systems.
|
||||
"""
|
||||
global _discovered_plugins
|
||||
_discovered_plugins = {}
|
||||
|
||||
def add_plugin(ext):
|
||||
_discovered_plugins[ext.name] = ext.plugin
|
||||
|
||||
ep_namespace = "watcherclient.openstack.common.apiclient.auth"
|
||||
mgr = extension.ExtensionManager(ep_namespace)
|
||||
mgr.map(add_plugin)
|
||||
|
||||
|
||||
def load_auth_system_opts(parser):
|
||||
"""Load options needed by the available auth-systems into a parser.
|
||||
|
||||
This function will try to populate the parser with options from the
|
||||
available plugins.
|
||||
"""
|
||||
group = parser.add_argument_group("Common auth options")
|
||||
BaseAuthPlugin.add_common_opts(group)
|
||||
for name, auth_plugin in six.iteritems(_discovered_plugins):
|
||||
group = parser.add_argument_group(
|
||||
"Auth-system '%s' options" % name,
|
||||
conflict_handler="resolve")
|
||||
auth_plugin.add_opts(group)
|
||||
|
||||
|
||||
def load_plugin(auth_system):
|
||||
try:
|
||||
plugin_class = _discovered_plugins[auth_system]
|
||||
except KeyError:
|
||||
raise exceptions.AuthSystemNotFound(auth_system)
|
||||
return plugin_class(auth_system=auth_system)
|
||||
|
||||
|
||||
def load_plugin_from_args(args):
|
||||
"""Load required plugin and populate it with options.
|
||||
|
||||
Try to guess auth system if it is not specified. Systems are tried in
|
||||
alphabetical order.
|
||||
|
||||
:type args: argparse.Namespace
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
auth_system = args.os_auth_system
|
||||
if auth_system:
|
||||
plugin = load_plugin(auth_system)
|
||||
plugin.parse_opts(args)
|
||||
plugin.sufficient_options()
|
||||
return plugin
|
||||
|
||||
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
|
||||
plugin_class = _discovered_plugins[plugin_auth_system]
|
||||
plugin = plugin_class()
|
||||
plugin.parse_opts(args)
|
||||
try:
|
||||
plugin.sufficient_options()
|
||||
except exceptions.AuthPluginOptionsMissing:
|
||||
continue
|
||||
return plugin
|
||||
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
"""Base class for authentication plugins.
|
||||
|
||||
An authentication plugin needs to override at least the authenticate
|
||||
method to be a valid plugin.
|
||||
"""
|
||||
|
||||
auth_system = None
|
||||
opt_names = []
|
||||
common_opt_names = [
|
||||
"auth_system",
|
||||
"username",
|
||||
"password",
|
||||
"tenant_name",
|
||||
"token",
|
||||
"auth_url",
|
||||
]
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.auth_system = auth_system or self.auth_system
|
||||
self.opts = dict((name, kwargs.get(name))
|
||||
for name in self.opt_names)
|
||||
|
||||
@staticmethod
|
||||
def _parser_add_opt(parser, opt):
|
||||
"""Add an option to parser in two variants.
|
||||
|
||||
:param opt: option name (with underscores)
|
||||
"""
|
||||
dashed_opt = opt.replace("_", "-")
|
||||
env_var = "OS_%s" % opt.upper()
|
||||
arg_default = os.environ.get(env_var, "")
|
||||
arg_help = "Defaults to env[%s]." % env_var
|
||||
parser.add_argument(
|
||||
"--os-%s" % dashed_opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
default=arg_default,
|
||||
help=arg_help)
|
||||
parser.add_argument(
|
||||
"--os_%s" % opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@classmethod
|
||||
def add_opts(cls, parser):
|
||||
"""Populate the parser with the options for this plugin.
|
||||
"""
|
||||
for opt in cls.opt_names:
|
||||
# use `BaseAuthPlugin.common_opt_names` since it is never
|
||||
# changed in child classes
|
||||
if opt not in BaseAuthPlugin.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@classmethod
|
||||
def add_common_opts(cls, parser):
|
||||
"""Add options that are common for several plugins.
|
||||
"""
|
||||
for opt in cls.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@staticmethod
|
||||
def get_opt(opt_name, args):
|
||||
"""Return option name and value.
|
||||
|
||||
:param opt_name: name of the option, e.g., "username"
|
||||
:param args: parsed arguments
|
||||
"""
|
||||
return (opt_name, getattr(args, "os_%s" % opt_name, None))
|
||||
|
||||
def parse_opts(self, args):
|
||||
"""Parse the actual auth-system options if any.
|
||||
|
||||
This method is expected to populate the attribute `self.opts` with a
|
||||
dict containing the options and values needed to make authentication.
|
||||
"""
|
||||
self.opts.update(dict(self.get_opt(opt_name, args)
|
||||
for opt_name in self.opt_names))
|
||||
|
||||
def authenticate(self, http_client):
|
||||
"""Authenticate using plugin defined method.
|
||||
|
||||
The method usually analyses `self.opts` and performs
|
||||
a request to authentication server.
|
||||
|
||||
:param http_client: client object that needs authentication
|
||||
:type http_client: HTTPClient
|
||||
:raises: AuthorizationFailure
|
||||
"""
|
||||
self.sufficient_options()
|
||||
self._do_authenticate(http_client)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_authenticate(self, http_client):
|
||||
"""Protected method for authentication.
|
||||
"""
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
missing = [opt
|
||||
for opt in self.opt_names
|
||||
if not self.opts.get(opt)]
|
||||
if missing:
|
||||
raise exceptions.AuthPluginOptionsMissing(missing)
|
||||
|
||||
@abc.abstractmethod
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
"""Return token and endpoint.
|
||||
|
||||
:param service_type: Service type of the endpoint
|
||||
:type service_type: string
|
||||
:param endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL,
|
||||
admin or adminURL
|
||||
:type endpoint_type: string
|
||||
:returns: tuple of token and endpoint strings
|
||||
:raises: EndpointException
|
||||
"""
|
||||
@@ -1,388 +0,0 @@
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
import requests
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""This client handles sending HTTP requests to OpenStack servers.
|
||||
|
||||
Features:
|
||||
|
||||
- share authentication information between several clients to different
|
||||
services (e.g., for compute and image clients);
|
||||
- reissue authentication request for expired tokens;
|
||||
- encode/decode JSON bodies;
|
||||
- raise exceptions on HTTP errors;
|
||||
- pluggable authentication;
|
||||
- store authentication information in a keyring;
|
||||
- store time spent for requests;
|
||||
- register clients for particular services, so one can use
|
||||
`http_client.identity` or `http_client.compute`;
|
||||
- log requests and responses in a format that is easy to copy-and-paste
|
||||
into terminal and send the same request with curl.
|
||||
"""
|
||||
|
||||
user_agent = "watcherclient.openstack.common.apiclient"
|
||||
|
||||
def __init__(self,
|
||||
auth_plugin,
|
||||
region_name=None,
|
||||
endpoint_type="publicURL",
|
||||
original_ip=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
timeout=None,
|
||||
timings=False,
|
||||
keyring_saver=None,
|
||||
debug=False,
|
||||
user_agent=None,
|
||||
http=None):
|
||||
self.auth_plugin = auth_plugin
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
|
||||
self.original_ip = original_ip
|
||||
self.timeout = timeout
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
self.keyring_saver = keyring_saver
|
||||
self.debug = debug
|
||||
self.user_agent = user_agent or self.user_agent
|
||||
|
||||
self.times = [] # [("item", starttime, endtime), ...]
|
||||
self.timings = timings
|
||||
|
||||
# requests within the same session can reuse TCP connections from pool
|
||||
self.http = http or requests.Session()
|
||||
|
||||
self.cached_token = None
|
||||
self.last_request_id = None
|
||||
|
||||
def _safe_header(self, name, value):
|
||||
if name in SENSITIVE_HEADERS:
|
||||
# because in python3 byte string handling is ... ug
|
||||
v = value.encode('utf-8')
|
||||
h = hashlib.sha1(v)
|
||||
d = h.hexdigest()
|
||||
return encodeutils.safe_decode(name), "{SHA1}%s" % d
|
||||
else:
|
||||
return (encodeutils.safe_decode(name),
|
||||
encodeutils.safe_decode(value))
|
||||
|
||||
def _http_log_req(self, method, url, kwargs):
|
||||
if not self.debug:
|
||||
return
|
||||
|
||||
string_parts = [
|
||||
"curl -g -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = ("-H '%s: %s'" %
|
||||
self._safe_header(element, kwargs['headers'][element]))
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
if 'data' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
|
||||
|
||||
def _http_log_resp(self, resp):
|
||||
if not self.debug:
|
||||
return
|
||||
_logger.debug(
|
||||
"RESP: [%s] %s\n",
|
||||
resp.status_code,
|
||||
resp.headers)
|
||||
if resp._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s\n",
|
||||
resp.text)
|
||||
|
||||
def serialize(self, kwargs):
|
||||
if kwargs.get('json') is not None:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['data'] = json.dumps(kwargs['json'])
|
||||
try:
|
||||
del kwargs['json']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_timings(self):
|
||||
return self.times
|
||||
|
||||
def reset_timings(self):
|
||||
self.times = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
requests.Session.request (such as `headers`) or `json`
|
||||
that will be encoded as JSON and used as `data` argument
|
||||
"""
|
||||
kwargs.setdefault("headers", {})
|
||||
kwargs["headers"]["User-Agent"] = self.user_agent
|
||||
if self.original_ip:
|
||||
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
|
||||
self.original_ip, self.user_agent)
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("verify", self.verify)
|
||||
if self.cert is not None:
|
||||
kwargs.setdefault("cert", self.cert)
|
||||
self.serialize(kwargs)
|
||||
|
||||
self._http_log_req(method, url, kwargs)
|
||||
if self.timings:
|
||||
start_time = time.time()
|
||||
resp = self.http.request(method, url, **kwargs)
|
||||
if self.timings:
|
||||
self.times.append(("%s %s" % (method, url),
|
||||
start_time, time.time()))
|
||||
self._http_log_resp(resp)
|
||||
|
||||
self.last_request_id = resp.headers.get('x-openstack-request-id')
|
||||
|
||||
if resp.status_code >= 400:
|
||||
_logger.debug(
|
||||
"Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def concat_url(endpoint, url):
|
||||
"""Concatenate endpoint and final URL.
|
||||
|
||||
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
|
||||
"http://keystone/v2.0/tokens".
|
||||
|
||||
:param endpoint: the base URL
|
||||
:param url: the final URL
|
||||
"""
|
||||
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
"""Send an http request using `client`'s endpoint and specified `url`.
|
||||
|
||||
If request was rejected as unauthorized (possibly because the token is
|
||||
expired), issue one authorization attempt and send the request once
|
||||
again.
|
||||
|
||||
:param client: instance of BaseClient descendant
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
`HTTPClient.request`
|
||||
"""
|
||||
|
||||
filter_args = {
|
||||
"endpoint_type": client.endpoint_type or self.endpoint_type,
|
||||
"service_type": client.service_type,
|
||||
}
|
||||
token, endpoint = (self.cached_token, client.cached_endpoint)
|
||||
just_authenticated = False
|
||||
if not (token and endpoint):
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
pass
|
||||
if not (token and endpoint):
|
||||
self.authenticate()
|
||||
just_authenticated = True
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
if not (token and endpoint):
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Cannot find endpoint or token for request"))
|
||||
|
||||
old_token_endpoint = (token, endpoint)
|
||||
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
# Perform the request once. If we get Unauthorized, then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
except exceptions.Unauthorized as unauth_ex:
|
||||
if just_authenticated:
|
||||
raise
|
||||
self.cached_token = None
|
||||
client.cached_endpoint = None
|
||||
if self.auth_plugin.opts.get('token'):
|
||||
self.auth_plugin.opts['token'] = None
|
||||
if self.auth_plugin.opts.get('endpoint'):
|
||||
self.auth_plugin.opts['endpoint'] = None
|
||||
self.authenticate()
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
raise unauth_ex
|
||||
if (not (token and endpoint) or
|
||||
old_token_endpoint == (token, endpoint)):
|
||||
raise unauth_ex
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
kwargs["headers"]["X-Auth-Token"] = token
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
|
||||
def add_client(self, base_client_instance):
|
||||
"""Add a new instance of :class:`BaseClient` descendant.
|
||||
|
||||
`self` will store a reference to `base_client_instance`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def test_clients():
|
||||
... from keystoneclient.auth import keystone
|
||||
... from openstack.common.apiclient import client
|
||||
... auth = keystone.KeystoneAuthPlugin(
|
||||
... username="user", password="pass", tenant_name="tenant",
|
||||
... auth_url="http://auth:5000/v2.0")
|
||||
... openstack_client = client.HTTPClient(auth)
|
||||
... # create nova client
|
||||
... from novaclient.v1_1 import client
|
||||
... client.Client(openstack_client)
|
||||
... # create keystone client
|
||||
... from keystoneclient.v2_0 import client
|
||||
... client.Client(openstack_client)
|
||||
... # use them
|
||||
... openstack_client.identity.tenants.list()
|
||||
... openstack_client.compute.servers.list()
|
||||
"""
|
||||
service_type = base_client_instance.service_type
|
||||
if service_type and not hasattr(self, service_type):
|
||||
setattr(self, service_type, base_client_instance)
|
||||
|
||||
def authenticate(self):
|
||||
self.auth_plugin.authenticate(self)
|
||||
# Store the authentication results in the keyring for later requests
|
||||
if self.keyring_saver:
|
||||
self.keyring_saver.save(self)
|
||||
|
||||
|
||||
class BaseClient(object):
|
||||
"""Top-level object to access the OpenStack API.
|
||||
|
||||
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
|
||||
will handle a bunch of issues such as authentication.
|
||||
"""
|
||||
|
||||
service_type = None
|
||||
endpoint_type = None # "publicURL" will be used
|
||||
cached_endpoint = None
|
||||
|
||||
def __init__(self, http_client, extensions=None):
|
||||
self.http_client = http_client
|
||||
http_client.add_client(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
return self.http_client.client_request(
|
||||
self, method, url, **kwargs)
|
||||
|
||||
@property
|
||||
def last_request_id(self):
|
||||
return self.http_client.last_request_id
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.client_request("HEAD", url, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.client_request("GET", url, **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.client_request("POST", url, **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.client_request("PUT", url, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.client_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_class(api_name, version, version_map):
|
||||
"""Returns the client class for the requested API version
|
||||
|
||||
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||
:param version: the requested API version
|
||||
:param version_map: a dict of client classes keyed by version
|
||||
:rtype: a client class for the requested API version
|
||||
"""
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
except (KeyError, ValueError):
|
||||
msg = _("Invalid %(api_name)s client version '%(version)s'. "
|
||||
"Must be one of: %(version_map)s") % {
|
||||
'api_name': api_name,
|
||||
'version': version,
|
||||
'version_map': ', '.join(version_map.keys())}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
return importutils.import_class(client_path)
|
||||
@@ -1,190 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A fake server that "responds" to API methods with pre-canned responses.
|
||||
|
||||
All of these responses come from the spec, so if for some reason the spec's
|
||||
wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
# W0102: Dangerous default value %s as argument
|
||||
# pylint: disable=W0102
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from watcherclient.openstack.common.apiclient import client
|
||||
|
||||
|
||||
def assert_has_keys(dct, required=None, optional=None):
|
||||
required = required or []
|
||||
optional = optional or []
|
||||
for k in required:
|
||||
try:
|
||||
assert k in dct
|
||||
except AssertionError:
|
||||
extra_keys = set(dct.keys()).difference(set(required + optional))
|
||||
raise AssertionError("found unexpected keys: %s" %
|
||||
list(extra_keys))
|
||||
|
||||
|
||||
class TestResponse(requests.Response):
|
||||
"""Wrap requests.Response and provide a convenient initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
super(TestResponse, self).__init__()
|
||||
self._content_consumed = True
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', 200)
|
||||
# Fake the text attribute to streamline Response creation
|
||||
text = data.get('text', "")
|
||||
if isinstance(text, (dict, list)):
|
||||
self._content = json.dumps(text)
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
else:
|
||||
self._content = text
|
||||
default_headers = {}
|
||||
if six.PY3 and isinstance(self._content, six.string_types):
|
||||
self._content = self._content.encode('utf-8', 'strict')
|
||||
self.headers = data.get('headers') or default_headers
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.status_code == other.status_code and
|
||||
self.headers == other.headers and
|
||||
self._content == other._content)
|
||||
|
||||
|
||||
class FakeHTTPClient(client.HTTPClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.callstack = []
|
||||
self.fixtures = kwargs.pop("fixtures", None) or {}
|
||||
if not args and "auth_plugin" not in kwargs:
|
||||
args = (None, )
|
||||
super(FakeHTTPClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def assert_called(self, method, url, body=None, pos=-1):
|
||||
"""Assert than an API method was just called.
|
||||
"""
|
||||
expected = (method, url)
|
||||
called = self.callstack[pos][0:2]
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
assert expected == called, 'Expected %s %s; got %s %s' % \
|
||||
(expected + called)
|
||||
|
||||
if body is not None:
|
||||
if self.callstack[pos][3] != body:
|
||||
raise AssertionError('%r != %r' %
|
||||
(self.callstack[pos][3], body))
|
||||
|
||||
def assert_called_anytime(self, method, url, body=None):
|
||||
"""Assert than an API method was called anytime in the test.
|
||||
"""
|
||||
expected = (method, url)
|
||||
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
found = False
|
||||
entry = None
|
||||
for entry in self.callstack:
|
||||
if expected == entry[0:2]:
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found, 'Expected %s %s; got %s' % \
|
||||
(method, url, self.callstack)
|
||||
if body is not None:
|
||||
assert entry[3] == body, "%s != %s" % (entry[3], body)
|
||||
|
||||
self.callstack = []
|
||||
|
||||
def clear_callstack(self):
|
||||
self.callstack = []
|
||||
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ["GET", "DELETE"]:
|
||||
assert "json" not in kwargs
|
||||
|
||||
# Note the call
|
||||
self.callstack.append(
|
||||
(method,
|
||||
url,
|
||||
kwargs.get("headers") or {},
|
||||
kwargs.get("json") or kwargs.get("data")))
|
||||
try:
|
||||
fixture = self.fixtures[url][method]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return TestResponse({"headers": fixture[0],
|
||||
"text": fixture[1]})
|
||||
|
||||
# Call the method
|
||||
args = parse.parse_qsl(parse.urlparse(url)[4])
|
||||
kwargs.update(args)
|
||||
munged_url = url.rsplit('?', 1)[0]
|
||||
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
|
||||
munged_url = munged_url.replace('-', '_')
|
||||
|
||||
callback = "%s_%s" % (method.lower(), munged_url)
|
||||
|
||||
if not hasattr(self, callback):
|
||||
raise AssertionError('Called unknown API method: %s %s, '
|
||||
'expected fakes method name: %s' %
|
||||
(method, url, callback))
|
||||
|
||||
resp = getattr(self, callback)(**kwargs)
|
||||
if len(resp) == 3:
|
||||
status, headers, body = resp
|
||||
else:
|
||||
status, body = resp
|
||||
headers = {}
|
||||
self.last_request_id = headers.get('x-openstack-request-id',
|
||||
'req-test')
|
||||
return TestResponse({
|
||||
"status_code": status,
|
||||
"text": body,
|
||||
"headers": headers,
|
||||
})
|
||||
@@ -1,100 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id, **find_args):
|
||||
"""Look for resource in a given manager.
|
||||
|
||||
Used as a helper for the _find_* methods.
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def _find_hypervisor(cs, hypervisor):
|
||||
#Get a hypervisor by name or ID.
|
||||
return cliutils.find_resource(cs.hypervisors, hypervisor)
|
||||
"""
|
||||
# first try to get entity as integer id
|
||||
try:
|
||||
return manager.get(int(name_or_id))
|
||||
except (TypeError, ValueError, exceptions.NotFound):
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
if six.PY2:
|
||||
tmp_id = encodeutils.safe_encode(name_or_id)
|
||||
else:
|
||||
tmp_id = encodeutils.safe_decode(name_or_id)
|
||||
|
||||
if uuidutils.is_uuid_like(tmp_id):
|
||||
return manager.get(tmp_id)
|
||||
except (TypeError, ValueError, exceptions.NotFound):
|
||||
pass
|
||||
|
||||
# for str id which is not uuid
|
||||
if getattr(manager, 'is_alphanum_id_allowed', False):
|
||||
try:
|
||||
return manager.get(name_or_id)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
try:
|
||||
try:
|
||||
return manager.find(human_id=name_or_id, **find_args)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
resource = getattr(manager, 'resource_class', None)
|
||||
name_attr = resource.NAME_ATTR if resource else 'name'
|
||||
kwargs = {name_attr: name_or_id}
|
||||
kwargs.update(find_args)
|
||||
return manager.find(**kwargs)
|
||||
except exceptions.NotFound:
|
||||
msg = _("No %(name)s with a name or "
|
||||
"ID of '%(name_or_id)s' exists.") % \
|
||||
{
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"name_or_id": name_or_id
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
except exceptions.NoUniqueMatch:
|
||||
msg = _("Multiple %(name)s matches found for "
|
||||
"'%(name_or_id)s', use an ID to be more specific.") % \
|
||||
{
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"name_or_id": name_or_id
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
@@ -1,271 +0,0 @@
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# W0603: Using the global statement
|
||||
# W0621: Redefining name %s from outer scope
|
||||
# pylint: disable=W0603,W0621
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import strutils
|
||||
import prettytable
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
|
||||
|
||||
class MissingArgs(Exception):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
def validate_args(fn, *args, **kwargs):
|
||||
"""Check that the supplied args are sufficient for calling a function.
|
||||
|
||||
>>> validate_args(lambda a: None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): a
|
||||
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): b, d
|
||||
|
||||
:param fn: the function to check
|
||||
:param arg: the positional arguments supplied
|
||||
:param kwargs: the keyword arguments supplied
|
||||
"""
|
||||
argspec = inspect.getargspec(fn)
|
||||
|
||||
num_defaults = len(argspec.defaults or [])
|
||||
required_args = argspec.args[:len(argspec.args) - num_defaults]
|
||||
|
||||
def isbound(method):
|
||||
return getattr(method, '__self__', None) is not None
|
||||
|
||||
if isbound(fn):
|
||||
required_args.pop(0)
|
||||
|
||||
missing = [arg for arg in required_args if arg not in kwargs]
|
||||
missing = missing[len(args):]
|
||||
if missing:
|
||||
raise MissingArgs(missing)
|
||||
|
||||
|
||||
def arg(*args, **kwargs):
|
||||
"""Decorator for CLI args.
|
||||
|
||||
Example:
|
||||
|
||||
>>> @arg("name", help="Name of the new entity")
|
||||
... def entity_create(args):
|
||||
... pass
|
||||
"""
|
||||
def _decorator(func):
|
||||
add_arg(func, *args, **kwargs)
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def env(*args, **kwargs):
|
||||
"""Returns the first environment variable set.
|
||||
|
||||
If all are empty, defaults to '' or keyword arg `default`.
|
||||
"""
|
||||
for arg in args:
|
||||
value = os.environ.get(arg)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def add_arg(func, *args, **kwargs):
|
||||
"""Bind CLI arguments to a shell.py `do_foo` function."""
|
||||
|
||||
if not hasattr(func, 'arguments'):
|
||||
func.arguments = []
|
||||
|
||||
# NOTE(sirp): avoid dups that can occur when the module is shared across
|
||||
# tests.
|
||||
if (args, kwargs) not in func.arguments:
|
||||
# Because of the semantics of decorator composition if we just append
|
||||
# to the options list positional options will appear to be backwards.
|
||||
func.arguments.insert(0, (args, kwargs))
|
||||
|
||||
|
||||
def unauthenticated(func):
|
||||
"""Adds 'unauthenticated' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> @unauthenticated
|
||||
... def mymethod(f):
|
||||
... pass
|
||||
"""
|
||||
func.unauthenticated = True
|
||||
return func
|
||||
|
||||
|
||||
def isunauthenticated(func):
|
||||
"""Checks if the function does not require authentication.
|
||||
|
||||
Mark such functions with the `@unauthenticated` decorator.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return getattr(func, 'unauthenticated', False)
|
||||
|
||||
|
||||
def print_list(objs, fields, formatters=None, sortby_index=0,
|
||||
mixed_case_fields=None, field_labels=None):
|
||||
"""Print a list or objects as a table, one row per object.
|
||||
|
||||
:param objs: iterable of :class:`Resource`
|
||||
:param fields: attributes that correspond to columns, in order
|
||||
:param formatters: `dict` of callables for field formatting
|
||||
:param sortby_index: index of the field for sorting table rows
|
||||
:param mixed_case_fields: fields corresponding to object attributes that
|
||||
have mixed case names (e.g., 'serverId')
|
||||
:param field_labels: Labels to use in the heading of the table, default to
|
||||
fields.
|
||||
"""
|
||||
formatters = formatters or {}
|
||||
mixed_case_fields = mixed_case_fields or []
|
||||
field_labels = field_labels or fields
|
||||
if len(field_labels) != len(fields):
|
||||
raise ValueError(_("Field labels list %(labels)s has different number "
|
||||
"of elements than fields list %(fields)s"),
|
||||
{'labels': field_labels, 'fields': fields})
|
||||
|
||||
if sortby_index is None:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = {'sortby': field_labels[sortby_index]}
|
||||
pt = prettytable.PrettyTable(field_labels)
|
||||
pt.align = 'l'
|
||||
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
if field in formatters:
|
||||
row.append(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)
|
||||
pt.add_row(row)
|
||||
|
||||
if six.PY3:
|
||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
|
||||
else:
|
||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
|
||||
|
||||
|
||||
def print_dict(dct, dict_property="Property", wrap=0):
|
||||
"""Print a `dict` as a table of two columns.
|
||||
|
||||
:param dct: `dict` to print
|
||||
:param dict_property: name of the first column
|
||||
:param wrap: wrapping for the second column
|
||||
"""
|
||||
pt = prettytable.PrettyTable([dict_property, 'Value'])
|
||||
pt.align = 'l'
|
||||
for k, v in six.iteritems(dct):
|
||||
# convert dict to str to check length
|
||||
if isinstance(v, dict):
|
||||
v = six.text_type(v)
|
||||
if wrap > 0:
|
||||
v = textwrap.fill(six.text_type(v), wrap)
|
||||
# if value has a newline, add in multiple rows
|
||||
# e.g. fault with stacktrace
|
||||
if v and isinstance(v, six.string_types) and r'\n' in v:
|
||||
lines = v.strip().split(r'\n')
|
||||
col1 = k
|
||||
for line in lines:
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
else:
|
||||
pt.add_row([k, v])
|
||||
|
||||
if six.PY3:
|
||||
print(encodeutils.safe_encode(pt.get_string()).decode())
|
||||
else:
|
||||
print(encodeutils.safe_encode(pt.get_string()))
|
||||
|
||||
|
||||
def get_password(max_password_prompts=3):
|
||||
"""Read password from TTY."""
|
||||
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
|
||||
pw = None
|
||||
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
|
||||
# Check for Ctrl-D
|
||||
try:
|
||||
for __ in moves.range(max_password_prompts):
|
||||
pw1 = getpass.getpass("OS Password: ")
|
||||
if verify:
|
||||
pw2 = getpass.getpass("Please verify: ")
|
||||
else:
|
||||
pw2 = pw1
|
||||
if pw1 == pw2 and pw1:
|
||||
pw = pw1
|
||||
break
|
||||
except EOFError:
|
||||
pass
|
||||
return pw
|
||||
|
||||
|
||||
def service_type(stype):
|
||||
"""Adds 'service_type' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@service_type('volume')
|
||||
def mymethod(f):
|
||||
...
|
||||
"""
|
||||
def inner(f):
|
||||
f.service_type = stype
|
||||
return f
|
||||
return inner
|
||||
|
||||
|
||||
def get_service_type(f):
|
||||
"""Retrieves service type from function."""
|
||||
return getattr(f, 'service_type', None)
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print (msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -1,479 +0,0 @@
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
# Copyright 2013 IBM Corp.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
gettext for openstack-common modules.
|
||||
|
||||
Usual usage in an openstack.common module:
|
||||
|
||||
from watcherclient.openstack.common.gettextutils import _
|
||||
"""
|
||||
|
||||
import copy
|
||||
import gettext
|
||||
import locale
|
||||
from logging import handlers
|
||||
import os
|
||||
|
||||
from babel import localedata
|
||||
import six
|
||||
|
||||
_AVAILABLE_LANGUAGES = {}
|
||||
|
||||
# FIXME(dhellmann): Remove this when moving to oslo.i18n.
|
||||
USE_LAZY = False
|
||||
|
||||
|
||||
class TranslatorFactory(object):
|
||||
"""Create translator functions
|
||||
"""
|
||||
|
||||
def __init__(self, domain, localedir=None):
|
||||
"""Establish a set of translation functions for the domain.
|
||||
|
||||
:param domain: Name of translation domain,
|
||||
specifying a message catalog.
|
||||
:type domain: str
|
||||
:param lazy: Delays translation until a message is emitted.
|
||||
Defaults to False.
|
||||
:type lazy: Boolean
|
||||
:param localedir: Directory with translation catalogs.
|
||||
:type localedir: str
|
||||
"""
|
||||
self.domain = domain
|
||||
if localedir is None:
|
||||
localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
|
||||
self.localedir = localedir
|
||||
|
||||
def _make_translation_func(self, domain=None):
|
||||
"""Return a new translation function ready for use.
|
||||
|
||||
Takes into account whether or not lazy translation is being
|
||||
done.
|
||||
|
||||
The domain can be specified to override the default from the
|
||||
factory, but the localedir from the factory is always used
|
||||
because we assume the log-level translation catalogs are
|
||||
installed in the same directory as the main application
|
||||
catalog.
|
||||
|
||||
"""
|
||||
if domain is None:
|
||||
domain = self.domain
|
||||
t = gettext.translation(domain,
|
||||
localedir=self.localedir,
|
||||
fallback=True)
|
||||
# Use the appropriate method of the translation object based
|
||||
# on the python version.
|
||||
m = t.gettext if six.PY3 else t.ugettext
|
||||
|
||||
def f(msg):
|
||||
"""oslo.i18n.gettextutils translation function."""
|
||||
if USE_LAZY:
|
||||
return Message(msg, domain=domain)
|
||||
return m(msg)
|
||||
return f
|
||||
|
||||
@property
|
||||
def primary(self):
|
||||
"The default translation function."
|
||||
return self._make_translation_func()
|
||||
|
||||
def _make_log_translation_func(self, level):
|
||||
return self._make_translation_func(self.domain + '-log-' + level)
|
||||
|
||||
@property
|
||||
def log_info(self):
|
||||
"Translate info-level log messages."
|
||||
return self._make_log_translation_func('info')
|
||||
|
||||
@property
|
||||
def log_warning(self):
|
||||
"Translate warning-level log messages."
|
||||
return self._make_log_translation_func('warning')
|
||||
|
||||
@property
|
||||
def log_error(self):
|
||||
"Translate error-level log messages."
|
||||
return self._make_log_translation_func('error')
|
||||
|
||||
@property
|
||||
def log_critical(self):
|
||||
"Translate critical-level log messages."
|
||||
return self._make_log_translation_func('critical')
|
||||
|
||||
|
||||
# NOTE(dhellmann): When this module moves out of the incubator into
|
||||
# oslo.i18n, these global variables can be moved to an integration
|
||||
# module within each application.
|
||||
|
||||
# Create the global translation functions.
|
||||
_translators = TranslatorFactory('watcherclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# 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
|
||||
|
||||
# NOTE(dhellmann): End of globals that will move to the application's
|
||||
# integration module.
|
||||
|
||||
|
||||
def enable_lazy():
|
||||
"""Convenience function for configuring _() to use lazy gettext
|
||||
|
||||
Call this at the start of execution to enable the gettextutils._
|
||||
function to use lazy gettext functionality. This is useful if
|
||||
your project is importing _ directly instead of using the
|
||||
gettextutils.install() way of importing the _ function.
|
||||
"""
|
||||
global USE_LAZY
|
||||
USE_LAZY = True
|
||||
|
||||
|
||||
def install(domain):
|
||||
"""Install a _() function using the given translation domain.
|
||||
|
||||
Given a translation domain, install a _() function using gettext's
|
||||
install() function.
|
||||
|
||||
The main difference from gettext.install() is that we allow
|
||||
overriding the default localedir (e.g. /usr/share/locale) using
|
||||
a translation-domain-specific environment variable (e.g.
|
||||
NOVA_LOCALEDIR).
|
||||
|
||||
Note that to enable lazy translation, enable_lazy must be
|
||||
called.
|
||||
|
||||
:param domain: the translation domain
|
||||
"""
|
||||
from six import moves
|
||||
tf = TranslatorFactory(domain)
|
||||
moves.builtins.__dict__['_'] = tf.primary
|
||||
|
||||
|
||||
class Message(six.text_type):
|
||||
"""A Message object is a unicode object that can be translated.
|
||||
|
||||
Translation of Message is done explicitly using the translate() method.
|
||||
For all non-translation intents and purposes, a Message is simply unicode,
|
||||
and can be treated as such.
|
||||
"""
|
||||
|
||||
def __new__(cls, msgid, msgtext=None, params=None,
|
||||
domain='watcherclient', *args):
|
||||
"""Create a new Message object.
|
||||
|
||||
In order for translation to work gettext requires a message ID, this
|
||||
msgid will be used as the base unicode text. It is also possible
|
||||
for the msgid and the base unicode text to be different by passing
|
||||
the msgtext parameter.
|
||||
"""
|
||||
# If the base msgtext is not given, we use the default translation
|
||||
# of the msgid (which is in English) just in case the system locale is
|
||||
# not English, so that the base text will be in that locale by default.
|
||||
if not msgtext:
|
||||
msgtext = Message._translate_msgid(msgid, domain)
|
||||
# We want to initialize the parent unicode with the actual object that
|
||||
# would have been plain unicode if 'Message' was not enabled.
|
||||
msg = super(Message, cls).__new__(cls, msgtext)
|
||||
msg.msgid = msgid
|
||||
msg.domain = domain
|
||||
msg.params = params
|
||||
return msg
|
||||
|
||||
def translate(self, desired_locale=None):
|
||||
"""Translate this message to the desired locale.
|
||||
|
||||
:param desired_locale: The desired locale to translate the message to,
|
||||
if no locale is provided the message will be
|
||||
translated to the system's default locale.
|
||||
|
||||
:returns: the translated message in unicode
|
||||
"""
|
||||
|
||||
translated_message = Message._translate_msgid(self.msgid,
|
||||
self.domain,
|
||||
desired_locale)
|
||||
if self.params is None:
|
||||
# No need for more translation
|
||||
return translated_message
|
||||
|
||||
# This Message object may have been formatted with one or more
|
||||
# Message objects as substitution arguments, given either as a single
|
||||
# argument, part of a tuple, or as one or more values in a dictionary.
|
||||
# When translating this Message we need to translate those Messages too
|
||||
translated_params = _translate_args(self.params, desired_locale)
|
||||
|
||||
translated_message = translated_message % translated_params
|
||||
|
||||
return translated_message
|
||||
|
||||
@staticmethod
|
||||
def _translate_msgid(msgid, domain, desired_locale=None):
|
||||
if not desired_locale:
|
||||
system_locale = locale.getdefaultlocale()
|
||||
# If the system locale is not available to the runtime use English
|
||||
if not system_locale[0]:
|
||||
desired_locale = 'en_US'
|
||||
else:
|
||||
desired_locale = system_locale[0]
|
||||
|
||||
locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
|
||||
lang = gettext.translation(domain,
|
||||
localedir=locale_dir,
|
||||
languages=[desired_locale],
|
||||
fallback=True)
|
||||
if six.PY3:
|
||||
translator = lang.gettext
|
||||
else:
|
||||
translator = lang.ugettext
|
||||
|
||||
translated_message = translator(msgid)
|
||||
return translated_message
|
||||
|
||||
def __mod__(self, other):
|
||||
# When we mod a Message we want the actual operation to be performed
|
||||
# by the parent class (i.e. unicode()), the only thing we do here is
|
||||
# save the original msgid and the parameters in case of a translation
|
||||
params = self._sanitize_mod_params(other)
|
||||
unicode_mod = super(Message, self).__mod__(params)
|
||||
modded = Message(self.msgid,
|
||||
msgtext=unicode_mod,
|
||||
params=params,
|
||||
domain=self.domain)
|
||||
return modded
|
||||
|
||||
def _sanitize_mod_params(self, other):
|
||||
"""Sanitize the object being modded with this Message.
|
||||
|
||||
- Add support for modding 'None' so translation supports it
|
||||
- Trim the modded object, which can be a large dictionary, to only
|
||||
those keys that would actually be used in a translation
|
||||
- Snapshot the object being modded, in case the message is
|
||||
translated, it will be used as it was when the Message was created
|
||||
"""
|
||||
if other is None:
|
||||
params = (other,)
|
||||
elif isinstance(other, dict):
|
||||
# Merge the dictionaries
|
||||
# Copy each item in case one does not support deep copy.
|
||||
params = {}
|
||||
if isinstance(self.params, dict):
|
||||
for key, val in self.params.items():
|
||||
params[key] = self._copy_param(val)
|
||||
for key, val in other.items():
|
||||
params[key] = self._copy_param(val)
|
||||
else:
|
||||
params = self._copy_param(other)
|
||||
return params
|
||||
|
||||
def _copy_param(self, param):
|
||||
try:
|
||||
return copy.deepcopy(param)
|
||||
except Exception:
|
||||
# Fallback to casting to unicode this will handle the
|
||||
# python code-like objects that can't be deep-copied
|
||||
return six.text_type(param)
|
||||
|
||||
def __add__(self, other):
|
||||
msg = _('Message objects do not support addition.')
|
||||
raise TypeError(msg)
|
||||
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
if six.PY2:
|
||||
def __str__(self):
|
||||
# NOTE(luisg): Logging in python 2.6 tries to str() log records,
|
||||
# and it expects specifically a UnicodeError in order to proceed.
|
||||
msg = _('Message objects do not support str() because they may '
|
||||
'contain non-ascii characters. '
|
||||
'Please use unicode() or translate() instead.')
|
||||
raise UnicodeError(msg)
|
||||
|
||||
|
||||
def get_available_languages(domain):
|
||||
"""Lists the available languages for the given translation domain.
|
||||
|
||||
:param domain: the domain to get languages for
|
||||
"""
|
||||
if domain in _AVAILABLE_LANGUAGES:
|
||||
return copy.copy(_AVAILABLE_LANGUAGES[domain])
|
||||
|
||||
localedir = '%s_LOCALEDIR' % domain.upper()
|
||||
find = lambda x: gettext.find(domain,
|
||||
localedir=os.environ.get(localedir),
|
||||
languages=[x])
|
||||
|
||||
# NOTE(mrodden): en_US should always be available (and first in case
|
||||
# order matters) since our in-line message strings are en_US
|
||||
language_list = ['en_US']
|
||||
# NOTE(luisg): Babel <1.0 used a function called list(), which was
|
||||
# renamed to locale_identifiers() in >=1.0, the requirements master list
|
||||
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
|
||||
# this check when the master list updates to >=1.0, and update all projects
|
||||
list_identifiers = (getattr(localedata, 'list', None) or
|
||||
getattr(localedata, 'locale_identifiers'))
|
||||
locale_identifiers = list_identifiers()
|
||||
|
||||
for i in locale_identifiers:
|
||||
if find(i) is not None:
|
||||
language_list.append(i)
|
||||
|
||||
# NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
|
||||
# locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
|
||||
# are perfectly legitimate locales:
|
||||
# https://github.com/mitsuhiko/babel/issues/37
|
||||
# In Babel 1.3 they fixed the bug and they support these locales, but
|
||||
# they are still not explicitly "listed" by locale_identifiers().
|
||||
# That is why we add the locales here explicitly if necessary so that
|
||||
# they are listed as supported.
|
||||
aliases = {'zh': 'zh_CN',
|
||||
'zh_Hant_HK': 'zh_HK',
|
||||
'zh_Hant': 'zh_TW',
|
||||
'fil': 'tl_PH'}
|
||||
for (locale_, alias) in six.iteritems(aliases):
|
||||
if locale_ in language_list and alias not in language_list:
|
||||
language_list.append(alias)
|
||||
|
||||
_AVAILABLE_LANGUAGES[domain] = language_list
|
||||
return copy.copy(language_list)
|
||||
|
||||
|
||||
def translate(obj, desired_locale=None):
|
||||
"""Gets the translated unicode representation of the given object.
|
||||
|
||||
If the object is not translatable it is returned as-is.
|
||||
If the locale is None the object is translated to the system locale.
|
||||
|
||||
:param obj: the object to translate
|
||||
:param desired_locale: the locale to translate the message to, if None the
|
||||
default system locale will be used
|
||||
:returns: the translated object in unicode, or the original object if
|
||||
it could not be translated
|
||||
"""
|
||||
message = obj
|
||||
if not isinstance(message, Message):
|
||||
# If the object to translate is not already translatable,
|
||||
# let's first get its unicode representation
|
||||
message = six.text_type(obj)
|
||||
if isinstance(message, Message):
|
||||
# Even after unicoding() we still need to check if we are
|
||||
# running with translatable unicode before translating
|
||||
return message.translate(desired_locale)
|
||||
return obj
|
||||
|
||||
|
||||
def _translate_args(args, desired_locale=None):
|
||||
"""Translates all the translatable elements of the given arguments object.
|
||||
|
||||
This method is used for translating the translatable values in method
|
||||
arguments which include values of tuples or dictionaries.
|
||||
If the object is not a tuple or a dictionary the object itself is
|
||||
translated if it is translatable.
|
||||
|
||||
If the locale is None the object is translated to the system locale.
|
||||
|
||||
:param args: the args to translate
|
||||
:param desired_locale: the locale to translate the args to, if None the
|
||||
default system locale will be used
|
||||
:returns: a new args object with the translated contents of the original
|
||||
"""
|
||||
if isinstance(args, tuple):
|
||||
return tuple(translate(v, desired_locale) for v in args)
|
||||
if isinstance(args, dict):
|
||||
translated_dict = {}
|
||||
for (k, v) in six.iteritems(args):
|
||||
translated_v = translate(v, desired_locale)
|
||||
translated_dict[k] = translated_v
|
||||
return translated_dict
|
||||
return translate(args, desired_locale)
|
||||
|
||||
|
||||
class TranslationHandler(handlers.MemoryHandler):
|
||||
"""Handler that translates records before logging them.
|
||||
|
||||
The TranslationHandler takes a locale and a target logging.Handler object
|
||||
to forward LogRecord objects to after translating them. This handler
|
||||
depends on Message objects being logged, instead of regular strings.
|
||||
|
||||
The handler can be configured declaratively in the logging.conf as follows:
|
||||
|
||||
[handlers]
|
||||
keys = translatedlog, translator
|
||||
|
||||
[handler_translatedlog]
|
||||
class = handlers.WatchedFileHandler
|
||||
args = ('/var/log/api-localized.log',)
|
||||
formatter = context
|
||||
|
||||
[handler_translator]
|
||||
class = openstack.common.log.TranslationHandler
|
||||
target = translatedlog
|
||||
args = ('zh_CN',)
|
||||
|
||||
If the specified locale is not available in the system, the handler will
|
||||
log in the default locale.
|
||||
"""
|
||||
|
||||
def __init__(self, locale=None, target=None):
|
||||
"""Initialize a TranslationHandler
|
||||
|
||||
:param locale: locale to use for translating messages
|
||||
:param target: logging.Handler object to forward
|
||||
LogRecord objects to after translation
|
||||
"""
|
||||
# NOTE(luisg): In order to allow this handler to be a wrapper for
|
||||
# other handlers, such as a FileHandler, and still be able to
|
||||
# configure it using logging.conf, this handler has to extend
|
||||
# MemoryHandler because only the MemoryHandlers' logging.conf
|
||||
# parsing is implemented such that it accepts a target handler.
|
||||
handlers.MemoryHandler.__init__(self, capacity=0, target=target)
|
||||
self.locale = locale
|
||||
|
||||
def setFormatter(self, fmt):
|
||||
self.target.setFormatter(fmt)
|
||||
|
||||
def emit(self, record):
|
||||
# We save the message from the original record to restore it
|
||||
# after translation, so other handlers are not affected by this
|
||||
original_msg = record.msg
|
||||
original_args = record.args
|
||||
|
||||
try:
|
||||
self._translate_and_log_record(record)
|
||||
finally:
|
||||
record.msg = original_msg
|
||||
record.args = original_args
|
||||
|
||||
def _translate_and_log_record(self, record):
|
||||
record.msg = translate(record.msg, self.locale)
|
||||
|
||||
# In addition to translating the message, we also need to translate
|
||||
# arguments that were passed to the log method that were not part
|
||||
# of the main message e.g., log.info(_('Some message %s'), this_one))
|
||||
record.args = _translate_args(record.args, self.locale)
|
||||
|
||||
self.target.emit(record)
|
||||
@@ -1,73 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# 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 related utilities and helper functions.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
def import_class(import_str):
|
||||
"""Returns a class from a string including module and class."""
|
||||
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||
__import__(mod_str)
|
||||
try:
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
except AttributeError:
|
||||
raise ImportError('Class %s cannot be found (%s)' %
|
||||
(class_str,
|
||||
traceback.format_exception(*sys.exc_info())))
|
||||
|
||||
|
||||
def import_object(import_str, *args, **kwargs):
|
||||
"""Import a class and return an instance of it."""
|
||||
return import_class(import_str)(*args, **kwargs)
|
||||
|
||||
|
||||
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||
"""Tries to import object from default namespace.
|
||||
|
||||
Imports a class and return an instance of it, first by trying
|
||||
to find the class in a default namespace, then failing back to
|
||||
a full path if not found in the default namespace.
|
||||
"""
|
||||
import_value = "%s.%s" % (name_space, import_str)
|
||||
try:
|
||||
return import_class(import_value)(*args, **kwargs)
|
||||
except ImportError:
|
||||
return import_class(import_str)(*args, **kwargs)
|
||||
|
||||
|
||||
def import_module(import_str):
|
||||
"""Import a module."""
|
||||
__import__(import_str)
|
||||
return sys.modules[import_str]
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'watcherclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return import_module(module)
|
||||
|
||||
|
||||
def try_import(import_str, default=None):
|
||||
"""Try to import a module and if it fails return default."""
|
||||
try:
|
||||
return import_module(import_str)
|
||||
except ImportError:
|
||||
return default
|
||||
@@ -1,316 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
System-level utilities and helper functions.
|
||||
"""
|
||||
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
|
||||
import six
|
||||
|
||||
from watcherclient.openstack.common.gettextutils import _
|
||||
|
||||
|
||||
UNIT_PREFIX_EXPONENT = {
|
||||
'k': 1,
|
||||
'K': 1,
|
||||
'Ki': 1,
|
||||
'M': 2,
|
||||
'Mi': 2,
|
||||
'G': 3,
|
||||
'Gi': 3,
|
||||
'T': 4,
|
||||
'Ti': 4,
|
||||
}
|
||||
UNIT_SYSTEM_INFO = {
|
||||
'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
|
||||
'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
|
||||
}
|
||||
|
||||
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
|
||||
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
|
||||
|
||||
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
|
||||
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
|
||||
|
||||
|
||||
# NOTE(flaper87): The following globals are used by `mask_password`
|
||||
_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
|
||||
|
||||
# NOTE(ldbragst): Let's build a list of regex objects using the list of
|
||||
# _SANITIZE_KEYS we already have. This way, we only have to add the new key
|
||||
# to the list of _SANITIZE_KEYS and we can generate regular expressions
|
||||
# for XML and JSON automatically.
|
||||
_SANITIZE_PATTERNS_2 = []
|
||||
_SANITIZE_PATTERNS_1 = []
|
||||
|
||||
# NOTE(amrith): Some regular expressions have only one parameter, some
|
||||
# have two parameters. Use different lists of patterns here.
|
||||
_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+']
|
||||
_FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
|
||||
r'(%(key)s\s+[\"\']).*?([\"\'])',
|
||||
r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)',
|
||||
r'(<%(key)s>).*?(</%(key)s>)',
|
||||
r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
|
||||
r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
|
||||
r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?'
|
||||
'[\'"]).*?([\'"])',
|
||||
r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)']
|
||||
|
||||
for key in _SANITIZE_KEYS:
|
||||
for pattern in _FORMAT_PATTERNS_2:
|
||||
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
|
||||
_SANITIZE_PATTERNS_2.append(reg_ex)
|
||||
|
||||
for pattern in _FORMAT_PATTERNS_1:
|
||||
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
|
||||
_SANITIZE_PATTERNS_1.append(reg_ex)
|
||||
|
||||
|
||||
def int_from_bool_as_string(subject):
|
||||
"""Interpret a string as a boolean and return either 1 or 0.
|
||||
|
||||
Any string value in:
|
||||
|
||||
('True', 'true', 'On', 'on', '1')
|
||||
|
||||
is interpreted as a boolean True.
|
||||
|
||||
Useful for JSON-decoded stuff and config file parsing
|
||||
"""
|
||||
return bool_from_string(subject) and 1 or 0
|
||||
|
||||
|
||||
def bool_from_string(subject, strict=False, default=False):
|
||||
"""Interpret a string as a boolean.
|
||||
|
||||
A case-insensitive match is performed such that strings matching 't',
|
||||
'true', 'on', 'y', 'yes', or '1' are considered True and, when
|
||||
`strict=False`, anything else returns the value specified by 'default'.
|
||||
|
||||
Useful for JSON-decoded stuff and config file parsing.
|
||||
|
||||
If `strict=True`, unrecognized values, including None, will raise a
|
||||
ValueError which is useful when parsing values passed in from an API call.
|
||||
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
|
||||
"""
|
||||
if not isinstance(subject, six.string_types):
|
||||
subject = six.text_type(subject)
|
||||
|
||||
lowered = subject.strip().lower()
|
||||
|
||||
if lowered in TRUE_STRINGS:
|
||||
return True
|
||||
elif lowered in FALSE_STRINGS:
|
||||
return False
|
||||
elif strict:
|
||||
acceptable = ', '.join(
|
||||
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
|
||||
msg = _("Unrecognized value '%(val)s', acceptable values are:"
|
||||
" %(acceptable)s") % {'val': subject,
|
||||
'acceptable': acceptable}
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
def safe_decode(text, incoming=None, errors='strict'):
|
||||
"""Decodes incoming text/bytes string using `incoming` if they're not
|
||||
already unicode.
|
||||
|
||||
:param incoming: Text's current encoding
|
||||
:param errors: Errors handling policy. See here for valid
|
||||
values http://docs.python.org/2/library/codecs.html
|
||||
:returns: text or a unicode `incoming` encoded
|
||||
representation of it.
|
||||
:raises TypeError: If text is not an instance of str
|
||||
"""
|
||||
if not isinstance(text, (six.string_types, six.binary_type)):
|
||||
raise TypeError("%s can't be decoded" % type(text))
|
||||
|
||||
if isinstance(text, six.text_type):
|
||||
return text
|
||||
|
||||
if not incoming:
|
||||
incoming = (sys.stdin.encoding or
|
||||
sys.getdefaultencoding())
|
||||
|
||||
try:
|
||||
return text.decode(incoming, errors)
|
||||
except UnicodeDecodeError:
|
||||
# Note(flaper87) If we get here, it means that
|
||||
# sys.stdin.encoding / sys.getdefaultencoding
|
||||
# didn't return a suitable encoding to decode
|
||||
# text. This happens mostly when global LANG
|
||||
# var is not set correctly and there's no
|
||||
# default encoding. In this case, most likely
|
||||
# python will use ASCII or ANSI encoders as
|
||||
# default encodings but they won't be capable
|
||||
# of decoding non-ASCII characters.
|
||||
#
|
||||
# Also, UTF-8 is being used since it's an ASCII
|
||||
# extension.
|
||||
return text.decode('utf-8', errors)
|
||||
|
||||
|
||||
def safe_encode(text, incoming=None,
|
||||
encoding='utf-8', errors='strict'):
|
||||
"""Encodes incoming text/bytes string using `encoding`.
|
||||
|
||||
If incoming is not specified, text is expected to be encoded with
|
||||
current python's default encoding. (`sys.getdefaultencoding`)
|
||||
|
||||
:param incoming: Text's current encoding
|
||||
:param encoding: Expected encoding for text (Default UTF-8)
|
||||
:param errors: Errors handling policy. See here for valid
|
||||
values http://docs.python.org/2/library/codecs.html
|
||||
:returns: text or a bytestring `encoding` encoded
|
||||
representation of it.
|
||||
:raises TypeError: If text is not an instance of str
|
||||
"""
|
||||
if not isinstance(text, (six.string_types, six.binary_type)):
|
||||
raise TypeError("%s can't be encoded" % type(text))
|
||||
|
||||
if not incoming:
|
||||
incoming = (sys.stdin.encoding or
|
||||
sys.getdefaultencoding())
|
||||
|
||||
if isinstance(text, six.text_type):
|
||||
return text.encode(encoding, errors)
|
||||
elif text and encoding != incoming:
|
||||
# Decode text before encoding it with `encoding`
|
||||
text = safe_decode(text, incoming, errors)
|
||||
return text.encode(encoding, errors)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def string_to_bytes(text, unit_system='IEC', return_int=False):
|
||||
"""Converts a string into an float representation of bytes.
|
||||
|
||||
The units supported for IEC ::
|
||||
|
||||
Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
|
||||
KB, KiB, MB, MiB, GB, GiB, TB, TiB
|
||||
|
||||
The units supported for SI ::
|
||||
|
||||
kb(it), Mb(it), Gb(it), Tb(it)
|
||||
kB, MB, GB, TB
|
||||
|
||||
Note that the SI unit system does not support capital letter 'K'
|
||||
|
||||
:param text: String input for bytes size conversion.
|
||||
:param unit_system: Unit system for byte size conversion.
|
||||
:param return_int: If True, returns integer representation of text
|
||||
in bytes. (default: decimal)
|
||||
:returns: Numerical representation of text in bytes.
|
||||
:raises ValueError: If text has an invalid value.
|
||||
|
||||
"""
|
||||
try:
|
||||
base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
|
||||
except KeyError:
|
||||
msg = _('Invalid unit system: "%s"') % unit_system
|
||||
raise ValueError(msg)
|
||||
match = reg_ex.match(text)
|
||||
if match:
|
||||
magnitude = float(match.group(1))
|
||||
unit_prefix = match.group(2)
|
||||
if match.group(3) in ['b', 'bit']:
|
||||
magnitude /= 8
|
||||
else:
|
||||
msg = _('Invalid string format: %s') % text
|
||||
raise ValueError(msg)
|
||||
if not unit_prefix:
|
||||
res = magnitude
|
||||
else:
|
||||
res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
|
||||
if return_int:
|
||||
return int(math.ceil(res))
|
||||
return res
|
||||
|
||||
|
||||
def to_slug(value, incoming=None, errors="strict"):
|
||||
"""Normalize string.
|
||||
|
||||
Convert to lowercase, remove non-word characters, and convert spaces
|
||||
to hyphens.
|
||||
|
||||
Inspired by Django's `slugify` filter.
|
||||
|
||||
:param value: Text to slugify
|
||||
:param incoming: Text's current encoding
|
||||
:param errors: Errors handling policy. See here for valid
|
||||
values http://docs.python.org/2/library/codecs.html
|
||||
:returns: slugified unicode representation of `value`
|
||||
:raises TypeError: If text is not an instance of str
|
||||
"""
|
||||
value = safe_decode(value, incoming, errors)
|
||||
# NOTE(aababilov): no need to use safe_(encode|decode) here:
|
||||
# encodings are always "ascii", error handling is always "ignore"
|
||||
# and types are always known (first: unicode; second: str)
|
||||
value = unicodedata.normalize("NFKD", value).encode(
|
||||
"ascii", "ignore").decode("ascii")
|
||||
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
|
||||
return SLUGIFY_HYPHENATE_RE.sub("-", value)
|
||||
|
||||
|
||||
def mask_password(message, secret="***"):
|
||||
"""Replace password with 'secret' in message.
|
||||
|
||||
:param message: The string which includes security information.
|
||||
:param secret: value with which to replace passwords.
|
||||
:returns: The unicode value of message with the password fields masked.
|
||||
|
||||
For example:
|
||||
|
||||
>>> mask_password("'adminPass' : 'aaaaa'")
|
||||
"'adminPass' : '***'"
|
||||
>>> mask_password("'admin_pass' : 'aaaaa'")
|
||||
"'admin_pass' : '***'"
|
||||
>>> mask_password('"password" : "aaaaa"')
|
||||
'"password" : "***"'
|
||||
>>> mask_password("'original_password' : 'aaaaa'")
|
||||
"'original_password' : '***'"
|
||||
>>> mask_password("u'original_password' : u'aaaaa'")
|
||||
"u'original_password' : u'***'"
|
||||
"""
|
||||
try:
|
||||
message = six.text_type(message)
|
||||
except UnicodeDecodeError:
|
||||
# NOTE(jecarey): Temporary fix to handle cases where message is a
|
||||
# byte string. A better solution will be provided in Kilo.
|
||||
pass
|
||||
|
||||
# NOTE(ldbragst): Check to see if anything in message contains any key
|
||||
# specified in _SANITIZE_KEYS, if not then just return the message since
|
||||
# we don't have to mask any passwords.
|
||||
if not any(key in message for key in _SANITIZE_KEYS):
|
||||
return message
|
||||
|
||||
substitute = r'\g<1>' + secret + r'\g<2>'
|
||||
for pattern in _SANITIZE_PATTERNS_2:
|
||||
message = re.sub(pattern, substitute, message)
|
||||
|
||||
substitute = r'\g<1>' + secret
|
||||
for pattern in _SANITIZE_PATTERNS_1:
|
||||
message = re.sub(pattern, substitute, message)
|
||||
|
||||
return message
|
||||
90
watcherclient/osc/plugin.py
Normal file
90
watcherclient/osc/plugin.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib import utils
|
||||
|
||||
import watcherclient
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient.common import httpclient
|
||||
from watcherclient import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_API_VERSION = httpclient.LATEST_VERSION
|
||||
API_VERSION_OPTION = 'os_infra_optim_api_version'
|
||||
API_NAME = 'infra-optim'
|
||||
API_VERSIONS = {
|
||||
'1': 'watcherclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns an infra-optim service client."""
|
||||
|
||||
version = api_versioning.APIVersion(instance._api_version[API_NAME])
|
||||
|
||||
infraoptim_client_class = utils.get_client_class(
|
||||
API_NAME,
|
||||
version.ver_major,
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
|
||||
|
||||
client = infraoptim_client_class(
|
||||
os_infra_optim_api_version=instance._api_version[API_NAME],
|
||||
session=instance.session,
|
||||
region_name=instance._region_name,
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options."""
|
||||
parser.add_argument('--os-infra-optim-api-version',
|
||||
metavar='<infra-optim-api-version>',
|
||||
default=utils.env(
|
||||
'OS_INFRA_OPTIM_API_VERSION',
|
||||
default=DEFAULT_API_VERSION),
|
||||
help=('Watcher API version, default=' +
|
||||
DEFAULT_API_VERSION +
|
||||
' (Env: OS_INFRA_OPTIM_API_VERSION)'))
|
||||
return parser
|
||||
|
||||
|
||||
def check_api_version(check_version):
|
||||
"""Validate version supplied by user
|
||||
|
||||
Returns:
|
||||
* True if version is OK
|
||||
* False if the version has not been checked and the previous plugin
|
||||
check should be performed
|
||||
* throws an exception if the version is no good
|
||||
"""
|
||||
|
||||
infra_api_version = api_versioning.get_api_version(check_version)
|
||||
|
||||
# Bypass X.latest format microversion
|
||||
if not infra_api_version.is_latest():
|
||||
if infra_api_version > api_versioning.APIVersion("2.0"):
|
||||
if not infra_api_version.matches(
|
||||
watcherclient.API_MIN_VERSION,
|
||||
watcherclient.API_MAX_VERSION,
|
||||
):
|
||||
msg = "versions supported by client: %(min)s - %(max)s" % {
|
||||
"min": watcherclient.API_MIN_VERSION.get_string(),
|
||||
"max": watcherclient.API_MAX_VERSION.get_string(),
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
return True
|
||||
@@ -1,510 +1,212 @@
|
||||
# -*- 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.openstack.common.apiclient 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
|
||||
|
||||
from watcherclient import client as watcherclient
|
||||
from watcherclient import version
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
import watcherclient
|
||||
from watcherclient import client as watcher_client
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.openstack.common import gettextutils
|
||||
|
||||
gettextutils.install('watcherclient')
|
||||
API_NAME = 'infra-optim'
|
||||
API_VERSIONS = {
|
||||
'1': 'watcherclient.v1.client.Client',
|
||||
}
|
||||
DEFAULT_OS_INFRA_OPTIM_API_VERSION = '1.latest'
|
||||
_DEFAULT_IDENTITY_API_VERSION = '3'
|
||||
_IDENTITY_API_VERSION_2 = ['2', '2.0']
|
||||
_IDENTITY_API_VERSION_3 = ['3']
|
||||
|
||||
|
||||
class WatcherShell(object):
|
||||
class WatcherShell(app.App):
|
||||
"""Watcher command line interface."""
|
||||
|
||||
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.
|
||||
def __init__(self, **kwargs):
|
||||
self.client = None
|
||||
|
||||
# Register arguments needed for a Session
|
||||
kssession.Session.register_cli_options(parser)
|
||||
# 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-infra-optim-api-version',
|
||||
metavar='<infra-optim-api-version>',
|
||||
default=utils.env('OS_INFRA_OPTIM_API_VERSION',
|
||||
default=DEFAULT_OS_INFRA_OPTIM_API_VERSION),
|
||||
help='Accepts X, X.Y (where X is major and Y is minor part) or '
|
||||
'"X.latest", defaults to env[OS_INFRA_OPTIM_API_VERSION].')
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=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()
|
||||
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:
|
||||
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:]))
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
|
||||
from watcherclient.client import get_client
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.tests import utils
|
||||
|
||||
|
||||
def fake_get_ksclient(**kwargs):
|
||||
return utils.FakeKeystone('KSCLIENT_AUTH_TOKEN')
|
||||
|
||||
|
||||
class ClientTest(utils.BaseTestCase):
|
||||
|
||||
def test_get_client_with_auth_token_watcher_url(self):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', fake_get_ksclient))
|
||||
kwargs = {
|
||||
'watcher_url': 'http://watcher.example.org:6385/',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
client = get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
|
||||
self.assertEqual('http://watcher.example.org:6385/',
|
||||
client.http_client.endpoint)
|
||||
|
||||
def test_get_client_no_auth_token(self):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', fake_get_ksclient))
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
}
|
||||
client = get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
|
||||
self.assertEqual('http://localhost:6385/v1/f14b41234',
|
||||
client.http_client.endpoint)
|
||||
self.assertEqual(fake_get_ksclient().auth_ref,
|
||||
client.http_client.auth_ref)
|
||||
|
||||
def test_get_client_with_region_no_auth_token(self):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', fake_get_ksclient))
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
}
|
||||
client = get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
|
||||
self.assertEqual('http://regionhost:6385/v1/f14b41234',
|
||||
client.http_client.endpoint)
|
||||
self.assertEqual(fake_get_ksclient().auth_ref,
|
||||
client.http_client.auth_ref)
|
||||
|
||||
def test_get_client_with_auth_token(self):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', fake_get_ksclient))
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
client = get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
|
||||
self.assertEqual('http://localhost:6385/v1/f14b41234',
|
||||
client.http_client.endpoint)
|
||||
self.assertEqual(fake_get_ksclient().auth_ref,
|
||||
client.http_client.auth_ref)
|
||||
|
||||
def test_get_client_with_region_name_auth_token(self):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', fake_get_ksclient))
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
client = get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
|
||||
self.assertEqual('http://regionhost:6385/v1/f14b41234',
|
||||
client.http_client.endpoint)
|
||||
self.assertEqual(fake_get_ksclient().auth_ref,
|
||||
client.http_client.auth_ref)
|
||||
|
||||
def test_get_client_no_url_and_no_token(self):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', fake_get_ksclient))
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': '',
|
||||
'os_auth_token': '',
|
||||
}
|
||||
self.assertRaises(exc.AmbiguousAuthSystem, get_client, '1', **kwargs)
|
||||
# test the alias as well to ensure backwards compatibility
|
||||
self.assertRaises(exc.AmbigiousAuthSystem, get_client, '1', **kwargs)
|
||||
|
||||
def test_ensure_auth_ref_propagated(self):
|
||||
ksclient = fake_get_ksclient
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'watcherclient.client._get_ksclient', ksclient))
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
}
|
||||
client = get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual(ksclient().auth_ref, client.http_client.auth_ref)
|
||||
@@ -1,283 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
|
||||
import six
|
||||
|
||||
from watcherclient.common import http
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.tests import utils
|
||||
|
||||
|
||||
HTTP_CLASS = six.moves.http_client.HTTPConnection
|
||||
HTTPS_CLASS = http.VerifiedHTTPSConnection
|
||||
DEFAULT_TIMEOUT = 600
|
||||
|
||||
|
||||
def _get_error_body(faultstring=None, debuginfo=None):
|
||||
error_body = {
|
||||
'faultstring': faultstring,
|
||||
'debuginfo': debuginfo
|
||||
}
|
||||
raw_error_body = json.dumps(error_body)
|
||||
body = {'error_message': raw_error_body}
|
||||
raw_body = json.dumps(body)
|
||||
return raw_body
|
||||
|
||||
|
||||
class HttpClientTest(utils.BaseTestCase):
|
||||
|
||||
def test_url_generation_trailing_slash_in_base(self):
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
url = client._make_connection_url('/v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_url_generation_without_trailing_slash_in_base(self):
|
||||
client = http.HTTPClient('http://localhost')
|
||||
url = client._make_connection_url('/v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_url_generation_prefix_slash_in_path(self):
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
url = client._make_connection_url('/v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_url_generation_without_prefix_slash_in_path(self):
|
||||
client = http.HTTPClient('http://localhost')
|
||||
url = client._make_connection_url('v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_server_exception_empty_body(self):
|
||||
error_body = _get_error_body()
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual('Internal Server Error (HTTP 500)', str(error))
|
||||
|
||||
def test_server_exception_msg_only(self):
|
||||
error_msg = 'test error msg'
|
||||
error_body = _get_error_body(error_msg)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual(error_msg + ' (HTTP 500)', str(error))
|
||||
|
||||
def test_server_exception_msg_and_traceback(self):
|
||||
error_msg = 'another test error'
|
||||
error_trace = ("\"Traceback (most recent call last):\\n\\n "
|
||||
"File \\\"/usr/local/lib/python2.7/...")
|
||||
error_body = _get_error_body(error_msg, error_trace)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(
|
||||
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
|
||||
'trace': error_trace},
|
||||
"%(error)s\n%(details)s" % {'error': str(error),
|
||||
'details': str(error.details)})
|
||||
|
||||
def test_get_connection_params(self):
|
||||
endpoint = 'http://watcher-host:6385'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_trailing_slash(self):
|
||||
endpoint = 'http://watcher-host:6385/'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_ssl(self):
|
||||
endpoint = 'https://watcher-host:6385'
|
||||
expected = (HTTPS_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
{
|
||||
'timeout': DEFAULT_TIMEOUT,
|
||||
'ca_file': None,
|
||||
'cert_file': None,
|
||||
'key_file': None,
|
||||
'insecure': False,
|
||||
})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_ssl_params(self):
|
||||
endpoint = 'https://watcher-host:6385'
|
||||
ssl_args = {
|
||||
'ca_file': '/path/to/ca_file',
|
||||
'cert_file': '/path/to/cert_file',
|
||||
'key_file': '/path/to/key_file',
|
||||
'insecure': True,
|
||||
}
|
||||
|
||||
expected_kwargs = {'timeout': DEFAULT_TIMEOUT}
|
||||
expected_kwargs.update(ssl_args)
|
||||
expected = (HTTPS_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
expected_kwargs)
|
||||
params = http.HTTPClient.get_connection_params(endpoint, **ssl_args)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_timeout(self):
|
||||
endpoint = 'http://watcher-host:6385'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
{'timeout': 300.0})
|
||||
params = http.HTTPClient.get_connection_params(endpoint, timeout=300)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_version(self):
|
||||
endpoint = 'http://watcher-host:6385/v1'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_version_trailing_slash(self):
|
||||
endpoint = 'http://watcher-host:6385/v1/'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath(self):
|
||||
endpoint = 'http://watcher-host:6385/watcher'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, '/watcher'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath_trailing_slash(self):
|
||||
endpoint = 'http://watcher-host:6385/watcher/'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, '/watcher'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath_version(self):
|
||||
endpoint = 'http://watcher-host:6385/watcher/v1'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, '/watcher'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath_version_trailing_slash(self):
|
||||
endpoint = 'http://watcher-host:6385/watcher/v1/'
|
||||
expected = (HTTP_CLASS,
|
||||
('watcher-host', 6385, '/watcher'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_401_unauthorized_exception(self):
|
||||
error_body = _get_error_body()
|
||||
fake_resp = utils.FakeResponse({'content-type': 'text/plain'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=401)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
self.assertRaises(exc.Unauthorized, client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
|
||||
class SessionClientTest(utils.BaseTestCase):
|
||||
|
||||
def test_server_exception_msg_and_traceback(self):
|
||||
error_msg = 'another test error'
|
||||
error_trace = ("\"Traceback (most recent call last):\\n\\n "
|
||||
"File \\\"/usr/local/lib/python2.7/...")
|
||||
error_body = _get_error_body(error_msg, error_trace)
|
||||
|
||||
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
|
||||
error_body,
|
||||
500)
|
||||
|
||||
client = http.SessionClient(session=fake_session,
|
||||
auth=None,
|
||||
interface=None,
|
||||
service_type='publicURL',
|
||||
region_name='',
|
||||
service_name=None)
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(
|
||||
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
|
||||
'trace': error_trace},
|
||||
"%(error)s\n%(details)s" % {'error': str(error),
|
||||
'details': str(error.details)})
|
||||
|
||||
def test_server_exception_empty_body(self):
|
||||
error_body = _get_error_body()
|
||||
|
||||
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
|
||||
error_body,
|
||||
500)
|
||||
|
||||
client = http.SessionClient(session=fake_session,
|
||||
auth=None,
|
||||
interface=None,
|
||||
service_type='publicURL',
|
||||
region_name='',
|
||||
service_name=None)
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
self.assertEqual('Internal Server Error (HTTP 500)', str(error))
|
||||
@@ -1,306 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import fixtures
|
||||
import httpretty
|
||||
from keystoneclient import exceptions as keystone_exc
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneclient.fixture import v3 as ks_v3_fixture
|
||||
import mock
|
||||
import six
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient import shell as watcher_shell
|
||||
from watcherclient.tests import keystone_client_fixtures
|
||||
from watcherclient.tests import utils
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0/'}
|
||||
|
||||
FAKE_ENV_KEYSTONE_V2 = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
|
||||
}
|
||||
|
||||
FAKE_ENV_KEYSTONE_V3 = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
|
||||
'OS_USER_DOMAIN_ID': 'default',
|
||||
'OS_PROJECT_DOMAIN_ID': 'default',
|
||||
}
|
||||
|
||||
|
||||
class ShellTest(utils.BaseTestCase):
|
||||
re_options = re.DOTALL | re.MULTILINE
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
def make_env(self, exclude=None):
|
||||
env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude)
|
||||
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTest, self).setUp()
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
_shell = watcher_shell.WatcherShell()
|
||||
_shell.main(argstr.split())
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(0, exc_value.code)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
return out
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
def test_help(self):
|
||||
required = [
|
||||
'.*?^usage: watcher',
|
||||
'.*?^ +bash-completion',
|
||||
'.*?^See "watcher help COMMAND" '
|
||||
'for help on a specific command',
|
||||
]
|
||||
for argstr in ['--help', 'help']:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertThat(help_text,
|
||||
matchers.MatchesRegex(r,
|
||||
self.re_options))
|
||||
|
||||
def test_help_on_subcommand(self):
|
||||
required = [
|
||||
'.*?^usage: watcher action-show',
|
||||
".*?^Show detailed information about an action",
|
||||
]
|
||||
argstrings = [
|
||||
'help action-show',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertThat(help_text,
|
||||
matchers.MatchesRegex(r, self.re_options))
|
||||
|
||||
def test_auth_param(self):
|
||||
self.make_env(exclude='OS_USERNAME')
|
||||
self.test_help()
|
||||
|
||||
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
|
||||
@mock.patch('getpass.getpass', return_value='password')
|
||||
def test_password_prompted(self, mock_getpass, mock_stdin):
|
||||
self.make_env(exclude='OS_PASSWORD')
|
||||
# We will get a Connection Refused because there is no keystone.
|
||||
self.assertRaises(keystone_exc.ConnectionRefused,
|
||||
self.shell, 'action-list')
|
||||
# Make sure we are actually prompted.
|
||||
mock_getpass.assert_called_with('OpenStack Password: ')
|
||||
|
||||
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
|
||||
@mock.patch('getpass.getpass', side_effect=EOFError)
|
||||
def test_password_prompted_ctrlD(self, mock_getpass, mock_stdin):
|
||||
self.make_env(exclude='OS_PASSWORD')
|
||||
# We should get Command Error because we mock Ctl-D.
|
||||
self.assertRaises(exc.CommandError,
|
||||
self.shell, 'action-list')
|
||||
# Make sure we are actually prompted.
|
||||
mock_getpass.assert_called_with('OpenStack Password: ')
|
||||
|
||||
@mock.patch('sys.stdin')
|
||||
def test_no_password_no_tty(self, mock_stdin):
|
||||
# delete the isatty attribute so that we do not get
|
||||
# prompted when manually running the tests
|
||||
del mock_stdin.isatty
|
||||
required = ('You must provide a password'
|
||||
' via either --os-password, env[OS_PASSWORD],'
|
||||
' or prompted response',)
|
||||
self.make_env(exclude='OS_PASSWORD')
|
||||
try:
|
||||
self.shell('action-list')
|
||||
except exc.CommandError as message:
|
||||
self.assertEqual(required, message.args)
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_bash_completion(self):
|
||||
stdout = self.shell('bash-completion')
|
||||
# just check we have some output
|
||||
required = [
|
||||
'.*help',
|
||||
'.*audit-list',
|
||||
'.*audit-show',
|
||||
'.*audit-delete',
|
||||
'.*audit-update',
|
||||
'.*audit-template-create',
|
||||
'.*audit-template-update',
|
||||
'.*audit-template-list',
|
||||
'.*audit-template-show',
|
||||
'.*audit-template-delete',
|
||||
'.*action-list',
|
||||
'.*action-show',
|
||||
'.*action-update',
|
||||
'.*action-plan-list',
|
||||
'.*action-plan-show',
|
||||
'.*action-plan-update',
|
||||
]
|
||||
for r in required:
|
||||
self.assertThat(stdout,
|
||||
matchers.MatchesRegex(r, self.re_options))
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
tokenid = keystone_client_fixtures.TOKENID
|
||||
|
||||
def set_fake_env(self, fake_env):
|
||||
client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_ID',
|
||||
'OS_TENANT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME',
|
||||
'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE',
|
||||
'OS_ENDPOINT_TYPE')
|
||||
|
||||
for key in client_env:
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable(key, fake_env.get(key)))
|
||||
|
||||
# required for testing with Python 2.6
|
||||
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
||||
"""Fail the test unless the text matches the regular expression."""
|
||||
if isinstance(expected_regexp, six.string_types):
|
||||
expected_regexp = re.compile(expected_regexp)
|
||||
if not expected_regexp.search(text):
|
||||
msg = msg or "Regexp didn't match"
|
||||
msg = '%s: %r not found in %r' % (
|
||||
msg, expected_regexp.pattern, text)
|
||||
raise self.failureException(msg)
|
||||
|
||||
def register_keystone_v2_token_fixture(self):
|
||||
v2_token = ks_v2_fixture.Token(token_id=self.tokenid)
|
||||
service = v2_token.add_service('baremetal')
|
||||
service.add_endpoint('http://watcher.example.com', region='RegionOne')
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
'%s/tokens' % (keystone_client_fixtures.V2_URL),
|
||||
body=json.dumps(v2_token))
|
||||
|
||||
def register_keystone_v3_token_fixture(self):
|
||||
v3_token = ks_v3_fixture.Token()
|
||||
service = v3_token.add_service('baremetal')
|
||||
service.add_standard_endpoints(public='http://watcher.example.com')
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
'%s/auth/tokens' % (keystone_client_fixtures.V3_URL),
|
||||
body=json.dumps(v3_token),
|
||||
adding_headers={'X-Subject-Token': self.tokenid})
|
||||
|
||||
def register_keystone_auth_fixture(self):
|
||||
self.register_keystone_v2_token_fixture()
|
||||
self.register_keystone_v3_token_fixture()
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
keystone_client_fixtures.BASE_URL,
|
||||
body=keystone_client_fixtures.keystone_request_callback)
|
||||
|
||||
|
||||
class ShellTestNoMox(TestCase):
|
||||
def setUp(self):
|
||||
super(ShellTestNoMox, self).setUp()
|
||||
# httpretty doesn't work as expected if http proxy environment
|
||||
# variable is set.
|
||||
os.environ = dict((k, v) for (k, v) in os.environ.items()
|
||||
if k.lower() not in ('http_proxy', 'https_proxy'))
|
||||
self.set_fake_env(FAKE_ENV_KEYSTONE_V2)
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
_shell = watcher_shell.WatcherShell()
|
||||
_shell.main(argstr.split())
|
||||
self.subcommands = _shell.subcommands.keys()
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(0, exc_value.code)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
|
||||
return out
|
||||
|
||||
# @httpretty.activate
|
||||
# def test_action_list(self):
|
||||
# self.register_keystone_auth_fixture()
|
||||
# resp_dict = {"dummies": [
|
||||
# {"instance_uuid": "null",
|
||||
# "uuid": "351a82d6-9f04-4c36-b79a-a38b9e98ff71",
|
||||
# "links": [{"href": "http://watcher.example.com:6385/"
|
||||
# "v1/dummies/foo",
|
||||
# "rel": "self"},
|
||||
# {"href": "http://watcher.example.com:6385/"
|
||||
# "dummies/foo",
|
||||
# "rel": "bookmark"}],
|
||||
# "maintenance": "false",
|
||||
# "provision_state": "null",
|
||||
# "power_state": "power off"},
|
||||
# {"instance_uuid": "null",
|
||||
# "uuid": "66fbba13-29e8-4b8a-9e80-c655096a40d3",
|
||||
# "links": [{"href": "http://watcher.example.com:6385/"
|
||||
# "v1/dummies/foo2",
|
||||
# "rel": "self"},
|
||||
# {"href": "http://watcher.example.com:6385/"
|
||||
# "dummies/foo2",
|
||||
# "rel": "bookmark"}],
|
||||
# "maintenance": "false",
|
||||
# "provision_state": "null",
|
||||
# "power_state": "power off"}]}
|
||||
# httpretty.register_uri(
|
||||
# httpretty.GET,
|
||||
# 'http://watcher.example.com/v1/dummies',
|
||||
# status=200,
|
||||
# content_type='application/json; charset=UTF-8',
|
||||
# body=json.dumps(resp_dict))
|
||||
|
||||
# event_list_text = self.shell('action-list')
|
||||
|
||||
# required = [
|
||||
# '351a82d6-9f04-4c36-b79a-a38b9e98ff71',
|
||||
# '66fbba13-29e8-4b8a-9e80-c655096a40d3',
|
||||
# ]
|
||||
|
||||
# for r in required:
|
||||
# self.assertRegexpMatches(event_list_text, r)
|
||||
|
||||
|
||||
class ShellTestNoMoxV3(ShellTestNoMox):
|
||||
|
||||
def _set_fake_env(self):
|
||||
self.set_fake_env(FAKE_ENV_KEYSTONE_V3)
|
||||
150
watcherclient/tests/unit/common/test_api_versioning.py
Normal file
150
watcherclient/tests/unit/common/test_api_versioning.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# Copyright 2016 Mirantis
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient import exceptions
|
||||
from watcherclient.tests.unit import utils
|
||||
|
||||
|
||||
class APIVersionTestCase(utils.BaseTestCase):
|
||||
def test_valid_version_strings(self):
|
||||
def _test_string(version, exp_major, exp_minor):
|
||||
v = api_versioning.APIVersion(version)
|
||||
self.assertEqual(v.ver_major, exp_major)
|
||||
self.assertEqual(v.ver_minor, exp_minor)
|
||||
|
||||
_test_string("1.1", 1, 1)
|
||||
_test_string("2.10", 2, 10)
|
||||
_test_string("5.234", 5, 234)
|
||||
_test_string("12.5", 12, 5)
|
||||
_test_string("2.0", 2, 0)
|
||||
_test_string("2.200", 2, 200)
|
||||
|
||||
def test_null_version(self):
|
||||
v = api_versioning.APIVersion()
|
||||
self.assertTrue(v.is_null())
|
||||
|
||||
def test_invalid_version_strings(self):
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "2")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "200")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "2.1.4")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "200.23.66.3")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "5 .3")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "5. 3")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "5.03")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "02.1")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "2.001")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, " 2.1")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.APIVersion, "2.1 ")
|
||||
|
||||
def test_version_comparisons(self):
|
||||
v1 = api_versioning.APIVersion("2.0")
|
||||
v2 = api_versioning.APIVersion("2.5")
|
||||
v3 = api_versioning.APIVersion("5.23")
|
||||
v4 = api_versioning.APIVersion("2.0")
|
||||
v_null = api_versioning.APIVersion()
|
||||
|
||||
self.assertLess(v1, v2)
|
||||
self.assertGreater(v3, v2)
|
||||
self.assertNotEqual(v1, v2)
|
||||
self.assertEqual(v1, v4)
|
||||
self.assertNotEqual(v1, v_null)
|
||||
self.assertEqual(v_null, v_null)
|
||||
self.assertRaises(TypeError, v1.__le__, "2.1")
|
||||
|
||||
def test_version_matches(self):
|
||||
v1 = api_versioning.APIVersion("2.0")
|
||||
v2 = api_versioning.APIVersion("2.5")
|
||||
v3 = api_versioning.APIVersion("2.45")
|
||||
v4 = api_versioning.APIVersion("3.3")
|
||||
v5 = api_versioning.APIVersion("3.23")
|
||||
v6 = api_versioning.APIVersion("2.0")
|
||||
v7 = api_versioning.APIVersion("3.3")
|
||||
v8 = api_versioning.APIVersion("4.0")
|
||||
v_null = api_versioning.APIVersion()
|
||||
|
||||
self.assertTrue(v2.matches(v1, v3))
|
||||
self.assertTrue(v2.matches(v1, v_null))
|
||||
self.assertTrue(v1.matches(v6, v2))
|
||||
self.assertTrue(v4.matches(v2, v7))
|
||||
self.assertTrue(v4.matches(v_null, v7))
|
||||
self.assertTrue(v4.matches(v_null, v8))
|
||||
self.assertFalse(v1.matches(v2, v3))
|
||||
self.assertFalse(v5.matches(v2, v4))
|
||||
self.assertFalse(v2.matches(v3, v1))
|
||||
|
||||
self.assertRaises(ValueError, v_null.matches, v1, v3)
|
||||
|
||||
def test_get_string(self):
|
||||
v1_string = "3.23"
|
||||
v1 = api_versioning.APIVersion(v1_string)
|
||||
self.assertEqual(v1_string, v1.get_string())
|
||||
|
||||
self.assertRaises(ValueError,
|
||||
api_versioning.APIVersion().get_string)
|
||||
|
||||
|
||||
class GetAPIVersionTestCase(utils.BaseTestCase):
|
||||
def test_get_available_client_versions(self):
|
||||
output = api_versioning.get_available_major_versions()
|
||||
self.assertNotEqual([], output)
|
||||
|
||||
def test_wrong_format(self):
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.get_api_version, "something_wrong")
|
||||
|
||||
def test_wrong_major_version(self):
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versioning.get_api_version, "2")
|
||||
|
||||
@mock.patch("watcherclient.common.api_versioning.APIVersion")
|
||||
def test_only_major_part_is_presented(self, mock_apiversion):
|
||||
version = 7
|
||||
self.assertEqual(mock_apiversion.return_value,
|
||||
api_versioning.get_api_version(version))
|
||||
mock_apiversion.assert_called_once_with("%s.0" % str(version))
|
||||
|
||||
@mock.patch("watcherclient.common.api_versioning.APIVersion")
|
||||
def test_major_and_minor_parts_is_presented(self, mock_apiversion):
|
||||
version = "2.7"
|
||||
self.assertEqual(mock_apiversion.return_value,
|
||||
api_versioning.get_api_version(version))
|
||||
mock_apiversion.assert_called_once_with(version)
|
||||
@@ -10,11 +10,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneclient.fixture import v3 as ks_v3_fixture
|
||||
from keystoneauth1.fixture import v2 as ks_v2_fixture
|
||||
from keystoneauth1.fixture import v3 as ks_v3_fixture
|
||||
|
||||
# these are copied from python-keystoneclient tests
|
||||
BASE_HOST = 'http://keystone.example.com'
|
||||
@@ -49,15 +49,15 @@ V3_VERSION = {'id': 'v3.0',
|
||||
'status': 'stable',
|
||||
'updated': UPDATED}
|
||||
|
||||
TOKENID = uuid.uuid4().hex
|
||||
TOKENID = uuidutils.generate_uuid(dashed=False)
|
||||
|
||||
|
||||
def _create_version_list(versions):
|
||||
return json.dumps({'versions': {'values': versions}})
|
||||
return jsonutils.dumps({'versions': {'values': versions}})
|
||||
|
||||
|
||||
def _create_single_version(version):
|
||||
return json.dumps({'version': version})
|
||||
return jsonutils.dumps({'version': version})
|
||||
|
||||
|
||||
V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
|
||||
@@ -74,8 +74,8 @@ def keystone_request_callback(request, uri, headers):
|
||||
return (200, headers, V3_VERSION_LIST)
|
||||
elif uri == BASE_URL + "/v2.0":
|
||||
v2_token = ks_v2_fixture.Token(token_id)
|
||||
return (200, response_headers, json.dumps(v2_token))
|
||||
return (200, response_headers, jsonutils.dumps(v2_token))
|
||||
elif uri == BASE_URL + "/v3":
|
||||
v3_token = ks_v3_fixture.Token()
|
||||
response_headers["X-Subject-Token"] = token_id
|
||||
return (201, response_headers, json.dumps(v3_token))
|
||||
return (201, response_headers, jsonutils.dumps(v3_token))
|
||||
360
watcherclient/tests/unit/test_client.py
Normal file
360
watcherclient/tests/unit/test_client.py
Normal file
@@ -0,0 +1,360 @@
|
||||
# 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 unittest import mock
|
||||
|
||||
from keystoneauth1 import loading as kaloading
|
||||
|
||||
from watcherclient import client as watcherclient
|
||||
from watcherclient.common import httpclient
|
||||
from watcherclient import exceptions
|
||||
from watcherclient.tests.unit import utils
|
||||
|
||||
|
||||
class ClientTest(utils.BaseTestCase):
|
||||
|
||||
def test_get_client_with_auth_token_watcher_url(self):
|
||||
kwargs = {
|
||||
'watcher_url': 'http://watcher.example.org:9322/',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
client = watcherclient.get_client('1', **kwargs)
|
||||
|
||||
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
|
||||
self.assertEqual('http://watcher.example.org:9322/',
|
||||
client.http_client.endpoint)
|
||||
|
||||
@mock.patch.object(kaloading.session, 'Session', autospec=True)
|
||||
@mock.patch.object(kaloading, 'get_plugin_loader', autospec=True)
|
||||
def _test_get_client(self, mock_ks_loader, mock_ks_session,
|
||||
version=None, auth='password', **kwargs):
|
||||
session = mock_ks_session.return_value.load_from_options.return_value
|
||||
session.get_endpoint.return_value = 'http://localhost:9322/v1/f14b4123'
|
||||
mock_ks_loader.return_value.load_from_options.return_value = 'auth'
|
||||
|
||||
watcherclient.get_client('1', **kwargs)
|
||||
|
||||
mock_ks_loader.assert_called_once_with(auth)
|
||||
mock_ks_session.return_value.load_from_options.assert_called_once_with(
|
||||
auth='auth', timeout=kwargs.get('timeout'),
|
||||
insecure=kwargs.get('insecure'), cert=kwargs.get('cert'),
|
||||
cacert=kwargs.get('cacert'), key=kwargs.get('key'))
|
||||
session.get_endpoint.assert_called_once_with(
|
||||
service_type=kwargs.get('os_service_type') or 'infra-optim',
|
||||
interface=kwargs.get('os_endpoint_type') or 'publicURL',
|
||||
region_name=kwargs.get('os_region_name'))
|
||||
|
||||
def test_get_client_no_auth_token(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
}
|
||||
self._test_get_client(**kwargs)
|
||||
|
||||
def test_get_client_service_and_endpoint_type_defaults(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
'os_service_type': '',
|
||||
'os_endpoint_type': ''
|
||||
}
|
||||
self._test_get_client(**kwargs)
|
||||
|
||||
def test_get_client_with_region_no_auth_token(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
}
|
||||
self._test_get_client(**kwargs)
|
||||
|
||||
def test_get_client_no_url(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': '',
|
||||
}
|
||||
self.assertRaises(
|
||||
exceptions.AmbiguousAuthSystem, watcherclient.get_client,
|
||||
'1', **kwargs)
|
||||
# test the alias as well to ensure backwards compatibility
|
||||
self.assertRaises(
|
||||
exceptions.AmbigiousAuthSystem, watcherclient.get_client,
|
||||
'1', **kwargs)
|
||||
|
||||
def test_get_client_incorrect_auth_params(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
}
|
||||
self.assertRaises(
|
||||
exceptions.AmbiguousAuthSystem, watcherclient.get_client,
|
||||
'1', **kwargs)
|
||||
|
||||
def test_get_client_with_api_version_latest(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
'os_infra_optim_api_version': "latest",
|
||||
}
|
||||
self._test_get_client(**kwargs)
|
||||
|
||||
def test_get_client_with_api_version_numeric(self):
|
||||
kwargs = {
|
||||
'os_tenant_name': 'TENANT_NAME',
|
||||
'os_username': 'USERNAME',
|
||||
'os_password': 'PASSWORD',
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': '',
|
||||
'os_infra_optim_api_version': "1.4",
|
||||
}
|
||||
self._test_get_client(**kwargs)
|
||||
|
||||
def test_get_client_with_auth_token(self):
|
||||
kwargs = {
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
self._test_get_client(auth='token', **kwargs)
|
||||
|
||||
def test_get_client_with_region_name_auth_token(self):
|
||||
kwargs = {
|
||||
'os_auth_url': 'http://localhost:35357/v2.0',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
self._test_get_client(auth='token', **kwargs)
|
||||
|
||||
def test_get_client_only_session_passed(self):
|
||||
session = mock.Mock()
|
||||
session.get_endpoint.return_value = 'http://localhost:35357/v2.0'
|
||||
kwargs = {
|
||||
'session': session,
|
||||
}
|
||||
watcherclient.get_client('1', **kwargs)
|
||||
session.get_endpoint.assert_called_once_with(
|
||||
service_type='infra-optim',
|
||||
interface='publicURL',
|
||||
region_name=None)
|
||||
|
||||
def test_get_client_incorrect_session_passed(self):
|
||||
session = mock.Mock()
|
||||
session.get_endpoint.side_effect = Exception('boo')
|
||||
kwargs = {
|
||||
'session': session,
|
||||
}
|
||||
self.assertRaises(
|
||||
exceptions.AmbiguousAuthSystem, watcherclient.get_client,
|
||||
'1', **kwargs)
|
||||
|
||||
@mock.patch.object(kaloading.session, 'Session', autospec=True)
|
||||
@mock.patch.object(kaloading, 'get_plugin_loader', autospec=True)
|
||||
def _test_loader_arguments_passed_correctly(
|
||||
self, mock_ks_loader, mock_ks_session,
|
||||
passed_kwargs, expected_kwargs):
|
||||
session = mock_ks_session.return_value.load_from_options.return_value
|
||||
session.get_endpoint.return_value = 'http://localhost:9322/v1/f14b4123'
|
||||
mock_ks_loader.return_value.load_from_options.return_value = 'auth'
|
||||
|
||||
watcherclient.get_client('1', **passed_kwargs)
|
||||
|
||||
mock_ks_loader.return_value.load_from_options.assert_called_once_with(
|
||||
**expected_kwargs)
|
||||
mock_ks_session.return_value.load_from_options.assert_called_once_with(
|
||||
auth='auth', timeout=passed_kwargs.get('timeout'),
|
||||
insecure=passed_kwargs.get('insecure'),
|
||||
cert=passed_kwargs.get('cert'),
|
||||
cacert=passed_kwargs.get('cacert'), key=passed_kwargs.get('key'))
|
||||
session.get_endpoint.assert_called_once_with(
|
||||
service_type=passed_kwargs.get('os_service_type') or 'infra-optim',
|
||||
interface=passed_kwargs.get('os_endpoint_type') or 'publicURL',
|
||||
region_name=passed_kwargs.get('os_region_name'))
|
||||
|
||||
def test_loader_arguments_token(self):
|
||||
passed_kwargs = {
|
||||
'os_auth_url': 'http://localhost:35357/v3',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
}
|
||||
expected_kwargs = {
|
||||
'auth_url': 'http://localhost:35357/v3',
|
||||
'project_id': None,
|
||||
'project_name': None,
|
||||
'user_domain_id': None,
|
||||
'user_domain_name': None,
|
||||
'project_domain_id': None,
|
||||
'project_domain_name': None,
|
||||
'token': 'USER_AUTH_TOKEN'
|
||||
}
|
||||
self._test_loader_arguments_passed_correctly(
|
||||
passed_kwargs=passed_kwargs, expected_kwargs=expected_kwargs)
|
||||
|
||||
def test_loader_arguments_password_tenant_name(self):
|
||||
passed_kwargs = {
|
||||
'os_auth_url': 'http://localhost:35357/v3',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_tenant_name': 'TENANT',
|
||||
'os_username': 'user',
|
||||
'os_password': '1234',
|
||||
'os_project_domain_id': 'DEFAULT',
|
||||
'os_user_domain_id': 'DEFAULT'
|
||||
}
|
||||
expected_kwargs = {
|
||||
'auth_url': 'http://localhost:35357/v3',
|
||||
'project_id': None,
|
||||
'project_name': 'TENANT',
|
||||
'user_domain_id': 'DEFAULT',
|
||||
'user_domain_name': None,
|
||||
'project_domain_id': 'DEFAULT',
|
||||
'project_domain_name': None,
|
||||
'username': 'user',
|
||||
'password': '1234'
|
||||
}
|
||||
self._test_loader_arguments_passed_correctly(
|
||||
passed_kwargs=passed_kwargs, expected_kwargs=expected_kwargs)
|
||||
|
||||
def test_loader_arguments_password_project_id(self):
|
||||
passed_kwargs = {
|
||||
'os_auth_url': 'http://localhost:35357/v3',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_project_id': '1000',
|
||||
'os_username': 'user',
|
||||
'os_password': '1234',
|
||||
'os_project_domain_name': 'domain1',
|
||||
'os_user_domain_name': 'domain1'
|
||||
}
|
||||
expected_kwargs = {
|
||||
'auth_url': 'http://localhost:35357/v3',
|
||||
'project_id': '1000',
|
||||
'project_name': None,
|
||||
'user_domain_id': None,
|
||||
'user_domain_name': 'domain1',
|
||||
'project_domain_id': None,
|
||||
'project_domain_name': 'domain1',
|
||||
'username': 'user',
|
||||
'password': '1234'
|
||||
}
|
||||
self._test_loader_arguments_passed_correctly(
|
||||
passed_kwargs=passed_kwargs, expected_kwargs=expected_kwargs)
|
||||
|
||||
@mock.patch.object(watcherclient, 'Client')
|
||||
@mock.patch.object(kaloading.session, 'Session', autospec=True)
|
||||
def test_correct_arguments_passed_to_client_constructor_noauth_mode(
|
||||
self, mock_ks_session, mock_client):
|
||||
kwargs = {
|
||||
'watcher_url': 'http://watcher.example.org:9322/',
|
||||
'os_auth_token': 'USER_AUTH_TOKEN',
|
||||
'os_infra_optim_api_version': 'latest',
|
||||
'insecure': True,
|
||||
'max_retries': 10,
|
||||
'retry_interval': 10,
|
||||
'os_cacert': 'data'
|
||||
}
|
||||
watcherclient.get_client('1', **kwargs)
|
||||
mock_client.assert_called_once_with(
|
||||
'1', 'http://watcher.example.org:9322/',
|
||||
**{
|
||||
'os_infra_optim_api_version': 'latest',
|
||||
'max_retries': 10,
|
||||
'retry_interval': 10,
|
||||
'token': 'USER_AUTH_TOKEN',
|
||||
'insecure': True,
|
||||
'ca_file': 'data',
|
||||
'cert_file': None,
|
||||
'key_file': None,
|
||||
'timeout': None,
|
||||
'session': None
|
||||
}
|
||||
)
|
||||
self.assertFalse(mock_ks_session.called)
|
||||
|
||||
@mock.patch.object(watcherclient, 'Client')
|
||||
@mock.patch.object(kaloading.session, 'Session', autospec=True)
|
||||
def test_correct_arguments_passed_to_client_constructor_session_created(
|
||||
self, mock_ks_session, mock_client):
|
||||
session = mock_ks_session.return_value.load_from_options.return_value
|
||||
kwargs = {
|
||||
'os_auth_url': 'http://localhost:35357/v3',
|
||||
'os_region_name': 'REGIONONE',
|
||||
'os_project_id': '1000',
|
||||
'os_username': 'user',
|
||||
'os_password': '1234',
|
||||
'os_project_domain_name': 'domain1',
|
||||
'os_user_domain_name': 'domain1'
|
||||
}
|
||||
watcherclient.get_client('1', **kwargs)
|
||||
mock_client.assert_called_once_with(
|
||||
'1', session.get_endpoint.return_value,
|
||||
**{
|
||||
'os_infra_optim_api_version': None,
|
||||
'max_retries': None,
|
||||
'retry_interval': None,
|
||||
'session': session,
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch.object(watcherclient, 'Client')
|
||||
@mock.patch.object(kaloading.session, 'Session', autospec=True)
|
||||
def test_correct_arguments_passed_to_client_constructor_session_passed(
|
||||
self, mock_ks_session, mock_client):
|
||||
session = mock.Mock()
|
||||
kwargs = {
|
||||
'session': session,
|
||||
}
|
||||
watcherclient.get_client('1', **kwargs)
|
||||
mock_client.assert_called_once_with(
|
||||
'1', session.get_endpoint.return_value,
|
||||
**{
|
||||
'os_infra_optim_api_version': None,
|
||||
'max_retries': None,
|
||||
'retry_interval': None,
|
||||
'session': session,
|
||||
}
|
||||
)
|
||||
self.assertFalse(mock_ks_session.called)
|
||||
|
||||
def test_safe_header_with_auth_token(self):
|
||||
(name, value) = ('X-Auth-Token', u'3b640e2e64d946ac8f55615aff221dc1')
|
||||
expected_header = (u'X-Auth-Token',
|
||||
'{SHA1}6de9fb3b0b89099030a54abfeb468e7b1b1f0f2b')
|
||||
client = httpclient.HTTPClient('http://localhost/')
|
||||
header_redact = client._process_header(name, value)
|
||||
self.assertEqual(expected_header, header_redact)
|
||||
|
||||
def test_safe_header_with_no_auth_token(self):
|
||||
name, value = ('Accept', 'application/json')
|
||||
header = ('Accept', 'application/json')
|
||||
client = httpclient.HTTPClient('http://localhost/')
|
||||
header_redact = client._process_header(name, value)
|
||||
self.assertEqual(header, header_redact)
|
||||
|
||||
def test_make_connection_url(self):
|
||||
endpoint = 'http://localhost/infra-optim'
|
||||
url = '/v1/goals'
|
||||
expected_url = 'http://localhost/infra-optim/v1/goals'
|
||||
client = httpclient.HTTPClient(endpoint)
|
||||
conn_url = client._make_connection_url(url)
|
||||
self.assertEqual(expected_url, conn_url)
|
||||
@@ -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
|
||||
@@ -12,7 +11,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from watcherclient.tests import utils
|
||||
from watcherclient.tests.unit import utils
|
||||
|
||||
module_str = 'watcherclient'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
@@ -15,11 +14,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient.common.apiclient import exceptions as exc
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.tests import utils as test_utils
|
||||
from watcherclient.tests.unit import utils as test_utils
|
||||
|
||||
|
||||
class UtilsTest(test_utils.BaseTestCase):
|
||||
@@ -98,7 +97,8 @@ class UtilsTest(test_utils.BaseTestCase):
|
||||
class CommonParamsForListTest(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(CommonParamsForListTest, self).setUp()
|
||||
self.args = mock.Mock(limit=None, sort_key=None, sort_dir=None)
|
||||
self.args = mock.Mock(limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None)
|
||||
self.args.detail = False
|
||||
self.expected_params = {'detail': False}
|
||||
|
||||
@@ -118,6 +118,13 @@ class CommonParamsForListTest(test_utils.BaseTestCase):
|
||||
utils.common_params_for_list,
|
||||
self.args, [], [])
|
||||
|
||||
def test_marker(self):
|
||||
self.args.marker = 'e420a881-d7df-4de2-bbf3-378cc13d9b3a'
|
||||
self.expected_params.update(
|
||||
{'marker': 'e420a881-d7df-4de2-bbf3-378cc13d9b3a'})
|
||||
self.assertEqual(self.expected_params,
|
||||
utils.common_params_for_list(self.args, [], []))
|
||||
|
||||
def test_sort_key_and_sort_dir(self):
|
||||
self.args.sort_key = 'field'
|
||||
self.args.sort_dir = 'desc'
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -16,16 +14,14 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import io
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from watcherclient.common import http
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
@@ -55,7 +51,7 @@ class FakeAPI(object):
|
||||
|
||||
def raw_request(self, *args, **kwargs):
|
||||
response = self._request(*args, **kwargs)
|
||||
body_iter = http.ResponseBodyIterator(six.StringIO(response[1]))
|
||||
body_iter = iter(io.StringIO(response[1]))
|
||||
return FakeResponse(response[0]), body_iter
|
||||
|
||||
def json_request(self, *args, **kwargs):
|
||||
@@ -77,6 +73,9 @@ class FakeConnection(object):
|
||||
def getresponse(self):
|
||||
return self._response
|
||||
|
||||
def __repr__(self):
|
||||
return ("FakeConnection(response=%s)" % (self._response))
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __init__(self, headers, body=None, version=None, status=None,
|
||||
@@ -88,8 +87,9 @@ class FakeResponse(object):
|
||||
"""
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.version = version
|
||||
self.status = status
|
||||
self.raw = mock.Mock()
|
||||
self.raw.version = version
|
||||
self.status_code = status
|
||||
self.reason = reason
|
||||
|
||||
def getheaders(self):
|
||||
@@ -101,44 +101,37 @@ class FakeResponse(object):
|
||||
def read(self, amt):
|
||||
return self.body.read(amt)
|
||||
|
||||
|
||||
class FakeServiceCatalog(object):
|
||||
def url_for(self, endpoint_type, service_type, attr=None,
|
||||
filter_value=None):
|
||||
if attr == 'region' and filter_value:
|
||||
return 'http://regionhost:6385/v1/f14b41234'
|
||||
else:
|
||||
return 'http://localhost:6385/v1/f14b41234'
|
||||
|
||||
|
||||
class FakeKeystone(object):
|
||||
service_catalog = FakeServiceCatalog()
|
||||
timestamp = datetime.datetime.utcnow() + datetime.timedelta(days=5)
|
||||
|
||||
def __init__(self, auth_token):
|
||||
self.auth_token = auth_token
|
||||
self.auth_ref = {
|
||||
'token': {'expires': FakeKeystone.timestamp.strftime(
|
||||
'%Y-%m-%dT%H:%M:%S.%f'),
|
||||
'id': 'd1a541311782870742235'}
|
||||
}
|
||||
def __repr__(self):
|
||||
return ("FakeResponse(%s, body=%s, version=%s, status=%s, reason=%s)" %
|
||||
(self.headers, self.body, self.version, self.status,
|
||||
self.reason))
|
||||
|
||||
|
||||
class FakeSessionResponse(object):
|
||||
|
||||
def __init__(self, headers, content=None, status_code=None):
|
||||
def __init__(self, headers, content=None, status_code=None, version=None):
|
||||
self.headers = headers
|
||||
self.content = content
|
||||
self.status_code = status_code
|
||||
self.raw = mock.Mock()
|
||||
self.raw.version = version
|
||||
self.reason = ''
|
||||
|
||||
def iter_content(self, chunk_size):
|
||||
return iter(self.content)
|
||||
|
||||
|
||||
class FakeSession(object):
|
||||
|
||||
def __init__(self, headers, content=None, status_code=None):
|
||||
def __init__(self, headers, content=None, status_code=None, version=None):
|
||||
self.headers = headers
|
||||
self.content = content
|
||||
self.status_code = status_code
|
||||
self.version = version
|
||||
self.verify = False
|
||||
self.cert = ('test_cert', 'test_key')
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
return FakeSessionResponse(self.headers, self.content,
|
||||
self.status_code)
|
||||
request = FakeSessionResponse(
|
||||
self.headers, self.content, self.status_code, self.version)
|
||||
return request
|
||||
0
watcherclient/tests/unit/v1/__init__.py
Normal file
0
watcherclient/tests/unit/v1/__init__.py
Normal file
78
watcherclient/tests/unit/v1/base.py
Normal file
78
watcherclient/tests/unit/v1/base.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import shlex
|
||||
from unittest import mock
|
||||
|
||||
from osc_lib import utils as oscutils
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from watcherclient.common import httpclient
|
||||
from watcherclient.tests.unit import utils
|
||||
|
||||
|
||||
class CommandTestCase(utils.BaseTestCase):
|
||||
|
||||
def setUp(self, os_infra_optim_api_version='1.0'):
|
||||
super(CommandTestCase, self).setUp()
|
||||
|
||||
self.fake_env = {
|
||||
'debug': False,
|
||||
'insecure': False,
|
||||
'no_auth': False,
|
||||
'os_auth_token': '',
|
||||
'os_auth_url': 'http://127.0.0.1:5000/v2.0',
|
||||
'os_endpoint_override': 'http://watcher-endpoint:9322',
|
||||
'os_username': 'test',
|
||||
'os_password': 'test',
|
||||
'timeout': 600,
|
||||
'os_infra_optim_api_version': os_infra_optim_api_version}
|
||||
self.m_env = mock.Mock(
|
||||
name='m_env',
|
||||
side_effect=lambda x, *args, **kwargs: self.fake_env.get(
|
||||
x.lower(), kwargs.get('default', '')))
|
||||
self.p_env = mock.patch.object(oscutils, 'env', self.m_env)
|
||||
self.p_env.start()
|
||||
self.addCleanup(self.p_env.stop)
|
||||
|
||||
self.p_construct_http_client = mock.patch.object(
|
||||
httpclient, '_construct_http_client')
|
||||
self.m_construct_http_client = self.p_construct_http_client.start()
|
||||
self.addCleanup(self.p_construct_http_client.stop)
|
||||
|
||||
def run_cmd(self, cmd, formatting='json'):
|
||||
if formatting and formatting != 'table':
|
||||
formatter_arg = " -f %s" % formatting
|
||||
formatter = jsonutils.loads
|
||||
else:
|
||||
formatter_arg = ''
|
||||
formatter = str
|
||||
formatted_cmd = "%(cmd)s%(formatter)s" % dict(
|
||||
cmd=cmd, formatter=formatter_arg)
|
||||
|
||||
exit_code = self.cmd.run(shlex.split(formatted_cmd))
|
||||
|
||||
try:
|
||||
raw_data = self.stdout.getvalue()
|
||||
formatted_output = formatter(self.stdout.getvalue())
|
||||
except Exception:
|
||||
self.fail("Formatting error (`%s` -> '%s')" %
|
||||
(raw_data, formatting))
|
||||
return exit_code, formatted_output
|
||||
|
||||
def resource_as_dict(self, resource, columns=(), column_headers=()):
|
||||
mapping = dict(zip(columns, column_headers))
|
||||
return {mapping[k]: v for k, v in resource.to_dict().items()
|
||||
if not columns or columns and k in mapping}
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -15,12 +13,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.action
|
||||
|
||||
ACTION1 = {
|
||||
@@ -30,7 +26,6 @@ ACTION1 = {
|
||||
'description': 'Action_1 description',
|
||||
'next': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'state': 'PENDING',
|
||||
'alarm': None
|
||||
}
|
||||
|
||||
ACTION2 = {
|
||||
@@ -40,7 +35,6 @@ ACTION2 = {
|
||||
'description': 'Action_2 description',
|
||||
'next': '67653274-eb24-c7ba-70f6-a84e73d80843',
|
||||
'state': 'PENDING',
|
||||
'alarm': None
|
||||
}
|
||||
|
||||
ACTION3 = {
|
||||
@@ -50,7 +44,6 @@ ACTION3 = {
|
||||
'description': 'Action_3 description',
|
||||
'next': None,
|
||||
'state': 'PENDING',
|
||||
'alarm': None
|
||||
}
|
||||
|
||||
ACTION_PLAN1 = {
|
||||
@@ -60,10 +53,6 @@ ACTION_PLAN1 = {
|
||||
'state': 'RECOMMENDED'
|
||||
}
|
||||
|
||||
UPDATED_ACTION1 = copy.deepcopy(ACTION1)
|
||||
NEW_EXTRA = 'key1=val1'
|
||||
UPDATED_ACTION1['extra'] = NEW_EXTRA
|
||||
|
||||
fake_responses = {
|
||||
'/v1/actions':
|
||||
{
|
||||
@@ -103,10 +92,6 @@ fake_responses = {
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_ACTION1,
|
||||
),
|
||||
},
|
||||
'/v1/actions/detail?action_plan_uuid=%s' % ACTION1['action_plan']:
|
||||
{
|
||||
@@ -130,7 +115,7 @@ fake_responses_pagination = {
|
||||
'GET': (
|
||||
{},
|
||||
{"actions": [ACTION1],
|
||||
"next": "http://127.0.0.1:6385/v1/actions/?limit=1"}
|
||||
"next": "http://127.0.0.1:9322/v1/actions/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/actions/?limit=1':
|
||||
@@ -159,6 +144,16 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_marker = {
|
||||
'/v1/actions/?marker=770ef053-ecb3-48b0-85b5-d55a2dbc6588':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"actions": [ACTION2, ACTION3]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ActionManagerTest(testtools.TestCase):
|
||||
|
||||
@@ -213,7 +208,7 @@ class ActionManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/actions/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(actions, HasLength(1))
|
||||
self.assertThat(actions, matchers.HasLength(1))
|
||||
|
||||
def test_actions_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -224,7 +219,7 @@ class ActionManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/actions/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(actions, HasLength(2))
|
||||
self.assertThat(actions, matchers.HasLength(2))
|
||||
|
||||
def test_actions_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
@@ -246,6 +241,20 @@ class ActionManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(3, len(actions))
|
||||
|
||||
def test_actions_list_marker(self):
|
||||
self.api = utils.FakeAPI(fake_responses_marker)
|
||||
self.mgr = watcherclient.v1.action.ActionManager(self.api)
|
||||
actions = self.mgr.list(
|
||||
marker='770ef053-ecb3-48b0-85b5-d55a2dbc6588')
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/actions/?marker=770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
{},
|
||||
None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(actions))
|
||||
|
||||
def test_actions_show(self):
|
||||
action = self.mgr.get(ACTION1['uuid'])
|
||||
expect = [
|
||||
@@ -254,24 +263,4 @@ class ActionManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(ACTION1['uuid'], action.uuid)
|
||||
self.assertEqual(ACTION1['action_plan'], action.action_plan)
|
||||
self.assertEqual(ACTION1['alarm'], action.alarm)
|
||||
self.assertEqual(ACTION1['next'], action.next)
|
||||
|
||||
def test_delete(self):
|
||||
action = self.mgr.delete(action_id=ACTION1['uuid'])
|
||||
expect = [
|
||||
('DELETE', '/v1/actions/%s' % ACTION1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(action)
|
||||
|
||||
def test_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_EXTRA,
|
||||
'path': '/extra'}
|
||||
action = self.mgr.update(action_id=ACTION1['uuid'], patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/actions/%s' % ACTION1['uuid'], {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_EXTRA, action.extra)
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -16,11 +14,14 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
from unittest import mock
|
||||
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
from oslo_utils.uuidutils import generate_uuid
|
||||
from watcherclient.common.apiclient.exceptions import HTTPClientError
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.action_plan
|
||||
|
||||
ACTION_PLAN1 = {
|
||||
@@ -38,9 +39,21 @@ ACTION_PLAN2 = {
|
||||
}
|
||||
|
||||
UPDATED_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
|
||||
NEW_STATE = 'STARTING'
|
||||
NEW_STATE = 'PENDING'
|
||||
UPDATED_ACTION_PLAN['state'] = NEW_STATE
|
||||
|
||||
START_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
|
||||
START_ACTION_PLAN['state'] = NEW_STATE
|
||||
|
||||
ONGOING_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
|
||||
ONGOING_ACTION_PLAN['state'] = 'ONGOING'
|
||||
|
||||
CANCELLING_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
|
||||
CANCELLING_ACTION_PLAN['state'] = 'CANCELLING'
|
||||
|
||||
CANCELD_ACTION_PLAN = copy.deepcopy(ACTION_PLAN2)
|
||||
CANCELD_ACTION_PLAN['state'] = 'CANCELLED'
|
||||
|
||||
fake_responses = {
|
||||
'/v1/action_plans':
|
||||
{
|
||||
@@ -71,6 +84,13 @@ fake_responses = {
|
||||
UPDATED_ACTION_PLAN,
|
||||
),
|
||||
},
|
||||
'/v1/action_plans/%s/start' % ACTION_PLAN1['uuid']:
|
||||
{
|
||||
'POST': (
|
||||
{},
|
||||
START_ACTION_PLAN,
|
||||
),
|
||||
},
|
||||
'/v1/action_plans/detail?uuid=%s' % ACTION_PLAN1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
@@ -86,7 +106,7 @@ fake_responses_pagination = {
|
||||
'GET': (
|
||||
{},
|
||||
{"action_plans": [ACTION_PLAN1],
|
||||
"next": "http://127.0.0.1:6385/v1/action_plans/?limit=1"}
|
||||
"next": "http://127.0.0.1:9322/v1/action_plans/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/action_plans/?limit=1':
|
||||
@@ -96,6 +116,13 @@ fake_responses_pagination = {
|
||||
{"action_plans": [ACTION_PLAN2]}
|
||||
),
|
||||
},
|
||||
'/v1/action_plans/?marker=f8e47706-efcf-49a4-a5c4-af604eb492f2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"action_plans": [ACTION_PLAN2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
@@ -115,6 +142,31 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_cancel = {
|
||||
'/v1/action_plans/%s' % ACTION_PLAN1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
[ONGOING_ACTION_PLAN],
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
CANCELLING_ACTION_PLAN,
|
||||
),
|
||||
},
|
||||
'/v1/action_plans/%s' % ACTION_PLAN2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
[ACTION_PLAN2],
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
CANCELD_ACTION_PLAN,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ActionPlanManagerTest(testtools.TestCase):
|
||||
|
||||
@@ -147,7 +199,7 @@ class ActionPlanManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/action_plans/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(action_plans, HasLength(1))
|
||||
self.assertThat(action_plans, matchers.HasLength(1))
|
||||
|
||||
def test_action_plans_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -158,7 +210,20 @@ class ActionPlanManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/action_plans/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(action_plans, HasLength(2))
|
||||
self.assertThat(action_plans, matchers.HasLength(2))
|
||||
|
||||
def test_action_plans_list_marker(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
|
||||
action_plans = self.mgr.list(
|
||||
marker='f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
expect = [
|
||||
('GET', '/v1/action_plans/?'
|
||||
'marker=f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(action_plans, matchers.HasLength(1))
|
||||
|
||||
def test_action_plans_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
@@ -188,6 +253,54 @@ class ActionPlanManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(ACTION_PLAN1['uuid'], action_plan.uuid)
|
||||
|
||||
def test_action_plans_get_index_error(self):
|
||||
|
||||
# verify this method will return None when meet indexError
|
||||
fake_uuid = generate_uuid()
|
||||
self.api.json_request = mock.Mock(return_value=('404', []))
|
||||
self.assertIsNone(self.mgr.get(fake_uuid))
|
||||
|
||||
def test_action_plans_delete(self):
|
||||
# verity that action plan was successfully deleted
|
||||
self.api.raw_request = mock.Mock(return_value=('204', []))
|
||||
self.assertIsNone(self.mgr.delete(ACTION_PLAN1['uuid']))
|
||||
|
||||
# verity that delete a wrong action plan will raise Exception
|
||||
fake_uuid = generate_uuid()
|
||||
self.api.raw_request = mock.Mock(
|
||||
side_effect=HTTPClientError('404 Not Found'))
|
||||
self.assertRaises(HTTPClientError, self.mgr.delete, fake_uuid)
|
||||
|
||||
def test_action_plans_cancel(self):
|
||||
# verity that the status of action plan can be converted from
|
||||
# 'ONGOING' to 'CANCELLING'
|
||||
self.api = utils.FakeAPI(fake_responses_cancel)
|
||||
self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
|
||||
patch = {'op': 'replace',
|
||||
'value': 'CANCELLING',
|
||||
'path': '/state'}
|
||||
action_plan = self.mgr.cancel(action_plan_id=ACTION_PLAN1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/action_plans/%s' % ACTION_PLAN1['uuid'], {}, None),
|
||||
('PATCH', '/v1/action_plans/%s' % ACTION_PLAN1['uuid'], {},
|
||||
[patch])
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual('CANCELLING', action_plan.state)
|
||||
|
||||
# verity that the status of action plan can be converted from
|
||||
# 'RECOMMENDED' to 'CANCELLED'
|
||||
patch['value'] = 'CANCELLED'
|
||||
self.api.calls = []
|
||||
action_plan = self.mgr.cancel(action_plan_id=ACTION_PLAN2['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/action_plans/%s' % ACTION_PLAN2['uuid'], {}, None),
|
||||
('PATCH', '/v1/action_plans/%s' % ACTION_PLAN2['uuid'], {},
|
||||
[patch])
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual('CANCELLED', action_plan.state)
|
||||
|
||||
def test_action_plan_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_STATE,
|
||||
@@ -202,3 +315,10 @@ class ActionPlanManagerTest(testtools.TestCase):
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_STATE, action_plan.state)
|
||||
|
||||
def test_action_plan_start(self):
|
||||
action_plan = self.mgr.start(ACTION_PLAN1['uuid'])
|
||||
expect = [('POST', '/v1/action_plans/%s/start'
|
||||
% ACTION_PLAN1['uuid'], {}, {})]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_STATE, action_plan.state)
|
||||
338
watcherclient/tests/unit/v1/test_action_plan_shell.py
Normal file
338
watcherclient/tests/unit/v1/test_action_plan_shell.py
Normal file
@@ -0,0 +1,338 @@
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils.uuidutils import generate_uuid
|
||||
|
||||
from watcherclient import exceptions
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
ACTION_PLAN_1 = {
|
||||
'uuid': 'd9d9978e-6db5-4a05-8eab-1531795d7004',
|
||||
'audit_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'strategy_name': 'dummy',
|
||||
'state': 'RECOMMENDED',
|
||||
'efficacy_indicators': [{'description': 'Indicator 1',
|
||||
'name': 'indicator1',
|
||||
'unit': '%'}],
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'global_efficacy': [
|
||||
{"value": 99,
|
||||
"unit": "%",
|
||||
"name": "dummy_global_efficacy",
|
||||
"description": "Dummy Global Efficacy"},
|
||||
{"value": 75,
|
||||
"unit": "%",
|
||||
"name": "dummy_global_efficacy2",
|
||||
"description": "Dummy Global Efficacy2"}
|
||||
],
|
||||
'deleted_at': None,
|
||||
'hostname': ''
|
||||
}
|
||||
|
||||
ACTION_PLAN_2 = {
|
||||
'uuid': 'd6363285-5afa-4a26-96f2-89441e335765',
|
||||
'audit_uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'strategy_name': 'dummy',
|
||||
'state': 'RECOMMENDED',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'efficacy_indicators': [{'description': 'Indicator 2',
|
||||
'name': 'indicator2',
|
||||
'unit': '%'}],
|
||||
'updated_at': None,
|
||||
'global_efficacy': [{
|
||||
"value": 87,
|
||||
"unit": "%",
|
||||
"name": "dummy_global_efficacy",
|
||||
"description": "Dummy Global Efficacy",
|
||||
}],
|
||||
'deleted_at': None,
|
||||
'hostname': ''
|
||||
}
|
||||
|
||||
|
||||
class ActionPlanShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.ACTION_PLAN_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.ACTION_PLAN_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.ACTION_PLAN_FIELDS
|
||||
FIELD_LABELS = resource_fields.ACTION_PLAN_FIELD_LABELS
|
||||
GLOBAL_EFFICACY_FIELDS = resource_fields.GLOBAL_EFFICACY_FIELDS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_audit_manager = mock.patch.object(resource, 'AuditManager')
|
||||
p_audit_template_manager = mock.patch.object(
|
||||
resource, 'ActionPlanManager')
|
||||
p_action_plan_manager = mock.patch.object(
|
||||
resource, 'ActionPlanManager')
|
||||
|
||||
self.m_audit_mgr_cls = p_audit_manager.start()
|
||||
self.m_audit_template_mgr_cls = p_audit_template_manager.start()
|
||||
self.m_action_plan_mgr_cls = p_action_plan_manager.start()
|
||||
|
||||
self.addCleanup(p_audit_manager.stop)
|
||||
self.addCleanup(p_audit_template_manager.stop)
|
||||
self.addCleanup(p_action_plan_manager.stop)
|
||||
|
||||
self.m_audit_mgr = mock.Mock()
|
||||
self.m_audit_template_mgr = mock.Mock()
|
||||
self.m_action_plan_mgr = mock.Mock()
|
||||
|
||||
self.m_audit_mgr_cls.return_value = self.m_audit_mgr
|
||||
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
|
||||
self.m_action_plan_mgr_cls.return_value = self.m_action_plan_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_action_plan_list(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
action_plan2 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_2)
|
||||
self.m_action_plan_mgr.list.return_value = [
|
||||
action_plan1, action_plan2]
|
||||
|
||||
exit_code, results = self.run_cmd('actionplan list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action_plan1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(action_plan2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.assertEqual(action_plan1.global_efficacy,
|
||||
results[0]['Global efficacy'])
|
||||
self.assertEqual(action_plan2.global_efficacy,
|
||||
results[1]['Global efficacy'])
|
||||
|
||||
def test_do_action_plan_list_by_table(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
action_plan2 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_2)
|
||||
self.m_action_plan_mgr.list.return_value = [
|
||||
action_plan1, action_plan2]
|
||||
|
||||
exit_code, results = self.run_cmd('actionplan list', 'table')
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertIn(ACTION_PLAN_1['uuid'], results)
|
||||
self.assertIn(ACTION_PLAN_2['uuid'], results)
|
||||
|
||||
self.m_action_plan_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_action_plan_list_detail(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
action_plan2 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_2)
|
||||
self.m_action_plan_mgr.list.return_value = [
|
||||
action_plan1, action_plan2]
|
||||
|
||||
exit_code, results = self.run_cmd('actionplan list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action_plan1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(action_plan2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
self.assertEqual(action_plan1.global_efficacy,
|
||||
results[0]['Global efficacy'])
|
||||
self.assertEqual(action_plan2.global_efficacy,
|
||||
results[1]['Global efficacy'])
|
||||
|
||||
self.m_action_plan_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_action_plan_list_filter_by_audit(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.list.return_value = [action_plan1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'actionplan list --audit '
|
||||
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action_plan1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_plan_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
audit='770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
)
|
||||
|
||||
def test_do_action_plan_show_by_uuid(self):
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.get.return_value = action_plan
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan show d9d9978e-6db5-4a05-8eab-1531795d7004')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(
|
||||
action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.assertEqual(action_plan.global_efficacy,
|
||||
result['Global efficacy'])
|
||||
self.m_action_plan_mgr.get.assert_called_once_with(
|
||||
'd9d9978e-6db5-4a05-8eab-1531795d7004')
|
||||
|
||||
def test_do_action_plan_show_by_not_uuid(self):
|
||||
self.m_action_plan_mgr.get.side_effect = exceptions.HTTPNotFound
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan show not_uuid', formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_show_by_random_uuid(self):
|
||||
# verify that show a wrong actionplan will raise Exception
|
||||
self.m_action_plan_mgr.get.side_effect = exceptions.HTTPNotFound
|
||||
fake_uuid = generate_uuid()
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan show {}'.format(fake_uuid), formatting=None)
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
self.m_action_plan_mgr.get.assert_called_once_with(fake_uuid)
|
||||
|
||||
def test_do_action_plan_show_uuid_by_table(self):
|
||||
# verify that show an actionplan can be in a 'table' format
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.get.return_value = action_plan
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan show d9d9978e-6db5-4a05-8eab-1531795d7004',
|
||||
formatting='table')
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertIn(ACTION_PLAN_1['uuid'], result)
|
||||
|
||||
self.m_action_plan_mgr.get.assert_called_once_with(
|
||||
'd9d9978e-6db5-4a05-8eab-1531795d7004')
|
||||
|
||||
def test_do_action_plan_delete(self):
|
||||
self.m_action_plan_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan delete 5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_action_plan_mgr.delete.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_plan_delete_not_uuid(self):
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan delete not_uuid', formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_delete_multiple(self):
|
||||
self.m_action_plan_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan delete 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'c20627fa-ea70-4d56-ae15-4106358f773b',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_action_plan_mgr.delete.assert_any_call(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
self.m_action_plan_mgr.delete.assert_any_call(
|
||||
'c20627fa-ea70-4d56-ae15-4106358f773b')
|
||||
|
||||
def test_do_action_plan_update(self):
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.update.return_value = action_plan
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan update 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'replace state=CANCELLED')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_plan_mgr.update.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
[{'op': 'replace', 'path': '/state', 'value': 'CANCELLED'}])
|
||||
|
||||
def test_do_action_plan_update_not_uuid(self):
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan update not_uuid '
|
||||
'replace state=CANCELLED',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_start(self):
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.start.return_value = action_plan
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan start 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_plan_mgr.start.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_plan_start_not_uuid(self):
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan start not_uuid',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_cancel(self):
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.cancel.return_value = action_plan
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan cancel 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_plan_mgr.cancel.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_plan_cancel_not_uuid(self):
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan cancel not_uuid',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
178
watcherclient/tests/unit/v1/test_action_shell.py
Normal file
178
watcherclient/tests/unit/v1/test_action_shell.py
Normal file
@@ -0,0 +1,178 @@
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import exceptions
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
ACTION_1 = {
|
||||
'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'action_plan_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'state': 'PENDING',
|
||||
'action_type': 'migrate',
|
||||
'parents': ['239f02a5-9649-4e14-9d33-ac2bf67cb755'],
|
||||
'input_parameters': {"test": 1},
|
||||
'description': 'test',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_2 = {
|
||||
'uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'action_plan_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'state': 'PENDING',
|
||||
'action_type': 'migrate',
|
||||
'parents': ['67653274-eb24-c7ba-70f6-a84e73d80843'],
|
||||
'input_parameters': {"test": 2},
|
||||
'description': 'test',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_3 = {
|
||||
'uuid': '67653274-eb24-c7ba-70f6-a84e73d80843',
|
||||
'action_plan_uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'parents': [],
|
||||
'state': 'PENDING',
|
||||
'action_type': 'sleep',
|
||||
'description': 'test',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_PLAN_1 = {
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'action': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'state': 'RECOMMENDED',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class ActionShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.ACTION_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = resource_fields.ACTION_SHORT_LIST_FIELD_LABELS
|
||||
FIELDS = resource_fields.ACTION_FIELDS
|
||||
FIELD_LABELS = resource_fields.ACTION_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_action_manager = mock.patch.object(resource, 'ActionManager')
|
||||
p_action_plan_manager = mock.patch.object(
|
||||
resource, 'ActionPlanManager')
|
||||
self.m_action_mgr_cls = p_action_manager.start()
|
||||
self.m_action_plan_mgr_cls = p_action_plan_manager.start()
|
||||
self.addCleanup(p_action_manager.stop)
|
||||
self.addCleanup(p_action_plan_manager.stop)
|
||||
|
||||
self.m_action_mgr = mock.Mock()
|
||||
self.m_action_plan_mgr = mock.Mock()
|
||||
|
||||
self.m_action_mgr_cls.return_value = self.m_action_mgr
|
||||
self.m_action_plan_mgr_cls.return_value = self.m_action_plan_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_action_list(self):
|
||||
action1 = resource.Action(mock.Mock(), ACTION_1)
|
||||
action2 = resource.Action(mock.Mock(), ACTION_2)
|
||||
self.m_action_mgr.list.return_value = [action1, action2]
|
||||
|
||||
exit_code, results = self.run_cmd('action list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(action2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_action_list_detail(self):
|
||||
action1 = resource.Action(mock.Mock(), ACTION_1)
|
||||
action2 = resource.Action(mock.Mock(), ACTION_2)
|
||||
self.m_action_mgr.list.return_value = [action1, action2]
|
||||
|
||||
exit_code, results = self.run_cmd('action list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(action2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_action_list_marker(self):
|
||||
action2 = resource.Action(mock.Mock(), ACTION_2)
|
||||
action3 = resource.Action(mock.Mock(), ACTION_3)
|
||||
self.m_action_mgr.list.return_value = [
|
||||
action2, action3]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'action list --marker 770ef053-ecb3-48b0-85b5-d55a2dbc6588')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(action3, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
marker='770ef053-ecb3-48b0-85b5-d55a2dbc6588')
|
||||
|
||||
def test_do_action_show_by_uuid(self):
|
||||
action = resource.Action(mock.Mock(), ACTION_1)
|
||||
self.m_action_mgr.get.return_value = action
|
||||
self.m_action_plan_mgr.get.return_value = action
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'action show 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_mgr.get.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_show_by_not_uuid(self):
|
||||
self.m_action_mgr.get.side_effect = exceptions.HTTPNotFound
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'action show not_uuid', formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -18,27 +16,29 @@
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.audit
|
||||
|
||||
|
||||
AUDIT1 = {
|
||||
'id': 1,
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'deadline': None,
|
||||
'type': 'ONE_SHOT',
|
||||
'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588'
|
||||
'uuid': '5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
'audit_type': 'ONE_SHOT',
|
||||
'goal': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'strategy': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
}
|
||||
|
||||
AUDIT2 = {
|
||||
'id': 2,
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'deadline': None,
|
||||
'type': 'ONE_SHOT',
|
||||
'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588'
|
||||
'audit_type': 'ONE_SHOT',
|
||||
'goal': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'strategy': None,
|
||||
}
|
||||
|
||||
|
||||
CREATE_AUDIT = copy.deepcopy(AUDIT1)
|
||||
del CREATE_AUDIT['id']
|
||||
del CREATE_AUDIT['uuid']
|
||||
@@ -96,7 +96,7 @@ fake_responses_pagination = {
|
||||
'GET': (
|
||||
{},
|
||||
{"audits": [AUDIT1],
|
||||
"next": "http://127.0.0.1:6385/v1/audits/?limit=1"}
|
||||
"next": "http://127.0.0.1:9322/v1/audits/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/audits/?limit=1':
|
||||
@@ -125,8 +125,28 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filters = {
|
||||
'/v1/audits/?audit_template=%s' % AUDIT2['audit_template_uuid']:
|
||||
fake_responses_goal = {
|
||||
'/v1/audits/?goal=dummy':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audits": [AUDIT2, AUDIT1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_strategy = {
|
||||
'/v1/audits/?strategy=dummy':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audits": [AUDIT1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_marker = {
|
||||
'/v1/audits/?marker=5869da81-4876-4687-a1ed-12cd64cf53d9':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
@@ -137,7 +157,6 @@ fake_responses_filters = {
|
||||
|
||||
|
||||
class AuditManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(AuditManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
@@ -167,7 +186,7 @@ class AuditManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audits/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audits, HasLength(1))
|
||||
self.assertThat(audits, matchers.HasLength(1))
|
||||
|
||||
def test_audits_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -178,7 +197,7 @@ class AuditManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audits/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audits, HasLength(2))
|
||||
self.assertThat(audits, matchers.HasLength(2))
|
||||
|
||||
def test_audits_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
@@ -200,15 +219,36 @@ class AuditManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(audits))
|
||||
|
||||
def test_audits_list_filter_by_audit_template(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filters)
|
||||
def test_audits_list_goal(self):
|
||||
self.api = utils.FakeAPI(fake_responses_goal)
|
||||
self.mgr = watcherclient.v1.audit.AuditManager(self.api)
|
||||
self.mgr.list(audit_template=AUDIT2['audit_template_uuid'])
|
||||
audits = self.mgr.list(goal='dummy')
|
||||
expect = [
|
||||
('GET', '/v1/audits/?audit_template=%s' %
|
||||
AUDIT2['audit_template_uuid'], {}, None),
|
||||
('GET', '/v1/audits/?goal=dummy', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(audits))
|
||||
|
||||
def test_audits_list_strategy(self):
|
||||
self.api = utils.FakeAPI(fake_responses_strategy)
|
||||
self.mgr = watcherclient.v1.audit.AuditManager(self.api)
|
||||
audits = self.mgr.list(strategy='dummy')
|
||||
expect = [
|
||||
('GET', '/v1/audits/?strategy=dummy', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audits))
|
||||
|
||||
def test_audits_list_marker(self):
|
||||
self.api = utils.FakeAPI(fake_responses_marker)
|
||||
self.mgr = watcherclient.v1.audit.AuditManager(self.api)
|
||||
audits = self.mgr.list(marker=AUDIT1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/audits/?marker=5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audits))
|
||||
|
||||
def test_audits_show(self):
|
||||
audit = self.mgr.get(AUDIT1['uuid'])
|
||||
699
watcherclient/tests/unit/v1/test_audit_shell.py
Executable file
699
watcherclient/tests/unit/v1/test_audit_shell.py
Executable file
@@ -0,0 +1,699 @@
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
|
||||
AUDIT_TEMPLATE_1 = {
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'name': 'at1',
|
||||
'description': 'Audit Template 1 description',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'strategy_uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
GOAL_1 = {
|
||||
'uuid': "fc087747-61be-4aad-8126-b701731ae836",
|
||||
'name': "SERVER_CONSOLIDATION",
|
||||
'display_name': 'Server Consolidation',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
STRATEGY_1 = {
|
||||
'uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'name': 'basic',
|
||||
'display_name': 'Basic consolidation',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class AuditShellTest(base.CommandTestCase):
|
||||
|
||||
AUDIT_1 = {
|
||||
'uuid': '5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
'audit_type': 'ONESHOT',
|
||||
'state': 'PENDING',
|
||||
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'audit_template_name': 'at1',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_name': 'basic',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters': None,
|
||||
'interval': None,
|
||||
'scope': '',
|
||||
'auto_trigger': False,
|
||||
'next_run_time': None,
|
||||
'name': 'my_audit1',
|
||||
'hostname': '',
|
||||
}
|
||||
|
||||
AUDIT_2 = {
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'audit_type': 'ONESHOT',
|
||||
'state': 'PENDING',
|
||||
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'audit_template_name': 'at1',
|
||||
'goal_name': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'strategy_name': 'auto',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters': None,
|
||||
'interval': None,
|
||||
'scope': '',
|
||||
'auto_trigger': False,
|
||||
'next_run_time': None,
|
||||
'name': 'my_audit2',
|
||||
'hostname': '',
|
||||
}
|
||||
|
||||
AUDIT_3 = {
|
||||
'uuid': '43199d0e-0712-1213-9674-5ae2af8dhgte',
|
||||
'audit_type': 'ONESHOT',
|
||||
'state': 'PENDING',
|
||||
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'audit_template_name': 'at1',
|
||||
'goal_name': None,
|
||||
'strategy_name': 'auto',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters': None,
|
||||
'interval': 3600,
|
||||
'scope': '',
|
||||
'auto_trigger': True,
|
||||
'next_run_time': None,
|
||||
'name': 'my_audit3',
|
||||
'hostname': '',
|
||||
}
|
||||
|
||||
SHORT_LIST_FIELDS = ['uuid', 'name', 'audit_type',
|
||||
'state', 'goal_name', 'strategy_name',
|
||||
'auto_trigger']
|
||||
SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Audit Type', 'State', 'Goal',
|
||||
'Strategy', 'Auto Trigger']
|
||||
FIELDS = ['uuid', 'name', 'created_at', 'updated_at', 'deleted_at',
|
||||
'state', 'audit_type', 'parameters', 'interval', 'goal_name',
|
||||
'strategy_name', 'scope', 'auto_trigger', 'next_run_time',
|
||||
'hostname']
|
||||
FIELD_LABELS = ['UUID', 'Name', 'Created At', 'Updated At', 'Deleted At',
|
||||
'State', 'Audit Type', 'Parameters', 'Interval', 'Goal',
|
||||
'Strategy', 'Audit Scope', 'Auto Trigger',
|
||||
'Next Run Time', 'Hostname']
|
||||
|
||||
def setUp(self, os_infra_optim_api_version='1.0'):
|
||||
super(AuditShellTest, self).setUp(
|
||||
os_infra_optim_api_version=os_infra_optim_api_version)
|
||||
|
||||
# goal mock
|
||||
p_goal_manager = mock.patch.object(resource, 'GoalManager')
|
||||
self.m_goal_mgr_cls = p_goal_manager.start()
|
||||
self.addCleanup(p_goal_manager.stop)
|
||||
|
||||
self.m_goal_mgr = mock.Mock()
|
||||
self.m_goal_mgr_cls.return_value = self.m_goal_mgr
|
||||
|
||||
# strategy mock
|
||||
p_strategy_manager = mock.patch.object(resource, 'StrategyManager')
|
||||
self.m_strategy_mgr_cls = p_strategy_manager.start()
|
||||
self.addCleanup(p_strategy_manager.stop)
|
||||
|
||||
self.m_strategy_mgr = mock.Mock()
|
||||
self.m_strategy_mgr_cls.return_value = self.m_strategy_mgr
|
||||
|
||||
p_audit_manager = mock.patch.object(resource, 'AuditManager')
|
||||
p_audit_template_manager = mock.patch.object(
|
||||
resource, 'AuditTemplateManager')
|
||||
self.m_audit_mgr_cls = p_audit_manager.start()
|
||||
self.m_audit_template_mgr_cls = p_audit_template_manager.start()
|
||||
self.addCleanup(p_audit_manager.stop)
|
||||
self.addCleanup(p_audit_template_manager.stop)
|
||||
|
||||
self.m_audit_mgr = mock.Mock()
|
||||
self.m_audit_template_mgr = mock.Mock()
|
||||
|
||||
self.m_audit_mgr_cls.return_value = self.m_audit_mgr
|
||||
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
|
||||
|
||||
# stdout mock
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_audit_list(self):
|
||||
audit1 = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
|
||||
self.m_audit_mgr.list.return_value = [
|
||||
audit1, audit2]
|
||||
|
||||
exit_code, results = self.run_cmd('audit list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_audit_list_marker(self):
|
||||
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
|
||||
self.m_audit_mgr.list.return_value = [audit2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audit list --marker 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
marker='5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_audit_list_detail(self):
|
||||
audit1 = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
|
||||
self.m_audit_mgr.list.return_value = [
|
||||
audit1, audit2]
|
||||
|
||||
exit_code, results = self.run_cmd('audit list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(audit2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_audit_show_by_uuid(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.get.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit show 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.get.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_audit_show_by_name(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.get.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit show my_audit')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.get.assert_called_once_with(
|
||||
'my_audit')
|
||||
|
||||
def test_do_audit_delete(self):
|
||||
self.m_audit_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit delete 5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_mgr.delete.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_audit_delete_by_name(self):
|
||||
self.m_audit_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit delete my_audit',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_mgr.delete.assert_called_once_with(
|
||||
'my_audit')
|
||||
|
||||
def test_do_audit_delete_multiple(self):
|
||||
self.m_audit_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit delete 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_mgr.delete.assert_any_call(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
self.m_audit_mgr.delete.assert_any_call(
|
||||
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f')
|
||||
|
||||
def test_do_audit_update(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.update.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit update 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'replace state=PENDING')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.update.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
|
||||
|
||||
def test_do_audit_update_by_name(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.update.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit update my_audit replace state=PENDING')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.update.assert_called_once_with(
|
||||
'my_audit',
|
||||
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
|
||||
|
||||
def test_do_audit_create_with_audit_template_uuid(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT',
|
||||
auto_trigger=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_audit_template_name(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd('audit create -a at1')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT'
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_goal(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT'
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_goal_and_strategy(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -s '
|
||||
'2cf86250-d309-4b81-818e-1537f3dba6e5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
strategy='2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT'
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_type(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -t ONESHOT')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT'
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_parameter(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -p para1=10 '
|
||||
'-p para2=20')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
audit_type='ONESHOT',
|
||||
auto_trigger=False,
|
||||
parameters={'para1': 10, 'para2': 20}
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_type_event(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -t EVENT')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
auto_trigger=False,
|
||||
audit_type='EVENT'
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_type_continuous(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-t CONTINUOUS -i 3600')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
audit_type='CONTINUOUS',
|
||||
auto_trigger=False,
|
||||
interval='3600'
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_name(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-t CONTINUOUS -i 3600 --name my_audit')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
audit_type='CONTINUOUS',
|
||||
auto_trigger=False,
|
||||
interval='3600',
|
||||
name='my_audit'
|
||||
)
|
||||
|
||||
|
||||
class AuditShellTestv11(AuditShellTest):
|
||||
def setUp(self):
|
||||
super(AuditShellTestv11, self).setUp(os_infra_optim_api_version='1.1')
|
||||
v11 = dict(start_time=None, end_time=None)
|
||||
for audit in (self.AUDIT_1, self.AUDIT_2, self.AUDIT_3):
|
||||
audit.update(v11)
|
||||
self.FIELDS.extend(['start_time', 'end_time'])
|
||||
self.FIELD_LABELS.extend(['Start Time', 'End Time'])
|
||||
|
||||
|
||||
class AuditShellTestv12(AuditShellTest):
|
||||
def setUp(self):
|
||||
super(AuditShellTestv12, self).setUp(os_infra_optim_api_version='1.2')
|
||||
v11 = dict(start_time=None, end_time=None)
|
||||
v12 = dict(force=False)
|
||||
for audit in (self.AUDIT_1, self.AUDIT_2, self.AUDIT_3):
|
||||
audit.update(v11)
|
||||
audit.update(v12)
|
||||
self.FIELDS.extend(['start_time', 'end_time', 'force'])
|
||||
self.FIELD_LABELS.extend(['Start Time', 'End Time', 'Force'])
|
||||
|
||||
def test_do_audit_create_with_force(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a f8e47706-efcf-49a4-a5c4-af604eb492f2 --force')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT',
|
||||
auto_trigger=False,
|
||||
force=True
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_audit_template_uuid(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT',
|
||||
auto_trigger=False,
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_audit_template_name(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd('audit create -a at1')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT',
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_goal(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT',
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_goal_and_strategy(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -s '
|
||||
'2cf86250-d309-4b81-818e-1537f3dba6e5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
strategy='2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT',
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_type(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -t ONESHOT')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
auto_trigger=False,
|
||||
audit_type='ONESHOT',
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_parameter(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -p para1=10 '
|
||||
'-p para2=20')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
audit_type='ONESHOT',
|
||||
auto_trigger=False,
|
||||
parameters={'para1': 10, 'para2': 20},
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_type_continuous(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-t CONTINUOUS -i 3600')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
audit_type='CONTINUOUS',
|
||||
auto_trigger=False,
|
||||
interval='3600',
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_type_event(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -t EVENT')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
auto_trigger=False,
|
||||
audit_type='EVENT',
|
||||
force=False
|
||||
)
|
||||
|
||||
def test_do_audit_create_with_name(self):
|
||||
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -g fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-t CONTINUOUS -i 3600 --name my_audit')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
audit_type='CONTINUOUS',
|
||||
auto_trigger=False,
|
||||
interval='3600',
|
||||
name='my_audit',
|
||||
force=False
|
||||
)
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -16,12 +14,11 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
from urllib import parse as urlparse
|
||||
|
||||
from six.moves.urllib import parse as urlparse
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.audit_template
|
||||
|
||||
AUDIT_TMPL1 = {
|
||||
@@ -29,9 +26,10 @@ AUDIT_TMPL1 = {
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'name': 'Audit Template 1',
|
||||
'description': 'Audit Template 1 description',
|
||||
'host_aggregate': 5,
|
||||
'extra': {'automatic': False},
|
||||
'goal': 'MINIMIZE_LICENSING_COST'
|
||||
'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_uuid': 'bbe6b966-f98e-439b-a01a-17b9b3b8478b',
|
||||
'strategy_name': 'server_consolidation',
|
||||
}
|
||||
|
||||
AUDIT_TMPL2 = {
|
||||
@@ -39,9 +37,10 @@ AUDIT_TMPL2 = {
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'name': 'Audit Template 2',
|
||||
'description': 'Audit Template 2 description',
|
||||
'host_aggregate': 8,
|
||||
'extra': {'automatic': True},
|
||||
'goal': 'SERVERS_CONSOLIDATION'
|
||||
'goal_uuid': 'e75ee410-b32b-465f-88b5-4397705f9473',
|
||||
'goal_name': 'DUMMY',
|
||||
'strategy_uuid': 'ae99a4a4-acbc-4c67-abe1-e37128fac45d',
|
||||
'strategy_name': 'dummy',
|
||||
}
|
||||
|
||||
AUDIT_TMPL3 = {
|
||||
@@ -49,14 +48,17 @@ AUDIT_TMPL3 = {
|
||||
'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'name': 'Audit Template 3',
|
||||
'description': 'Audit Template 3 description',
|
||||
'host_aggregate': 7,
|
||||
'extra': {'automatic': True},
|
||||
'goal': 'MINIMIZE_LICENSING_COST'
|
||||
'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
}
|
||||
|
||||
CREATE_AUDIT_TEMPLATE = copy.deepcopy(AUDIT_TMPL1)
|
||||
del CREATE_AUDIT_TEMPLATE['id']
|
||||
del CREATE_AUDIT_TEMPLATE['uuid']
|
||||
del CREATE_AUDIT_TEMPLATE['goal_name']
|
||||
del CREATE_AUDIT_TEMPLATE['strategy_name']
|
||||
CREATE_AUDIT_TEMPLATE['goal'] = CREATE_AUDIT_TEMPLATE.pop('goal_uuid')
|
||||
CREATE_AUDIT_TEMPLATE['strategy'] = CREATE_AUDIT_TEMPLATE.pop('strategy_uuid')
|
||||
|
||||
UPDATED_AUDIT_TMPL1 = copy.deepcopy(AUDIT_TMPL1)
|
||||
NEW_NAME = 'Audit Template_1 new name'
|
||||
@@ -125,14 +127,14 @@ fake_responses = {
|
||||
{"audit_templates": [AUDIT_TMPL1]},
|
||||
),
|
||||
},
|
||||
'/v1/audit_templates/detail?goal=%s' % AUDIT_TMPL1['goal']:
|
||||
'/v1/audit_templates/detail?goal=%s' % AUDIT_TMPL1['goal_uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL1, AUDIT_TMPL3]},
|
||||
),
|
||||
},
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL1['goal']:
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL1['goal_uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
@@ -147,7 +149,7 @@ fake_responses_pagination = {
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL1],
|
||||
"next": "http://127.0.0.1:6385/v1/audit_templates/?limit=1"}
|
||||
"next": "http://127.0.0.1:9322/v1/audit_templates/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/audit_templates/?limit=1':
|
||||
@@ -176,8 +178,68 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_marker = {
|
||||
'/v1/audit_templates/?marker=f8e47706-efcf-49a4-a5c4-af604eb492f2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2, AUDIT_TMPL3]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
class AuditTemplateManagerTest(testtools.TestCase):
|
||||
fake_responses_filter_by_goal_uuid = {
|
||||
'/v1/audit_templates/?goal=e75ee410-b32b-465f-88b5-4397705f9473':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_goal_name = {
|
||||
'/v1/audit_templates/?goal=DUMMY':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_strategy_uuid = {
|
||||
'/v1/audit_templates/?strategy=ae99a4a4-acbc-4c67-abe1-e37128fac45d':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_strategy_name = {
|
||||
'/v1/audit_templates/?strategy=dummy':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_strategy_and_goal_name = {
|
||||
'/v1/audit_templates/?goal=DUMMY&strategy=dummy':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class AuditTemplateManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(AuditTemplateManagerTest, self).setUp()
|
||||
@@ -193,7 +255,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_by_name(self):
|
||||
def test_audit_templates_list_filter_by_name(self):
|
||||
audit_templates = self.mgr.list(name=AUDIT_TMPL1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/audit_templates/?name=%s' % AUDIT_TMPL1['name'],
|
||||
@@ -202,6 +264,77 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_goal_uuid(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_goal_uuid)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(
|
||||
goal="e75ee410-b32b-465f-88b5-4397705f9473")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL2['goal_uuid'],
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_goal_name(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_goal_name)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(goal="DUMMY")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL2['goal_name'],
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_strategy_uuid(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_strategy_uuid)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(
|
||||
strategy="ae99a4a4-acbc-4c67-abe1-e37128fac45d")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?strategy=%s' % (
|
||||
AUDIT_TMPL2['strategy_uuid']),
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_strategy_name(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_strategy_name)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(strategy="dummy")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?strategy=%s' % (
|
||||
AUDIT_TMPL2['strategy_name']),
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_goal_and_strategy_name(self):
|
||||
self.api = utils.FakeAPI(
|
||||
fake_responses_filter_by_strategy_and_goal_name)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(goal="DUMMY", strategy="dummy")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?goal=%s&strategy=%s' % (
|
||||
AUDIT_TMPL2['goal_name'], AUDIT_TMPL2['strategy_name']),
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_detail(self):
|
||||
audit_templates = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
@@ -230,7 +363,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audit_templates/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audit_templates, HasLength(1))
|
||||
self.assertThat(audit_templates, matchers.HasLength(1))
|
||||
|
||||
def test_audit_templates_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -242,7 +375,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audit_templates/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audit_templates, HasLength(2))
|
||||
self.assertThat(audit_templates, matchers.HasLength(2))
|
||||
|
||||
def test_audit_templates_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
@@ -266,6 +399,18 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(3, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_marker(self):
|
||||
self.api = utils.FakeAPI(fake_responses_marker)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(marker=AUDIT_TMPL1['uuid'])
|
||||
expect_url = '/v1/audit_templates/?marker=%s' % AUDIT_TMPL1['uuid']
|
||||
expect = [
|
||||
('GET', expect_url, {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(audit_templates))
|
||||
|
||||
def test_audit_templates_show(self):
|
||||
audit_template = self.mgr.get(AUDIT_TMPL1['uuid'])
|
||||
expect = [
|
||||
@@ -276,11 +421,9 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(AUDIT_TMPL1['name'], audit_template.name)
|
||||
self.assertEqual(AUDIT_TMPL1['description'],
|
||||
audit_template.description)
|
||||
self.assertEqual(AUDIT_TMPL1['host_aggregate'],
|
||||
audit_template.host_aggregate)
|
||||
self.assertEqual(AUDIT_TMPL1['goal'], audit_template.goal)
|
||||
self.assertEqual(AUDIT_TMPL1['extra'],
|
||||
audit_template.extra)
|
||||
self.assertEqual(AUDIT_TMPL1['goal_uuid'], audit_template.goal_uuid)
|
||||
self.assertEqual(AUDIT_TMPL1['strategy_uuid'],
|
||||
audit_template.strategy_uuid)
|
||||
|
||||
def test_audit_templates_show_by_name(self):
|
||||
audit_template = self.mgr.get(urlparse.quote(AUDIT_TMPL1['name']))
|
||||
@@ -295,11 +438,9 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(AUDIT_TMPL1['name'], audit_template.name)
|
||||
self.assertEqual(AUDIT_TMPL1['description'],
|
||||
audit_template.description)
|
||||
self.assertEqual(AUDIT_TMPL1['host_aggregate'],
|
||||
audit_template.host_aggregate)
|
||||
self.assertEqual(AUDIT_TMPL1['goal'], audit_template.goal)
|
||||
self.assertEqual(AUDIT_TMPL1['extra'],
|
||||
audit_template.extra)
|
||||
self.assertEqual(AUDIT_TMPL1['goal_uuid'], audit_template.goal_uuid)
|
||||
self.assertEqual(AUDIT_TMPL1['strategy_uuid'],
|
||||
audit_template.strategy_uuid)
|
||||
|
||||
def test_create(self):
|
||||
audit_template = self.mgr.create(**CREATE_AUDIT_TEMPLATE)
|
||||
372
watcherclient/tests/unit/v1/test_audit_template_shell.py
Normal file
372
watcherclient/tests/unit/v1/test_audit_template_shell.py
Normal file
@@ -0,0 +1,372 @@
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
GOAL_1 = {
|
||||
'uuid': "fc087747-61be-4aad-8126-b701731ae836",
|
||||
'name': "SERVER_CONSOLIDATION",
|
||||
'display_name': 'Server Consolidation',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
STRATEGY_1 = {
|
||||
'uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'name': 'basic',
|
||||
'display_name': 'Basic consolidation',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
AUDIT_TEMPLATE_1 = {
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'name': 'at1',
|
||||
'description': 'Audit Template 1 description',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'strategy_name': 'basic',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'scope': []
|
||||
}
|
||||
|
||||
AUDIT_TEMPLATE_2 = {
|
||||
'uuid': '2a60ca9b-09b0-40ff-8674-de8a36fc4bc8',
|
||||
'name': 'at2',
|
||||
'description': 'Audit Template 2',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_uuid': None,
|
||||
'strategy_name': None,
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'scope': []
|
||||
}
|
||||
|
||||
|
||||
class AuditTemplateShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.AUDIT_TEMPLATE_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.AUDIT_TEMPLATE_FIELDS
|
||||
FIELD_LABELS = resource_fields.AUDIT_TEMPLATE_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
# goal mock
|
||||
p_goal_manager = mock.patch.object(resource, 'GoalManager')
|
||||
self.m_goal_mgr_cls = p_goal_manager.start()
|
||||
self.addCleanup(p_goal_manager.stop)
|
||||
|
||||
self.m_goal_mgr = mock.Mock()
|
||||
self.m_goal_mgr_cls.return_value = self.m_goal_mgr
|
||||
|
||||
# strategy mock
|
||||
p_strategy_manager = mock.patch.object(resource, 'StrategyManager')
|
||||
self.m_strategy_mgr_cls = p_strategy_manager.start()
|
||||
self.addCleanup(p_strategy_manager.stop)
|
||||
|
||||
self.m_strategy_mgr = mock.Mock()
|
||||
self.m_strategy_mgr_cls.return_value = self.m_strategy_mgr
|
||||
|
||||
# audit template mock
|
||||
p_audit_template_manager = mock.patch.object(
|
||||
resource, 'AuditTemplateManager')
|
||||
self.m_audit_template_mgr_cls = p_audit_template_manager.start()
|
||||
self.addCleanup(p_audit_template_manager.stop)
|
||||
|
||||
self.m_audit_template_mgr = mock.Mock()
|
||||
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
|
||||
|
||||
# stdout mock
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_audit_template_list(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd('audittemplate list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_audit_template_list_marker(self):
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --marker '
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
marker='f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
def test_do_audit_template_list_detail(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd('audittemplate list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_audit_template_list_filter_by_goal_uuid(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --goal '
|
||||
'fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
)
|
||||
|
||||
def test_do_audit_template_list_filter_by_goal_name(self):
|
||||
goal1 = resource.Goal(mock.Mock(), GOAL_1)
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_goal_mgr.get.return_value = goal1
|
||||
self.m_strategy_mgr.get.return_value = strategy1
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --goal SERVER_CONSOLIDATION')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
goal='SERVER_CONSOLIDATION',
|
||||
)
|
||||
|
||||
def test_do_audit_template_list_filter_by_strategy_uuid(self):
|
||||
goal1 = resource.Goal(mock.Mock(), GOAL_1)
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_goal_mgr.get.return_value = goal1
|
||||
self.m_strategy_mgr.get.return_value = strategy1
|
||||
self.m_audit_template_mgr.list.return_value = [audit_template1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --strategy '
|
||||
'2cf86250-d309-4b81-818e-1537f3dba6e5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
strategy='2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
)
|
||||
|
||||
def test_do_audit_template_list_filter_by_strategy_name(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.list.return_value = [audit_template1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --strategy '
|
||||
'basic')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
strategy='basic',
|
||||
)
|
||||
|
||||
def test_do_audit_template_show_by_name(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd('audittemplate show at1')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.get.assert_called_once_with('at1')
|
||||
|
||||
def test_do_audit_template_show_by_uuid(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate show f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.get.assert_called_once_with(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
def test_do_audit_template_delete(self):
|
||||
self.m_audit_template_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate delete f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_template_mgr.delete.assert_called_once_with(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
def test_do_audit_template_delete_multiple(self):
|
||||
self.m_audit_template_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate delete f8e47706-efcf-49a4-a5c4-af604eb492f2 '
|
||||
'92dfce2f-0a5e-473f-92b7-d92e21839e4d',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_template_mgr.delete.assert_any_call(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
self.m_audit_template_mgr.delete.assert_any_call(
|
||||
'92dfce2f-0a5e-473f-92b7-d92e21839e4d')
|
||||
|
||||
def test_do_audit_template_update(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.update.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate update at1 replace description="New description"')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.update.assert_called_once_with(
|
||||
'at1',
|
||||
[{'op': 'replace', 'path': '/description',
|
||||
'value': 'New description'}])
|
||||
|
||||
def test_do_audit_template_create(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1')
|
||||
|
||||
def test_do_audit_template_create_with_description(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-d "Audit Template 1 description"')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1',
|
||||
description='Audit Template 1 description')
|
||||
|
||||
def test_do_audit_template_create_with_aggregate(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1')
|
||||
75
watcherclient/tests/unit/v1/test_data_model.py
Normal file
75
watcherclient/tests/unit/v1/test_data_model.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# Copyright 2019 ZTE corporation.
|
||||
# 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 testtools
|
||||
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.data_model
|
||||
|
||||
DATA_MODEL = {
|
||||
'context': [{
|
||||
"server_uuid": "1bf91464-9b41-428d-a11e-af691e5563bb",
|
||||
"server_name": "fake-name",
|
||||
"server_state": "active",
|
||||
"node_uuid": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
|
||||
"node_hostname": "localhost.localdomain",
|
||||
}]
|
||||
}
|
||||
|
||||
AUDIT = "81332bfc-36f8-444d-99e2-b7285d602528"
|
||||
|
||||
fake_responses = {
|
||||
'/v1/data_model/?data_model_type=compute':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
DATA_MODEL,
|
||||
),
|
||||
},
|
||||
'/v1/data_model/?audit_uuid=%s&data_model_type=compute' % AUDIT:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
DATA_MODEL,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class DataModelManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DataModelManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = watcherclient.v1.data_model.DataModelManager(self.api)
|
||||
|
||||
def test_data_model_list(self):
|
||||
data_model = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/data_model/?data_model_type=compute', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(data_model.context))
|
||||
|
||||
def test_data_model_list_audit(self):
|
||||
data_model = self.mgr.list(
|
||||
audit='%s' % AUDIT)
|
||||
expect = [
|
||||
('GET', '/v1/data_model/?'
|
||||
'audit_uuid=81332bfc-36f8-444d-99e2-b7285d602528'
|
||||
'&data_model_type=compute',
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(data_model.context))
|
||||
130
watcherclient/tests/unit/v1/test_data_model_shell.py
Normal file
130
watcherclient/tests/unit/v1/test_data_model_shell.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Copyright 2019 ZTE 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 io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
|
||||
DATA_MODEL = {
|
||||
'context': [{
|
||||
"server_uuid": "1bf91464-9b41-428d-a11e-af691e5563bb",
|
||||
"server_name": "fake-name",
|
||||
"server_state": "active",
|
||||
"server_vcpus": "1",
|
||||
"server_memory": "512",
|
||||
"server_disk": "1",
|
||||
"node_uuid": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
|
||||
"node_hostname": "localhost.localdomain",
|
||||
"node_vcpus": "4",
|
||||
"node_vcpu_ratio": "16.0",
|
||||
"node_memory": "16383",
|
||||
"node_memory_ratio": "1.5",
|
||||
"node_disk": "37",
|
||||
"node_disk_ratio": "1.0",
|
||||
"node_state": "up",
|
||||
}]
|
||||
}
|
||||
|
||||
LIST_RESULT = [{
|
||||
"Server UUID": "1bf91464-9b41-428d-a11e-af691e5563bb",
|
||||
"Server Name": "fake-name",
|
||||
"Server Vcpus": "1",
|
||||
"Server Memory": "512",
|
||||
"Server Disk": "1",
|
||||
"Server State": "active",
|
||||
"Node UUID": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
|
||||
"Node Host Name": "localhost.localdomain",
|
||||
"Node Vcpus": "4",
|
||||
"Node Vcpu Ratio": "16.0",
|
||||
"Node Memory": "16383",
|
||||
"Node Memory Ratio": "1.5",
|
||||
"Node Disk": "37",
|
||||
"Node Disk Ratio": "1.0",
|
||||
"Node State": "up",
|
||||
}]
|
||||
|
||||
SHORT_LIST_RESULT = [{
|
||||
"Server UUID": "1bf91464-9b41-428d-a11e-af691e5563bb",
|
||||
"Server Name": "fake-name",
|
||||
"Server State": "active",
|
||||
"Node UUID": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
|
||||
"Node Host Name": "localhost.localdomain",
|
||||
}]
|
||||
|
||||
|
||||
class DataModelShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.COMPUTE_MODEL_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.COMPUTE_MODEL_LIST_FIELDS
|
||||
FIELD_LABELS = resource_fields.COMPUTE_MODEL_LIST_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_data_model_manager = mock.patch.object(
|
||||
resource, 'DataModelManager')
|
||||
|
||||
self.m_data_model_mgr_cls = p_data_model_manager.start()
|
||||
|
||||
self.addCleanup(p_data_model_manager.stop)
|
||||
|
||||
self.m_data_model_mgr = mock.Mock()
|
||||
|
||||
self.m_data_model_mgr_cls.return_value = self.m_data_model_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_data_model_list(self):
|
||||
data_model = resource.DataModel(mock.Mock(), DATA_MODEL)
|
||||
self.m_data_model_mgr.list.return_value = data_model
|
||||
|
||||
exit_code, results = self.run_cmd('datamodel list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
expect_values = sorted(SHORT_LIST_RESULT[0].values())
|
||||
result_values = sorted(results[0].values())
|
||||
self.assertEqual(expect_values, result_values)
|
||||
|
||||
def test_do_data_model_list_detail(self):
|
||||
data_model = resource.DataModel(mock.Mock(), DATA_MODEL)
|
||||
self.m_data_model_mgr.list.return_value = data_model
|
||||
|
||||
exit_code, results = self.run_cmd('datamodel list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
expect_values = sorted(LIST_RESULT[0].values())
|
||||
result_values = sorted(results[0].values())
|
||||
self.assertEqual(expect_values, result_values)
|
||||
|
||||
def test_do_data_model_list_filter_by_audit(self):
|
||||
data_model = resource.DataModel(mock.Mock(), DATA_MODEL)
|
||||
self.m_data_model_mgr.list.return_value = data_model
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'datamodel list --audit '
|
||||
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
expect_values = sorted(SHORT_LIST_RESULT[0].values())
|
||||
result_values = sorted(results[0].values())
|
||||
self.assertEqual(expect_values, result_values)
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -17,19 +15,21 @@
|
||||
|
||||
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.goal
|
||||
|
||||
GOAL1 = {
|
||||
'name': "BASIC_CONSOLIDATION",
|
||||
'strategy': 'basic'
|
||||
'uuid': "fc087747-61be-4aad-8126-b701731ae836",
|
||||
'name': "SERVER_CONSOLIDATION",
|
||||
'display_name': 'Server Consolidation'
|
||||
}
|
||||
|
||||
GOAL2 = {
|
||||
'uuid': "407b03b1-63c6-49b2-adaf-4df5c0090047",
|
||||
'name': "COST_OPTIMIZATION",
|
||||
'strategy': 'basic'
|
||||
'display_name': 'Cost Optimization'
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
@@ -47,6 +47,13 @@ fake_responses = {
|
||||
{"goals": [GOAL1]},
|
||||
)
|
||||
},
|
||||
'/v1/goals/%s' % GOAL1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
GOAL1,
|
||||
),
|
||||
},
|
||||
'/v1/goals/%s' % GOAL1['name']:
|
||||
{
|
||||
'GET': (
|
||||
@@ -62,7 +69,7 @@ fake_responses_pagination = {
|
||||
'GET': (
|
||||
{},
|
||||
{"goals": [GOAL1],
|
||||
"next": "http://127.0.0.1:6385/v1/goals/?limit=1"}
|
||||
"next": "http://127.0.0.1:9322/v1/goals/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/goals/?limit=1':
|
||||
@@ -75,7 +82,7 @@ fake_responses_pagination = {
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
'/v1/goals/?sort_key=name':
|
||||
'/v1/goals/?sort_key=id':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
@@ -91,6 +98,16 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_marker = {
|
||||
'/v1/goals/?marker=fc087747-61be-4aad-8126-b701731ae836':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"goals": [GOAL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class GoalManagerTest(testtools.TestCase):
|
||||
|
||||
@@ -123,7 +140,18 @@ class GoalManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/goals/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(goals, HasLength(1))
|
||||
self.assertThat(goals, matchers.HasLength(1))
|
||||
|
||||
def test_goals_list_marker(self):
|
||||
self.api = utils.FakeAPI(fake_responses_marker)
|
||||
self.mgr = watcherclient.v1.goal.GoalManager(self.api)
|
||||
goals = self.mgr.list(marker=GOAL1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/goals/?marker=fc087747-61be-4aad-8126-b701731ae836',
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(goals))
|
||||
|
||||
def test_goals_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -134,14 +162,14 @@ class GoalManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/goals/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(goals, HasLength(2))
|
||||
self.assertThat(goals, matchers.HasLength(2))
|
||||
|
||||
def test_goals_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.goal.GoalManager(self.api)
|
||||
goals = self.mgr.list(sort_key='name')
|
||||
goals = self.mgr.list(sort_key='id')
|
||||
expect = [
|
||||
('GET', '/v1/goals/?sort_key=name', {}, None)
|
||||
('GET', '/v1/goals/?sort_key=id', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(goals))
|
||||
@@ -157,6 +185,14 @@ class GoalManagerTest(testtools.TestCase):
|
||||
self.assertEqual(2, len(goals))
|
||||
|
||||
def test_goals_show(self):
|
||||
goal = self.mgr.get(GOAL1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/goals/%s' % GOAL1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(GOAL1['uuid'], goal.uuid)
|
||||
|
||||
def test_goals_show_by_name(self):
|
||||
goal = self.mgr.get(GOAL1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/goals/%s' % GOAL1['name'], {}, None),
|
||||
155
watcherclient/tests/unit/v1/test_goal_shell.py
Normal file
155
watcherclient/tests/unit/v1/test_goal_shell.py
Normal file
@@ -0,0 +1,155 @@
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
GOAL_1 = {
|
||||
'uuid': "fc087747-61be-4aad-8126-b701731ae836",
|
||||
'name': "SERVER_CONSOLIDATION",
|
||||
'display_name': 'Server Consolidation',
|
||||
'efficacy_specification': [
|
||||
{'description': 'Indicator 1', 'name': 'indicator1',
|
||||
'schema': 'Range(min=0, max=100, min_included=True, '
|
||||
'max_included=True, msg=None)',
|
||||
'unit': '%'}
|
||||
],
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
GOAL_2 = {
|
||||
'uuid': "407b03b1-63c6-49b2-adaf-4df5c0090047",
|
||||
'name': "COST_OPTIMIZATION",
|
||||
'display_name': 'Cost Optimization',
|
||||
'efficacy_specification': [
|
||||
{'description': 'Indicator 2', 'name': 'indicator2',
|
||||
'schema': 'Range(min=0, max=100, min_included=True, '
|
||||
'max_included=True, msg=None)',
|
||||
'unit': '%'}
|
||||
],
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class GoalShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.GOAL_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.GOAL_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.GOAL_FIELDS
|
||||
FIELD_LABELS = resource_fields.GOAL_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_goal_manager = mock.patch.object(
|
||||
resource, 'GoalManager')
|
||||
self.m_goal_mgr_cls = p_goal_manager.start()
|
||||
self.addCleanup(p_goal_manager.stop)
|
||||
|
||||
self.m_goal_mgr = mock.Mock()
|
||||
self.m_goal_mgr_cls.return_value = self.m_goal_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_goal_list(self):
|
||||
goal1 = resource.Goal(mock.Mock(), GOAL_1)
|
||||
goal2 = resource.Goal(mock.Mock(), GOAL_2)
|
||||
self.m_goal_mgr.list.return_value = [
|
||||
goal1, goal2]
|
||||
|
||||
exit_code, results = self.run_cmd('goal list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(goal1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(goal2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_goal_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_goal_list_marker(self):
|
||||
goal2 = resource.Goal(mock.Mock(), GOAL_2)
|
||||
self.m_goal_mgr.list.return_value = [goal2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'goal list --marker fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(goal2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_goal_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
marker='fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
def test_do_goal_list_detail(self):
|
||||
goal1 = resource.Goal(mock.Mock(), GOAL_1)
|
||||
goal2 = resource.Goal(mock.Mock(), GOAL_2)
|
||||
self.m_goal_mgr.list.return_value = [
|
||||
goal1, goal2]
|
||||
|
||||
exit_code, results = self.run_cmd('goal list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(goal1, self.FIELDS, self.FIELD_LABELS),
|
||||
self.resource_as_dict(goal2, self.FIELDS, self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_goal_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_goal_show_by_name(self):
|
||||
goal = resource.Goal(mock.Mock(), GOAL_1)
|
||||
self.m_goal_mgr.get.return_value = goal
|
||||
|
||||
exit_code, result = self.run_cmd('goal show SERVER_CONSOLIDATION')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(goal, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_goal_mgr.get.assert_called_once_with('SERVER_CONSOLIDATION')
|
||||
|
||||
def test_do_goal_show_by_uuid(self):
|
||||
goal = resource.Goal(mock.Mock(), GOAL_1)
|
||||
self.m_goal_mgr.get.return_value = goal
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'goal show fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(goal, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_goal_mgr.get.assert_called_once_with(
|
||||
'fc087747-61be-4aad-8126-b701731ae836')
|
||||
185
watcherclient/tests/unit/v1/test_scoring_engine.py
Normal file
185
watcherclient/tests/unit/v1/test_scoring_engine.py
Normal file
@@ -0,0 +1,185 @@
|
||||
#
|
||||
# Copyright 2016 Intel
|
||||
#
|
||||
# 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 testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.scoring_engine
|
||||
|
||||
SE1 = {
|
||||
'uuid': '5b558998-57ed-11e6-9ca8-08002722cb22',
|
||||
'name': 'se-01',
|
||||
'description': 'Some Scoring Engine'
|
||||
}
|
||||
|
||||
SE2 = {
|
||||
'uuid': '1f856554-57ee-11e6-ac72-08002722cb22',
|
||||
'name': 'se-02',
|
||||
'description': 'Some Other Scoring Engine'
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
'/v1/scoring_engines':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"scoring_engines": [SE1]},
|
||||
),
|
||||
},
|
||||
'/v1/scoring_engines/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"scoring_engines": [SE1]},
|
||||
)
|
||||
},
|
||||
'/v1/scoring_engines/%s' % SE1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
SE1,
|
||||
),
|
||||
},
|
||||
'/v1/scoring_engines/%s' % SE1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
SE1,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_pagination = {
|
||||
'/v1/scoring_engines':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"scoring_engines": [SE1],
|
||||
"next": "http://127.0.0.1:9322/v1/scoring_engines/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/scoring_engines/?limit=1':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"scoring_engines": [SE2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
'/v1/scoring_engines/?sort_key=id':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"scoring_engines": [SE1, SE2]}
|
||||
),
|
||||
},
|
||||
'/v1/scoring_engines/?sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"scoring_engines": [SE2, SE1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ScoringEngineManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ScoringEngineManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = \
|
||||
watcherclient.v1.scoring_engine.ScoringEngineManager(self.api)
|
||||
|
||||
def test_scoring_engines_list(self):
|
||||
scoring_engines = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(scoring_engines))
|
||||
|
||||
def test_scoring_engines_list_detail(self):
|
||||
scoring_engines = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines/detail', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(scoring_engines))
|
||||
|
||||
def test_scoring_engines_list_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = \
|
||||
watcherclient.v1.scoring_engine.ScoringEngineManager(self.api)
|
||||
scoring_engines = self.mgr.list(limit=1)
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(scoring_engines, matchers.HasLength(1))
|
||||
|
||||
def test_scoring_engines_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = \
|
||||
watcherclient.v1.scoring_engine.ScoringEngineManager(self.api)
|
||||
scoring_engines = self.mgr.list(limit=0)
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines', {}, None),
|
||||
('GET', '/v1/scoring_engines/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(scoring_engines, matchers.HasLength(2))
|
||||
|
||||
def test_scoring_engines_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = \
|
||||
watcherclient.v1.scoring_engine.ScoringEngineManager(self.api)
|
||||
scoring_engines = self.mgr.list(sort_key='id')
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines/?sort_key=id', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(scoring_engines))
|
||||
|
||||
def test_scoring_engines_list_sort_dir(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = \
|
||||
watcherclient.v1.scoring_engine.ScoringEngineManager(self.api)
|
||||
scoring_engines = self.mgr.list(sort_dir='desc')
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines/?sort_dir=desc', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(scoring_engines))
|
||||
|
||||
def test_scoring_engines_show(self):
|
||||
scoring_engine = self.mgr.get(SE1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines/%s' % SE1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(SE1['uuid'], scoring_engine.uuid)
|
||||
|
||||
def test_scoring_engines_show_by_name(self):
|
||||
scoring_engine = self.mgr.get(SE1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/scoring_engines/%s' % SE1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(SE1['name'], scoring_engine.name)
|
||||
128
watcherclient/tests/unit/v1/test_scoring_engine_shell.py
Normal file
128
watcherclient/tests/unit/v1/test_scoring_engine_shell.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) 2016 Intel
|
||||
#
|
||||
# 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 datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
SCORING_ENGINE_1 = {
|
||||
'uuid': '5b558998-57ed-11e6-9ca8-08002722cb22',
|
||||
'name': 'se-01',
|
||||
'description': 'Scoring Engine 0.1',
|
||||
'metainfo': '{ "columns": ["cpu", "mem", "pci"] }',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
SCORING_ENGINE_2 = {
|
||||
'uuid': '1f856554-57ee-11e6-ac72-08002722cb22',
|
||||
'name': 'se-02',
|
||||
'description': 'Some other Scoring Engine',
|
||||
'metainfo': 'mode=simplified',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class ScoringEngineShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.SCORING_ENGINE_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.SCORING_ENGINE_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.SCORING_ENGINE_FIELDS
|
||||
FIELD_LABELS = resource_fields.SCORING_ENGINE_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_se_manager = mock.patch.object(
|
||||
resource, 'ScoringEngineManager')
|
||||
self.m_se_mgr_cls = p_se_manager.start()
|
||||
self.addCleanup(p_se_manager.stop)
|
||||
|
||||
self.m_se_mgr = mock.Mock()
|
||||
self.m_se_mgr_cls.return_value = self.m_se_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_scoringengine_list(self):
|
||||
se1 = resource.ScoringEngine(mock.Mock(), SCORING_ENGINE_1)
|
||||
se2 = resource.ScoringEngine(mock.Mock(), SCORING_ENGINE_2)
|
||||
self.m_se_mgr.list.return_value = [
|
||||
se1, se2]
|
||||
|
||||
exit_code, results = self.run_cmd('scoringengine list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(se1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(se2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_se_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_scoringengine_list_detail(self):
|
||||
se1 = resource.Goal(mock.Mock(), SCORING_ENGINE_1)
|
||||
se2 = resource.Goal(mock.Mock(), SCORING_ENGINE_2)
|
||||
self.m_se_mgr.list.return_value = [
|
||||
se1, se2]
|
||||
|
||||
exit_code, results = self.run_cmd('scoringengine list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(se1, self.FIELDS, self.FIELD_LABELS),
|
||||
self.resource_as_dict(se2, self.FIELDS, self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_se_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_scoringengine_show_by_name(self):
|
||||
scoringengine = resource.Goal(mock.Mock(), SCORING_ENGINE_1)
|
||||
self.m_se_mgr.get.return_value = scoringengine
|
||||
|
||||
exit_code, result = self.run_cmd('scoringengine show se-01')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(scoringengine, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_se_mgr.get.assert_called_once_with('se-01')
|
||||
|
||||
def test_do_scoringengine_show_by_uuid(self):
|
||||
scoringengine = resource.Goal(mock.Mock(), SCORING_ENGINE_1)
|
||||
self.m_se_mgr.get.return_value = scoringengine
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'scoringengine show 5b558998-57ed-11e6-9ca8-08002722cb22')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(scoringengine, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_se_mgr.get.assert_called_once_with(
|
||||
'5b558998-57ed-11e6-9ca8-08002722cb22')
|
||||
182
watcherclient/tests/unit/v1/test_service.py
Normal file
182
watcherclient/tests/unit/v1/test_service.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# 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 testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.service
|
||||
|
||||
SERVICE1 = {
|
||||
'id': 1,
|
||||
'name': 'watcher-applier',
|
||||
'host': 'controller',
|
||||
'status': 'ACTIVE',
|
||||
}
|
||||
|
||||
SERVICE2 = {
|
||||
'id': 2,
|
||||
'name': 'watcher-decision-engine',
|
||||
'host': 'controller',
|
||||
'status': 'FAILED',
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
'/v1/services':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1]},
|
||||
),
|
||||
},
|
||||
'/v1/services/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1]},
|
||||
)
|
||||
},
|
||||
'/v1/services/%s' % SERVICE1['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE1,
|
||||
),
|
||||
},
|
||||
'/v1/services/%s' % SERVICE1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE1,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_pagination = {
|
||||
'/v1/services':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1],
|
||||
"next": "http://127.0.0.1:6385/v1/services/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/services/?limit=1':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
'/v1/services/?sort_key=id':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE1, SERVICE2]}
|
||||
),
|
||||
},
|
||||
'/v1/services/?sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"services": [SERVICE2, SERVICE1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ServiceManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ServiceManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
|
||||
def test_services_list(self):
|
||||
services = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/services', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(services))
|
||||
|
||||
def test_services_list_detail(self):
|
||||
services = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
('GET', '/v1/services/detail', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(services))
|
||||
|
||||
def test_services_list_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(limit=1)
|
||||
expect = [
|
||||
('GET', '/v1/services/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(services, matchers.HasLength(1))
|
||||
|
||||
def test_services_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(limit=0)
|
||||
expect = [
|
||||
('GET', '/v1/services', {}, None),
|
||||
('GET', '/v1/services/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(services, matchers.HasLength(2))
|
||||
|
||||
def test_services_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(sort_key='id')
|
||||
expect = [
|
||||
('GET', '/v1/services/?sort_key=id', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(services))
|
||||
|
||||
def test_services_list_sort_dir(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.service.ServiceManager(self.api)
|
||||
services = self.mgr.list(sort_dir='desc')
|
||||
expect = [
|
||||
('GET', '/v1/services/?sort_dir=desc', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(services))
|
||||
|
||||
def test_services_show(self):
|
||||
service = self.mgr.get(SERVICE1['id'])
|
||||
expect = [
|
||||
('GET', '/v1/services/%s' % SERVICE1['id'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(SERVICE1['id'], service.id)
|
||||
|
||||
def test_services_show_by_name(self):
|
||||
service = self.mgr.get(SERVICE1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/services/%s' % SERVICE1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(SERVICE1['name'], service.name)
|
||||
121
watcherclient/tests/unit/v1/test_service_shell.py
Normal file
121
watcherclient/tests/unit/v1/test_service_shell.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# 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.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
SERVICE_1 = {
|
||||
'name': 'watcher-applier',
|
||||
'host': 'controller',
|
||||
'status': 'ACTIVE',
|
||||
'last_seen_up': None,
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
SERVICE_2 = {
|
||||
'name': 'watcher-decision-engine',
|
||||
'host': 'controller',
|
||||
'status': 'FAILED',
|
||||
'last_seen_up': None,
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class ServiceShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.SERVICE_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.SERVICE_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.SERVICE_FIELDS
|
||||
FIELD_LABELS = resource_fields.SERVICE_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_service_manager = mock.patch.object(resource, 'ServiceManager')
|
||||
self.m_service_mgr_cls = p_service_manager.start()
|
||||
self.addCleanup(p_service_manager.stop)
|
||||
|
||||
self.m_service_mgr = mock.Mock()
|
||||
self.m_service_mgr_cls.return_value = self.m_service_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_service_list(self):
|
||||
service1 = resource.Service(mock.Mock(), SERVICE_1)
|
||||
service2 = resource.Service(mock.Mock(), SERVICE_2)
|
||||
self.m_service_mgr.list.return_value = [
|
||||
service1, service2]
|
||||
|
||||
exit_code, results = self.run_cmd('service list')
|
||||
|
||||
for res in results:
|
||||
del res['ID']
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(service1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(service2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_service_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_service_list_detail(self):
|
||||
service1 = resource.Service(mock.Mock(), SERVICE_1)
|
||||
service2 = resource.Service(mock.Mock(), SERVICE_2)
|
||||
self.m_service_mgr.list.return_value = [
|
||||
service1, service2]
|
||||
|
||||
exit_code, results = self.run_cmd('service list --detail')
|
||||
|
||||
for res in results:
|
||||
del res['ID']
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(service1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(service2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_service_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_service_show_by_name(self):
|
||||
service = resource.Service(mock.Mock(), SERVICE_1)
|
||||
self.m_service_mgr.get.return_value = service
|
||||
|
||||
exit_code, result = self.run_cmd('service show watcher-applier')
|
||||
|
||||
del result['ID']
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(service, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_service_mgr.get.assert_called_once_with('watcher-applier')
|
||||
219
watcherclient/tests/unit/v1/test_strategy.py
Normal file
219
watcherclient/tests/unit/v1/test_strategy.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# 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 testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests.unit import utils
|
||||
import watcherclient.v1.strategy
|
||||
|
||||
STRATEGY1 = {
|
||||
'uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'name': 'basic',
|
||||
'display_name': 'Basic consolidation',
|
||||
'strategy_id': 'SERVER_CONSOLIDATION',
|
||||
}
|
||||
|
||||
STRATEGY2 = {
|
||||
'uuid': 'b20bb987-ea8f-457a-a4ea-ab3ffdfeff8b',
|
||||
'name': 'dummy',
|
||||
'display_name': 'Dummy',
|
||||
'strategy_id': 'DUMMY',
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
'/v1/strategies':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY1]},
|
||||
),
|
||||
},
|
||||
'/v1/strategies/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY1]},
|
||||
)
|
||||
},
|
||||
'/v1/strategies/%s' % STRATEGY1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
STRATEGY1,
|
||||
),
|
||||
},
|
||||
'/v1/strategies/%s' % STRATEGY1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
STRATEGY1,
|
||||
),
|
||||
},
|
||||
'/v1/strategies/%s/state' % STRATEGY1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
STRATEGY1,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_pagination = {
|
||||
'/v1/strategies':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY1],
|
||||
"next": "http://127.0.0.1:9322/v1/strategies/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/strategies/?limit=1':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
'/v1/strategies/?sort_key=id':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY1, STRATEGY2]}
|
||||
),
|
||||
},
|
||||
'/v1/strategies/?sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY2, STRATEGY1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_marker = {
|
||||
'/v1/strategies/?marker=2cf86250-d309-4b81-818e-1537f3dba6e5':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"strategies": [STRATEGY2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class StrategyManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(StrategyManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
|
||||
|
||||
def test_strategies_list(self):
|
||||
strategies = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/strategies', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(strategies))
|
||||
|
||||
def test_strategies_list_detail(self):
|
||||
strategies = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
('GET', '/v1/strategies/detail', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(strategies))
|
||||
|
||||
def test_strategies_list_marker(self):
|
||||
self.api = utils.FakeAPI(fake_responses_marker)
|
||||
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
|
||||
strategies = self.mgr.list(marker=STRATEGY1['uuid'])
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/strategies/?marker=2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(strategies))
|
||||
|
||||
def test_strategies_list_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
|
||||
strategies = self.mgr.list(limit=1)
|
||||
expect = [
|
||||
('GET', '/v1/strategies/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(strategies, matchers.HasLength(1))
|
||||
|
||||
def test_strategies_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
|
||||
strategies = self.mgr.list(limit=0)
|
||||
expect = [
|
||||
('GET', '/v1/strategies', {}, None),
|
||||
('GET', '/v1/strategies/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(strategies, matchers.HasLength(2))
|
||||
|
||||
def test_strategies_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
|
||||
strategies = self.mgr.list(sort_key='id')
|
||||
expect = [
|
||||
('GET', '/v1/strategies/?sort_key=id', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(strategies))
|
||||
|
||||
def test_strategies_list_sort_dir(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
|
||||
strategies = self.mgr.list(sort_dir='desc')
|
||||
expect = [
|
||||
('GET', '/v1/strategies/?sort_dir=desc', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(strategies))
|
||||
|
||||
def test_strategies_show(self):
|
||||
strategy = self.mgr.get(STRATEGY1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/strategies/%s' % STRATEGY1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(STRATEGY1['uuid'], strategy.uuid)
|
||||
|
||||
def test_strategies_show_by_name(self):
|
||||
strategy = self.mgr.get(STRATEGY1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/strategies/%s' % STRATEGY1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(STRATEGY1['name'], strategy.name)
|
||||
|
||||
def test_strategies_state(self):
|
||||
self.mgr.state(STRATEGY1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/strategies/%s/state' % STRATEGY1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
208
watcherclient/tests/unit/v1/test_strategy_shell.py
Normal file
208
watcherclient/tests/unit/v1/test_strategy_shell.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.unit.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
STRATEGY_1 = {
|
||||
'uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'name': 'basic',
|
||||
'display_name': 'Basic consolidation',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters_spec': {},
|
||||
}
|
||||
|
||||
STRATEGY_2 = {
|
||||
'uuid': 'b20bb987-ea8f-457a-a4ea-ab3ffdfeff8b',
|
||||
'name': 'dummy',
|
||||
'display_name': 'Dummy',
|
||||
'goal_uuid': '407b03b1-63c6-49b2-adaf-4df5c0090047',
|
||||
'goal_name': 'DUMMY',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters_spec': {},
|
||||
}
|
||||
|
||||
|
||||
class StrategyShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.STRATEGY_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.STRATEGY_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.STRATEGY_FIELDS
|
||||
FIELD_LABELS = resource_fields.STRATEGY_FIELD_LABELS
|
||||
STATE_FIELDS = resource_fields.STRATEGY_STATE_FIELDS
|
||||
STATE_FIELD_LABELS = resource_fields.STRATEGY_STATE_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_strategy_manager = mock.patch.object(resource, 'StrategyManager')
|
||||
self.m_strategy_mgr_cls = p_strategy_manager.start()
|
||||
self.addCleanup(p_strategy_manager.stop)
|
||||
|
||||
self.m_strategy_mgr = mock.Mock()
|
||||
self.m_strategy_mgr_cls.return_value = self.m_strategy_mgr
|
||||
|
||||
self.stdout = io.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_strategy_list(self):
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
strategy2 = resource.Strategy(mock.Mock(), STRATEGY_2)
|
||||
self.m_strategy_mgr.list.return_value = [
|
||||
strategy1, strategy2]
|
||||
|
||||
exit_code, results = self.run_cmd('strategy list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(strategy1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(strategy2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_strategy_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_strategy_list_marker(self):
|
||||
strategy2 = resource.Strategy(mock.Mock(), STRATEGY_2)
|
||||
self.m_strategy_mgr.list.return_value = [strategy2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'strategy list --marker 2cf86250-d309-4b81-818e-1537f3dba6e5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(strategy2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_strategy_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
marker='2cf86250-d309-4b81-818e-1537f3dba6e5')
|
||||
|
||||
def test_do_strategy_list_detail(self):
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
strategy2 = resource.Strategy(mock.Mock(), STRATEGY_2)
|
||||
self.m_strategy_mgr.list.return_value = [
|
||||
strategy1, strategy2]
|
||||
|
||||
exit_code, results = self.run_cmd('strategy list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(strategy1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(strategy2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_strategy_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_strategy_list_filter_by_goal_name(self):
|
||||
strategy2 = resource.Strategy(mock.Mock(), STRATEGY_2)
|
||||
self.m_strategy_mgr.list.return_value = [strategy2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'strategy list --goal '
|
||||
'DUMMY')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(strategy2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_strategy_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
goal='DUMMY',
|
||||
)
|
||||
|
||||
def test_do_strategy_list_filter_by_goal_uuid(self):
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
self.m_strategy_mgr.list.return_value = [strategy1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'strategy list --goal '
|
||||
'fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(strategy1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_strategy_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
)
|
||||
|
||||
def test_do_strategy_show_by_uuid(self):
|
||||
strategy = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
self.m_strategy_mgr.get.return_value = strategy
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'strategy show f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(strategy, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_strategy_mgr.get.assert_called_once_with(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
def test_do_strategy_state(self):
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
strategy_req = [
|
||||
{'type': 'Datasource', 'mandatory': True,
|
||||
'comment': '', 'state': 'gnocchi: True'},
|
||||
{'type': 'Metrics', 'mandatory': False,
|
||||
'comment': '', 'state': jsonutils.dumps([
|
||||
{'compute.node.cpu.percent': 'available'},
|
||||
{'cpu_util': 'available'},
|
||||
{'memory.resident': 'available'},
|
||||
{'hardware.memory.used': 'not available'}])},
|
||||
{'type': 'CDM', 'mandatory': True,
|
||||
'comment': '',
|
||||
'state': jsonutils.dumps([{'compute_model': 'available'},
|
||||
{'storage_model': 'not available'}])},
|
||||
{'type': 'Name', 'mandatory': '', 'comment': '',
|
||||
'state': strategy1.name}]
|
||||
requirements = [resource.Strategy(mock.Mock(), req)
|
||||
for req in strategy_req]
|
||||
self.m_strategy_mgr.state.return_value = requirements
|
||||
|
||||
exit_code, results = self.run_cmd('strategy state basic')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(req, self.STATE_FIELDS,
|
||||
self.STATE_FIELD_LABELS)
|
||||
for req in requirements],
|
||||
results)
|
||||
@@ -1,148 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.action_plan_shell as ap_shell
|
||||
|
||||
|
||||
class ActionPlanShellTest(utils.BaseTestCase):
|
||||
def test_do_action_plan_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
action_plan = object()
|
||||
ap_shell._print_action_plan_show(action_plan)
|
||||
exp = ['uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'state', 'audit_uuid']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
|
||||
def test_do_action_plan_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan', 'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
ap_shell.do_action_plan_show(client_mock, args)
|
||||
client_mock.action_plan.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.action_plan.get_by_name.called)
|
||||
|
||||
def test_do_action_plan_show_by_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan', 'not_uuid')
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_show,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_action_plan_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
delete = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
|
||||
setattr(args, 'action-plan', delete)
|
||||
|
||||
ap_shell.do_action_plan_delete(client_mock, args)
|
||||
client_mock.action_plan.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
def test_do_action_plan_delete_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan', ['not_uuid'])
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_delete,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_action_plan_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan',
|
||||
["a5199d0e-0702-4613-9234-5ae2af8dafea",
|
||||
"a5199d0e-0702-4613-9234-5ae2af8dafeb"])
|
||||
|
||||
ap_shell.do_action_plan_delete(client_mock, args)
|
||||
client_mock.action_plan.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
|
||||
def test_do_action_plan_delete_multiple_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan',
|
||||
["a5199d0e-0702-4613-9234-5ae2af8dafea",
|
||||
"not_uuid",
|
||||
"a5199d0e-0702-4613-9234-5ae2af8dafeb"])
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_delete,
|
||||
client_mock, args)
|
||||
client_mock.action_plan.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
|
||||
|
||||
def test_do_action_plan_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
setattr(args, 'action-plan', "a5199d0e-0702-4613-9234-5ae2af8dafea")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
ap_shell.do_action_plan_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.action_plan.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
def test_do_action_plan_update_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
setattr(args, 'action-plan', "not_uuid")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_update,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_action_plan_start(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
action_plan = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
setattr(args, 'action-plan', action_plan)
|
||||
|
||||
ap_shell.do_action_plan_start(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
'replace', ['state=STARTING'])
|
||||
client_mock.action_plan.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
def test_do_action_plan_start_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
action_plan = 'not_uuid'
|
||||
setattr(args, 'action-plan', action_plan)
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_start,
|
||||
client_mock, args)
|
||||
@@ -1,132 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.action_shell as a_shell
|
||||
|
||||
|
||||
class ActionShellTest(utils.BaseTestCase):
|
||||
def test_do_action_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
action = object()
|
||||
a_shell._print_action_show(action)
|
||||
exp = ['action_type',
|
||||
'alarm',
|
||||
'applies_to',
|
||||
'created_at',
|
||||
'deleted_at',
|
||||
'description',
|
||||
'dst',
|
||||
'next_uuid',
|
||||
'parameter',
|
||||
'src',
|
||||
'state',
|
||||
'action_plan_uuid',
|
||||
'updated_at',
|
||||
'uuid']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
|
||||
def test_do_action_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
|
||||
a_shell.do_action_show(client_mock, args)
|
||||
client_mock.action.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.action.get_by_name.called)
|
||||
|
||||
def test_do_action_show_by_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'not_uuid'
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_show,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_action_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
|
||||
|
||||
a_shell.do_action_delete(client_mock, args)
|
||||
client_mock.action.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
def test_do_action_delete_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['not_uuid']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_delete,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_action_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
|
||||
a_shell.do_action_delete(client_mock, args)
|
||||
client_mock.action.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
|
||||
def test_do_action_delete_multiple_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'not_uuid'
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_delete,
|
||||
client_mock, args)
|
||||
client_mock.action.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
|
||||
|
||||
def test_do_action_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
a_shell.do_action_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.action.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
def test_do_action_update_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'not_uuid'
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_update,
|
||||
client_mock, args)
|
||||
@@ -1,145 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.audit_shell as a_shell
|
||||
|
||||
|
||||
class AuditShellTest(utils.BaseTestCase):
|
||||
def test_do_audit_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
audit = object()
|
||||
a_shell._print_audit_show(audit)
|
||||
exp = ['created_at', 'audit_template_uuid', 'audit_template_name',
|
||||
'updated_at', 'uuid', 'deleted_at', 'state', 'type',
|
||||
'deadline']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
|
||||
def test_do_audit_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
|
||||
a_shell.do_audit_show(client_mock, args)
|
||||
client_mock.audit.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.audit.get_by_name.called)
|
||||
|
||||
def test_do_audit_show_by_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = 'not_uuid'
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_show,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_audit_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
|
||||
|
||||
a_shell.do_audit_delete(client_mock, args)
|
||||
client_mock.audit.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
def test_do_audit_delete_with_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['not_uuid']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_delete,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_audit_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
|
||||
a_shell.do_audit_delete(client_mock, args)
|
||||
client_mock.audit.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
|
||||
def test_do_audit_delete_multiple_with_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'not_uuid',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_delete,
|
||||
client_mock, args)
|
||||
client_mock.audit.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
|
||||
|
||||
def test_do_audit_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
a_shell.do_audit_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.audit.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
def test_do_audit_update_with_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['not_uuid']
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_update,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_audit_create(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
a_shell.do_audit_create(client_mock, args)
|
||||
client_mock.audit.create.assert_called_once_with()
|
||||
|
||||
def test_do_audit_create_with_deadline(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.deadline = 'deadline'
|
||||
|
||||
a_shell.do_audit_create(client_mock, args)
|
||||
client_mock.audit.create.assert_called_once_with(
|
||||
deadline='deadline')
|
||||
|
||||
def test_do_audit_create_with_type(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.type = 'type'
|
||||
|
||||
a_shell.do_audit_create(client_mock, args)
|
||||
client_mock.audit.create.assert_called_once_with(type='type')
|
||||
@@ -1,137 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.audit_template_shell as at_shell
|
||||
|
||||
|
||||
class AuditTemplateShellTest(utils.BaseTestCase):
|
||||
def test_do_audit_template_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
audit_template = object()
|
||||
at_shell._print_audit_template_show(audit_template)
|
||||
exp = [
|
||||
'uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'description', 'host_aggregate', 'name',
|
||||
'extra', 'goal']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
|
||||
def test_do_audit_template_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template', 'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
at_shell.do_audit_template_show(client_mock, args)
|
||||
client_mock.audit_template.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.audit_template.get_by_name.called)
|
||||
|
||||
def test_do_audit_template_show_by_name(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template', "a5199d0e-0702-4613-9234-5ae2af8dafea")
|
||||
|
||||
at_shell.do_audit_template_show(client_mock, args)
|
||||
client_mock.audit_template.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
def test_do_audit_template_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template',
|
||||
['a5199d0e-0702-4613-9234-5ae2af8dafea'])
|
||||
|
||||
at_shell.do_audit_template_delete(client_mock, args)
|
||||
client_mock.audit_template.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
def test_do_audit_template_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template',
|
||||
['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb'])
|
||||
|
||||
at_shell.do_audit_template_delete(client_mock, args)
|
||||
client_mock.audit_template.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
|
||||
def test_do_audit_template_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template', "a5199d0e-0702-4613-9234-5ae2af8dafea")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
at_shell.do_audit_template_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.audit_template.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
def test_do_audit_template_create(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with()
|
||||
|
||||
def test_do_audit_template_create_with_name(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.name = 'my audit template'
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
name='my audit template')
|
||||
|
||||
def test_do_audit_template_create_with_description(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.description = 'description'
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
description='description')
|
||||
|
||||
def test_do_audit_template_create_with_aggregate(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.host_aggregate = 5
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
host_aggregate=5)
|
||||
|
||||
def test_do_audit_template_create_with_extra(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.extra = ['automatic=true']
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
extra={'automatic': True})
|
||||
@@ -1,33 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.goal_shell as a_shell
|
||||
|
||||
|
||||
class GoalShellTest(utils.BaseTestCase):
|
||||
|
||||
def test_do_goal_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
goal = object()
|
||||
a_shell._print_goal_show(goal)
|
||||
exp = ['name', 'strategy']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
@@ -1,321 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# 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 testtools
|
||||
from testtools.matchers import HasLength
|
||||
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.metric_collector
|
||||
|
||||
METRIC_COLLECTOR1 = {
|
||||
'id': 1,
|
||||
'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'category': 'cat1',
|
||||
'endpoint': 'http://metric_collector_1:6446',
|
||||
}
|
||||
|
||||
METRIC_COLLECTOR2 = {
|
||||
'id': 2,
|
||||
'uuid': '67653274-eb24-c7ba-70f6-a84e73d80843',
|
||||
'category': 'cat2',
|
||||
}
|
||||
|
||||
METRIC_COLLECTOR3 = {
|
||||
'id': 3,
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'category': 'cat2',
|
||||
'endpoint': 'http://metric_collector_3:6446',
|
||||
}
|
||||
|
||||
CREATE_METRIC_COLLECTOR = copy.deepcopy(METRIC_COLLECTOR1)
|
||||
del CREATE_METRIC_COLLECTOR['id']
|
||||
del CREATE_METRIC_COLLECTOR['uuid']
|
||||
|
||||
UPDATED_METRIC_COLLECTOR1 = copy.deepcopy(METRIC_COLLECTOR1)
|
||||
NEW_ENDPOINT = 'http://metric_collector_1:6447'
|
||||
UPDATED_METRIC_COLLECTOR1['endpoint'] = NEW_ENDPOINT
|
||||
|
||||
fake_responses = {
|
||||
'/v1/metric-collectors':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR1]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
CREATE_METRIC_COLLECTOR,
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/?category=%s' % METRIC_COLLECTOR1['category']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR1]},
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/?category=%s' % METRIC_COLLECTOR2['category']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR3]},
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR1]},
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/%s' % METRIC_COLLECTOR1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
METRIC_COLLECTOR1,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_METRIC_COLLECTOR1,
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/detail?category=%s' % METRIC_COLLECTOR1['category']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR1]},
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/detail?category=%s' % METRIC_COLLECTOR2['category']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR3]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_pagination = {
|
||||
'/v1/metric-collectors':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR1],
|
||||
"next": "http://127.0.0.1:6385/v1/metric-collectors/?limit=1"}
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/?limit=1':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_sorting = {
|
||||
'/v1/metric-collectors/?sort_key=updated_at':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR1]}
|
||||
),
|
||||
},
|
||||
'/v1/metric-collectors/?sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR1]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class MetricCollectorManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MetricCollectorManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = watcherclient.v1.metric_collector \
|
||||
.MetricCollectorManager(self.api)
|
||||
|
||||
def test_metric_collectors_list(self):
|
||||
metric_collectors = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_by_category(self):
|
||||
metric_collectors = self.mgr.list(
|
||||
category=METRIC_COLLECTOR1['category']
|
||||
)
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/metric-collectors/?category=%s' %
|
||||
METRIC_COLLECTOR1['category'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_by_category_bis(self):
|
||||
metric_collectors = self.mgr.list(
|
||||
category=METRIC_COLLECTOR2['category']
|
||||
)
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/metric-collectors/?category=%s' %
|
||||
METRIC_COLLECTOR2['category'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_detail(self):
|
||||
metric_collectors = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors/detail', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_by_category_detail(self):
|
||||
metric_collectors = self.mgr.list(
|
||||
category=METRIC_COLLECTOR1['category'],
|
||||
detail=True)
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/metric-collectors/detail?category=%s' %
|
||||
METRIC_COLLECTOR1['category'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_by_category_detail_bis(self):
|
||||
metric_collectors = self.mgr.list(
|
||||
category=METRIC_COLLECTOR2['category'],
|
||||
detail=True)
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/metric-collectors/detail?category=%s' %
|
||||
METRIC_COLLECTOR2['category'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.metric_collector \
|
||||
.MetricCollectorManager(self.api)
|
||||
metric_collectors = self.mgr.list(limit=1)
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(metric_collectors, HasLength(1))
|
||||
|
||||
def test_metric_collectors_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
self.mgr = watcherclient.v1.metric_collector \
|
||||
.MetricCollectorManager(self.api)
|
||||
metric_collectors = self.mgr.list(limit=0)
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors', {}, None),
|
||||
('GET', '/v1/metric-collectors/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(metric_collectors, HasLength(2))
|
||||
|
||||
def test_metric_collectors_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.metric_collector \
|
||||
.MetricCollectorManager(self.api)
|
||||
metric_collectors = self.mgr.list(sort_key='updated_at')
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors/?sort_key=updated_at', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_list_sort_dir(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
self.mgr = watcherclient.v1.metric_collector \
|
||||
.MetricCollectorManager(self.api)
|
||||
metric_collectors = self.mgr.list(sort_dir='desc')
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors/?sort_dir=desc', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(2, len(metric_collectors))
|
||||
|
||||
def test_metric_collectors_show(self):
|
||||
metric_collector = self.mgr.get(METRIC_COLLECTOR1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/metric-collectors/%s' %
|
||||
METRIC_COLLECTOR1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(METRIC_COLLECTOR1['uuid'], metric_collector.uuid)
|
||||
self.assertEqual(METRIC_COLLECTOR1['category'],
|
||||
metric_collector.category)
|
||||
self.assertEqual(METRIC_COLLECTOR1['endpoint'],
|
||||
metric_collector.endpoint)
|
||||
|
||||
def test_create(self):
|
||||
metric_collector = self.mgr.create(**CREATE_METRIC_COLLECTOR)
|
||||
expect = [
|
||||
('POST', '/v1/metric-collectors', {}, CREATE_METRIC_COLLECTOR),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(metric_collector)
|
||||
|
||||
def test_delete(self):
|
||||
metric_collector = self.mgr.delete(
|
||||
metric_collector_id=METRIC_COLLECTOR1['uuid'])
|
||||
expect = [
|
||||
('DELETE', '/v1/metric-collectors/%s' %
|
||||
METRIC_COLLECTOR1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(metric_collector)
|
||||
|
||||
def test_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_ENDPOINT,
|
||||
'path': '/endpoint'}
|
||||
metric_collector = self.mgr.update(
|
||||
metric_collector_id=METRIC_COLLECTOR1['uuid'], patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/metric-collectors/%s' %
|
||||
METRIC_COLLECTOR1['uuid'], {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_ENDPOINT, metric_collector.endpoint)
|
||||
@@ -1,107 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.metric_collector_shell as mc_shell
|
||||
|
||||
|
||||
class MetricCollectorShellTest(utils.BaseTestCase):
|
||||
def test_do_metric_collector_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
metric_collector = object()
|
||||
mc_shell._print_metric_collector_show(metric_collector)
|
||||
exp = ['uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'category', 'endpoint']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
|
||||
def test_do_metric_collector_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.metric_collector = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
|
||||
mc_shell.do_metric_collector_show(client_mock, args)
|
||||
client_mock.metric_collector.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.metric_collector.get_by_name.called)
|
||||
|
||||
def test_do_metric_collector_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.metric_collector = ['metric_collector_uuid']
|
||||
|
||||
mc_shell.do_metric_collector_delete(client_mock, args)
|
||||
client_mock.metric_collector.delete.assert_called_once_with(
|
||||
'metric_collector_uuid'
|
||||
)
|
||||
|
||||
def test_do_metric_collector_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.metric_collector = ['metric_collector_uuid1',
|
||||
'metric_collector_uuid2']
|
||||
|
||||
mc_shell.do_metric_collector_delete(client_mock, args)
|
||||
client_mock.metric_collector.delete.assert_has_calls(
|
||||
[mock.call('metric_collector_uuid1'),
|
||||
mock.call('metric_collector_uuid2')])
|
||||
|
||||
def test_do_metric_collector_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'metric-collector', "metric_collector_uuid")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
mc_shell.do_metric_collector_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.metric_collector.update.assert_called_once_with(
|
||||
'metric_collector_uuid', patch)
|
||||
|
||||
def test_do_metric_collector_create(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
||||
mc_shell.do_metric_collector_create(client_mock, args)
|
||||
client_mock.metric_collector.create.assert_called_once_with()
|
||||
|
||||
def test_do_metric_collector_create_with_category(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.category = 'mc_category'
|
||||
|
||||
mc_shell.do_metric_collector_create(client_mock, args)
|
||||
client_mock.metric_collector.create.assert_called_once_with(
|
||||
category='mc_category')
|
||||
|
||||
def test_do_metric_collector_create_with_endpoint(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.endpoint = 'mc_endpoint'
|
||||
|
||||
mc_shell.do_metric_collector_create(client_mock, args)
|
||||
client_mock.metric_collector.create.assert_called_once_with(
|
||||
endpoint='mc_endpoint')
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from watcherclient.v1 import action
|
||||
from watcherclient.v1 import action_plan
|
||||
from watcherclient.v1 import audit
|
||||
from watcherclient.v1 import audit_template
|
||||
from watcherclient.v1 import data_model
|
||||
from watcherclient.v1 import goal
|
||||
from watcherclient.v1 import scoring_engine
|
||||
from watcherclient.v1 import service
|
||||
from watcherclient.v1 import strategy
|
||||
|
||||
Action = action.Action
|
||||
ActionManager = action.ActionManager
|
||||
ActionPlan = action_plan.ActionPlan
|
||||
ActionPlanManager = action_plan.ActionPlanManager
|
||||
Audit = audit.Audit
|
||||
AuditManager = audit.AuditManager
|
||||
AuditTemplate = audit_template.AuditTemplate
|
||||
AuditTemplateManager = audit_template.AuditTemplateManager
|
||||
Goal = goal.Goal
|
||||
GoalManager = goal.GoalManager
|
||||
ScoringEngine = scoring_engine.ScoringEngine
|
||||
ScoringEngineManager = scoring_engine.ScoringEngineManager
|
||||
Service = service.Service
|
||||
ServiceManager = service.ServiceManager
|
||||
Strategy = strategy.Strategy
|
||||
StrategyManager = strategy.StrategyManager
|
||||
DataModel = data_model.DataModel
|
||||
DataModelManager = data_model.DataModelManager
|
||||
|
||||
__all__ = (
|
||||
"Action", "ActionManager", "ActionPlan", "ActionPlanManager",
|
||||
"Audit", "AuditManager", "AuditTemplate", "AuditTemplateManager",
|
||||
"Goal", "GoalManager", "ScoringEngine", "ScoringEngineManager",
|
||||
"Service", "ServiceManager", "Strategy", "StrategyManager",
|
||||
"DataModel", "DataModelManager")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
#
|
||||
@@ -31,7 +30,7 @@ class ActionManager(base.Manager):
|
||||
return '/v1/actions/%s' % id if id else '/v1/actions'
|
||||
|
||||
def list(self, action_plan=None, audit=None, limit=None, sort_key=None,
|
||||
sort_dir=None, detail=False):
|
||||
sort_dir=None, detail=False, marker=None):
|
||||
"""Retrieve a list of action.
|
||||
|
||||
:param action_plan: UUID of the action plan
|
||||
@@ -53,13 +52,15 @@ class ActionManager(base.Manager):
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about actions.
|
||||
|
||||
:param marker: Optional, UUID of the last action in the previous page.
|
||||
|
||||
:returns: A list of actions.
|
||||
|
||||
"""
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(limit, sort_key, sort_dir)
|
||||
filters = utils.common_filters(limit, sort_key, sort_dir, marker)
|
||||
if action_plan is not None:
|
||||
filters.append('action_plan_uuid=%s' % action_plan)
|
||||
if audit is not None:
|
||||
@@ -82,9 +83,3 @@ class ActionManager(base.Manager):
|
||||
return self._list(self._path(action_id))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def delete(self, action_id):
|
||||
return self._delete(self._path(action_id))
|
||||
|
||||
def update(self, action_id, patch):
|
||||
return self._update(self._path(action_id), patch)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
#
|
||||
@@ -28,11 +27,16 @@ class ActionPlanManager(base.Manager):
|
||||
resource_class = ActionPlan
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None):
|
||||
return '/v1/action_plans/%s' % id if id else '/v1/action_plans'
|
||||
def _path(id=None, q_param=None):
|
||||
if id and q_param:
|
||||
return '/v1/action_plans/%s/%s' % (id, q_param)
|
||||
elif id:
|
||||
return '/v1/action_plans/%s' % id
|
||||
else:
|
||||
return '/v1/action_plans'
|
||||
|
||||
def list(self, audit=None, limit=None, sort_key=None,
|
||||
sort_dir=None, detail=False):
|
||||
sort_dir=None, detail=False, marker=None):
|
||||
"""Retrieve a list of action plan.
|
||||
|
||||
:param audit: Name of the audit
|
||||
@@ -53,13 +57,16 @@ class ActionPlanManager(base.Manager):
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about action plans.
|
||||
|
||||
:param marker: The last actionplan UUID of the previous page;
|
||||
displays list of actionplans after "marker".
|
||||
|
||||
:returns: A list of action plans.
|
||||
|
||||
"""
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(limit, sort_key, sort_dir)
|
||||
filters = utils.common_filters(limit, sort_key, sort_dir, marker)
|
||||
if audit is not None:
|
||||
filters.append('audit_uuid=%s' % audit)
|
||||
|
||||
@@ -86,3 +93,15 @@ class ActionPlanManager(base.Manager):
|
||||
|
||||
def update(self, action_plan_id, patch):
|
||||
return self._update(self._path(action_plan_id), patch)
|
||||
|
||||
def start(self, action_plan_id):
|
||||
return self._start(self._path(action_plan_id, 'start'))
|
||||
|
||||
def cancel(self, action_plan_id):
|
||||
action_plan = self.get(action_plan_id)
|
||||
if action_plan.state == "ONGOING":
|
||||
patch = [{'op': 'replace', 'value': 'CANCELLING',
|
||||
'path': '/state'}]
|
||||
else:
|
||||
patch = [{'op': 'replace', 'value': 'CANCELLED', 'path': '/state'}]
|
||||
return self._update(self._path(action_plan_id), patch)
|
||||
|
||||
@@ -1,156 +1,318 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
|
||||
# import argparse
|
||||
import io
|
||||
|
||||
from cliff.formatters import yaml_format
|
||||
from osc_lib import utils
|
||||
from oslo_utils import uuidutils
|
||||
from watcherclient.common import utils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common import command
|
||||
from watcherclient.common import utils as common_utils
|
||||
from watcherclient import exceptions
|
||||
from watcherclient.v1 import resource_fields as res_fields
|
||||
|
||||
|
||||
def _print_action_plan_show(action_plan):
|
||||
fields = res_fields.ACTION_PLAN_FIELDS
|
||||
data = dict([(f, getattr(action_plan, f, '')) for f in fields])
|
||||
cliutils.print_dict(data, wrap=72)
|
||||
def format_global_efficacy(global_efficacy):
|
||||
formatted_global_eff = {}
|
||||
for eff in global_efficacy:
|
||||
eff_name = eff.get('name')
|
||||
if (eff.get('value') is not None and eff.get('unit')):
|
||||
formatted_global_eff[eff_name] = "%(value).2f %(unit)s" % dict(
|
||||
unit=eff.get('unit'),
|
||||
value=eff.get('value'))
|
||||
elif eff.get('value') is not None:
|
||||
formatted_global_eff[eff_name] = eff.get('value')
|
||||
return formatted_global_eff
|
||||
|
||||
|
||||
@cliutils.arg(
|
||||
'action-plan',
|
||||
metavar='<action-plan>',
|
||||
help="UUID of the action_plan.")
|
||||
def do_action_plan_show(cc, args):
|
||||
"""Show detailed information about an action plan."""
|
||||
action_plan_uuid = getattr(args, 'action-plan')
|
||||
if uuidutils.is_uuid_like(action_plan_uuid):
|
||||
action_plan = cc.action_plan.get(action_plan_uuid)
|
||||
_print_action_plan_show(action_plan)
|
||||
else:
|
||||
raise ValidationError()
|
||||
class ShowActionPlan(command.ShowOne):
|
||||
"""Show detailed information about a given action plan."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowActionPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'action_plan',
|
||||
metavar='<action-plan>',
|
||||
help=_('UUID of the action plan'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def _format_indicators(self, action_plan, parsed_args):
|
||||
out = io.StringIO()
|
||||
efficacy_indicators = action_plan.efficacy_indicators
|
||||
fields = ['name', 'description', 'value', 'unit']
|
||||
yaml_format.YAMLFormatter().emit_list(
|
||||
column_names=list(field.capitalize()
|
||||
for field in fields),
|
||||
data=[utils.get_dict_properties(spec, fields)
|
||||
for spec in efficacy_indicators],
|
||||
stdout=out,
|
||||
parsed_args=parsed_args,
|
||||
)
|
||||
return out.getvalue() or ''
|
||||
|
||||
def _format_global_efficacy(self, global_efficacy, parsed_args):
|
||||
formatted_global_efficacy = format_global_efficacy(global_efficacy)
|
||||
out = io.StringIO()
|
||||
yaml_format.YAMLFormatter().emit_one(
|
||||
column_names=list(resource.capitalize()
|
||||
for resource in formatted_global_efficacy),
|
||||
data=[value for value in formatted_global_efficacy.values()],
|
||||
stdout=out,
|
||||
parsed_args=parsed_args,
|
||||
)
|
||||
return out.getvalue() or ''
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
action_plan_uuid = parsed_args.action_plan
|
||||
|
||||
if not uuidutils.is_uuid_like(action_plan_uuid):
|
||||
raise exceptions.ValidationError()
|
||||
|
||||
try:
|
||||
action_plan = client.action_plan.get(action_plan_uuid)
|
||||
except exceptions.HTTPNotFound as exc:
|
||||
raise exceptions.CommandError(str(exc))
|
||||
|
||||
if parsed_args.formatter == 'table':
|
||||
# Update the raw efficacy indicators with the formatted ones
|
||||
action_plan.efficacy_indicators = (
|
||||
self._format_indicators(action_plan, parsed_args))
|
||||
|
||||
# Update the raw global efficacy with the formatted one
|
||||
action_plan.global_efficacy = self._format_global_efficacy(
|
||||
action_plan.global_efficacy, parsed_args)
|
||||
|
||||
columns = res_fields.ACTION_PLAN_FIELDS
|
||||
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
|
||||
return column_headers, utils.get_item_properties(action_plan, columns)
|
||||
|
||||
|
||||
@cliutils.arg(
|
||||
'--audit',
|
||||
metavar='<audit>',
|
||||
help='UUID of an audit used for filtering.')
|
||||
@cliutils.arg(
|
||||
'--detail',
|
||||
dest='detail',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Show detailed information about action plans.")
|
||||
@cliutils.arg(
|
||||
'--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help='Maximum number of action plans to return per request, '
|
||||
'0 for no limit. Default is the maximum number used '
|
||||
'by the Watcher API Service.')
|
||||
@cliutils.arg(
|
||||
'--sort-key',
|
||||
metavar='<field>',
|
||||
help='Action Plan field that will be used for sorting.')
|
||||
@cliutils.arg(
|
||||
'--sort-dir',
|
||||
metavar='<direction>',
|
||||
choices=['asc', 'desc'],
|
||||
help='Sort direction: "asc" (the default) or "desc".')
|
||||
def do_action_plan_list(cc, args):
|
||||
"""List the action plans."""
|
||||
params = {}
|
||||
class ListActionPlan(command.Lister):
|
||||
"""List information on retrieved action plans."""
|
||||
|
||||
if args.audit is not None:
|
||||
params['audit'] = args.audit
|
||||
if args.detail:
|
||||
fields = res_fields.ACTION_PLAN_FIELDS
|
||||
field_labels = res_fields.ACTION_PLAN_FIELD_LABELS
|
||||
else:
|
||||
fields = res_fields.ACTION_PLAN_SHORT_LIST_FIELDS
|
||||
field_labels = res_fields.ACTION_PLAN_SHORT_LIST_FIELD_LABELS
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListActionPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--audit',
|
||||
metavar='<audit>',
|
||||
help=_('UUID of an audit used for filtering.'))
|
||||
parser.add_argument(
|
||||
'--detail',
|
||||
dest='detail',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_("Show detailed information about action plans."))
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of action plans to return per request, '
|
||||
'0 for no limit. Default is the maximum number used '
|
||||
'by the Watcher API Service.'))
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<actionplan>',
|
||||
help=_('The last actionplan UUID of the previous page; '
|
||||
'displays list of actionplans after "marker".'))
|
||||
parser.add_argument(
|
||||
'--sort-key',
|
||||
metavar='<field>',
|
||||
help=_('Action Plan field that will be used for sorting.'))
|
||||
parser.add_argument(
|
||||
'--sort-dir',
|
||||
metavar='<direction>',
|
||||
choices=['asc', 'desc'],
|
||||
help=_('Sort direction: "asc" (the default) or "desc".'))
|
||||
|
||||
params.update(utils.common_params_for_list(args,
|
||||
fields,
|
||||
field_labels))
|
||||
return parser
|
||||
|
||||
action_plan = cc.action_plan.list(**params)
|
||||
cliutils.print_list(action_plan, fields,
|
||||
field_labels=field_labels,
|
||||
sortby_index=None)
|
||||
def _format_indicators(self, action_plan, parsed_args):
|
||||
out = io.StringIO()
|
||||
efficacy_indicators = action_plan.efficacy_indicators
|
||||
fields = ['name', 'value', 'unit']
|
||||
yaml_format.YAMLFormatter().emit_list(
|
||||
column_names=list(field.capitalize()
|
||||
for field in fields),
|
||||
data=[utils.get_dict_properties(spec, fields)
|
||||
for spec in efficacy_indicators],
|
||||
stdout=out,
|
||||
parsed_args=parsed_args,
|
||||
)
|
||||
return out.getvalue() or ''
|
||||
|
||||
def _format_global_efficacy(self, global_efficacy, parsed_args):
|
||||
formatted_global_efficacy = format_global_efficacy(global_efficacy)
|
||||
out = io.StringIO()
|
||||
yaml_format.YAMLFormatter().emit_one(
|
||||
column_names=list(resource.capitalize()
|
||||
for resource in formatted_global_efficacy),
|
||||
data=[value for value in formatted_global_efficacy.values()],
|
||||
stdout=out,
|
||||
parsed_args=parsed_args,
|
||||
)
|
||||
return out.getvalue() or ''
|
||||
|
||||
@cliutils.arg(
|
||||
'action-plan',
|
||||
metavar='<action-plan>',
|
||||
nargs='+',
|
||||
help="UUID of the action plan.")
|
||||
def do_action_plan_delete(cc, args):
|
||||
"""Delete an action plan."""
|
||||
for p in getattr(args, 'action-plan'):
|
||||
if uuidutils.is_uuid_like(p):
|
||||
cc.action_plan.delete(p)
|
||||
print ('Deleted action plan %s' % p)
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
params = {}
|
||||
if parsed_args.audit is not None:
|
||||
params['audit'] = parsed_args.audit
|
||||
if parsed_args.detail:
|
||||
fields = res_fields.ACTION_PLAN_FIELDS
|
||||
field_labels = res_fields.ACTION_PLAN_FIELD_LABELS
|
||||
else:
|
||||
raise ValidationError()
|
||||
fields = res_fields.ACTION_PLAN_SHORT_LIST_FIELDS
|
||||
field_labels = res_fields.ACTION_PLAN_SHORT_LIST_FIELD_LABELS
|
||||
|
||||
params.update(common_utils.common_params_for_list(
|
||||
parsed_args, fields, field_labels))
|
||||
|
||||
data = client.action_plan.list(**params)
|
||||
|
||||
if parsed_args.formatter == 'table':
|
||||
for action_plan in data:
|
||||
# Update the raw efficacy indicators with the formatted ones
|
||||
action_plan.efficacy_indicators = (
|
||||
self._format_indicators(action_plan, parsed_args))
|
||||
|
||||
# Update the raw global efficacy with the formatted one
|
||||
action_plan.global_efficacy = self._format_global_efficacy(
|
||||
action_plan.global_efficacy, parsed_args)
|
||||
|
||||
return (field_labels,
|
||||
(utils.get_item_properties(item, fields) for item in data))
|
||||
|
||||
|
||||
@cliutils.arg(
|
||||
'action-plan',
|
||||
metavar='<action-plan>',
|
||||
help="UUID of the action plan.")
|
||||
@cliutils.arg(
|
||||
'op',
|
||||
metavar='<op>',
|
||||
choices=['add', 'replace', 'remove'],
|
||||
help="Operation: 'add', 'replace', or 'remove'.")
|
||||
@cliutils.arg(
|
||||
'attributes',
|
||||
metavar='<path=value>',
|
||||
nargs='+',
|
||||
action='append',
|
||||
default=[],
|
||||
help="Attribute to add, replace, or remove. Can be specified multiple "
|
||||
"times. For 'remove', only <path> is necessary.")
|
||||
def do_action_plan_update(cc, args):
|
||||
"""Update information about an action plan."""
|
||||
action_plan_uuid = getattr(args, 'action-plan')
|
||||
if uuidutils.is_uuid_like(action_plan_uuid):
|
||||
patch = utils.args_array_to_patch(args.op, args.attributes[0])
|
||||
action_plan = cc.action_plan.update(action_plan_uuid, patch)
|
||||
_print_action_plan_show(action_plan)
|
||||
else:
|
||||
raise ValidationError()
|
||||
class UpdateActionPlan(command.ShowOne):
|
||||
"""Update action plan command."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(UpdateActionPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'action_plan',
|
||||
metavar='<action-plan>',
|
||||
help=_("UUID of the action_plan."))
|
||||
parser.add_argument(
|
||||
'op',
|
||||
metavar='<op>',
|
||||
choices=['add', 'replace', 'remove'],
|
||||
help=_("Operation: 'add', 'replace', or 'remove'."))
|
||||
parser.add_argument(
|
||||
'attributes',
|
||||
metavar='<path=value>',
|
||||
nargs='+',
|
||||
action='append',
|
||||
default=[],
|
||||
help=_("Attribute to add, replace, or remove. Can be specified "
|
||||
"multiple times. For 'remove', only <path> is necessary."))
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
if not uuidutils.is_uuid_like(parsed_args.action_plan):
|
||||
raise exceptions.ValidationError()
|
||||
|
||||
patch = common_utils.args_array_to_patch(
|
||||
parsed_args.op, parsed_args.attributes[0])
|
||||
|
||||
action_plan = client.action_plan.update(parsed_args.action_plan, patch)
|
||||
|
||||
columns = res_fields.ACTION_PLAN_FIELDS
|
||||
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
|
||||
|
||||
return column_headers, utils.get_item_properties(action_plan, columns)
|
||||
|
||||
|
||||
@cliutils.arg('action-plan',
|
||||
metavar='<action-plan>',
|
||||
help="UUID of the action plan.")
|
||||
def do_action_plan_start(cc, args):
|
||||
"""Execute an action plan."""
|
||||
action_plan_uuid = getattr(args, 'action-plan')
|
||||
if uuidutils.is_uuid_like(action_plan_uuid):
|
||||
args.op = 'replace'
|
||||
args.attributes = [['state=STARTING']]
|
||||
class StartActionPlan(command.ShowOne):
|
||||
"""Start action plan command."""
|
||||
|
||||
patch = utils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(StartActionPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'action_plan',
|
||||
metavar='<action-plan>',
|
||||
help=_("UUID of the action_plan."))
|
||||
|
||||
action_plan = cc.action_plan.update(action_plan_uuid, patch)
|
||||
_print_action_plan_show(action_plan)
|
||||
else:
|
||||
raise ValidationError()
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
if not uuidutils.is_uuid_like(parsed_args.action_plan):
|
||||
raise exceptions.ValidationError()
|
||||
|
||||
action_plan = client.action_plan.start(parsed_args.action_plan)
|
||||
|
||||
columns = res_fields.ACTION_PLAN_FIELDS
|
||||
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
|
||||
|
||||
return column_headers, utils.get_item_properties(action_plan, columns)
|
||||
|
||||
|
||||
class DeleteActionPlan(command.Command):
|
||||
"""Delete action plan command."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeleteActionPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'action_plans',
|
||||
metavar='<action-plan>',
|
||||
nargs='+',
|
||||
help=_('UUID of the action plan'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
for action_plan in parsed_args.action_plans:
|
||||
if not uuidutils.is_uuid_like(action_plan):
|
||||
raise exceptions.ValidationError()
|
||||
|
||||
client.action_plan.delete(action_plan)
|
||||
|
||||
|
||||
class CancelActionPlan(command.ShowOne):
|
||||
"""Cancel action plan command."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CancelActionPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'action_plan',
|
||||
metavar='<action-plan>',
|
||||
help=_("UUID of the action_plan."))
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = getattr(self.app.client_manager, "infra-optim")
|
||||
|
||||
if not uuidutils.is_uuid_like(parsed_args.action_plan):
|
||||
raise exceptions.ValidationError()
|
||||
|
||||
action_plan = client.action_plan.cancel(parsed_args.action_plan)
|
||||
|
||||
columns = res_fields.ACTION_PLAN_FIELDS
|
||||
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
|
||||
|
||||
return column_headers, utils.get_item_properties(action_plan, columns)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user