Compare commits

...

148 Commits

Author SHA1 Message Date
Zuul
6a33b44d7f Merge "Remove install unnecessary packages" 2020-10-14 01:52:37 +00:00
maaoyu
41feafc420 Remove install unnecessary packages
The docs requirements migrated to doc/requirements.txt
we need not install things from requirements.txt.

Change-Id: I31972595411d814700b6b51113827f438085170a
2020-09-24 17:28:12 +08:00
e1bcc5f758 Add Python3 wallaby unit tests
This is an automatically generated patch to ensure unit testing
is in place for all the of the tested runtimes for wallaby.

See also the PTI in governance [1].

[1]: https://governance.openstack.org/tc/reference/project-testing-interface.html

Change-Id: Iae7748124e2ab374637bc7a30a77433b5981e8b5
2020-09-09 17:40:22 +00:00
Ghanshyam Mann
80f728b14f [goal] Migrate testing to ubuntu focal
As per victoria cycle testing runtime and community goal[1]
we need to migrate upstream CI/CD to Ubuntu Focal(20.04).

Fixing:
- bug#1886298
Bump the lower constraints for required deps which added python3.8 support
in their later version.

Story: #2007865
Task: #40227

Closes-Bug: #1886298

[1] https://governance.openstack.org/tc/goals/selected/victoria/migrate-ci-cd-jobs-to-ubuntu-focal

Change-Id: I56837a6877a22020048b095b9eb528700c786c66
2020-08-16 23:39:29 +00:00
licanwei
2e97950670 remove mox3
Change-Id: I4a1de074db42854871a43ba49dbaa9dc2dfc6621
2020-06-19 09:52:37 +08:00
Andreas Jaeger
21b106dc15 Switch to newer openstackdocstheme version
Switch to openstackdocstheme 2.2.0. Using
the version will allow especially:
* Linking from HTML to PDF document
* Allow parallel building of documents
* Fix some rendering problems

Update Sphinx version as well.

Set openstackdocs_pdf_link to link to PDF file. Note that
the link to the published document only works on docs.openstack.org
where the PDF file is placed in the top-level html directory. The
site-preview places the PDF in a pdf directory.

Remove docs requirements from lower-constraints, they are not needed
during install or test but only for docs building.

openstackdocstheme renames some variables, so follow the renames
before the next release removes them. A couple of variables are also
not needed anymore, remove them.

Change pygments_style to 'native' since old theme version always used
'native' and the theme now respects the setting and using 'sphinx' can
lead to some strange rendering.

See also
http://lists.openstack.org/pipermail/openstack-discuss/2020-May/014971.html

Change-Id: I58edf52ff891328b14edecc9e6e990b34cb730b7
2020-05-21 09:31:12 +02:00
Ghanshyam Mann
7213f7195e Fix hacking min version to 3.0.1
flake8 new release 3.8.0 added new checks and gate pep8
job start failing. hacking 3.0.1 fix the pinning of flake8 to
avoid bringing in a new version with new checks.

Though it is fixed in latest hacking but 2.0 and 3.0 has cap for
flake8 as <4.0.0 which mean flake8 new version 3.9.0 can also
break the pep8 job if new check are added.

To avoid similar gate break in future, we need to bump the hacking min
version.

- http://lists.openstack.org/pipermail/openstack-discuss/2020-May/014828.html

Change-Id: I794bcf026f8c30d81c84b8c26e8b982b0e7b5b17
2020-05-12 21:35:48 -05:00
Zuul
21f42884f1 Merge "Use unittest.mock instead of third party mock" 2020-05-07 06:59:31 +00:00
Zuul
4c57d61f8d Merge "Remove future imports" 2020-05-07 06:58:34 +00:00
qiufossen
e05b498a5f Remove Babel requirement
Babel is not needed as requirement, remove it.

See also
http://lists.openstack.org/pipermail/openstack-discuss/2020-April/014227.html

Change-Id: I3cec266e4326b4f0749d70f6733023c73ca08d80
2020-05-07 10:15:07 +08:00
zhangbailin
8d3ce6c58d Remove future imports
These particular imports are no longer needed in a Python 3-only world.

Change-Id: Ifecdc3c35820977ad561cd2d78471d14b94ee65a
2020-04-30 09:58:27 +00:00
Zuul
bb7af48350 Merge "Bump default tox env from py37 to py38" 2020-04-30 03:10:21 +00:00
jacky06
92c15fa38a Use unittest.mock instead of third party mock
Now that we no longer support py27, we can use the standard library
unittest.mock module instead of the third party mock lib.

Change-Id: Idac937dd704ef11dfc33e197f7539e3f7a5feb92
2020-04-29 23:50:12 +08:00
Sean McGinnis
d3cc035ff7 Bump default tox env from py37 to py38
Python 3.8 is now our highest level supported python runtime.
This updates the default tox target environments to swap out
py37 for py38 to make sure local development testing is
covering this version.

This does not impact zuul jobs in any way, nor prevent local
tests against py37. It just changes the default if none is
explicitly provided.

Change-Id: Iac6ba63153af308cd82f3d3a303ce4c3fead0a5f
Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
2020-04-24 10:25:59 -05:00
Sean McGinnis
fb22d3e1f8 Add py38 package metadata
Now that we are running the Victoria tests that include a
voting py38, we can now add the Python 3.8 metadata to the
package information to reflect that support.

Change-Id: Ia0db684e1c03231a93f71cab49cbbd4f23768dda
Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
2020-04-24 08:23:19 -05:00
ac0ccb67a6 Add Python3 victoria unit tests
This is an automatically generated patch to ensure unit testing
is in place for all the of the tested runtimes for victoria.

See also the PTI in governance [1].

[1]: https://governance.openstack.org/tc/reference/project-testing-interface.html

Change-Id: I0ec7925e847386f9ea8e2976c0f99dc03904a922
2020-04-11 18:49:26 +00:00
Andreas Jaeger
5946fbb5b1 Cleanup py27 support
Make a few cleanups:
- Remove python 2.7 stanza from setup.py
- Add requires on python >= 3.6 to setup.cfg so that pypi and pip
  know about the requirement
- Remove obsolete sections from setup.cfg: Wheel is not needed for python
  3 only repo
- Update requirements, no need for python_version anymore
- Use modern sphinx-build for docs building, update openstackdocstheme,
  fix api-doc building.

Change-Id: I40b67f330ac850bf45cca742a8b967fd12480ce3
2020-04-04 14:27:51 +02:00
Andreas Jaeger
2830afdc75 Update hacking for Python3
The repo is Python 3 now, so update hacking to version 3.0 which
supports Python 3.

Fix problems found.

Change-Id: I6bd9fdee0ee275e4d9d3fcaf96c1512933ce35a6
2020-03-31 13:29:10 +02:00
Zuul
1912b9aa82 Merge "Support audit type event" 2020-01-10 01:56:48 +00:00
licanwei
1f9194ced5 Support audit type event
Add a new EVENT type to audit type and change the microversion
to 1.4

Partially Implements: blueprint event-driven-optimization-based

Change-Id: Ia9608534d12f6877f9b93e88fa75fe9457c29347
2020-01-07 15:57:11 +08:00
Zuul
9387369cd7 Merge "Drop python 2.7 support and testing" 2019-12-16 02:42:17 +00:00
licanwei
431319ee4d Watcherclient supports list data model API
Change-Id: I5527414b5e87253d87a4a26398ad5dcce1193e43
Related-Bug: #1854121
2019-12-02 14:41:32 +08:00
Ghanshyam Mann
e3c903cfce Drop python 2.7 support and testing
OpenStack is dropping the py2.7 support in ussuri cycle.

python-watcherclient is ready with python 3 and ok to drop the
python 2.7 support.

Complete discussion & schedule can be found in
- http://lists.openstack.org/pipermail/openstack-discuss/2019-October/010142.html
- https://etherpad.openstack.org/p/drop-python2-support

Ussuri Communtiy-wide goal - https://review.opendev.org/#/c/691178/

Change-Id: Id66df6c12e7b533381a354937e23e33f992adbfa
2019-10-30 04:34:43 +00:00
Zuul
e3f0a3d2b5 Merge "Fix details doc format error" 2019-10-26 07:23:01 +00:00
chenke
88351df1c2 Fix details doc format error
This error was caused because of rst format.

pls see:
https://github.com/openstack/python-watcherclient/blob/master/doc/source/cli/details.rst

This patch solved this error.

Change-Id: I756d2f6327349c36e4523513a2ac84ad91f7785f
2019-10-26 09:55:34 +08:00
Zuul
df3ec7849e Merge "Switch to Ussuri jobs" 2019-10-25 03:43:15 +00:00
Eric Fried
0c7e306642 Fix python-openstackclient plugin doc build
A help string in a plugin command argument help string contained (a typo
and) some invalid sphinx-isms causing autoprogram-cliff building from
python-openstackclient to fail with:

.../python-openstackclient/doc/source/cli/plugin-commands/watcher.rst:30:Inline emphasis start-string without end-string.
and
<CreateAuditTemplate>:1:Unexpected indentation.

This commit fixes the (typo and) help string formatting.

Change-Id: Ifb668b9ada94b679672f3bab562410bf12f4ca64
2019-10-24 01:34:12 +00:00
kangyufei
db70063cee Switch to Ussuri jobs
Change-Id: I13ae604be95e8b8ddd81a957900199a1113fc3f2
2019-10-22 15:13:55 +08:00
licanwei
1d1f28a256 Build pdf docs
Add a new pdf-docs environment to enable PDF build.

Change-Id: I59bdc7cf339b022458b3b0d1fa3b694b7998c20f
2019-09-17 00:20:25 -07:00
Zuul
62fdb73dae Merge "Implement watcher datamodel list in watcherclient" 2019-09-09 02:30:52 +00:00
chenke
297ca1dfc5 Implement watcher datamodel list in watcherclient
1. Add data_model.py and data_model_shell.py for accept and
process request.

2. Add relative unittest.

Partically Implements: blueprint show-datamodel-api

Change-Id: I5e080453acaedf7e20734d67922b64511dd5f7fd
2019-08-28 09:45:03 +08:00
chenke
a8f919e1f7 Add datamodel doc in watcherclient
Change-Id: I319d47dc66cb10e5ba5b6e0e318ecff0438b2d05
2019-08-26 20:28:47 +08:00
licanwei
ea74a22b49 Fix _make_connection_url
After support uWSGI[1], Watcher endpoint changed from http://ip:port
to http://ip/infra-optim. method _make_connection_url in HTTPClient
class can't return the correct url. This patch fixed it.
[1]: https://review.opendev.org/#/c/666779/

Change-Id: I77e610cd6781b252327a49aa5cd5bc63d395bf91
Closes-Bug: #1837186
2019-07-19 17:17:19 +08:00
licanwei
3caa385548 Add Python3 Train unit tests
This is one goal of Train Cycle.
See the Train python3-updates goal document for details:
https://governance.openstack.org/tc/goals/train/python3-updates.html

Change-Id: Id5a150cbfa658383f6ae7958a06ebe3a4f9047e6
2019-07-03 11:18:27 +08:00
chenke
0e9c827c76 Switch to the new canonical constraints URL on master
Reference:
1. http://lists.openstack.org/pipermail/openstack-discuss/2019-May/006478.html
2. https://review.opendev.org/#/c/601188

Change-Id: If04d918774d0254c7d386f80c5e8b1ce84b7eac1
2019-06-14 19:55:06 +08:00
licanwei
dbbe7df1e2 Remove unused readme file
Change-Id: I87c6e6ce1bb6e253014d844a3a67b51cd6b11135
2019-05-30 15:34:32 +08:00
Zuul
be479feef9 Merge "Add force option" 2019-05-30 02:06:14 +00:00
Zuul
02583f7469 Merge "remove unused cliutils.py file as osc_lib is used" 2019-05-25 06:22:08 +00:00
Dantali0n
1aebe93477 remove unused cliutils.py file as osc_lib is used
Removes old cliutils.py file that contains methods provided by osc_lib
and remove prettytable dependency that was only required by cliutils.py

Depends-on: I8bc3330a456f8bb76a17c4277a094a64fe9be449
Change-Id: Ib9eedafb211c205ff8b7dd18a9fc87e1f0b2739f
2019-05-23 09:34:30 +02:00
licanwei
cde518be31 Add force option
Partially Implements: blueprint add-force-field-to-audit
Depends-on: Ib2d221ea9c994dea396c54cc8d2d32237025a1d4

Change-Id: I235bbd298b7fa8ad9d8b01a94d68376c8ac27e8d
2019-05-23 07:17:00 +00:00
Matt Riedemann
573ca6ed59 Cleanup doc dependencies
This creates a dedicated doc/requirements.txt for doc-only
dependencies like sphinx packages so we can decouple those
from the test-requirements.txt.

As a result, the missing oslo.serialization runtime dependency
is added (I used the same version that python-novaclient uses).

Due to change If558f184c959e4b63b56dec3ca1571d1034cfe5c in the
openstack/requirements repo, in order to pass the
requirements-check job we have to specify both the py27 and py3
versions for sphinx.

Change-Id: I8bc3330a456f8bb76a17c4277a094a64fe9be449
2019-05-13 13:35:25 -04:00
Matt Riedemann
f062e60990 Remove --test-path option from stestr run
The .stestr.conf already defines the test path
so we don't need to specify it on the command line.

Change-Id: I66892fa0b17895207307bc15b5ef8b322a7a010e
2019-05-11 18:54:37 -04:00
Matt Riedemann
c0b2604e9b Whitelist rm external usage in tox.ini
This cleans up the error message from tox:

WARNING: test command found but not installed in testenv
  cmd: /bin/rm
  env: /home/osboxes/git/python-watcherclient/.tox/py27
Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting.

DEPRECATION WARNING: this will be an error in tox 4 and above!

Change-Id: I854676b070720df496cb6c66f2fd1a92d232fa11
2019-05-11 18:53:00 -04:00
Matt Riedemann
2cab116e30 Use upper-constraints in tox runs
The upper-constraints file wasn't being enforced
when installing dependencies in tox runs which can
lead to failures like installing Sphinx>2.0 in py27
where it's not supported.

This is loosely based on novaclient change
I8be883215f27abb58d15b85e8542cbdf32000bac for the
same kind of issue.

Note there are other things that should be done
in here regarding requirements cleanup, like the
sphinx dependnecies should move to a
doc/requirements.txt file to separate them from
unit test runs, but that can be dealt with in a
follow up change.

Change-Id: I359a35af5b653299110a8d0faddb255da0b10118
Closes-Bug: #1828698
2019-05-11 18:45:44 -04:00
Zuul
bb5586b477 Merge "Add tempest voting" 2019-05-07 05:49:36 +00:00
licanwei
c30f095481 Add tempest voting
Change-Id: Ic104274f445869ebc18090f9adc17b94261822db
2019-04-30 16:20:01 +08:00
jacky06
ffb71506aa Replace git.openstack.org URLs with opendev.org URLs
Change-Id: Ib764e351a42de92b532f4c0b4ff51758bb47b6a2
2019-04-28 09:36:25 +00:00
Zuul
32a8799516 Merge "Remove py35" 2019-04-20 04:02:58 +00:00
OpenDev Sysadmins
b52df6899d OpenDev Migration Patch
This commit was bulk generated and pushed by the OpenDev sysadmins
as a part of the Git hosting and code review systems migration
detailed in these mailing list posts:

http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html
http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html

Attempts have been made to correct repository namespaces and
hostnames based on simple pattern matching, but it's possible some
were updated incorrectly or missed entirely. Please reach out to us
via the contact information listed at https://opendev.org/ with any
questions you may have.
2019-04-19 19:45:00 +00:00
licanwei
103bb38475 Remove py35
We just need python3.6 and python3.7

https://governance.openstack.org/tc/reference/runtimes/train.html

Change-Id: Id5d58a3b0973bd64b4655ebc8b85ec550bb39d4e
2019-04-16 16:41:41 +08:00
Zuul
76a01b0c7c Merge "Add marker option for strategy in watcher-client" 2019-03-22 02:05:44 +00:00
Zuul
939371d180 Merge "Increase actionplan_shell.py unittest coverage from 70% to 97%" 2019-03-21 03:11:45 +00:00
Zuul
c62757b093 Merge "Update .gitignore to ignore cover and .coverage*" 2019-03-20 01:47:32 +00:00
chenke
e2082907ff Increase actionplan_shell.py unittest coverage from 70% to 97%
Change-Id: Iaca67b517c2caf4938dbf861bee3add0a41c18a2
2019-03-19 16:10:25 +08:00
Zuul
16ff74f54c Merge "Add marker option for goal" 2019-03-19 03:42:33 +00:00
Zuul
de9b8d124b Merge "Increase the unit test coverage of action_plan.py from 79% to 95%" 2019-03-19 03:42:32 +00:00
chenke
a25cd66052 Update .gitignore to ignore cover and .coverage*
Change-Id: I8856741461611aac45a644e62470846824409219
2019-03-18 11:39:15 +08:00
chenke
9b1e234e36 Increase the unit test coverage of action_plan.py from 79% to 95%
Change-Id: Ie33b00d690746609663492c63b8f778048a689f2
2019-03-18 11:19:57 +08:00
licanwei
512e43eb32 Remove functional in tox.ini
Functional code was removed to watcher-tempest-plugin

Change-Id: I279e256940c44d3643f1cdd3000050674bcd6e95
2019-03-18 10:45:44 +08:00
chenke
d0f2f10f5d Add marker option for strategy in watcher-client
Change-Id: I40ab57889c5402fe5b69d28a3372e7a0a14592f8
2019-03-18 01:12:17 +00:00
zhurong
8342e64051 Remove the functional test
Depends-On: https://review.openstack.org/643247
Change-Id: I5723575d44dccf81779959c1cae43e2cab2abc9b
2019-03-15 03:50:20 +00:00
chenke
2c29c49f24 Add marker option for goal
Change-Id: I27f06019e731633349389781545fe8c3c036d723
2019-03-13 21:23:36 +08:00
Zuul
a70ebd8a41 Merge "Add openstack-cover-jobs for watcherclient" 2019-03-13 06:21:14 +00:00
chenke
4db175f342 Add openstack-cover-jobs for watcherclient
Change-Id: Idc5e67bc5bf61bd02147e52872a9fcf7d71eeae4
2019-03-12 14:05:46 +08:00
ZhongShengping
775199cb42 add python 3.7 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.7.

See ML discussion here [1] for context.

[1] http://lists.openstack.org/pipermail/openstack-dev/2018-October/135626.html

Change-Id: I9cd0ea328ae6e88dc17c4a1c447e3d42d07bef3d
Story: #2004073
Task: #27462
2019-02-19 17:06:05 +08:00
Zuul
9d9c5fe3be Merge "DEFAULT_VER should be '1.latest'" 2019-02-01 09:30:20 +00:00
licanwei
e4ee4ff03c DEFAULT_VER should be '1.latest'
Change-Id: I359838db9ee8427812215833f379a84463c8dbfb
2019-01-26 14:10:20 +08:00
licanwei
ca6190c3a1 Use openstack common CLI
Change-Id: Ia013cafe7dfa3a54c86ff44e57f98d64b15b6a86
2019-01-25 09:40:38 +08:00
Zuul
74cbf1b17b Merge "[Doc] Fix OS_AUTH_URL value" 2019-01-24 06:59:15 +00:00
ZhijunWei
4a1b6eaf81 Update hacking version
1. update the hacking to latest, due to[1]
2. fix pep8 failed

[1]: https://github.com/openstack/python-watcherclient/blob/master/HACKING.rst

Change-Id: I02891ca17e2655b9567cc56b5c2c0b4fb6a96446
2019-01-17 14:20:33 +00:00
Zuul
1186c7c34f Merge "Add deletion of actionplan to osc plugin" 2019-01-14 11:38:15 +00:00
Alexander Chadin
02498f85f6 Add deletion of actionplan to osc plugin
Change-Id: I3674f6945adb9a7e9b1ffa9e8ea81ddeaa893c67
2019-01-14 13:26:10 +03:00
Zuul
9d1d5e2cae Merge "Pass API microversion to Client class" 2019-01-14 08:57:24 +00:00
Hidekazu Nakamura
b0ac926ee6 [Doc] Fix OS_AUTH_URL value
Since Keystone v2 was removed in Queens release, this patch set
updates keystone version from v2.0 to v3 in OS_AUTH_URL value.

Change-Id: I77e8b1519ea8241e3c5cdeaffd9be9046b6315ce
2019-01-09 14:01:59 +09:00
licanwei
3e734b177d update audit start/end time help
Change-Id: I47232f671eacd9a6b8923d4ee588c4413f65d015
2018-12-24 13:55:09 +08:00
Zuul
2cfe7f9afc Merge "Add Python 3.6 classifier to setup.cfg" 2018-12-20 10:40:28 +00:00
Alexander Chadin
df57eb800d Pass API microversion to Client class
Change-Id: If6ac21330cb6c8303f62eec6ee5082f523ff73a1
2018-12-17 17:30:01 +03:00
Zuul
abea1a8815 Merge "Change openstack-dev to openstack-discuss" 2018-12-14 14:08:40 +00:00
Zuul
ab22110f7b Merge "Revert "Pass actual API version to Client class"" 2018-12-14 12:13:20 +00:00
melissaml
ec7e8ca803 Change openstack-dev to openstack-discuss
Mailinglists have been updated. Openstack-discuss replaces openstack-dev.

Change-Id: I141558842442c165ea662ddd5a85914faef8ddb8
2018-12-14 20:09:07 +08:00
Alexander Chadin
bdd99d60f3 Revert "Pass actual API version to Client class"
This reverts commit e391d53c2f.

Change-Id: I87c18b2e0d2b291f1de446cc6d8fc39a63fba589
2018-12-14 10:47:49 +00:00
Zuul
25ff2811d5 Merge "add python 3.6 unit test job" 2018-12-10 09:53:15 +00:00
Zuul
8ce78f8c1f Merge "Fix obsolete version of osc" 2018-12-07 11:21:16 +00:00
Alexander Chadin
ffc50c5001 Fix obsolete version of osc
This patch set adds a link to actual API version for OSC.

Closes-Bug: #1807180
Change-Id: I622bfec99c2e6fd9f69293aaa04c3184215af0d3
2018-12-06 18:03:30 +03:00
Zuul
6c66276040 Merge "Add continuous audit functional test" 2018-12-06 14:43:31 +00:00
98k
c256ee2a21 Change openstack-dev to openstack-discuss
Mailinglists have been updated. Openstack-discuss replaces openstack-dev.

Change-Id: Icc4b0796a8944cc2f59779e6eab242e55cd00114
2018-12-04 04:13:18 +00:00
qingszhao
7abd55a823 Add Python 3.6 classifier to setup.cfg
Change-Id: Ia42baeea21d624437dc2fbe4769a2857ab460d38
2018-11-30 07:41:11 +00:00
licanwei
98e09044c1 Add continuous audit functional test
Change-Id: Ibee323f7c894ae9ead2ea872561e0e7e79320017
2018-11-30 10:11:33 +08:00
Vieri
6c8dfd366d add python 3.6 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.6 as part of the python3-first goal.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: I554919fa8eccd8bb9946b80415903e0bb128e85e
Story: #2002586
2018-11-28 08:12:25 +00:00
Zuul
112e372239 Merge "Pass actual API version to Client class" 2018-11-20 02:00:44 +00:00
Zuul
c5b4db7fc1 Merge "Refactor the getid method base.py" 2018-11-19 09:01:44 +00:00
Zuul
7c6b39eeba Merge "Replace latest with explicit version" 2018-11-19 08:59:01 +00:00
wanghui
94f6864bf0 Refactor the getid method base.py
Refer to a merged commit.
https://review.openstack.org/#/c/588983/

Change-Id: I9765543c288370cfcdaaa930b2839e28b51a9380
2018-11-09 20:11:16 +08:00
Alexander Chadin
e391d53c2f Pass actual API version to Client class
Change-Id: Ie5f563c6982239ad7a99af06f704e2d931d164be
2018-11-08 17:39:22 +03:00
licanwei
b7928eb155 Replace latest with explicit version
Change-Id: I8aae4595f7ca9dae7ed36705b1311070a531523f
Closes-Bug: #1802217
2018-11-08 16:59:38 +08:00
huang.zhiping
7b7d07fac1 Update min tox version to 2.0
The commands used by constraints need at least tox 2.0.  Update to
reflect reality, which should help with local running of constraints
targets.

Change-Id: Ie378b24c559c04f235890f7eaa2ba82161b0604d
Closes-Bug:  #1801660
2018-11-07 11:53:12 +00:00
Hidekazu Nakamura
946ef06dc6 Update watcher api command argument
This patch set updates watcher api command argument.
1. os_watcher_api_version to os_infra_optim_api_version
2. OS_WATCHER_API_VERSION to OS_INFRA_OPTIM_API_VERSION

This patch set also updated --os-infra-optim-api-version help message.

Change-Id: I2502377b350b8cabfeba80c68f0569d567d24b3a
2018-11-05 13:07:00 +09:00
licanwei
69f0493968 add start and end time for continuous audit
Add new start_time and end_time fields when audit create

Partially Implements: blueprint add-start-end-time-for-continuous-audit

Change-Id: I37d1bd13649dfb92ce7526f04b25054ed088c4f2
2018-11-01 12:01:07 +08:00
Zuul
8c89b3327b Merge "Prepare watcherclient for microversioning" 2018-10-31 12:42:22 +00:00
Alexander Chadin
2e9de6868b Prepare watcherclient for microversioning
Implements: blueprint bp/api-microversioning

Change-Id: I049f9aac84a5658efaccc9e9fdf6c9d4be677d29
2018-10-18 11:29:30 +03:00
Alexander Chadin
db7a5e4bc9 Allow CLI to pass goal and strategy names
This patch set removes additional type verification
for goal and strategy entities to let CLI pass goal
and strategy names straight to API.

Depends-On: I89a9c7661616f49639151869055d8f5ebe723d5f
Change-Id: I323a66dd5bdef6f33d5fd5c60c99e1b4dcd7a7e0
Closes-Bug: #1794233
2018-10-02 13:58:48 +03:00
Andreas Jaeger
825e5cb081 Use templates lower-constraints, remove cover
Use openstack-lower-constraints-jobs template

Remove jobs that are part of the templates.

Remove cover job, this one is not working at all and always fails. If
anybody wants to enable it again, they can add the openstack-cover-jobs
template and fix it - I couldn't find an easy way to fix it without
knowledge of this repo.

Change-Id: I3b50c8384507d22339ed11f556dbe32181fe0518
2018-09-08 15:40:46 +02:00
Zuul
a8185c7067 Merge "add python 3.6 unit test job" 2018-09-03 10:26:54 +00:00
Zuul
4a0b0e76b4 Merge "switch documentation job to new PTI" 2018-09-03 10:26:53 +00:00
Zuul
1918d3f26b Merge "import zuul job settings from project-config" 2018-08-28 07:52:55 +00:00
Zuul
2ca6b6b421 Merge "Switch to stestr" 2018-08-28 02:42:24 +00:00
zhangyangyang
1086b23454 Switch to stestr
According to Openstack summit session [1],
stestr is maintained project to which all Openstack projects should migrate.
Let's switch to stestr as other projects have already moved to it.

[1] https://etherpad.openstack.org/p/YVR-python-pti

Change-Id: Ie5133b8e627f762402df38969aee15f48d192520
2018-08-20 11:08:43 +08:00
Nguyen Hai
535b9da7d2 add python 3.6 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.6 as part of the python3-first goal.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: I5f1f5655c5a4ea9dfbfba1397ad0207fbdf53c58
Story: #2002586
Task: #24344
2018-08-19 00:58:24 +09:00
Nguyen Hai
34a4ad3041 switch documentation job to new PTI
This is a mechanically generated patch to switch the documentation
jobs to use the new PTI versions of the jobs as part of the
python3-first goal.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: Ia23c18578c4fa2854ed706a96b79f700bd751c43
Story: #2002586
Task: #24344
2018-08-19 00:58:23 +09:00
Nguyen Hai
39d4c1e944 import zuul job settings from project-config
This is a mechanically generated patch to complete step 1 of moving
the zuul job settings out of project-config and into each project
repository.

Because there will be a separate patch on each branch, the branch
specifiers for branch-specific jobs have been removed.

Because this patch is generated by a script, there may be some
cosmetic changes to the layout of the YAML file(s) as the contents are
normalized.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: I05e014be3cdc08d35d3a1efef6fae418bd4511c8
Story: #2002586
Task: #24344
2018-08-19 00:57:31 +09:00
Alexander Chadin
c1fd569b76 fix watcher actionplan show command
This patch set is intended for Python 3.x support and replace
itervalues() method with values().

Change-Id: I6852239d2d6634a4304c87a1694e64479f560de1
Closes-Bug: #1786784
2018-08-13 17:31:02 +03:00
Zuul
eed22ea2bb Merge "Add audit name to CLI help doc" 2018-08-03 08:03:15 +00:00
Zuul
47241e0462 Merge "Add --marker help info" 2018-08-03 01:47:22 +00:00
Zuul
88d122a21f Merge "Update watcher strategy state in CLI" 2018-08-03 01:46:51 +00:00
Yumeng Bao
5e928b80be Update watcher strategy state in CLI
Change-Id: Ie77336207c9affcf2c9a0ef0247cf5adef38c87f
2018-08-02 05:40:55 +00:00
Yumeng Bao
723ad12afa Add --marker help info
Change-Id: Ia132e45d7f9665a386d1e40360a75c4234aecc7d
2018-08-02 11:12:33 +08:00
chenke
3926dcc541 Organize the use of 'log' and replace 'log' with LOG
Change-Id: I42ddf63dd73559e43efce401f3912c4b9413a6d4
2018-07-31 11:25:25 +08:00
Zuul
c557e45ae2 Merge "Add --marker help info" 2018-07-30 07:34:55 +00:00
Zuul
0b8dba2cb9 Merge "Fix watherclient error in py3" 2018-07-27 09:27:56 +00:00
licanwei
496f4c1365 Add audit name to CLI help doc
Change-Id: I9749bb679d66a65de3d4abe3564109bf4a8f8ccd
2018-07-26 18:36:09 -07:00
Zuul
6681724daa Merge "Add hostname field to support HA" 2018-07-26 13:45:54 +00:00
zhurong
2551ff0934 Fix watherclient error in py3
watchercliet will raise error:
Recoverable error: sequence item 0: expected str instance, bytes found

This patch fix this.

Change-Id: I6fe21766f320b0a09a14202203a5d0451175e1d3
2018-07-26 11:37:35 +08:00
licanwei
adc812746c Add --marker help info
Change-Id: I34c1a83f49b101d5ef85e1a19858b9d2b59c921d
2018-07-22 23:20:29 -07:00
Alexander Chadin
8d62995cf4 Add hostname field to support HA
Change-Id: Ic099070f93edbea738e58f7686d10f354df69532
2018-07-17 18:01:39 +03:00
Dao Cong Tien
d82be4c8c4 Invalid doc for Client exceptions
There is no BaseException any more.

Change-Id: I9bf4613fa27cfc6d62a466afe703d907e8aa737a
2018-07-11 14:54:17 +07:00
Andreas Jaeger
152804168d Don't run voting jobs in gate
Non-voting jobs should not be in gate queue at all, remove them.
They are wasting needlessly our resources since they get ignored.

Mark the job explictely as non-voting, since another change will make
the job voting by default - which allows to make it easily voting here.

See also https://review.openstack.org/580110

Change-Id: I73fd45772ba9c793dad3221c852af4e9606362e1
2018-07-04 11:46:11 +02:00
Doug Hellmann
4a4add3dcc fix tox python3 overrides
We want to default to running all tox environments under python 3, so
set the basepython value in each environment.

We do not want to specify a minor version number, because we do not
want to have to update the file every time we upgrade python.

We do not want to set the override once in testenv, because that
breaks the more specific versions used in default environments like
py35 and py36.

Change-Id: Ie9d1d45811450e3feadd0dd3d027d3a20370f206
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-06-06 17:58:20 -04:00
Zuul
db012f1f8f Merge "Modify Watcher start actionplan command" 2018-05-24 08:08:21 +00:00
deepak_mourya
a4c020c19f Modify Watcher start actionplan command
Currently, watcher start actionplan command uses update request
to start the action plan.
PATCH /v1/action_plans

This Patch Set will send the request to new API for action plan start
POST /v1/action_plans/action_plan_uuid/start

Depends-on: I5353e4aa58d1675d8afb94bea35d9b953514129a
Change-Id: I15291efa994daf719342d764f6179ca7699d8b08
Related-Bug: #1756274
2018-05-08 15:40:27 +05:30
rajat29
8ef72f18c4 Remove 'actionplan create' command
Actionplan create command can create new
audit which looks ambiguous as audit should only
be created by audit command.

Change-Id: I6d8116b4520be5cbb4289de7caf9cc99251e1f9f
Closes-bug: #1764654
2018-04-26 10:26:09 +05:30
Zuul
673905fead Merge "Update links in README" 2018-04-02 08:09:51 +00:00
Zuul
d886672fb5 Merge "add lower-constraints job" 2018-03-29 10:01:43 +00:00
Alexander Chadin
83ccfbb177 [WiP] functional jobs fix
Change-Id: If92660ad48b19fce47dd7a88ed33796427f70a80
2018-03-28 09:11:18 +00:00
Zuul
4c47c7364c Merge "ZuulV3 support for watcherclient" 2018-03-27 16:48:01 +00:00
Alexander Chadin
8acf01859c ZuulV3 support for watcherclient
Change-Id: I0efc302dae72d18f86dbdae1d22a6ea798cfb8f5
2018-03-27 18:38:55 +03:00
Zuul
d4af8d8834 Merge "Delete the unnecessary '-'" 2018-03-26 09:30:30 +00:00
Doug Hellmann
40d8df21c8 add lower-constraints job
Create a tox environment for running the unit tests against the lower
bounds of the dependencies.

Create a lower-constraints.txt to be used to enforce the lower bounds
in those tests.

Add openstack-tox-lower-constraints job to the zuul configuration.

See http://lists.openstack.org/pipermail/openstack-dev/2018-March/128352.html
for more details.

Change-Id: I11c8526b413e6dfc6e2457026ccc2f35e38d1699
Depends-On: https://review.openstack.org/555034
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-03-25 11:32:15 -04:00
Zuul
b87399cb2c Merge "Add tempest plugin" 2018-03-23 12:56:05 +00:00
Alexander Chadin
9bfefbf9ab Add tempest plugin
This patch set adds tempest plugin to register existing tempest
tests of watcherclient. It is also required to let devstack-tempest
job to launch watcherclient functional tests.

Change-Id: I4d906fd597048bb37803a2bb42f8357502cf1b76
2018-03-23 14:36:38 +03:00
huang.zhiping
b84039d7d4 Delete the unnecessary '-'
Change-Id: I6f36a4114026368d854fac37dcd003d3f4236455
2018-03-23 11:29:03 +08:00
OpenStack Proposal Bot
f8ead12481 Updated from global requirements
Change-Id: I35f49cea5915272e21ac1351dfb190c2b55be335
2018-03-15 08:06:52 +00:00
OpenStack Proposal Bot
85e4e364b7 Updated from global requirements
Change-Id: I46b763854d736e74523106240e1faf06150a3139
2018-03-13 07:30:46 +00:00
melissaml
ed9ce5b952 Update links in README
Change the outdated links to the latest links in README

Change-Id: I0a0781d4a86bb860bb8c85923e7be8b803f91ae2
2018-03-11 03:01:04 +08:00
Zuul
731e019bc1 Merge "Fix global efficacy format" 2018-02-23 09:09:47 +00:00
OpenStack Proposal Bot
a22dab5f88 Updated from global requirements
Change-Id: Ib45b0cc6fefd94b3c3a8181bd10b0d85f8044800
2018-02-17 10:20:48 +00:00
Alexander Chadin
0d7233055d Fix global efficacy format
Closes-Bug: #1749463
Change-Id: I0496f0d82bbf72dbbc8b808feb3a529ad6d49402
2018-02-14 17:06:15 +03:00
Zuul
2777a9bba8 Merge "Zuul: Remove project name" 2018-02-07 08:23:00 +00:00
James E. Blair
e359488cde Zuul: Remove project name
Zuul no longer requires the project-name for in-repo configuration.
Omitting it makes forking or renaming projects easier.

Change-Id: I884a00879ac30b504833951303578de42174ba09
2018-02-02 10:11:34 -08:00
81 changed files with 1641 additions and 1590 deletions

11
.coveragerc Normal file
View File

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

9
.gitignore vendored
View File

@@ -23,11 +23,12 @@ lib64
pip-log.txt
# Unit test / coverage reports
.coverage
.coverage*
.tox
nosetests.xml
.testrepository
.stestr/
.venv
.testrepository/
# Translations
*.mo
@@ -56,6 +57,10 @@ ChangeLog
sftp-config.json
/.idea/
/cover/
# Desktop Service Store
*.DS_Store
# Atom
.remote-sync.json

View File

@@ -1,4 +1,4 @@
[gerrit]
host=review.openstack.org
host=review.opendev.org
port=29418
project=openstack/python-watcherclient.git

3
.stestr.conf Normal file
View File

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

View File

@@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \
${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./watcherclient/tests} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@@ -1,8 +1,11 @@
- project:
name: openstack/python-watcherclient
templates:
- openstack-cover-jobs
- openstack-lower-constraints-jobs
- openstack-python3-wallaby-jobs
- publish-openstack-docs-pti
- check-requirements
- openstackclient-plugin-jobs
check:
jobs:
- watcherclient-tempest-functional
gate:
jobs:
- watcherclient-tempest-functional

View File

@@ -2,8 +2,8 @@
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/python-watcherclient.svg
:target: https://governance.openstack.org/reference/tags/index.html
.. 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
@@ -20,11 +20,11 @@ metrics receiver, complex event processor and profiler, optimization processor
and an action plan applier. This provides a robust framework to realize a wide
range of cloud optimization goals, including the reduction of data center
operating costs, increased system performance via intelligent virtual machine
migration, increased energy efficiency-and more!
migration, increased energy efficiency and more!
* Free software: Apache license
* Wiki: https://wiki.openstack.org/wiki/Watcher
* Source: https://git.openstack.org/cgit/openstack/python-watcher
* Source: https://opendev.org/openstack/python-watcherclient
* Bugs: https://bugs.launchpad.net/watcher
Installation

View File

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

8
doc/requirements.txt Normal file
View 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

View File

@@ -136,7 +136,7 @@ watcher optional arguments
Defaults to ``env[OS_AUTH_TOKEN]``.
``--os-watcher-api-version <os-watcher-api-version>``
Defaults to ``env[OS_WATCHER_API_VERSION]``.
Defaults to ``env[OS_INFRA_OPTIM_API_VERSION]``.
``--os-endpoint-type OS_ENDPOINT_TYPE``
Defaults to ``env[OS_ENDPOINT_TYPE]`` or "publicURL"
@@ -157,7 +157,7 @@ watcher action list
[--quote {all,minimal,none,nonnumeric}]
[--action-plan <action-plan>] [--audit <audit>]
[--detail] [--limit <limit>] [--sort-key <field>]
[--sort-dir <direction>]
[--sort-dir <direction>] [--marker <marker>]
List information on retrieved actions.
@@ -186,6 +186,10 @@ List information on retrieved actions.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last action in the previous page; displays
list of actions after "marker".
.. _watcher_action_show:
watcher action show
@@ -295,7 +299,8 @@ watcher actionplan list
[--fit-width] [--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}]
[--audit <audit>] [--detail] [--limit <limit>]
[--sort-key <field>] [--sort-dir <direction>]
[--marker <actionplan>] [--sort-key <field>]
[--sort-dir <direction>]
List information on retrieved action plans.
@@ -315,6 +320,10 @@ List information on retrieved action plans.
0 for no limit. Default is the maximum number used by
the Watcher API Service.
``--marker <actionplan>``
The last actionplan UUID of the previous page;
displays list of actionplans after "marker".
``--sort-key <field>``
Action Plan field that will be used for sorting.
@@ -466,7 +475,7 @@ Delete audit command.
**Positional arguments:**
``<audit>``
UUID of the audit
UUID or name of the audit
**Optional arguments:**
@@ -532,7 +541,7 @@ Show detailed information about a given audit.
**Positional arguments:**
``<audit>``
UUID of the audit
UUID or name of the audit
**Optional arguments:**
@@ -556,7 +565,7 @@ Update audit command.
**Positional arguments:**
``<audit>``
UUID of the audit.
UUID or name of the audit.
``<op>``
Operation: 'add', 'replace', or 'remove'.
@@ -611,38 +620,43 @@ Create new audit template.
``--scope <path>``
Part of the cluster on which an audit will be done.
Can be provided either in yaml or json file.
YAML example:
---- host_aggregates:
- id: 1
- id: 2
- id: 3
YAML example:
::
---
- host_aggregates:
- id: 1
- id: 2
- id: 3
- availability_zones:
- name: AZ1
- name: AZ2
- name: AZ1
- name: AZ2
- exclude:
- instances:
- uuid: UUID1
- uuid: UUID2
- compute_nodes:
- name: compute1
JSON example:
[{'host_aggregates': [
{'id': 1},
{'id': 2},
{'id': 3}]},
{'availability_zones': [
{'name': 'AZ1'},
{'name': 'AZ2'}]},
{'exclude': [
{'instances': [
{'uuid': 'UUID1'},
{'uuid': 'UUID2'}
]},
{'compute_nodes': [
{'name': 'compute1'}
]}
]}]
- instances:
- uuid: UUID1
- uuid: UUID2
- compute_nodes:
- name: compute1
JSON example:
::
[
{'host_aggregates': [
{'id': 1},
{'id': 2},
{'id': 3}]},
{'availability_zones': [
{'name': 'AZ1'},
{'name': 'AZ2'}]},
{'exclude': [
{'instances': [
{'uuid': 'UUID1'},
{'uuid': 'UUID2'}]},
{'compute_nodes': [
{'name': 'compute1'}]}]}
]
.. _watcher_audittemplate_delete:
@@ -680,7 +694,7 @@ watcher audittemplate list
[--detail] [--goal <goal>]
[--strategy <strategy>] [--limit <limit>]
[--sort-key <field>]
[--sort-dir <direction>]
[--sort-dir <direction>][--marker <marker>]
List information on retrieved audit templates.
@@ -709,6 +723,10 @@ List information on retrieved audit templates.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last audittemplate in the previous page; displays
list of audittemplates after "marker".
.. _watcher_audittemplate_show:
watcher audittemplate show
@@ -781,7 +799,7 @@ watcher goal list
[--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}] [--detail]
[--limit <limit>] [--sort-key <field>]
[--sort-dir <direction>]
[--sort-dir <direction>][--marker <marker>]
List information on retrieved goals.
@@ -804,6 +822,10 @@ List information on retrieved goals.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last goal in the previous page; displays
list of goals after "marker".
.. _watcher_goal_show:
watcher goal show
@@ -841,7 +863,7 @@ watcher scoringengine list
[--quote {all,minimal,none,nonnumeric}]
[--detail] [--limit <limit>]
[--sort-key <field>]
[--sort-dir <direction>]
[--sort-dir <direction>][--marker <marker>]
List information on retrieved scoring engines.
@@ -864,6 +886,10 @@ List information on retrieved scoring engines.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last scoringengine in the previous page; displays
list of scoringengines after "marker".
.. _watcher_scoringengine_show:
watcher scoringengine show
@@ -948,6 +974,40 @@ Show detailed information about a given service.
``-h, --help``
show this help message and exit
.. _watcher_datamodel_list:
watcher datamodel list
----------------------
.. code-block:: console
usage: watcher datamodel list [-h] [-f {csv,json,table,value,yaml}]
[-c COLUMN] [--max-width <integer>]
[--fit-width] [--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}]
[--sort-column SORT_COLUMN] [--type <type>]
[--audit <audit>] [--detail]
List information on retrieved data model.
**Optional arguments:**
``-h, --help``
show this help message and exit
``--type <type>``
Type of Datamodel user want to list. Supported values:
compute. Future support values: storage, baremetal.
Default type is compute.
``--audit <audit>``
UUID of the audit. used to filter data model
by the scope in audit.
``--detail``
Show detailed information about data model.
.. _watcher_strategy_list:
watcher strategy list
@@ -961,6 +1021,7 @@ watcher strategy list
[--quote {all,minimal,none,nonnumeric}]
[--goal <goal>] [--detail] [--limit <limit>]
[--sort-key <field>] [--sort-dir <direction>]
[--marker <marker>]
List information on retrieved strategies.
@@ -986,6 +1047,10 @@ List information on retrieved strategies.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last strategy in the previous page; displays
list of strategies after "marker".
.. _watcher_strategy_show:
watcher strategy show
@@ -1010,3 +1075,28 @@ Show detailed information about a given strategy.
``-h, --help``
show this help message and exit
.. _watcher_strategy_state:
watcher strategy state
----------------------
.. code-block:: console
usage: watcher strategy state [-h] [-f {csv,html,json,table,value,yaml}]
[-c COLUMN] [--max-width <integer>]
[--fit-width] [--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}]
[--sort-column SORT_COLUMN]
<strategy>
Retrieve information about strategy requirements.
**Positional arguments:**
``<strategy>``
Name of the strategy
**Optional arguments:**
``-h, --help``
show this help message and exit

View File

@@ -11,7 +11,7 @@ variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
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

View File

@@ -33,7 +33,7 @@ environment variables::
$ export OS_PASSWORD=password
$ export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
$ export OS_TENANT_NAME=project # or OS_TENANT_ID
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
$ export OS_AUTH_URL=http://auth.example.com:5000/v3/
The command-line tool will attempt to reauthenticate using the provided
credentials for every request. You can override this behavior by manually

View File

@@ -32,7 +32,7 @@ environment variables::
$ export OS_PASSWORD=password
$ export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
$ export OS_TENANT_NAME=project # or OS_TENANT_ID
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
$ export OS_AUTH_URL=http://auth.example.com:5000/v3/
The command-line tool will attempt to reauthenticate using the provided
credentials for every request. You can override this behavior by manually
@@ -52,7 +52,7 @@ environment variable. (It defaults to the first in the list returned.)
Watcher CLI supports bash completion. The command-line tool can automatically
fill partially typed commands. To use this feature, source the below file
(available at
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
https://opendev.org/openstack/python-watcherclient/src/branch/master/tools/watcher.bash_completion)
to your terminal and then bash completion should work::
$ . watcher.bash_completion

View File

@@ -12,20 +12,26 @@
# limitations under the License.
from watcherclient import version as watcherclient_version
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'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']
@@ -39,16 +45,6 @@ master_doc = 'index'
project = u'python-watcherclient'
copyright = u'OpenStack Foundation'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
release = watcherclient_version.version_info.release_string()
# The short X.Y version.
version = watcherclient_version.version_info.version_string()
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['watcherclient.']
@@ -60,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 --------------------------------------------------
@@ -88,12 +84,22 @@ latex_documents = [
),
]
# openstackdocstheme options
repository_name = 'openstack/python-watcherclient'
bug_project = 'python-watcherclient'
bug_tag = ''
# 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 = ''
# Must set this variable to include year, month, day, hours, and minutes.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
#html_theme_options = {"show_other_versions": "True"}

View File

@@ -44,11 +44,11 @@ Bug tracker
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

View File

@@ -5,6 +5,6 @@ Installation
If you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
$ mkvirtualenv python-watcherclient
$ git clone https://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

View File

@@ -1,8 +0,0 @@
======================
Python API Reference
======================
.. toctree::
:maxdepth: 2
autoindex

View File

@@ -71,8 +71,7 @@ Once you have an watcher `Client`_, you can perform various tasks::
>>> 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.
@@ -84,5 +83,4 @@ watcherclient Modules
.. _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

View File

@@ -10,5 +10,5 @@ done so, you can use the API like so.
.. toctree::
:maxdepth: 2
api/index
api/modules
api_v1

82
lower-constraints.txt Normal file
View File

@@ -0,0 +1,82 @@
alabaster==0.7.10
appdirs==1.4.3
asn1crypto==0.23.0
certifi==2018.1.18
cffi==1.14.0
chardet==3.0.4
cliff==2.11.0
cmd2==0.8.2
coverage==4.0
cryptography==2.7
debtcollector==1.19.0
decorator==4.2.1
deprecation==2.0
docutils==0.11
dogpile.cache==0.6.5
dulwich==0.15.0
extras==1.0.0
fasteners==0.7.0
fixtures==3.0.0
flake8==2.5.5
hacking==0.12.0
idna==2.6
imagesize==0.7.1
iso8601==0.1.12
Jinja2==2.10
jmespath==0.9.3
jsonpatch==1.21
jsonpointer==2.0
jsonschema==2.6.0
keystoneauth1==3.4.0
linecache2==1.0.0
MarkupSafe==1.0
mccabe==0.2.1
monotonic==1.4
msgpack-python==0.4.0
munch==2.2.0
netaddr==0.7.19
netifaces==0.10.6
openstacksdk==0.12.0
os-client-config==1.29.0
os-service-types==1.2.0
os-testr==1.0.0
osc-lib==1.10.0
oslo.concurrency==3.25.0
oslo.config==5.2.0
oslo.context==2.19.2
oslo.i18n==3.20.0
oslo.log==3.36.0
oslo.serialization==2.18.0
oslo.utils==3.36.0
oslotest==3.2.0
packaging==17.1
paramiko==2.0.0
pbr==3.1.1
pep8==1.5.7
pyasn1==0.1.8
pycparser==2.18
pyflakes==0.8.1
Pygments==2.2.0
pyinotify==0.9.6
pyparsing==2.2.0
pyperclip==1.6.0
python-dateutil==2.5.3
python-mimeparse==1.6.0
python-subunit==1.0.0
pytz==2018.3
PyYAML==3.13
requests==2.18.4
requestsexceptions==1.4.0
rfc3986==0.3.1
simplejson==3.13.2
six==1.11.0
snowballstemmer==1.2.1
stestr==2.0.0
stevedore==1.28.0
tempest==17.1.0
testscenarios==0.4
testtools==2.2.0
traceback2==1.4.0
unittest2==1.1.0
urllib3==1.22
wrapt==1.10.11

View 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.

View File

@@ -2,13 +2,12 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
Babel!=2.4.0,>=2.3.4 # BSD
cliff!=2.9.0,>=2.8.0 # Apache-2.0
osc-lib>=1.8.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
pbr!=2.1.0,>=2.0.0 # Apache-2.0
PrettyTable<0.8,>=0.7.1 # BSD
keystoneauth1>=3.3.0 # Apache-2.0
six>=1.10.0 # MIT
PyYAML>=3.10 # MIT
cliff!=2.9.0,>=2.11.0 # Apache-2.0
osc-lib>=1.10.0 # Apache-2.0
oslo.i18n>=3.20.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.36.0 # Apache-2.0
pbr!=2.1.0,>=3.1.1 # Apache-2.0
keystoneauth1>=3.4.0 # Apache-2.0
six>=1.11.0 # MIT
PyYAML>=3.13 # MIT

View File

@@ -4,8 +4,9 @@ summary = Python client library for Watcher API
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/python-watcherclient/latest/
python-requires = >=3.6
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -13,10 +14,10 @@ classifier =
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
[files]
packages =
@@ -51,8 +52,8 @@ openstack.infra_optim.v1 =
optimize_audit_delete = watcherclient.v1.audit_shell:DeleteAudit
optimize_actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
optimize_actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
optimize_actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
optimize_actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
optimize_actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
optimize_actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
optimize_actionplan_cancel = watcherclient.v1.action_plan_shell:CancelActionPlan
@@ -66,6 +67,8 @@ openstack.infra_optim.v1 =
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
@@ -89,7 +92,6 @@ watcherclient.v1 =
actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
@@ -104,31 +106,10 @@ watcherclient.v1 =
service_show = watcherclient.v1.service_shell:ShowService
service_list = watcherclient.v1.service_shell:ListService
datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
[pbr]
autodoc_index_modules = True
autodoc_exclude_modules =
watcherclient.tests.*
api_doc_dir = reference/api
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 1
[bdist_wheel]
universal = 1
[compile_catalog]
directory = watcherclient/locale
domain = watcherclient
[update_catalog]
domain = watcherclient
output_dir = watcherclient/locale
input_file = watcherclient/locale/watcherclient.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC
mapping_file = babel.cfg
output_file = watcherclient/locale/watcherclient.pot

View File

@@ -13,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>=2.0.0'],
pbr=True)

View File

@@ -4,16 +4,10 @@
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
mock>=2.0.0 # BSD
openstackdocstheme>=1.18.1 # Apache-2.0
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
sphinx!=1.6.6,>=1.6.2 # BSD
testrepository>=0.0.18 # 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
# Needed for pypi packaging
wheel>=0.24.0 # MIT

80
tox.ini
View File

@@ -1,62 +1,88 @@
[tox]
minversion = 1.8
envlist = py35,py27,pep8
minversion = 2.0
envlist = py36,py38,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command =
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
pip install -U {opts} {packages}
passenv = ZUUL_CACHE_DIR
REQUIREMENTS_PIP_LOCATION
install_command = pip install {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
OS_TEST_PATH=./watcherclient/tests/unit
deps = -r{toxinidir}/test-requirements.txt
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
whitelist_externals =
rm
commands = rm -f .testrepository/times.dbm
python setup.py testr --slowest --testr-args='{posargs}'
# 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]
basepython = python3
setenv =
PYTHON=coverage run --source watcherclient --parallel-mode
commands =
python setup.py testr --coverage --testr-args='{posargs}'
coverage report
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:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
[testenv:pdf-docs]
basepython = python3
envdir = {toxworkdir}/docs
deps = {[testenv:docs]deps}
whitelist_externals =
rm
make
commands =
rm -rf doc/build/pdf
sphinx-build -W -b latex doc/source doc/build/pdf
make -C doc/build/pdf
[testenv:debug]
basepython = python3
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
[testenv:functional]
basepython = python2.7
passenv =
OS_PROJECT_DOMAIN_NAME
OS_USER_DOMAIN_NAME
OS_PROJECT_NAME
OS_USERNAME
OS_PASSWORD
OS_AUTH_URL
OS_IDENTITY_API_VERSION
OS_IMAGE_API_VERSION
setenv =
OS_TEST_PATH = ./watcherclient/tests/functional
commands = python setup.py testr --slowest --testr-args='--concurrency=1 {posargs}'
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
enable-extensions = H203,H106
ignore = E123,E125
ignore = E123,E125,W504
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
[testenv:wheel]
basepython = python3
commands = python setup.py bdist_wheel
[hacking]
import_exceptions = watcherclient._i18n
[testenv:lower-constraints]
basepython = python3
install_command = pip install -U {opts} {packages}
deps =
-c{toxinidir}/lower-constraints.txt
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt

View File

@@ -17,6 +17,7 @@
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")

View File

@@ -28,7 +28,7 @@ def get_client(api_version, os_auth_token=None, watcher_url=None,
os_service_type=None, os_endpoint_type=None,
insecure=None, timeout=None, os_cacert=None, ca_file=None,
os_cert=None, cert_file=None, os_key=None, key_file=None,
os_watcher_api_version=None, max_retries=None,
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.
@@ -61,7 +61,7 @@ def get_client(api_version, os_auth_token=None, watcher_url=None,
:param cert_file: path to cert file, deprecated in favour of os_cert
:param os_key: path to key file
:param key_file: path to key file, deprecated in favour of os_key
:param os_watcher_api_version: watcher API version to use
:param 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
@@ -75,7 +75,7 @@ def get_client(api_version, os_auth_token=None, watcher_url=None,
project_id = (os_project_id or os_tenant_id)
project_name = (os_project_name or os_tenant_name)
kwargs = {
'os_watcher_api_version': os_watcher_api_version,
'os_infra_optim_api_version': os_infra_optim_api_version,
'max_retries': max_retries,
'retry_interval': retry_interval,
}
@@ -182,4 +182,11 @@ def Client(version, *args, **kwargs):
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)

View File

@@ -26,14 +26,34 @@ 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"
SERVICE_TYPE = "infra-optim"
# key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {}
_type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'")
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.

View File

@@ -31,10 +31,7 @@ def getid(obj):
Abstracts the common pattern of allowing both an object or an
object's ID (UUID) as a parameter when dealing with relationships.
"""
try:
return obj.id
except AttributeError:
return obj
return getattr(obj, 'id', obj)
class Manager(object):
@@ -134,6 +131,11 @@ class Manager(object):
def _delete(self, url):
self.api.raw_request('DELETE', url)
def _start(self, url, body=None, method='POST'):
resp, body = self.api.json_request(method, url, body={})
if body:
return self.resource_class(self, body)
class Resource(base.Resource):
"""Represents a particular instance of an object (tenant, user, etc).

View File

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

View File

@@ -34,16 +34,15 @@ from six.moves import http_client
import six.moves.urllib.parse as urlparse
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient import exceptions
# NOTE(deva): Record the latest version that this client was tested with.
# We still have a lot of work to do in the client to implement
# microversion support in the client properly! See
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html # noqa
# for full details.
DEFAULT_VER = '1.0'
# 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'
@@ -104,20 +103,8 @@ class VersionNegotiationMixin(object):
{'valid': ', '.join(API_VERSION_SELECTED_STATES),
'value': self.api_version_select_state})
min_ver, max_ver = self._parse_version_headers(resp)
# NOTE: servers before commit 32fb6e99 did not return version headers
# on error, so we need to perform a GET to determine
# the supported version range
if not max_ver:
LOG.debug('No version header in response, requesting from server')
if self.os_watcher_api_version:
base_version = ("/v%s" %
str(self.os_watcher_api_version).split('.')[0])
else:
base_version = API_VERSION
resp = self._make_simple_request(conn, 'GET', base_version)
min_ver, max_ver = self._parse_version_headers(resp)
# If the user requested an explicit version or we have negotiated a
# version and still failing then error now. The server could
# 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':
@@ -126,33 +113,33 @@ class VersionNegotiationMixin(object):
"server or the requested operation is not supported by the "
"requested version. Supported version range is %(min)s to "
"%(max)s")
% {'req': self.os_watcher_api_version,
% {'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_watcher_api_version,
% {'req': self.os_infra_optim_api_version,
'min': min_ver, 'max': max_ver}))
negotiated_ver = str(
min(version.StrictVersion(self.os_watcher_api_version),
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_watcher_api_version = negotiated_ver
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('X-OpenStack-Watcher-API-Minimum-Version',
min_ver = accessor_func('OpenStack-API-Minimum-Version',
None)
max_ver = accessor_func('X-OpenStack-Watcher-API-Maximum-Version',
max_ver = accessor_func('OpenStack-API-Maximum-Version',
None)
return min_ver, max_ver
@@ -206,8 +193,8 @@ class HTTPClient(VersionNegotiationMixin):
self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
self.auth_token = kwargs.get('token')
self.auth_ref = kwargs.get('auth_ref')
self.os_watcher_api_version = kwargs.get('os_watcher_api_version',
DEFAULT_VER)
self.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',
@@ -289,7 +276,7 @@ class HTTPClient(VersionNegotiationMixin):
LOG.debug('\n'.join(dump))
def _make_connection_url(self, url):
return urlparse.urljoin(self.endpoint_trimmed, 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)
@@ -307,9 +294,15 @@ class HTTPClient(VersionNegotiationMixin):
# Copy the kwargs so we can reuse the original in case of redirects
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
if self.os_watcher_api_version:
kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
self.os_watcher_api_version)
if self.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)
@@ -334,8 +327,8 @@ class HTTPClient(VersionNegotiationMixin):
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['X-OpenStack-Watcher-API-Version'] = (
negotiated_ver)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
return self._http_request(url, method, **kwargs)
except requests.exceptions.RequestException as e:
@@ -354,7 +347,15 @@ class HTTPClient(VersionNegotiationMixin):
# Read body into string if it isn't obviously image data
body_str = None
if resp.headers.get('Content-Type') != 'application/octet-stream':
body_str = ''.join([chunk for chunk in body_iter])
# decoding byte to string is necessary for Python 3 compatibility
# this issues has not been found with Python 3 unit tests
# because the test creates a fake http response of type str
# the if statement satisfies test (str) and real (bytes) behavior
body_list = [
chunk.decode("utf-8") if isinstance(chunk, bytes)
else chunk for chunk in body_iter
]
body_str = ''.join(body_list)
self.log_http_response(resp, body_str)
body_iter = six.StringIO(body_str)
else:
@@ -387,8 +388,8 @@ class HTTPClient(VersionNegotiationMixin):
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):
http_client.RESET_CONTENT) or
content_type is None):
return resp, list()
if 'application/json' in content_type:
@@ -477,13 +478,13 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session."""
def __init__(self,
os_watcher_api_version,
os_infra_optim_api_version,
api_version_select_state,
max_retries,
retry_interval,
endpoint,
**kwargs):
self.os_watcher_api_version = os_watcher_api_version
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
@@ -508,9 +509,16 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
_trim_endpoint_api_version(self.endpoint_override)
)
if getattr(self, 'os_watcher_api_version', None):
kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
self.os_watcher_api_version)
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)
@@ -521,8 +529,8 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
raise_exc=False, **kwargs)
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['X-OpenStack-Watcher-API-Version'] = (
negotiated_ver)
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)
@@ -574,7 +582,7 @@ def _construct_http_client(endpoint=None,
session=None,
token=None,
auth_ref=None,
os_watcher_api_version=DEFAULT_VER,
os_infra_optim_api_version=DEFAULT_VER,
api_version_select_state='default',
max_retries=DEFAULT_MAX_RETRIES,
retry_interval=DEFAULT_RETRY_INTERVAL,
@@ -605,27 +613,29 @@ def _construct_http_client(endpoint=None,
'the session to construct a client: %s',
', '.join(dvars))
return SessionClient(session=session,
os_watcher_api_version=os_watcher_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
endpoint=endpoint,
**kwargs)
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_watcher_api_version=os_watcher_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
timeout=timeout,
ca_file=ca_file,
cert_file=cert_file,
key_file=key_file,
insecure=insecure)
return HTTPClient(
endpoint=endpoint,
token=token,
auth_ref=auth_ref,
os_infra_optim_api_version=os_infra_optim_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
timeout=timeout,
ca_file=ca_file,
cert_file=cert_file,
key_file=key_file,
insecure=insecure)

View File

@@ -14,8 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import argparse
import os
import uuid

View File

@@ -71,6 +71,7 @@ class AmbiguousAuthSystem(exceptions.ClientException):
"""Could not obtain token and endpoint using provided credentials."""
pass
# Alias for backwards compatibility
AmbigiousAuthSystem = AmbiguousAuthSystem

View File

@@ -15,9 +15,14 @@ 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 = '1'
DEFAULT_API_VERSION = httpclient.LATEST_VERSION
API_VERSION_OPTION = 'os_infra_optim_api_version'
API_NAME = 'infra-optim'
API_VERSIONS = {
@@ -27,14 +32,17 @@ API_VERSIONS = {
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,
instance._api_version[API_NAME],
version.ver_major,
API_VERSIONS)
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
client = infraoptim_client_class(
os_watcher_api_version=instance._api_version[API_NAME],
os_infra_optim_api_version=instance._api_version[API_NAME],
session=instance.session,
region_name=instance._region_name,
)
@@ -53,3 +61,30 @@ def build_option_parser(parser):
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

View File

@@ -39,6 +39,7 @@ API_NAME = 'infra-optim'
API_VERSIONS = {
'1': 'watcherclient.v1.client.Client',
}
DEFAULT_OS_INFRA_OPTIM_API_VERSION = '1.latest'
_DEFAULT_IDENTITY_API_VERSION = '3'
_IDENTITY_API_VERSION_2 = ['2', '2.0']
_IDENTITY_API_VERSION_3 = ['3']
@@ -47,8 +48,6 @@ _IDENTITY_API_VERSION_3 = ['3']
class WatcherShell(app.App):
"""Watcher command line interface."""
log = logging.getLogger(__name__)
def __init__(self, **kwargs):
self.client = None
@@ -149,11 +148,13 @@ class WatcherShell(app.App):
metavar='<auth-token>',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN].')
parser.add_argument('--os-watcher-api-version',
metavar='<os-watcher-api-version>',
default=utils.env('OS_WATCHER_API_VERSION',
default='1'),
help='Defaults to env[OS_WATCHER_API_VERSION].')
parser.add_argument(
'--os-infra-optim-api-version',
metavar='<infra-optim-api-version>',
default=utils.env('OS_INFRA_OPTIM_API_VERSION',
default=DEFAULT_OS_INFRA_OPTIM_API_VERSION),
help='Accepts X, X.Y (where X is major and Y is minor part) or '
'"X.latest", defaults to env[OS_INFRA_OPTIM_API_VERSION].')
parser.add_argument('--os-endpoint-type',
default=utils.env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE] or '
@@ -194,17 +195,18 @@ class WatcherShell(app.App):
except Exception as e:
if not logging.getLogger('').handlers:
logging.basicConfig()
self.log.error('Exception raised: %s', str(e))
LOG.error('Exception raised: %s', str(e))
return ret_val
finally:
self.log.info("END return value: %s", ret_val)
LOG.info("END return value: %s", ret_val)
def main(argv=sys.argv[1:]):
watcher_app = WatcherShell()
return watcher_app.run(argv)
if __name__ == '__main__': # pragma: no cover
sys.exit(main(sys.argv[1:]))

View File

@@ -1,61 +0,0 @@
..
Except where otherwise noted, this document is licensed under Creative
Commons Attribution 3.0 License. You can view the license at:
https://creativecommons.org/licenses/by/3.0/
=======
Testing
=======
.. _functional_tests:
Functional tests
================
The following procedure gets you started with Tempest testing but you can also
refer to the `Tempest documentation`_ for more details.
.. _Tempest documentation: https://docs.openstack.org/tempest/latest/
Tempest installation
--------------------
You need to install virtualenv, create a virtual environment and activate it::
$ pip install virtualenv
$ virtualenv watcher-env
$ . watcher-env/bin/activate
Then, to install Tempest you can issue the following commands::
$ git clone https://github.com/openstack/tempest/
$ pip install tempest/
There should be set environment variables using the OpenStack RC file. If
you don't have RC file yet, create ``admin-openrc`` file and fill it using
the following example::
export OS_PROJECT_DOMAIN_NAME=default
export OS_USER_DOMAIN_NAME=default
export OS_PROJECT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD=admin
export OS_AUTH_URL=http://controller:35357/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
Then, save file and execute ``source admin-openrc`` to set environment
variables.
To run functional tests you need to go to python-watcherclient folder, install
all requirements and execute ``tempest run`` command::
$ pip install -r requirements.txt -r test-requirements.txt
$ pip install .
$ tempest run --regex watcherclient.tests.functional
You can run specified test(s) by using regular expression::
$ tempest run --regex watcherclient.tests.functional.v1.test_action.ActionTests.test_action_list

View File

@@ -1,53 +0,0 @@
#!/bin/bash -xe
# 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 script is executed inside post_test_hook function in devstack gate.
# Default gate uses /opt/stack/new... but some of us may install differently
STACK_DIR=$BASE/new/devstack
function generate_testr_results {
if [ -f .testrepository/0 ]; then
sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit
sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
sudo gzip -9 $BASE/logs/testrepository.subunit
sudo gzip -9 $BASE/logs/testr_results.html
sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
fi
}
export WATCHERCLIENT_DIR="$BASE/new/python-watcherclient"
sudo chown -R $USER:stack $WATCHERCLIENT_DIR
# Get admin credentials
cd $STACK_DIR
source openrc admin admin
# Go to the watcherclient dir
cd $WATCHERCLIENT_DIR
# Run tests
echo "Running watcherclient functional test suite"
set +e
# Preserve env for OS_ credentials
sudo -E -H -u $USER tox -efunctional
EXIT_CODE=$?
set -e
# Collect and parse result
generate_testr_results
exit $EXIT_CODE

View File

@@ -1,125 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import re
import shlex
import subprocess
import testtools
import six
from tempest.lib.cli import output_parser
from tempest.lib import exceptions
def credentials():
creds = {
'--os-username': os.environ.get('OS_USERNAME', 'admin'),
'--os-password': os.environ.get('OS_PASSWORD', 'secretadmin'),
'--os-project-name': os.environ.get('OS_PROJECT_NAME', 'admin'),
'--os-auth-url': os.environ.get('OS_AUTH_URL',
'http://10.0.1.94/identity'),
'--os-project-domain-id': os.environ.get('OS_PROJECT_DOMAIN_ID',
'default'),
'--os-user-domain-id': os.environ.get('OS_USER_DOMAIN_ID', 'default'),
}
return [x for sub in creds.items() for x in sub]
def execute(cmd, fail_ok=False, merge_stderr=False):
"""Executes specified command for the given action."""
cmdlist = shlex.split(cmd)
cmdlist.extend(credentials())
stdout = subprocess.PIPE
stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr)
result, result_err = proc.communicate()
result = result.decode('utf-8')
if not fail_ok and proc.returncode != 0:
raise exceptions.CommandFailed(proc.returncode, cmd, result,
result_err)
return result
class TestCase(testtools.TestCase):
delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
@classmethod
def watcher(cls, cmd, fail_ok=False):
"""Executes watcherclient command for the given action."""
return execute('watcher {0}'.format(cmd), fail_ok=fail_ok)
@classmethod
def get_opts(cls, fields, format='value'):
return ' -f {0} {1}'.format(format,
' '.join(['-c ' + it for it in fields]))
@classmethod
def assertOutput(cls, expected, actual):
if expected != actual:
raise Exception('{0} != {1}'.format(expected, actual))
@classmethod
def assertInOutput(cls, expected, actual):
if expected not in actual:
raise Exception('{0} not in {1}'.format(expected, actual))
def assert_table_structure(self, items, field_names):
"""Verify that all items have keys listed in field_names."""
for item in items:
for field in field_names:
self.assertIn(field, item)
def assert_show_fields(self, items, field_names):
"""Verify that all items have keys listed in field_names."""
for item in items:
for key in six.iterkeys(item):
self.assertIn(key, field_names)
def assert_show_structure(self, items, field_names):
"""Verify that all field_names listed in keys of all items."""
if isinstance(items, list):
o = {}
for d in items:
o.update(d)
else:
o = items
item_keys = o.keys()
for field in field_names:
self.assertIn(field, item_keys)
@staticmethod
def parse_show_as_object(raw_output):
"""Return a dict with values parsed from cli output."""
items = TestCase.parse_show(raw_output)
o = {}
for item in items:
o.update(item)
return o
@staticmethod
def parse_show(raw_output):
"""Return list of dicts with item values parsed from cli output."""
items = []
table_ = output_parser.table(raw_output)
for row in table_['values']:
item = {}
item[row[0]] = row[1]
items.append(item)
return items
def parse_listing(self, raw_output):
"""Return list of dicts with basic item parsed from cli output."""
return output_parser.listing(raw_output)

View File

@@ -1,71 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_utils import uuidutils
from watcherclient.tests.functional.v1 import base
class ActionTests(base.TestCase):
"""Functional tests for action."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Parents', 'State', 'Action Plan', 'Action']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Parameters']
audit_template_name = 'a' + uuidutils.generate_uuid()
audit_uuid = None
@classmethod
def setUpClass(cls):
template_raw_output = cls.watcher(
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
template_output = cls.parse_show_as_object(template_raw_output)
audit_raw_output = cls.watcher(
'audit create -a %s' % template_output['Name'])
audit_output = cls.parse_show_as_object(audit_raw_output)
cls.audit_uuid = audit_output['UUID']
@classmethod
def tearDownClass(cls):
# Delete Action Plan and all related actions.
output = cls.parse_show(
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
action_plan_uuid = output[0].keys()[0]
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
cls.assertOutput('', raw_output)
# Delete audit
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
cls.assertOutput('', raw_output)
# Delete Template
raw_output = cls.watcher(
'audittemplate delete %s' % cls.audit_template_name)
cls.assertOutput('', raw_output)
def test_action_list(self):
raw_output = self.watcher('action list')
self.assert_table_structure([raw_output], self.list_fields)
def test_action_detailed_list(self):
raw_output = self.watcher('action list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_action_show(self):
action_list = self.parse_show(self.watcher('action list'))
action_uuid = action_list[0].keys()[0]
action = self.watcher('action show ' + action_uuid)
self.assertIn(action_uuid, action)
self.assert_table_structure([action],
self.detailed_list_fields)

View File

@@ -1,133 +0,0 @@
# 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 time
from oslo_utils import uuidutils
from watcherclient.tests.functional.v1 import base
class ActionPlanTests(base.TestCase):
"""Functional tests for action plan."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Audit', 'State', 'Updated At', 'Global efficacy']
detailed_list_fields = list_fields + ['Created At', 'Deleted At',
'Strategy', 'Efficacy indicators']
audit_template_name = 'a' + uuidutils.generate_uuid()
audit_uuid = None
@classmethod
def setUpClass(cls):
template_raw_output = cls.watcher(
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
template_output = cls.parse_show_as_object(template_raw_output)
audit_raw_output = cls.watcher('audit create -a %s'
% template_output['Name'])
audit_output = cls.parse_show_as_object(audit_raw_output)
cls.audit_uuid = audit_output['UUID']
@classmethod
def tearDownClass(cls):
# Delete action plan
output = cls.parse_show(
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
action_plan_uuid = output[0].keys()[0]
retry = 10
while retry > 0:
output = cls.parse_show(
cls.watcher('actionplan show %s' % action_plan_uuid))
state = [x for x in output if x.keys()[0] == 'State'][0]['State']
if state == 'SUCCEEDED':
break
time.sleep(1)
retry -= 1
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
cls.assertOutput('', raw_output)
# Delete audit
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
cls.assertOutput('', raw_output)
# Delete Template
raw_output = cls.watcher(
'audittemplate delete %s' % cls.audit_template_name)
cls.assertOutput('', raw_output)
def test_action_plan_list(self):
raw_output = self.watcher('actionplan list')
self.assert_table_structure([raw_output], self.list_fields)
def test_action_plan_detailed_list(self):
raw_output = self.watcher('actionplan list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_action_plan_show(self):
action_plan_list = self.parse_show(self.watcher('actionplan list'))
action_plan_uuid = action_plan_list[0].keys()[0]
actionplan = self.watcher('actionplan show %s' % action_plan_uuid)
self.assertIn(action_plan_uuid, actionplan)
self.assert_table_structure([actionplan],
self.detailed_list_fields)
def test_action_plan_start(self):
output = self.parse_show(self.watcher('actionplan list --audit %s'
% self.audit_uuid))
action_plan_uuid = output[0].keys()[0]
self.watcher('actionplan start %s' % action_plan_uuid)
raw_output = self.watcher('actionplan show %s' % action_plan_uuid)
self.assert_table_structure([raw_output], self.detailed_list_fields)
class ActionPlanActiveTests(base.TestCase):
audit_uuid = None
audit_template_name = 'b' + uuidutils.generate_uuid()
list_fields = ['UUID', 'Audit', 'State', 'Updated At', 'Global efficacy']
detailed_list_fields = list_fields + ['Created At', 'Deleted At',
'Strategy', 'Efficacy indicators']
def _delete_action_plan(self):
output = self.parse_show(
self.watcher('actionplan list --audit %s' % self.audit_uuid))
action_plan_uuid = output[0].keys()[0]
raw_output = self.watcher('actionplan delete %s' % action_plan_uuid)
self.assertOutput('', raw_output)
def _delete_audit(self):
raw_output = self.watcher('audit delete %s' % self.audit_uuid)
self.assertOutput('', raw_output)
def _delete_audit_template(self):
raw_output = self.watcher(
'audittemplate delete %s' % self.audit_template_name)
self.assertOutput('', raw_output)
def _create_audit_template(self):
template_raw_output = self.watcher(
'audittemplate create %s dummy -s dummy'
% self.audit_template_name)
template_output = self.parse_show_as_object(template_raw_output)
return template_output
def test_action_plan_create(self):
template_output = self._create_audit_template()
action_plan = self.watcher(
'actionplan create -a %s' % template_output['Name'])
self.audit_uuid = self.parse_show_as_object(action_plan)['UUID']
self.assert_table_structure([action_plan], self.detailed_list_fields)
self._delete_action_plan()
self._delete_audit()
self._delete_audit_template()

View File

@@ -1,114 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_utils import uuidutils
from watcherclient.tests.functional.v1 import base
class AuditTests(base.TestCase):
"""Functional tests for audit."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Name', 'Audit Type', 'State', 'Goal', 'Strategy']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Parameters',
'Interval', 'Audit Scope',
'Next Run Time']
audit_template_name = 'a' + uuidutils.generate_uuid()
audit_uuid = None
@classmethod
def setUpClass(cls):
raw_output = cls.watcher('audittemplate create %s dummy -s dummy'
% cls.audit_template_name)
template_output = cls.parse_show_as_object(raw_output)
audit_raw_output = cls.watcher(
'audit create -a %s' % template_output['Name'])
audit_output = cls.parse_show_as_object(audit_raw_output)
cls.audit_uuid = audit_output['UUID']
@classmethod
def tearDownClass(cls):
output = cls.parse_show(
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
action_plan_uuid = output[0].keys()[0]
cls.watcher('actionplan delete %s' % action_plan_uuid)
cls.watcher('audit delete %s' % cls.audit_uuid)
cls.watcher('audittemplate delete %s' % cls.audit_template_name)
def test_audit_list(self):
raw_output = self.watcher('audit list')
self.assert_table_structure([raw_output], self.list_fields)
def test_audit_detailed_list(self):
raw_output = self.watcher('audit list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_audit_show(self):
audit = self.watcher('audit show ' + self.audit_uuid)
self.assertIn(self.audit_uuid, audit)
self.assert_table_structure([audit], self.detailed_list_fields)
def test_audit_update(self):
audit_raw_output = self.watcher('audit update %s add interval=2'
% self.audit_uuid)
audit_output = self.parse_show_as_object(audit_raw_output)
assert int(audit_output['Interval']) == 2
class AuditActiveTests(base.TestCase):
list_fields = ['UUID', 'Name', 'Audit Type', 'State', 'Goal', 'Strategy']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Parameters',
'Interval', 'Audit Scope']
audit_template_name = 'a' + uuidutils.generate_uuid()
audit_uuid = None
def _create_audit(self):
raw_output = self.watcher('audittemplate create %s dummy -s dummy'
% self.audit_template_name)
template_output = self.parse_show_as_object(raw_output)
self.audit_uuid = self.parse_show_as_object(
self.watcher('audit create -a %s'
% template_output['Name']))['UUID']
def _delete_audit(self):
output = self.parse_show(
self.watcher('actionplan list --audit %s' % self.audit_uuid))
action_plan_uuid = output[0].keys()[0]
self.watcher('actionplan delete %s' % action_plan_uuid)
self.watcher('audit delete %s' % self.audit_uuid)
self.watcher('audittemplate delete %s' % self.audit_template_name)
def test_create_audit(self):
raw_output = self.watcher('audittemplate create %s dummy -s dummy'
% self.audit_template_name)
template_output = self.parse_show_as_object(raw_output)
audit = self.watcher('audit create -a %s' % template_output['Name'])
self.audit_uuid = self.parse_show_as_object(audit)['UUID']
self.assert_table_structure([audit], self.detailed_list_fields)
self._delete_audit()
def test_delete_audit(self):
self._create_audit()
raw_output = self.watcher('audit delete %s' % self.audit_uuid)
self.assertOutput('', raw_output)
output = self.parse_show(
self.watcher('actionplan list --audit %s' % self.audit_uuid))
action_plan_uuid = output[0].keys()[0]
self.watcher('actionplan delete %s' % action_plan_uuid)
self.watcher('audittemplate delete %s' % self.audit_template_name)

View File

@@ -1,89 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_utils import uuidutils
from watcherclient.tests.functional.v1 import base
class AuditTemplateTests(base.TestCase):
"""Functional tests for audit template."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Name', 'Goal', 'Strategy']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Description',
'Audit Scope']
audit_template_name = 'a' + uuidutils.generate_uuid()
@classmethod
def setUpClass(cls):
cls.watcher('audittemplate create %s dummy -s dummy'
% cls.audit_template_name)
@classmethod
def tearDownClass(cls):
cls.watcher('audittemplate delete %s' % cls.audit_template_name)
def test_audit_template_list(self):
raw_output = self.watcher('audittemplate list')
self.assert_table_structure([raw_output], self.list_fields)
def test_audit_template_detailed_list(self):
raw_output = self.watcher('audittemplate list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_audit_template_show(self):
audit_template = self.watcher(
'audittemplate show %s' % self.audit_template_name)
self.assertIn(self.audit_template_name, audit_template)
self.assert_table_structure([audit_template],
self.detailed_list_fields)
def test_audit_template_update(self):
raw_output = self.watcher('audittemplate update %s replace '
'description="Updated Desc"'
% self.audit_template_name)
audit_template_output = self.parse_show_as_object(raw_output)
assert audit_template_output['Description'] == 'Updated Desc'
class AuditTemplateActiveTests(base.TestCase):
audit_template_name = 'b' + uuidutils.generate_uuid()
list_fields = ['UUID', 'Name', 'Goal', 'Strategy']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Description',
'Audit Scope']
def _create_audit_template(self):
self.watcher('audittemplate create %s dummy -s dummy '
'-d "Test Audit Template"' % self.audit_template_name)
def _delete_audit_template(self):
self.watcher('audittemplate delete %s' % self.audit_template_name)
def test_create_audit_template(self):
raw_output = self.watcher('audittemplate create %s dummy '
'-s dummy -d "Test Audit Template"'
% self.audit_template_name)
self.assert_table_structure([raw_output], self.detailed_list_fields)
self._delete_audit_template()
def test_delete_audit_template(self):
self._create_audit_template()
raw_output = self.watcher('audittemplate delete %s'
% self.audit_template_name)
self.assertOutput('', raw_output)

View File

@@ -1,41 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.functional.v1 import base
class GoalTests(base.TestCase):
"""Functional tests for goal."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Name', 'Display name']
def test_goal_list(self):
raw_output = self.watcher('goal list')
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output], self.list_fields)
def test_goal_detailed_list(self):
raw_output = self.watcher('goal list --detail')
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure(
[raw_output], self.list_fields + ['Efficacy specification'])
def test_goal_show(self):
raw_output = self.watcher('goal show %s' % self.dummy_name)
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure(
[raw_output], self.list_fields + ['Efficacy specification'])
self.assertNotIn('server_consolidation', raw_output)

View File

@@ -1,40 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.functional.v1 import base
class ScoringEngineTests(base.TestCase):
"""Functional tests for scoring engine."""
dummy_name = 'dummy_scorer'
list_fields = ['UUID', 'Name', 'Description']
detailed_list_fields = list_fields + ['Metainfo']
def test_scoringengine_list(self):
raw_output = self.watcher('scoringengine list')
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output], self.list_fields)
def test_scoringengine_detailed_list(self):
raw_output = self.watcher('scoringengine list --detail')
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_scoringengine_show(self):
raw_output = self.watcher('scoringengine show %s' % self.dummy_name)
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output], self.detailed_list_fields)
self.assertNotIn('dummy_avg_scorer', raw_output)

View File

@@ -1,45 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.functional.v1 import base
class ServiceTests(base.TestCase):
"""Functional tests for service."""
decision_engine_name = 'watcher-decision-engine'
applier_name = 'watcher-applier'
list_fields = ['ID', 'Name', 'Host', 'Status']
def test_service_list(self):
raw_output = self.watcher('service list')
self.assertIn(self.decision_engine_name, raw_output)
self.assertIn(self.applier_name, raw_output)
self.assert_table_structure([raw_output], self.list_fields)
def test_service_detailed_list(self):
raw_output = self.watcher('service list --detail')
self.assertIn(self.decision_engine_name, raw_output)
self.assertIn(self.applier_name, raw_output)
self.assert_table_structure([raw_output],
self.list_fields + ['Last seen up'])
def test_service_show(self):
raw_output = self.watcher('service show %s'
% self.decision_engine_name)
self.assertIn(self.decision_engine_name, raw_output)
self.assert_table_structure([raw_output],
self.list_fields + ['Last seen up'])
self.assertNotIn(self.applier_name, raw_output)

View File

@@ -1,48 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.functional.v1 import base
class StrategyTests(base.TestCase):
"""Functional tests for strategy."""
dummy_name = 'dummy'
basic_strategy = 'basic'
list_fields = ['UUID', 'Name', 'Display name', 'Goal']
state_fields = ['Datasource', 'Metrics', 'CDM', 'Name']
def test_strategy_list(self):
raw_output = self.watcher('strategy list')
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output], self.list_fields)
def test_strategy_detailed_list(self):
raw_output = self.watcher('strategy list --detail')
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output],
self.list_fields + ['Parameters spec'])
def test_strategy_show(self):
raw_output = self.watcher('strategy show %s' % self.dummy_name)
self.assertIn(self.dummy_name, raw_output)
self.assert_table_structure([raw_output],
self.list_fields + ['Parameters spec'])
self.assertNotIn('basic', raw_output)
def test_strategy_state(self):
raw_output = self.watcher('strategy state %s' % self.basic_strategy)
self.assertIn(self.basic_strategy, raw_output)
self.assert_table_structure([raw_output], self.state_fields)

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from unittest import mock
from watcherclient.common import api_versioning
from watcherclient import exceptions

View File

@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from unittest import mock
from keystoneauth1 import loading as kaloading
@@ -118,7 +118,7 @@ class ClientTest(utils.BaseTestCase):
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
'os_watcher_api_version': "latest",
'os_infra_optim_api_version': "latest",
}
self._test_get_client(**kwargs)
@@ -129,7 +129,7 @@ class ClientTest(utils.BaseTestCase):
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
'os_watcher_api_version': "1.4",
'os_infra_optim_api_version': "1.4",
}
self._test_get_client(**kwargs)
@@ -267,7 +267,7 @@ class ClientTest(utils.BaseTestCase):
kwargs = {
'watcher_url': 'http://watcher.example.org:9322/',
'os_auth_token': 'USER_AUTH_TOKEN',
'os_watcher_api_version': 'latest',
'os_infra_optim_api_version': 'latest',
'insecure': True,
'max_retries': 10,
'retry_interval': 10,
@@ -277,7 +277,7 @@ class ClientTest(utils.BaseTestCase):
mock_client.assert_called_once_with(
'1', 'http://watcher.example.org:9322/',
**{
'os_watcher_api_version': 'latest',
'os_infra_optim_api_version': 'latest',
'max_retries': 10,
'retry_interval': 10,
'token': 'USER_AUTH_TOKEN',
@@ -309,7 +309,7 @@ class ClientTest(utils.BaseTestCase):
mock_client.assert_called_once_with(
'1', session.get_endpoint.return_value,
**{
'os_watcher_api_version': None,
'os_infra_optim_api_version': None,
'max_retries': None,
'retry_interval': None,
'session': session,
@@ -328,7 +328,7 @@ class ClientTest(utils.BaseTestCase):
mock_client.assert_called_once_with(
'1', session.get_endpoint.return_value,
**{
'os_watcher_api_version': None,
'os_infra_optim_api_version': None,
'max_retries': None,
'retry_interval': None,
'session': session,
@@ -350,3 +350,11 @@ class ClientTest(utils.BaseTestCase):
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)

View File

@@ -14,7 +14,7 @@
# 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

View File

@@ -15,9 +15,9 @@
import copy
import os
from unittest import mock
import fixtures
import mock
from oslo_utils import strutils
import six
import testtools

View File

@@ -14,8 +14,8 @@
# limitations under the License.
import shlex
from unittest import mock
import mock
from osc_lib import utils as oscutils
from oslo_serialization import jsonutils
@@ -25,7 +25,7 @@ from watcherclient.tests.unit import utils
class CommandTestCase(utils.BaseTestCase):
def setUp(self):
def setUp(self, os_infra_optim_api_version='1.0'):
super(CommandTestCase, self).setUp()
self.fake_env = {
@@ -38,7 +38,7 @@ class CommandTestCase(utils.BaseTestCase):
'os_username': 'test',
'os_password': 'test',
'timeout': 600,
'os_watcher_api_version': '1'}
'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(
@@ -53,7 +53,7 @@ class CommandTestCase(utils.BaseTestCase):
self.addCleanup(self.p_construct_http_client.stop)
def run_cmd(self, cmd, formatting='json'):
if formatting:
if formatting and formatting != 'table':
formatter_arg = " -f %s" % formatting
formatter = jsonutils.loads
else:

View File

@@ -14,10 +14,13 @@
# under the License.
import copy
from unittest import mock
import testtools
from testtools import matchers
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
@@ -39,6 +42,18 @@ UPDATED_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
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':
{
@@ -69,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': (
@@ -120,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):
@@ -206,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,
@@ -220,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)

View File

@@ -14,7 +14,9 @@
# limitations under the License.
import datetime
import mock
from unittest import mock
from oslo_utils.uuidutils import generate_uuid
import six
from watcherclient import exceptions
@@ -44,6 +46,7 @@ ACTION_PLAN_1 = {
"description": "Dummy Global Efficacy2"}
],
'deleted_at': None,
'hostname': ''
}
ACTION_PLAN_2 = {
@@ -63,6 +66,7 @@ ACTION_PLAN_2 = {
"description": "Dummy Global Efficacy",
}],
'deleted_at': None,
'hostname': ''
}
@@ -124,6 +128,17 @@ class ActionPlanShellTest(base.CommandTestCase):
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):
@@ -193,6 +208,32 @@ class ActionPlanShellTest(base.CommandTestCase):
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 = ''

View File

@@ -14,7 +14,8 @@
# under the License.
import datetime
import mock
from unittest import mock
import six
from watcherclient import exceptions

View File

@@ -14,13 +14,13 @@
# under the License.
import datetime
import mock
from unittest import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
from watcherclient import v1 as resource
from watcherclient.v1 import resource_fields
AUDIT_TEMPLATE_1 = {
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
@@ -52,73 +52,86 @@ STRATEGY_1 = {
'deleted_at': None,
}
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',
}
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',
}
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',
}
class AuditShellTest(base.CommandTestCase):
SHORT_LIST_FIELDS = resource_fields.AUDIT_SHORT_LIST_FIELDS
SHORT_LIST_FIELD_LABELS = resource_fields.AUDIT_SHORT_LIST_FIELD_LABELS
FIELDS = resource_fields.AUDIT_FIELDS
FIELD_LABELS = resource_fields.AUDIT_FIELD_LABELS
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': '',
}
def setUp(self):
super(self.__class__, self).setUp()
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')
@@ -155,8 +168,8 @@ class AuditShellTest(base.CommandTestCase):
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_audit_list(self):
audit1 = resource.Audit(mock.Mock(), AUDIT_1)
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
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]
@@ -173,7 +186,7 @@ class AuditShellTest(base.CommandTestCase):
self.m_audit_mgr.list.assert_called_once_with(detail=False)
def test_do_audit_list_marker(self):
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
self.m_audit_mgr.list.return_value = [audit2]
exit_code, results = self.run_cmd(
@@ -190,8 +203,8 @@ class AuditShellTest(base.CommandTestCase):
marker='5869da81-4876-4687-a1ed-12cd64cf53d9')
def test_do_audit_list_detail(self):
audit1 = resource.Audit(mock.Mock(), AUDIT_1)
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
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]
@@ -208,7 +221,7 @@ class AuditShellTest(base.CommandTestCase):
self.m_audit_mgr.list.assert_called_once_with(detail=True)
def test_do_audit_show_by_uuid(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.get.return_value = audit
exit_code, result = self.run_cmd(
@@ -222,7 +235,7 @@ class AuditShellTest(base.CommandTestCase):
'5869da81-4876-4687-a1ed-12cd64cf53d9')
def test_do_audit_show_by_name(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.get.return_value = audit
exit_code, result = self.run_cmd(
@@ -275,7 +288,7 @@ class AuditShellTest(base.CommandTestCase):
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f')
def test_do_audit_update(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.update.return_value = audit
exit_code, result = self.run_cmd(
@@ -291,7 +304,7 @@ class AuditShellTest(base.CommandTestCase):
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
def test_do_audit_update_by_name(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.update.return_value = audit
exit_code, result = self.run_cmd(
@@ -306,7 +319,7 @@ class AuditShellTest(base.CommandTestCase):
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
def test_do_audit_create_with_audit_template_uuid(self):
audit = resource.Audit(mock.Mock(), AUDIT_3)
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
@@ -325,7 +338,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_audit_template_name(self):
audit = resource.Audit(mock.Mock(), AUDIT_3)
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
@@ -343,7 +356,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_goal(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -360,7 +373,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_goal_and_strategy(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -379,7 +392,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_type(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -396,7 +409,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_parameter(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -414,8 +427,25 @@ class AuditShellTest(base.CommandTestCase):
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(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -434,7 +464,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_name(self):
audit = resource.Audit(mock.Mock(), AUDIT_1)
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -452,3 +482,219 @@ class AuditShellTest(base.CommandTestCase):
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
)

View File

@@ -14,7 +14,8 @@
# under the License.
import datetime
import mock
from unittest import mock
import six
from watcherclient import shell

View 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))

View File

@@ -0,0 +1,131 @@
# 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.
from unittest import mock
import six
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 = six.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)

View File

@@ -98,6 +98,16 @@ fake_responses_sorting = {
},
}
fake_responses_marker = {
'/v1/goals/?marker=fc087747-61be-4aad-8126-b701731ae836':
{
'GET': (
{},
{"goals": [GOAL2]}
),
},
}
class GoalManagerTest(testtools.TestCase):
@@ -132,6 +142,17 @@ class GoalManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
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)
self.mgr = watcherclient.v1.goal.GoalManager(self.api)

View File

@@ -14,7 +14,8 @@
# limitations under the License.
import datetime
import mock
from unittest import mock
import six
from watcherclient import shell
@@ -93,6 +94,23 @@ class GoalShellTest(base.CommandTestCase):
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)

View File

@@ -14,7 +14,8 @@
# limitations under the License.
import datetime
import mock
from unittest import mock
import six
from watcherclient import shell

View File

@@ -14,7 +14,8 @@
# limitations under the License.
import datetime
import mock
from unittest import mock
import six
from watcherclient import shell

View File

@@ -107,6 +107,16 @@ fake_responses_sorting = {
},
}
fake_responses_marker = {
'/v1/strategies/?marker=2cf86250-d309-4b81-818e-1537f3dba6e5':
{
'GET': (
{},
{"strategies": [STRATEGY2]}
),
},
}
class StrategyManagerTest(testtools.TestCase):
@@ -131,6 +141,19 @@ class StrategyManagerTest(testtools.TestCase):
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)

View File

@@ -14,10 +14,10 @@
# limitations under the License.
import datetime
import mock
import six
from unittest import mock
from oslo_serialization import jsonutils
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -90,6 +90,23 @@ class StrategyShellTest(base.CommandTestCase):
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)

View File

@@ -17,6 +17,7 @@ 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
@@ -38,9 +39,12 @@ 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")
"Service", "ServiceManager", "Strategy", "StrategyManager",
"DataModel", "DataModelManager")

View File

@@ -27,8 +27,13 @@ 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, marker=None):
@@ -90,8 +95,7 @@ class ActionPlanManager(base.Manager):
return self._update(self._path(action_plan_id), patch)
def start(self, action_plan_id):
patch = [{'op': 'replace', 'value': 'PENDING', 'path': '/state'}]
return self._update(self._path(action_plan_id), patch)
return self._start(self._path(action_plan_id, 'start'))
def cancel(self, action_plan_id):
action_plan = self.get(action_plan_id)

View File

@@ -28,8 +28,8 @@ from watcherclient.v1 import resource_fields as res_fields
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')):
eff_name = eff.get('name')
formatted_global_eff[eff_name] = "%(value).2f %(unit)s" % dict(
unit=eff.get('unit'),
value=eff.get('value'))
@@ -70,7 +70,7 @@ class ShowActionPlan(command.ShowOne):
yaml_format.YAMLFormatter().emit_one(
column_names=list(resource.capitalize()
for resource in formatted_global_efficacy),
data=[value for value in formatted_global_efficacy.itervalues()],
data=[value for value in formatted_global_efficacy.values()],
stdout=out,
parsed_args=parsed_args,
)
@@ -200,47 +200,6 @@ class ListActionPlan(command.Lister):
(utils.get_item_properties(item, fields) for item in data))
class CreateActionPlan(command.ShowOne):
"""Create new audit."""
def get_parser(self, prog_name):
parser = super(CreateActionPlan, self).get_parser(prog_name)
parser.add_argument(
'-a', '--audit-template',
required=True,
dest='audit_template_uuid',
metavar='<audit_template>',
help=_('Audit template used for this audit (name or uuid).'))
parser.add_argument(
'-t', '--audit_type',
dest='audit_type',
metavar='<audit_type>',
default='ONESHOT',
choices=['ONESHOT', 'CONTINUOUS'],
help=_("Audit type. It must be ONESHOT or CONTINUOUS. "
"Default is ONESHOT."))
return parser
def take_action(self, parsed_args):
client = getattr(self.app.client_manager, "infra-optim")
field_list = ['audit_template_uuid', 'audit_type']
fields = dict((k, v) for (k, v) in vars(parsed_args).items()
if k in field_list and v is not None)
if fields.get('audit_template_uuid'):
if not uuidutils.is_uuid_like(fields['audit_template_uuid']):
fields['audit_template_uuid'] = client.audit_template.get(
fields['audit_template_uuid']).uuid
audit = client.audit.create(**fields)
columns = res_fields.ACTION_PLAN_FIELDS
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
return column_headers, utils.get_item_properties(audit, columns)
class UpdateActionPlan(command.ShowOne):
"""Update action plan command."""

View File

@@ -20,7 +20,7 @@ from watcherclient import exceptions as exc
CREATION_ATTRIBUTES = ['audit_template_uuid', 'audit_type', 'interval',
'parameters', 'goal', 'strategy', 'auto_trigger',
'name']
'name', 'start_time', 'end_time', 'force']
class Audit(base.Resource):

View File

@@ -13,16 +13,34 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from osc_lib import utils
from oslo_utils import uuidutils
from watcherclient._i18n import _
from watcherclient.common import api_versioning
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 drop_unsupported_field(app_args, fields, field_labels):
fields = copy.copy(fields)
field_labels = copy.copy(field_labels)
api_ver = app_args.os_infra_optim_api_version
if not api_versioning.allow_start_end_audit_time(api_ver):
for field, label in zip(('start_time', 'end_time'),
('Start Time', 'End Time')):
fields.remove(field)
field_labels.remove(label)
if not api_versioning.launch_audit_forced(api_ver):
fields.remove('force')
field_labels.remove('Force')
return fields, field_labels
class ShowAudit(command.ShowOne):
"""Show detailed information about a given audit."""
@@ -47,6 +65,8 @@ class ShowAudit(command.ShowOne):
columns = res_fields.AUDIT_FIELDS
column_headers = res_fields.AUDIT_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(audit, columns)
@@ -118,6 +138,10 @@ class ListAudit(command.Lister):
fields = res_fields.AUDIT_SHORT_LIST_FIELDS
field_labels = res_fields.AUDIT_SHORT_LIST_FIELD_LABELS
if parsed_args.detail:
fields, field_labels = drop_unsupported_field(
self.app_args, fields, field_labels)
params.update(common_utils.common_params_for_list(
parsed_args, fields, field_labels))
@@ -143,8 +167,8 @@ class CreateAudit(command.ShowOne):
dest='audit_type',
metavar='<audit_type>',
default='ONESHOT',
choices=['ONESHOT', 'CONTINUOUS'],
help=_("Audit type. It must be ONESHOT or CONTINUOUS. "
choices=['ONESHOT', 'CONTINUOUS', 'EVENT'],
help=_("Audit type. It must be ONESHOT, CONTINUOUS or EVENT. "
"Default is ONESHOT."))
parser.add_argument(
'-p', '--parameter',
@@ -158,7 +182,7 @@ class CreateAudit(command.ShowOne):
dest='interval',
metavar='<interval>',
help=_('Audit interval (in seconds or cron format). '
'Cron inteval can be used like: "*/5 * * * *". '
'Cron interval can be used like: ``*/5 * * * *``. '
'Only used if the audit is CONTINUOUS.'))
parser.add_argument(
'-g', '--goal',
@@ -187,6 +211,24 @@ class CreateAudit(command.ShowOne):
dest='name',
metavar='<name>',
help=_('Name for this audit.'))
parser.add_argument(
'--start-time',
dest='start_time',
metavar='<start_time>',
help=_('CONTINUOUS audit local start time. '
'Format: YYYY-MM-DD hh:mm:ss'))
parser.add_argument(
'--end-time',
dest='end_time',
metavar='<end_time>',
help=_('CONTINUOUS audit local end time. '
'Format: YYYY-MM-DD hh:mm:ss'))
parser.add_argument(
'--force',
dest='force',
action='store_true',
help=_('Launch audit even if action plan '
'is ongoing. default is False'))
return parser
@@ -194,25 +236,27 @@ class CreateAudit(command.ShowOne):
client = getattr(self.app.client_manager, "infra-optim")
field_list = ['audit_template_uuid', 'audit_type', 'parameters',
'interval', 'goal', 'strategy', 'auto_trigger', 'name']
'interval', 'goal', 'strategy', 'auto_trigger',
'name']
api_ver = self.app_args.os_infra_optim_api_version
if api_versioning.allow_start_end_audit_time(api_ver):
if parsed_args.start_time is not None:
field_list.append('start_time')
if parsed_args.end_time is not None:
field_list.append('end_time')
if api_versioning.launch_audit_forced(api_ver):
if parsed_args.force is not None:
field_list.append('force')
fields = dict((k, v) for (k, v) in vars(parsed_args).items()
if k in field_list and v is not None)
fields = common_utils.args_array_to_dict(fields, 'parameters')
if fields.get('goal'):
if not uuidutils.is_uuid_like(fields['goal']):
fields['goal'] = client.goal.get(fields['goal']).uuid
if fields.get('audit_template_uuid'):
if not uuidutils.is_uuid_like(fields['audit_template_uuid']):
fields['audit_template_uuid'] = client.audit_template.get(
fields['audit_template_uuid']).uuid
# optional
if fields.get('strategy'):
if not uuidutils.is_uuid_like(fields['strategy']):
fields['strategy'] = client.strategy.get(
fields['strategy']).uuid
audit = client.audit.create(**fields)
if audit.strategy_name is None:
@@ -220,6 +264,8 @@ class CreateAudit(command.ShowOne):
columns = res_fields.AUDIT_FIELDS
column_headers = res_fields.AUDIT_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(audit, columns)
@@ -261,6 +307,9 @@ class UpdateAudit(command.ShowOne):
columns = res_fields.AUDIT_FIELDS
column_headers = res_fields.AUDIT_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(audit, columns)

View File

@@ -167,8 +167,8 @@ class CreateAuditTemplate(command.ShowOne):
metavar='<path>',
help=_("Part of the cluster on which an audit will be done.\n"
"Can be provided either in yaml or json file.\n"
"YAML example:\n"
"---\n"
"YAML example::\n"
"\n"
" - compute:\n"
" - host_aggregates:\n"
" - id: 1\n"
@@ -201,48 +201,49 @@ class CreateAuditTemplate(command.ShowOne):
" - uuid: UUID1\n"
" - uuid: UUID2\n"
"\n"
"JSON example:\n"
"[\n"
" {\"compute\":\n"
" [{\"host_aggregates\": [\n"
" {\"id\": 1},\n"
" {\"id\": 2},\n"
" {\"id\": 3}]},\n"
" {\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"exclude\": [\n"
" {\"instances\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"compute_nodes\": [\n"
" {\"name\": \"compute1\"}\n"
" ]}\n"
" ]}]\n"
" },\n"
" {\"storage\":\n"
" [{\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"volume_types\": [\n"
" {\"name\": \"lvm1\"},\n"
" {\"name\": \"lvm2\"}]},\n"
" {\"exclude\": [\n"
" {\"storage_pools\": [\n"
" {\"name\": \"host0@backend0#pool0\"},\n"
" {\"name\": \"host1@backend1#pool1\"}\n"
" ]},\n"
" {\"volumes\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"projects\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" ]}]\n"
" }\n"
"JSON example::\n"
"\n"
" [\n"
" {\"compute\":\n"
" [{\"host_aggregates\": [\n"
" {\"id\": 1},\n"
" {\"id\": 2},\n"
" {\"id\": 3}]},\n"
" {\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"exclude\": [\n"
" {\"instances\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"compute_nodes\": [\n"
" {\"name\": \"compute1\"}\n"
" ]}\n"
" ]}]\n"
" },\n"
" {\"storage\":\n"
" [{\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"volume_types\": [\n"
" {\"name\": \"lvm1\"},\n"
" {\"name\": \"lvm2\"}]},\n"
" {\"exclude\": [\n"
" {\"storage_pools\": [\n"
" {\"name\": \"host0@backend0#pool0\"},\n"
" {\"name\": \"host1@backend1#pool1\"}\n"
" ]},\n"
" {\"volumes\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"projects\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" ]}]\n"
" }\n"
" ]\n"
)
)

View File

@@ -31,19 +31,19 @@ class Client(object):
def __init__(self, endpoint=None, *args, **kwargs):
"""Initialize a new client for the Watcher v1 API."""
if kwargs.get('os_watcher_api_version'):
if kwargs.get('os_infra_optim_api_version'):
kwargs['api_version_select_state'] = "user"
else:
if not endpoint:
raise exceptions.EndpointException(
_("Must provide 'endpoint' if os_watcher_api_version "
_("Must provide 'endpoint' if os_infra_optim_api_version "
"isn't specified"))
# If the user didn't specify a version, use a cached version if
# one has been stored
host, netport = httpclient.get_server(endpoint)
kwargs['api_version_select_state'] = "default"
kwargs['os_watcher_api_version'] = httpclient.DEFAULT_VER
kwargs['os_infra_optim_api_version'] = httpclient.DEFAULT_VER
self.http_client = httpclient._construct_http_client(
endpoint, *args, **kwargs)
@@ -56,3 +56,4 @@ class Client(object):
self.scoring_engine = v1.ScoringEngineManager(self.http_client)
self.service = v1.ServiceManager(self.http_client)
self.strategy = v1.StrategyManager(self.http_client)
self.data_model = v1.DataModelManager(self.http_client)

View File

@@ -0,0 +1,56 @@
# 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.
from watcherclient.common import base
from watcherclient.common import utils
class DataModel(base.Resource):
def __repr__(self):
return "<DataModel %s>" % self._info
class DataModelManager(base.Manager):
resource_class = DataModel
@staticmethod
def _path(filters=None):
if filters:
path = '/v1/data_model/%s' % filters
else:
path = '/v1/data_model'
return path
def list(self, data_model_type='compute', audit=None):
"""Retrieve a list of data model.
:param data_model_type: The type of data model user wants to list.
Supported values: compute.
Future support values: storage, baremetal.
The default value is compute.
:param audit: The UUID of the audit, used to filter data model
by the scope in audit.
:returns: A list of data model.
"""
path = ''
filters = utils.common_filters()
if audit:
filters.append('audit_uuid=%s' % audit)
filters.append('data_model_type=%s' % data_model_type)
path += '?' + '&'.join(filters)
return self._list(self._path(path))[0]

View File

@@ -0,0 +1,77 @@
# 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.
from watcherclient._i18n import _
from watcherclient.common import command
from watcherclient import exceptions
from watcherclient.v1 import resource_fields as res_fields
class ListDataModel(command.Lister):
"""List information on retrieved data model."""
def get_parser(self, prog_name):
parser = super(ListDataModel, self).get_parser(prog_name)
parser.add_argument(
'--type',
metavar='<type>',
dest='type',
help=_('Type of Datamodel user want to list. '
'Supported values: compute. '
'Future support values: storage, baremetal. '
'Default type is compute.'))
parser.add_argument(
'--audit',
metavar='<audit>',
dest='audit',
help=_('UUID of the audit'))
parser.add_argument(
'--detail',
dest='detail',
action='store_true',
default=False,
help=_("Show detailed information about data model."))
return parser
def get_tuple(self, dic, fields):
ret_tup = []
for item in fields:
ret_tup.append(dic.get(item))
return tuple(ret_tup)
def take_action(self, parsed_args):
client = getattr(self.app.client_manager, "infra-optim")
allowed_type = ['compute', 'storage', 'baremetal']
params = {}
if parsed_args.audit:
params["audit"] = parsed_args.audit
if parsed_args.type:
if parsed_args.type not in allowed_type:
raise exceptions.CommandError(
'Type %s error, '
'Please check the valid type!' % parsed_args.type)
params["data_model_type"] = parsed_args.type
try:
data_model = client.data_model.list(**params)
except exceptions.HTTPNotFound as exc:
raise exceptions.CommandError(str(exc))
# TODO(chenker) Add Storage MODEL_FIELDS when using Storage Datamodel.
if parsed_args.detail:
fields = res_fields.COMPUTE_MODEL_LIST_FIELDS
field_labels = res_fields.COMPUTE_MODEL_LIST_FIELD_LABELS
else:
fields = res_fields.COMPUTE_MODEL_SHORT_LIST_FIELDS
field_labels = res_fields.COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS
return (field_labels,
(self.get_tuple(item, fields) for item in data_model.context))

View File

@@ -29,7 +29,8 @@ class GoalManager(base.Manager):
def _path(goal=None):
return '/v1/goals/%s' % goal if goal else '/v1/goals'
def list(self, limit=None, sort_key=None, sort_dir=None, detail=False):
def list(self, limit=None, sort_key=None, sort_dir=None, detail=False,
marker=None):
"""Retrieve a list of goal.
:param limit: The maximum number of results to return per
@@ -49,13 +50,15 @@ class GoalManager(base.Manager):
:param detail: Optional, boolean whether to return detailed information
about audits.
:returns: A list of audits.
:param marker: Optional, UUID of the last goal in the previous page.
:returns: A list of goals.
"""
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)
path = ''
if detail:
path += 'detail'

View File

@@ -96,6 +96,13 @@ class ListGoal(command.Lister):
metavar='<direction>',
choices=['asc', 'desc'],
help=_('Sort direction: "asc" (the default) or "desc".'))
parser.add_argument(
'--marker',
dest='marker',
metavar='<marker>',
default=None,
help=_('UUID of the last goal in the previous page; '
'displays list of goals after "marker".'))
return parser

View File

@@ -32,12 +32,14 @@ AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Goal', 'Strategy']
# Audit
AUDIT_FIELDS = ['uuid', 'name', 'created_at', 'updated_at', 'deleted_at',
'state', 'audit_type', 'parameters', 'interval', 'goal_name',
'strategy_name', 'scope', 'auto_trigger', 'next_run_time']
'strategy_name', 'scope', 'auto_trigger', 'next_run_time',
'hostname', 'start_time', 'end_time', 'force']
AUDIT_FIELD_LABELS = ['UUID', 'Name', 'Created At', 'Updated At', 'Deleted At',
'State', 'Audit Type', 'Parameters', 'Interval', 'Goal',
'Strategy', 'Audit Scope', 'Auto Trigger',
'Next Run Time']
'Next Run Time', 'Hostname', 'Start Time', 'End Time',
'Force']
AUDIT_SHORT_LIST_FIELDS = ['uuid', 'name', 'audit_type',
'state', 'goal_name', 'strategy_name',
@@ -49,11 +51,12 @@ AUDIT_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Audit Type', 'State', 'Goal',
# Action Plan
ACTION_PLAN_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at',
'audit_uuid', 'strategy_name', 'state',
'efficacy_indicators', 'global_efficacy']
'efficacy_indicators', 'global_efficacy', 'hostname']
ACTION_PLAN_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
'Audit', 'Strategy', 'State',
'Efficacy indicators', 'Global efficacy']
'Efficacy indicators', 'Global efficacy',
'Hostname']
ACTION_PLAN_SHORT_LIST_FIELDS = ['uuid', 'audit_uuid', 'state',
'updated_at', 'global_efficacy']
@@ -95,6 +98,28 @@ STRATEGY_FIELDS = ['uuid', 'name', 'display_name', 'goal_name',
STRATEGY_FIELD_LABELS = ['UUID', 'Name', 'Display name', 'Goal',
'Parameters spec']
# Data Model
COMPUTE_MODEL_LIST_FIELDS = [
'server_uuid', 'server_name', 'server_vcpus',
'server_memory', 'server_disk', 'server_state', 'node_uuid',
'node_hostname', 'node_vcpus', 'node_vcpu_ratio', 'node_memory',
'node_memory_ratio', 'node_disk', 'node_disk_ratio', 'node_state']
COMPUTE_MODEL_LIST_FIELD_LABELS = [
'Server_UUID', 'Server Name', 'Server Vcpus',
'Server Memory', 'Server Disk', 'Server State', 'Node UUID',
'Node Host Name', 'Node Vcpus', 'Node Vcpu Ratio', 'Node Memory',
'Node Memory Ratio', 'Node Disk', 'Node Disk Ratio', 'Node State']
COMPUTE_MODEL_SHORT_LIST_FIELDS = [
'server_uuid', 'server_name',
'server_state', 'node_uuid', 'node_hostname']
COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS = [
'Server UUID', 'Server Name',
'Server State', 'Node UUID', 'Node Host Name']
STRATEGY_SHORT_LIST_FIELDS = ['uuid', 'name', 'display_name', 'goal_name']
STRATEGY_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Display name', 'Goal']

View File

@@ -38,7 +38,7 @@ class StrategyManager(base.Manager):
return path
def list(self, goal=None, limit=None, sort_key=None,
sort_dir=None, detail=False):
sort_dir=None, detail=False, marker=None):
"""Retrieve a list of strategy.
:param goal: The UUID of the goal to filter by
@@ -58,14 +58,15 @@ class StrategyManager(base.Manager):
:param detail: Optional, boolean whether to return detailed information
about audits.
:param marker: Optional, UUID of the last strategy in the previous
page.
:returns: A list of audits.
"""
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 goal:
filters.append(parse.urlencode(dict(goal=goal)))

View File

@@ -123,7 +123,13 @@ class ListStrategy(command.Lister):
metavar='<direction>',
choices=['asc', 'desc'],
help='Sort direction: "asc" (the default) or "desc".')
parser.add_argument(
'--marker',
dest='marker',
metavar='<marker>',
default=None,
help=_('UUID of the last strategy in the previous page; '
'displays list of strategies after "marker".'))
return parser
def take_action(self, parsed_args):