Compare commits

..

153 Commits

Author SHA1 Message Date
Alexander Chadin
c02f584b33 Update .gitignore because of doc migration
This patch set updates .gitignore and fixes autodoc_exclude_modules

Change-Id: If0e8a28c541b7e0711cc5a76ef93513c51080974
2017-07-26 11:58:58 +03:00
OpenStack Proposal Bot
a9405f5c2d Updated from global requirements
Change-Id: Iecde214cf1d386bb0a170f1456814a349a3c3f7d
2017-07-23 13:53:11 +00:00
Jenkins
4bb678f7a5 Merge "Add support for cron syntax" 2017-07-21 09:10:08 +00:00
Jenkins
baadbe3dca Merge "Update URLs in documents according to document migration" 2017-07-19 14:04:41 +00:00
Jenkins
24b089c057 Merge "Update permissions for post_test_hook.sh" 2017-07-19 13:42:36 +00:00
Alexander Chadin
c8acc20e82 Update permissions for post_test_hook.sh
This patch updates permissions up to 755 for post_test_hook.sh
to let it be executed using jenkins jobs.

Change-Id: I0224d10089df05729b8d479d6d910a2a2105c3f8
2017-07-19 14:45:56 +03:00
melissaml
fa91740b47 Update URLs in documents according to document migration
Change-Id: Ic1f137214fc16cdaab2e0da8fd5761a0ed153ad8
2017-07-14 11:38:36 +08:00
OpenStack Proposal Bot
504ca86b78 Updated from global requirements
Change-Id: Iee0f3a7ccade75a51bed40d08521306dd710732b
2017-07-13 14:25:08 +00:00
Jenkins
2b155f9505 Merge "Move existing content into the new standard structure" 2017-07-12 15:01:40 +00:00
Alexander Chadin
c8df16da8f Add support for cron syntax
Implements: blueprint cron-based-continuous-audits

Change-Id: Ib281964fbf9c160791d30eafe94fd83937cac23e
2017-07-10 07:30:38 +00:00
OpenStack Proposal Bot
0de3f627c5 Updated from global requirements
Change-Id: Ifd99e023666c7cfccff15cc4e7d2ffe06811dfa4
2017-07-06 01:45:35 +00:00
Jenkins
9c46ee00bb Merge "Add post_test_hook" 2017-07-06 00:22:40 +00:00
Jenkins
48703e19e4 Merge "Fix for README.rst of tests" 2017-07-06 00:21:33 +00:00
Jenkins
799084d3d1 Merge "Fixed wrap from taking negative values" 2017-07-06 00:17:15 +00:00
kavithahr
ee087b85d5 Fixed wrap from taking negative values
Now for wrap input it will take only postive integers as an input and
if any negative numbers are give it will give output as "Wrap argument
should be a positive integer".

Change-Id: I375dab0bdd53ee464f5634162472000119f247cb
2017-07-05 18:06:10 +05:30
Alexander Chadin
8524016170 Add post_test_hook
This patch set adds post_test_hook.sh which allows to
set jenkins job to execute functional jobs.

Change-Id: Icc4cdf8680da3ae83f36b3455fb32aecae2fb57f
2017-07-05 12:11:50 +03:00
Yumeng Bao
ed5e1b86c3 Move existing content into the new standard structure
This patch rearranges and reformats existing content.

Change-Id: I05088140504ba55abc724115828a7a977bd5c087
2017-07-05 16:10:09 +08:00
Alexander Chadin
42bf82c22c Fix for README.rst of tests
This patch set fixes one of steps to prepare functional env.

Change-Id: Ibb2536e61f847cda2590674e50d34d68726926a8
2017-07-05 11:07:38 +03:00
Yumeng Bao
0d0192c472 switch to openstackdocstheme
Depends-On: Ia750cb049c0f53a234ea70ce1f2bbbb7a2aa9454
Change-Id: I93dfcbd516810c41a1d64e872a28a539ec4f3155
2017-07-04 09:56:28 +08:00
Jenkins
641cd44adb Merge "Updated from global requirements" 2017-06-28 12:44:55 +00:00
Jenkins
da715f8e70 Merge "Enable some off-by-default checks" 2017-06-28 12:44:43 +00:00
OpenStack Proposal Bot
abe14a14d4 Updated from global requirements
Change-Id: Iffc25e19f78014db1d50b7778bf550f900f24a35
2017-06-27 12:23:00 +00:00
Yumeng Bao
984601ecfa Turn on warning-is-error in sphinx build
Turn on the flag to ensure that future warnings in the doc build
trigger a build failure.

Change-Id: I6f93e229cfd9bc56208750d1ddfe88404f89ab8e
2017-06-26 20:17:57 +08:00
blue55
f32956493b Enable some off-by-default checks
Some of the available checks are disabled by default, like:
[H106] Don’t put vim configuration in source files
[H203] Use assertIs(Not)None to check for None

Change-Id: I2809ab2cce76dd31a04d3cca0e97cdad26c14cb4
2017-06-23 10:13:40 +08:00
Jenkins
1e2a13fe1a Merge "Add CLI for Action Plan Cancel" 2017-06-19 09:17:59 +00:00
OpenStack Proposal Bot
417b3a2669 Updated from global requirements
Change-Id: Ie3692b21a749dec23583f302a42a822c5922bff5
2017-06-10 11:48:35 +00:00
aditi
3f25b6b223 Add CLI for Action Plan Cancel
This patch CLI for for action plan cancel
watcher action plan cancel <action-plan-uuid>

Change-Id: I9c8d53107b1926d6ce4532d7c696dc36c84c6cf8
Partially-Implements: blueprint cancel-action-plan
2017-05-31 11:19:20 +00:00
OpenStack Proposal Bot
5cbce9fb20 Updated from global requirements
Change-Id: I7e4668bd04f9a439296644f6d9dd181ba0b2ae9d
2017-05-23 12:00:31 +00:00
OpenStack Proposal Bot
f379b1544c Updated from global requirements
Change-Id: I02ae2ad14f93141e3aaadc873d43fec076480686
2017-05-17 03:58:59 +00:00
OpenStack Proposal Bot
30f49c9f70 Updated from global requirements
Change-Id: Ia162a2106be6cb4982c694d5a37d9c2ed845f143
2017-05-03 12:23:47 +00:00
Jenkins
af13d9cdd1 Merge "Updated from global requirements" 2017-05-03 12:21:47 +00:00
OpenStack Proposal Bot
8952b2fbd2 Updated from global requirements
Change-Id: I14c857805f9156640e9c0fd57233ca4cf2b5d99f
2017-05-01 14:08:23 +00:00
M V P Nitesh
66681ef9c9 Add 'rm -f .testrepository/times.dbm' command in testenv
Running py2* post py3* tests results in error. Add
'rm -f .testrepository/times.dbm' command in testenv to
resolve this.

Change-Id: Ie704b12c413c9e8cc0b2649c1d2bbc03a3b61a11
2017-04-25 12:55:27 +05:30
Jenkins
4285b388ca Merge "Updated from global requirements" 2017-04-12 12:23:36 +00:00
OpenStack Proposal Bot
723d39cd65 Updated from global requirements
Change-Id: I32bd693211fe91f86f1f2e1f5921ad56fbeaf1e5
2017-04-12 04:22:27 +00:00
M V P Nitesh
156d660cd3 Optimize the link address
Use https instead of http to ensure the safety

Change-Id: Ieeea614ce26b99b007a89cb12a8e83f120b64d32
2017-04-10 16:15:11 +05:30
Jenkins
07b19ede3f Merge "Remove log translations" 2017-03-29 12:57:12 +00:00
daohanli
94c317250e Remove log translations
Log messages are no longer being translated. This removes all use of
the _LE, _LI, and _LW translation markers to simplify logging and to
avoid confusion with new contributions.

See:
http://lists.openstack.org/pipermail/openstack-i18n/2016-November/002574.html
http://lists.openstack.org/pipermail/openstack-dev/2017-March/113365.html

Change-Id: Icfe6ab464fd9fbbbfb9c9a05dba2220aa9e0bace
2017-03-23 10:43:15 +08:00
Luong Anh Tuan
7250886d14 Indicating the location tests directory in oslo_debug_helper
According to [1], we can passing a "-t" argument to
oslo_debug_helper to indicate the directory where tests
are located. This will solves ImportError exception.

[1] https://docs.openstack.org/developer/oslotest/features.html

Change-Id: Id72596a8c3d9e2e344ef492f2d60b89bc0422aec
Closes-Bug: #1666560
2017-03-22 10:14:22 +00:00
Jenkins
03ac32b1be Merge "Using assert methods instead of assertTrue" 2017-03-08 09:35:39 +00:00
Jenkins
89390ac0a9 Merge "Updated from global requirements" 2017-03-03 08:28:09 +00:00
zhuzeyu
69c10379af Using assert methods instead of assertTrue
It will print a nicer error message if it fails.

Change-Id: I6fb9054e3285db76057db373fe87964e510de516
2017-03-03 06:33:21 +00:00
Jenkins
0ea4020d98 Merge "Remove support for py34" 2017-03-03 05:44:14 +00:00
Jenkins
cbcc990383 Merge "update help description about actionplan create" 2017-03-03 03:19:08 +00:00
OpenStack Proposal Bot
15f519f827 Updated from global requirements
Change-Id: I6055fa04daf29ae76845af307e844d94ba030117
2017-03-03 03:09:00 +00:00
ricolin
1ccf0588f9 [Fix gate]Update test requirement
Since pbr already landed and the old version of hacking seems not
work very well with pbr>=2, we should update it to match global
requirement.
Partial-Bug: #1668848

Change-Id: I21616cbbd11d2ccab60155ea78ed261ee12db5d4
2017-03-02 20:14:13 +08:00
Chris Spencer
cd267c9fa4 Remove RST files located in doc/source/api.
Change-Id: Ib9600297b6b65a44a72090cfb0c7d8f80dcfd34a
Closes-Bug: 1640811
2017-02-14 10:06:50 -07:00
gengchc2
ce477baa1f Remove support for py34
The gating on python 3.4 is restricted to <= Mitaka. This is due
to the change from Ubuntu Trusty to Xenial, where only python3.5
is available. There is no need to continue to keep these settings.

Change-Id: I66b76645b7799c6550583c675bc968310d5e0d1c
2017-02-08 16:33:34 +08:00
licanwei
cd918fe086 update help description about actionplan create
help=_("ActionPlan type.")
=>
help=_("Audit type. It must be ONESHOT or CONTINUOUS. "
       "Default is ONESHOT."))

Change-Id: Ic79b5d1d0cb5ed459aaa290269219ff0e00065d5
2017-02-07 09:56:47 +08:00
Jenkins
1f7df3d53d Merge "Using jsonutils instead of json" 2017-02-03 08:47:00 +00:00
zhuzeyu
dfe9d31ee4 Using jsonutils instead of json
TrivialFix

Change-Id: I1d17994f4cf854f6ba62c75ed18ebee3c7f5fa47
2017-02-03 15:48:12 +08:00
Jenkins
2ea26be247 Merge "Add functional env to tox" 2017-02-02 08:40:25 +00:00
OpenStack Proposal Bot
15b5fb791e Updated from global requirements
Change-Id: I487004e11fc37040601562bc2cf807d5d853335a
2017-01-25 09:37:23 +00:00
zhuzeyu
eafb6b21f7 Remove useless utf-8 coding
TrivialFix

Change-Id: I37d77df21349f3c113582a0a6e51a84be297cc73
2017-01-25 15:20:48 +08:00
Alexander Chadin
08cf7d99d3 Add functional env to tox
This patch set adds functional environment to tox
to be able to create post_hook file for functional gate.

Change-Id: I8e918fe47f6008b9570e39c2d0e9159d0d223362
2017-01-23 18:58:43 +03:00
Jenkins
cec8ee4f15 Merge "Add functional tests to watcherclient" 2017-01-19 14:54:54 +00:00
Jenkins
4713a69a18 Merge "Support parents field along with planner changes" 2017-01-19 14:19:12 +00:00
Jenkins
22bc9a97c9 Merge "Updated from global requirements" 2017-01-17 16:12:11 +00:00
licanwei
11e3568d66 update '--detail' help in goal list
help=_("Show detailed information about metric collectors."))
==>
help=_("Show detailed information about each goal."))

Change-Id: If6bf750a6a7941524dd144430abcc48b9f44a9d3
2017-01-17 16:49:19 +08:00
OpenStack Proposal Bot
96ef016b81 Updated from global requirements
Change-Id: I6239c242d4ab23ec885556daddc45d0f97b8234c
2017-01-16 17:28:36 +00:00
Alexander Chadin
c0f6ea54f6 Add functional tests to watcherclient
This Patch Set implements functional tests for
watcherclient to cover all use cases of watcher objects.

Implements : blueprint tempest-cli-test
Change-Id: I920d5b1ae25c62a72d3474538f866a5415c2e119
2017-01-16 17:08:27 +00:00
Alexander Chadin
b8666d511e Support parents field along with planner changes
This patch set allows python-watcherclient to show
parents field instead of next_uuid field.

Partially Implements: blueprint planner-storage-action-plan
Change-Id: I00cefd2ba6dad5daa6470c851c2fdfb7c22d7a63
Depends-On: Ide2c8fc521488e486eac8f9f89d3f808ccf4b4d7
2017-01-16 20:05:10 +03:00
Jenkins
4a093e168d Merge "Enable coverage report in console output" 2017-01-16 11:09:11 +00:00
Jenkins
1fa93eeb8f Merge "Add auto_trigger support" 2017-01-16 10:18:55 +00:00
Jenkins
d36f2de07e Merge "Add param 'goal' and 'strategy' in list()" 2017-01-13 16:38:00 +00:00
Jenkins
d17bc25fef Merge "Use keystoneauth instead of keystoneclient" 2017-01-13 15:06:21 +00:00
Lucky samadhiya
075b32efe2 changes to make consistent with other openstack component
Change-Id: I4a530ff7912f129823783c742f906d769965872e
2017-01-13 10:10:01 +00:00
Jeremy Liu
a2cbb8d4b2 Use keystoneauth instead of keystoneclient
keystoneauth was extracted from keystoneclient, this CR replaces usage
of keystoneclient in favor of keystoneauth.

Implements: blueprint use-keystoneauth-instead-of-keystoneclient
Change-Id: Iada0f8b45ae30a89908a4ae9e5f55a33e3744e17
2017-01-13 16:14:33 +08:00
Jeremy Liu
ecfa35ecc2 Remove unused files
`MANIFEST.in` was used to handle git files but now pbr can do it instead.
`openstack-common.conf` was used to copy files from oslo-incubator, but
oslo-incubator is no longer maintained. So delete the two files from repo.

Change-Id: I976434e8283c588191d7bfb9321f46cea724406a
2017-01-13 14:39:04 +08:00
Jeremy Liu
546d03c3aa Enable coverage report in console output
Change-Id: I861dbe49eb888e9c51a0c649dffde4479f19ee67
2017-01-13 10:51:12 +08:00
licanwei
3a04f9fb9a Add param 'goal' and 'strategy' in list()
watcher  audit list --goal dummy
list() got an unexpected keyword argument 'goal'

watcher  audit list --strategy dummy
list() got an unexpected keyword argument 'strategy'

Change-Id: Iba4b89da1669f07002718c885107ee3813ca676a
Closes-Bug: #1654962
2017-01-12 11:54:56 +08:00
DeepaJon
17a144e2f8 Removes unnecessary utf-8 encoding
This patches removes unnecessary utf-8 encoding in the
watcherclient/*
doc/source/conf.py

Change-Id: Ic74c9c57f52f7bf021dfa25d4de069b57e5cb474
2017-01-11 14:23:40 +05:30
licanwei
658fd038f3 update audit create '--interval' description
update audit create '--interval' description

Change-Id: I03779a8fe446c051397f02cfb2d73bda804706fe
2017-01-10 18:02:55 +01:00
Jenkins
d2aede7151 Merge "Fix a typo in audittemplate help" 2017-01-10 16:29:41 +00:00
Jenkins
de54d5b8ba Merge "Add unit for continuous audit's interval." 2017-01-10 16:27:10 +00:00
Jenkins
ae003b7e76 Merge "use 'auto' instead of None" 2017-01-10 16:27:01 +00:00
ericxiett
d08c0e3ef1 Add unit for continuous audit's interval.
End user does not get the unit of 'interval' when use command
``watcher audit create -h``. This patch adds unit for the option
'interval'. Aslo use single quotes.

Change-Id: I9e3df031822f2f2704a3c661c6a5953d7e0caae5
Closes-Bug: #1653322
2017-01-03 08:07:10 +08:00
licanwei
c68120edcd Fix a typo in audittemplate help
'Descrition'->'Description'

Change-Id: Ie1f06f14a06e27c52b41b1d8c7da1f160212288d
2016-12-26 10:29:02 +08:00
licanwei
bf7085fabd use 'auto' instead of None
In cli cmds:watcher audit create,
the Strategy is None if no --strategy
It's better to use default instead of None
watcher audit create -g thermal_optimization
+-------------+--------------------------------------+
| Field       | Value                                |
+-------------+--------------------------------------+
| UUID        | 2bc3001d-683a-48ee-b2f2-766cb8d6329e |
| Created At  | 2016-12-22T08:41:21.173601+00:00     |
| Updated At  | None                                 |
| Deleted At  | None                                 |
| State       | PENDING                              |
| Audit Type  | ONESHOT                              |
| Parameters  | {}                                   |
| Interval    | None                                 |
| Goal        | thermal_optimization                 |
| Strategy    | None                                 |
| Audit Scope | []                                   |
+-------------+--------------------------------------+

Change-Id: I5798df81303c425ed0e26e401589e6680b945bf6
2016-12-22 17:52:29 +08:00
Jenkins
ef93a57b46 Merge "too many digits after the decimal point" 2016-12-19 11:03:17 +00:00
Alexander Chadin
6e291f0f13 Add auto_trigger support
This patch set adds support of auto-triggering for action plans.
You can use it by adding new attribute '--auto-trigger' to
watcher audit create command.

Change-Id: I2e4ee53f9f639cb3248c0e4dcc7b9716ff1fc352
Partially-Implements: blueprint automatic-triggering-audit
Depends-On: I36b7dff8eab5f6ebb18f6f4e752cf4b263456293
2016-12-16 17:01:46 +00:00
Jenkins
73dcf25b63 Merge "Replace six.iteritems() with .items()" 2016-12-16 16:34:15 +00:00
zhangguoqing
0ecafcd6a0 Fix TOKENID format which should without dashed
[1] is wrong amendment which had change the TOKENID's format.
Since [2] had been merged, this patch fix the format without dashed.

[1] https://review.openstack.org/#/c/399359/1/watcherclient/tests/keystone_client_fixtures.py
[2] https://review.openstack.org/#/c/398788

Change-Id: I80b953cbf31e2b09160f0b55ffe48eea845c2dd4
2016-12-15 14:40:21 +00:00
licanwei
529f406075 too many digits after the decimal point
'global_efficacy' field print too many digits
 after the decimal point.
 for example 14.2857142857 %
 two digits are enough
 for example 14.29%

Closes-Bug: #1650159

Change-Id: I4af3ef5b250d7e0b921269cc91ec46978d090c10
2016-12-15 09:08:34 +00:00
Antoine Cabot
b66834506a Remove readme reference because of sphinx error
Change-Id: Ide4b84131fd516ae4784dab9673354c5bab0968a
Closes-Bug: #1650173
2016-12-15 09:42:46 +01:00
gengchc2
5b8e3d4d6c Replace six.iteritems() with .items()
1.As mentioned in [1], we should avoid using
six.iteritems to achieve iterators. We can
use dict.items instead, as it will return
iterators in PY3 as well. And dict.items/keys
will more readable. 2.In py2, the performance
about list should be negligible, see the link [2].
[1] https://wiki.openstack.org/wiki/Python3
[2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html

Change-Id: Ic6af69d5b4b36f3dd02aab0f9c446aa54970f388
2016-12-09 11:31:24 +08:00
OpenStack Proposal Bot
fce4e7e10c Updated from global requirements
Change-Id: Ifc867f3be4b569c6ec9a5fce1149d949bce09685
2016-12-02 17:18:38 +00:00
Flavio Percoco
8004b4ec0b Show team and repo badges on README
This patch adds the team's and repository's badges to the README file.
The motivation behind this is to communicate the project status and
features at first glance.

For more information about this effort, please read this email thread:

http://lists.openstack.org/pipermail/openstack-dev/2016-October/105562.html

To see an example of how this would look like check:

https://gist.github.com/f8cdc5cce16393ddd9885f99ad05a610

Change-Id: I58f89eb319919a824fea21961b4338e2d1132f6e
2016-11-25 17:23:24 +01:00
OpenStack Proposal Bot
0584350663 Updated from global requirements
Change-Id: I98b97ab69a726f1634f6c96274d02cde60b064c2
2016-11-18 21:33:33 +00:00
qinchunhua
ccef7be62c Use uuidutils instead of uuid.uuid4()
Openstack common has a wrapper for generating uuids.
We should only use that function when generating
uuids for consistency.

Change-Id: Ic36426c6ab4228826b66a926e74e3331e9a9abdc
Closes-Bug: #1082248
2016-11-18 11:27:48 +08:00
Jenkins
d960977c96 Merge "Remove unnecessary ')'" 2016-11-16 21:15:25 +00:00
OpenStack Proposal Bot
51b295c21c Updated from global requirements
Change-Id: Ib24775ef8844c56b64d1b8b2fea30eb69f252135
2016-11-14 15:13:17 +00:00
Jenkins
e3478de41d Merge "Fix a typo error in a help message in the client" 2016-11-14 13:54:30 +00:00
David.T
c8bf47d72d Remove obsolete object attributes
As we are about to version the Watcher objects, we need to make sure
that upcoming model/object modifications are additive in order to
avoid having to bump the major version of the API. Therefore,
this changeset removes 2 unused DB fields: extra in Audit Template
object and deadline in Audit object.

Change-Id: Ib9750d2dee8b565bd837341f826999b64c4e2cc3
Partially-Implements: blueprint watcher-versioned-objects
2016-11-14 10:03:37 +00:00
Tin Lam
c83499e85f Fix a typo error in a help message in the client
In the class UpdateAuditTemplate, there is a help message with an extra
')'.  This patch set removes that extra ')'.

Change-Id: I1463144e38ebb5db59f49a94406ff884f0e2450b
Closes-Bug: #1639681
2016-11-10 12:45:04 -06:00
Jenkins
28acfe1535 Merge "Updated from global requirements" 2016-11-08 12:55:57 +00:00
OpenStack Proposal Bot
9edd6afa28 Updated from global requirements
Change-Id: I330f1fe0daa0431d47719268cf2c4c92e89387e1
2016-11-06 02:07:40 +00:00
caihui
9923b51944 Remove unnecessary ')'
Change-Id: Ia16cfe517cbd4511fb29b32e4ce9f2cc42151dc4
2016-11-04 14:27:03 +08:00
Tony Xu
7dfcdba21e Add Python 3.5 classifier and venv
Now that there is a passing gate job, we can claim
support for Python 3.5 in the classifier.
This patch also adds the convenience py35 venv.

Change-Id: I2ed2d7c62c32df6593ae7b1e00da92fb56d391c6
2016-10-26 10:36:54 +08:00
gecong1973
dfdee212cc Add __ne__ built-in function
In Python 3 __ne__ by default delegates to __eq__ and inverts the
result, but in Python 2 they urge you to define __ne__ when you
define __eq__ for it to work properly [1].There are no implied
relationships among the comparison operators. The truth of x==y
does not imply that x!=y is false. Accordingly, when defining __eq__(),
one should also define __ne__() so that the operators will behave as
expected.
[1]https://docs.python.org/2/reference/datamodel.html#object.__ne__

Change-Id: I6b43b9ac33b4f6e6331b390366e68f2a5bbb997c
2016-10-17 09:02:05 +08:00
Jenkins
50af3b6bb8 Merge "Add support for Audit Scope" 2016-10-14 14:59:55 +00:00
OpenStack Proposal Bot
494b2ecc88 Updated from global requirements
Change-Id: I13491acd6fe8a7c2c9dcf4211769daaf58190ff3
2016-10-13 05:28:46 +00:00
Jenkins
d109e9d9a0 Merge "Add service support" 2016-10-12 12:48:46 +00:00
Alexander Chadin
c185f32ebb Add support for Audit Scope
This patch set adds audit scope support in python-watcherclient.

Change-Id: I2a94320bec0fd7051a38bee1c872148e39f69d0c
Partially-Implements: blueprint define-the-audit-scope
2016-10-07 12:30:44 +03:00
Jenkins
11056302b4 Merge "Add parameters in Audit creation attributes list" 2016-10-06 09:07:34 +00:00
David.T
466131e788 Add parameters in Audit creation attributes list
Change-Id: Ib4371223af76d20de2f6d019b73164058f1b4e89
Closes-Bug: #1617320
2016-10-05 17:18:20 +02:00
OpenStack Proposal Bot
c88551435d Updated from global requirements
Change-Id: I5e9d875b0049ae3d9fc0d2dde6943db0c2d84730
2016-10-04 14:02:54 +00:00
Jenkins
77d282e452 Merge "Added support for Client creation from KS session" 2016-10-04 08:08:06 +00:00
Vincent Francoise
940a7093e6 Added support for Client creation from KS session
In this changeset, I refactored some of the code to now support
the creation of a watcher client using a Keystone session as it was
already possible in most other OS projects.

Change-Id: I3462daeaac9103138366a0fcaa2fc46729bbd8ce
Related-Bug: #1625643
Closes-Bug: #1623070
2016-09-28 17:54:29 +02:00
OpenStack Proposal Bot
0c0f6cb079 Updated from global requirements
Change-Id: I5c16b623d05241e2744d89160322c5e4c3d08c2d
2016-09-27 10:08:00 +00:00
Jenkins
9c3440df44 Merge "Add constraint target to tox.ini and remove 1 dep" 2016-09-26 09:39:54 +00:00
Tony Xu
f50df0dc74 Add fixtures to test-requirements
Closes-Bug: #1626372
Change-Id: I671a925f36e3d177f55036766e6792bc87ab58a0
2016-09-22 17:38:14 +08:00
David.T
abf3257e1c Add constraint target to tox.ini and remove 1 dep
This adds a pip install command to tox.ini that is only used when the
tox env is passed with the 'constraints' factor appended onto it.
As such this will not effect developer workflows or current unit tests.

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

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

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

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

Change-Id: Id953a99cf2f83533a38a03f9934f4ec818bec03f
2016-09-21 12:15:05 +02:00
Alexander Chadin
4e86151f81 Add service support
This patch set adds service support to watcherclient.
There is two new commands:
1. service list
2. service show

Change-Id: I669fd69ca7e854f8f576a017447ef003c6cf3d3b
Partially-Implements: blueprint watcher-service-list
2016-09-21 10:31:15 +03:00
David.T
a48a9213f0 python-openstackclient ClientManager interface changed
I update the watcher client plugin to correctly used ClientManager
auth parameters

Change-Id: Ie2e4db3b63ea1671147a407480ce0d2569d5b057
Closes-bug: #1625643
2016-09-20 16:17:49 +02:00
avnish
ad4ee3fcc7 Update home page link in cfg file
Change-Id: I4630862bc2e7cd64f158173fdf05af5a054afd92
2016-09-20 08:30:18 +05:30
Cao Xuan Hoang
4df180fc1f Clean imports in code
This patch set modifies lines which are importing objects
instead of modules. As per openstack import guide lines, user should
import modules in a file not objects.

http://docs.openstack.org/developer/hacking/#imports

Change-Id: I17e692108c058ba393eb2a81b8814f879956a991
2016-09-06 10:58:04 +07:00
Jenkins
465d0d46a3 Merge "Add again parameters as Audit creation attributes" 2016-08-26 16:24:31 +00:00
David.T
6e858233dc Add again parameters as Audit creation attributes
'parameters' option for audit creation was removed due to
a bad merge conflict resolution.

Change-Id: I37848ca7c1e8d6cd03996a5a81471edd9802b673
Closes-Bug: #1617320
2016-08-26 16:02:42 +02:00
Jenkins
522bc4374a Merge "Add strategy name in action plan fields" 2016-08-26 09:53:58 +00:00
OpenStack Proposal Bot
c2b5eea3fb Updated from global requirements
Change-Id: I586e71238ca36d93f882acf7757208a26b094e8a
2016-08-25 01:11:54 +00:00
David.T
824017fa1f Add strategy name in action plan fields
Command 'actionplan show' will display associated strategy
name of the action plan.

Depends-On: I95d2739eb552d4a7a02c822b11844591008f648e
Partially Implements: blueprint efficacy-indicator

Change-Id: Ic1674a53373909bcae06e017fc5e2c53aa8df14a
2016-08-19 12:03:51 +02:00
OpenStack Proposal Bot
545f153bba Updated from global requirements
Change-Id: I219ac296553f8134fb69cf280f5c5523994760da
2016-08-18 12:40:48 +00:00
licanwei
008b50ea67 remove redundant ')'
There is more one ')' in the class UpdateAudit
help=_("Operation: 'add'), 'replace', or 'remove'."))

Change-Id: Ifdce30981fd63f3fb95520c49740b4bf67c23778
2016-08-15 15:29:05 +08:00
Jenkins
b836d83bc2 Merge "Add goal_id, strategy_id and host_aggregate CLI options to audit" 2016-08-04 16:07:40 +00:00
Prashanth Hari
6fbcfc794a Add goal_id, strategy_id and host_aggregate CLI options to audit
Add goal_id, strategy_id and host_aggregate CLI options to audit
so that an audit doesn't lose important information when an audit
template is deleted. Also, user can input a specific strategy to
accomplish a goal for an audit.

Partially-Implements: blueprint persistent-audit-parameters
Depends-On: I7b3eae4d0752a11208f5f92ee13ab1018d8521ad
Change-Id: Ied1da980f358343316ee726b0781188107bfba8d
2016-08-04 14:09:44 +00:00
zte-hanrong
aa90c536b3 Optimiz the help information for audit type parameter.
I want to create a audit from audit template, I don't know correct
audit type value I can use. So I optimize the help information for
audit create shell command.

Change-Id: Idee3bb2e62c9cdb9bf70cced4acbc4df4f8a968c
2016-08-04 16:41:51 +08:00
OpenStack Proposal Bot
9c32e6e6dc Updated from global requirements
Change-Id: I87e27feb5e84a5676f4316f655ebdae74e4f4542
2016-08-03 09:07:50 +00:00
Tomasz Kaczynski
7b55f92e11 Add scoring engine commands
Scoring Module is exposing information about scoring engines
through API. Additional commands make them available in CLI as well.

Partially-Implements: blueprint scoring-module
Change-Id: I6dae5617fdbd6917ce35741e62a2217e19dce510
Depends-On: I32168adeaf34fd12a731204c5b58fe68434ad087
2016-08-01 14:22:34 +00:00
Swapnil Kulkarni (coolsvap)
daa4a88447 Remove discover from test-requirements
It's only needed for python < 2.7 which is not supported

Change-Id: I4675fbf6eebc35dc2ea8dd0134dbca3a19af6ba5
2016-07-21 15:38:09 +00:00
OpenStack Proposal Bot
776a251d98 Updated from global requirements
Change-Id: Ife71cebe2866b55874c123366773871a78b9a67c
2016-07-21 04:10:51 +00:00
OpenStack Proposal Bot
613810e43c Updated from global requirements
Change-Id: I0b9abd912e1aa4e5afc0cf7bc842e5d2334e3366
2016-07-19 21:14:40 +00:00
Jenkins
df8997129e Merge "Updated from global requirements" 2016-07-19 19:40:48 +00:00
Alexandr Stavitskiy
e2586130e1 Fix for importing osc-lib instead openstackclient
This patch set stop importing from the python-openstackclient and
start importing from osc-lib.

Change-Id: I6e4000565e48467f3e32a9cc408ab8a04b9c5f8a
Closes-Bug: #1604047
2016-07-19 18:52:42 +03:00
OpenStack Proposal Bot
ea455477a0 Updated from global requirements
Change-Id: I716998ec31b06f3907140f464664a80f4fad6753
2016-07-13 17:22:48 +00:00
Alexandr Stavitskiy
5b914e8877 Fix for error importing of exception class
This patch set fixes error while importing of ConnectionRefused
exception class.

Change-Id: I4aee677ccc25334ba9bd12633b1c487db2b1dd3e
Closes-Bug: #1602214
2016-07-12 16:12:04 +03:00
Vladimir Ostroverkhov
918d2f0bbd Add support continuously-optimization
This patch set adds support for interval
field to python-watcherclient

Depends-On: I5f4ec97b2082c8a6b3ccebe36b2a343fa4a67d19
Implements: blueprint continuously-optimization

Change-Id: I1152c9c6d9379e250ba46adbc58789d8b13bfd5a
2016-07-12 07:20:28 +00:00
hongzhezheng
91d1d8b0e1 Remove the blank space between the function name and the parenthesis.
TrivialFix

Change-Id: I73e659d2cf30a2878767a9362d3671bdd06c0513
2016-07-12 15:23:37 +08:00
PanFengyun
3ffe4869f3 Prints '-' instead of 'None' when data is None
Client prints 'None' when data is None , and client also prints
'None' when data is a string 'None'. But string 'None' is  different
to None. To clear the confusion, Client should print '-' instead of
'None' when data is None. Nova Client and Cinder Client has cleared
the confusion.

Change-Id: I6a8908fcb2df39d7f2c5fc4055cc41779d668d6c
2016-07-10 19:29:01 +08:00
Edwin Zhai
bde259e38c Enable strategy parameter
Print strategy parameter spec and add new cmd line for parameters
input

Partially-implements: optimization-threshold

Change-Id: If2b8976c6d504754d9db46bb77d3c4ce04b1e2cf
2016-07-07 11:14:56 +02:00
digambar
75dd8dc561 Fix field type to audit_type
There's a number of places where a field of an object is named "type",
which isn't good thing. Actually type is keyword in python, so wherever
I found type used in models/api/object/test, I have fixed it to
audit_type in the audit files

Change-Id: Ic09458b455820787f5483e48aed940a2196b297e
Closes-Bug: #1533392
2016-07-06 21:55:53 +05:30
Daniel Pawlik
a6aed1772d Add license file
License file is the same as in Watcher project.

Change-Id: I4d20e5c269298b60ac9c169c996cd92b24fc502f
2016-07-04 18:04:10 +00:00
OpenStack Proposal Bot
63a1039fb2 Updated from global requirements
Change-Id: I499eefd614f64eb6bd167aa93ecbe892b78d297e
2016-06-30 18:50:11 +00:00
Chihiro Yokoyama
9d3a6c51b1 Remove tempest-lib
tempest-lib is still referenced in test-requirements.txt
although it is deprecated and also unused.

Change-Id: I13796eb7fc6360cac5f0898ebc8dbb8515dc6733
Closes-Bug: #1590867
2016-06-27 09:53:21 +00:00
OpenStack Proposal Bot
8bcdf13d7f Updated from global requirements
Change-Id: I0c1760cdfcea5c85031a08dd9631aaa54880842f
2016-06-23 15:38:14 +00:00
Edwin Zhai
f9fb0875e0 Revert "Add support continuously-optimization"
This reverts commit 888a58fd2a.
Will restore it once related changes in watcher get merged.

Change-Id: Icdde1678598867c5e1ce68c8bd4c189f7a37bedc
Closes-Bug: #1595368
2016-06-23 02:36:39 +00:00
Vladimir Ostroverkhov
d36d835d08 Replaced UUID of goal with name
This patch updated the CLI to display the name of the goal
associated to an strategy (instead UUID).
Related-Bug: #1590416.

Change-Id: Ia8bd0c18d63461da2336a690364f034d0f7f2343
2016-06-20 11:24:34 +03:00
Jenkins
20931e2ce7 Merge "Updated CLI to display efficacy related fields" 2016-06-15 08:59:32 +00:00
Jenkins
fb84ae9b25 Merge "Add support continuously-optimization" 2016-06-13 12:05:08 +00:00
Antoine Cabot
e64e597aa6 Remove useless index on root doc page
Change-Id: I37a882cd6725220d7fe99604f067030d50aed019
2016-06-10 10:47:22 +02:00
Vincent Francoise
f9aac4fc9f Updated CLI to display efficacy related fields
In this changeset, I updated the CLI as followed:

- The 'efficacy_specification' field is now shown by the
  'goal list --detail' and the 'goal show <goal>' subcommands.
- Both the 'global_efficacy' and the 'efficacy_indicators' fields
  are now displayed when issuing 'actionplan list --detail' or
  'actionplan show <action_plan_uuid>'.

Change-Id: I47462d0af92318773ec9284828180b62166ca936
Partially-Implements: blueprint efficacy-indicator
2016-06-08 15:03:17 +02:00
OpenStack Proposal Bot
0f276e483b Updated from global requirements
Change-Id: Ic2ef241b1026929ba8af14ca4b3a1322a9c4cdfd
2016-06-01 13:54:46 +00:00
Vladimir Ostroverkhov
888a58fd2a Add support continuously-optimization
This patch set adds support for period
field to python-watcherclient

Change-Id: I777429f9d2d596bea506664e8614da7e78a31904
Implements: blueprint continuously-optimization
2016-05-27 18:29:21 +03:00
147 changed files with 4430 additions and 2398 deletions

6
.gitignore vendored
View File

@@ -6,6 +6,7 @@
# Packages
*.egg
*.egg-info
.eggs
dist
build
eggs
@@ -42,7 +43,7 @@ output/*/index.html
# Sphinx
doc/build
doc/source/api
doc/source/reference/api
# pbr generates these
AUTHORS
@@ -55,3 +56,6 @@ ChangeLog
sftp-config.json
/.idea/
# Desktop Service Store
*.DS_Store

176
LICENSE Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

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

View File

@@ -1,3 +1,12 @@
========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/python-watcherclient.svg
:target: https://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
====================
python-watcherclient
====================

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

31
doc/source/cli/index.rst Normal file
View File

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

View File

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

View File

@@ -1,6 +1,6 @@
==============================================
===============================================
:program:`watcher` Command-Line Interface (CLI)
==============================================
===============================================
.. program:: watcher
.. highlight:: bash
@@ -23,9 +23,9 @@ OpenStack infra-optim Service (Watcher).
In order to use the CLI, you must provide your OpenStack username, password,
project (historically called tenant), and auth endpoint. You can use
configuration options :option:`--os-username`, :option:`--os-password`,
:option:`--os-tenant-id` (or :option:`--os-tenant-name`),
and :option:`--os-auth-url`, or set the corresponding
configuration options :option:``--os-username``, :option:``--os-password``,
:option:``--os-tenant-id`` (or :option:``--os-tenant-name``),
and :option:``--os-auth-url``, or set the corresponding
environment variables::
$ export OS_USERNAME=user
@@ -36,14 +36,14 @@ environment variables::
The command-line tool will attempt to reauthenticate using the provided
credentials for every request. You can override this behavior by manually
supplying an auth token using :option:`--watcher-url` and
:option:`--os-auth-token`, or by setting the corresponding environment variables::
supplying an auth token using :option:``--watcher-url`` and
:option:``--os-auth-token``, or by setting the corresponding environment variables::
$ export WATCHER_URL=http://watcher.example.org:9322/
$ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
Since Keystone can return multiple regions in the Service Catalog, you can
specify the one you want with :option:`--os-region-name` or set the following
specify the one you want with :option:``--os-region-name`` or set the following
environment variable. (It defaults to the first in the list returned.)
::

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -12,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient import version as watcherclient_version
# -- General configuration ----------------------------------------------------
@@ -20,9 +20,8 @@ from watcherclient import version as watcherclient_version
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'oslosphinx',
]
'openstackdocstheme',
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
@@ -70,7 +69,8 @@ pygments_style = 'sphinx'
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = ['_static']
html_theme_options = {'incubating': True}
html_theme = 'openstackdocs'
# html_theme_path = [openstackdocstheme.get_html_theme_path()]
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
@@ -87,3 +87,13 @@ latex_documents = [
u'OpenStack Foundation', 'manual'
),
]
# openstackdocstheme options
repository_name = 'openstack/python-watcherclient'
bug_project = 'python-watcherclient'
bug_tag = ''
# Must set this variable to include year, month, day, hours, and minutes.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
#html_theme_options = {"show_other_versions": "True"}

View File

@@ -1,8 +1,8 @@
.. _contributing:
===================================
====================================
Contributing to python-watcherclient
===================================
====================================
If you're interested in contributing to the python-watcherclient project,
the following will help get you started.

View File

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

View File

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

View File

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

View File

@@ -80,12 +80,6 @@ Refer to the modules themselves, for more details.
watcherclient Modules
=====================
.. toctree::
:maxdepth: 1
modules <api/autoindex>
.. _watcherclient.v1.audit: api/watcherclient.v1.audit.html#watcherclient.v1.audit.Audit
.. _watcherclient.v1.client.Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
.. _Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client

View File

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

View File

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

View File

@@ -2,12 +2,13 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.utils>=3.5.0 # Apache-2.0
pbr>=1.6 # Apache-2.0
PrettyTable>=0.7,<0.8 # BSD
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
python-openstackclient>=2.1.0 # Apache-2.0
Babel!=2.4.0,>=2.3.4 # BSD
cliff>=2.8.0 # Apache-2.0
osc-lib>=1.7.0 # Apache-2.0
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
pbr!=2.1.0,>=2.0.0 # Apache-2.0
PrettyTable<0.8,>=0.7.1 # BSD
keystoneauth1>=3.0.1 # Apache-2.0
six>=1.9.0 # MIT
PyYAML>=3.10.0 # MIT

View File

@@ -5,7 +5,7 @@ description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
home-page = http://docs.openstack.org/developer/python-watcherclient
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -16,7 +16,7 @@ classifier =
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[files]
packages =
@@ -27,7 +27,7 @@ console_scripts =
watcher = watcherclient.shell:main
openstack.cli.extension =
infra_optim = watcherclient.plugin
infra_optim = watcherclient.osc.plugin
# Entry points for the 'openstack' command
openstack.infra_optim.v1 =
@@ -54,10 +54,17 @@ openstack.infra_optim.v1 =
optimize_actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
optimize_actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
optimize_actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
optimize_actionplan_cancel = watcherclient.v1.action_plan_shell:CancelActionPlan
optimize_action_show = watcherclient.v1.action_shell:ShowAction
optimize_action_list = watcherclient.v1.action_shell:ListAction
optimize_scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
optimize_scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
optimize_service_show = watcherclient.v1.service_shell:ShowService
optimize_service_list = watcherclient.v1.service_shell:ListService
# The same as above but used by the 'watcher' command
watcherclient.v1 =
goal_show = watcherclient.v1.goal_shell:ShowGoal
@@ -84,17 +91,28 @@ watcherclient.v1 =
actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
actionplan_cancel = watcherclient.v1.action_plan_shell:CancelActionPlan
action_show = watcherclient.v1.action_shell:ShowAction
action_list = watcherclient.v1.action_shell:ListAction
scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
service_show = watcherclient.v1.service_shell:ShowService
service_list = watcherclient.v1.service_shell:ListService
[pbr]
autodoc_index_modules = True
autodoc_exclude_modules =
watcherclient.tests.*
api_doc_dir = reference/api
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 1
[bdist_wheel]
universal = 1

View File

@@ -25,5 +25,5 @@ except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=1.8'],
setup_requires=['pbr>=2.0.0'],
pbr=True)

View File

@@ -2,18 +2,18 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage>=3.6 # Apache-2.0
discover # BSD
hacking<0.11,>=0.10.2
mock>=1.2 # BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
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 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
tempest-lib>=0.14.0 # Apache-2.0
sphinx>=1.6.2 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
tempest>=16.1.0 # Apache-2.0
# Needed for pypi packaging
wheel # MIT

37
tox.ini
View File

@@ -1,16 +1,19 @@
[tox]
minversion = 1.6
envlist = py34,py27,pep8
minversion = 1.8
envlist = py35,py27,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
install_command =
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
OS_TEST_PATH=./watcherclient/tests/unit
deps = -r{toxinidir}/test-requirements.txt
commands = rm -f .testrepository/times.dbm
python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
commands = flake8
@@ -19,17 +22,35 @@ commands = flake8
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
commands =
python setup.py testr --coverage --testr-args='{posargs}'
coverage report
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:debug]
commands = oslo_debug_helper {posargs}
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
[testenv:functional]
basepython = python2.7
passenv =
OS_PROJECT_DOMAIN_NAME
OS_USER_DOMAIN_NAME
OS_PROJECT_NAME
OS_USERNAME
OS_PASSWORD
OS_AUTH_URL
OS_IDENTITY_API_VERSION
OS_IMAGE_API_VERSION
setenv =
OS_TEST_PATH = ./watcherclient/tests/functional
commands = python setup.py testr --slowest --testr-args='--concurrency=1 {posargs}'
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
enable-extensions = H203,H106
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.

View File

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

View File

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

View File

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

View File

@@ -317,7 +317,7 @@ class CrudManager(BaseManager):
def _filter_kwargs(self, kwargs):
"""Drop null values and handle ids."""
for key, ref in six.iteritems(kwargs.copy()):
for key, ref in kwargs.copy().items():
if ref is None:
kwargs.pop(key)
else:
@@ -475,7 +475,7 @@ class Resource(object):
return None
def _add_details(self, info):
for (k, v) in six.iteritems(info):
for (k, v) in info.items():
try:
setattr(self, k, v)
self._info[k] = v
@@ -497,7 +497,7 @@ class Resource(object):
def get(self):
"""Support for lazy loading details.
Some clients, such as novaclient have the option to lazy load the
Some clients, such as watcherclient have the option to lazy load the
details, details which can be loaded with this function.
"""
# set_loaded() first ... so if we have to bail, we know we tried.
@@ -521,6 +521,9 @@ class Resource(object):
return self.id == other.id
return self._info == other._info
def __ne__(self, other):
return not self.__eq__(other)
def is_loaded(self):
return self._loaded

View File

@@ -421,7 +421,7 @@ class HttpVersionNotSupported(HttpServerError):
# _code_map contains all the classes that have http_status attribute.
_code_map = dict(
(getattr(obj, 'http_status', None), obj)
for name, obj in six.iteritems(vars(sys.modules[__name__]))
for name, obj in vars(sys.modules[__name__]).items()
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
)
@@ -433,11 +433,7 @@ def from_response(response, method, url):
:param method: HTTP method used for request
:param url: URL used for request
"""
req_id = response.headers.get("x-openstack-request-id")
# NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.

View File

@@ -169,15 +169,18 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
for o in objs:
row = []
for field in fields:
data = '-'
if field in formatters:
row.append(formatters[field](o))
data = formatters[field](o)
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
if data is None:
data = '-'
row.append(data)
pt.add_row(row)
if six.PY3:
@@ -195,12 +198,14 @@ def print_dict(dct, dict_property="Property", wrap=0):
"""
pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l'
for k, v in six.iteritems(dct):
for k, v in dct.items():
# convert dict to str to check length
if isinstance(v, dict):
v = six.text_type(v)
if wrap > 0:
v = textwrap.fill(six.text_type(v), wrap)
elif wrap < 0:
raise ValueError(_("Wrap argument should be a positive integer"))
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, six.string_types) and r'\n' in v:
@@ -210,6 +215,8 @@ def print_dict(dct, dict_property="Property", wrap=0):
pt.add_row([col1, line])
col1 = ''
else:
if v is None:
v = '-'
pt.add_row([k, v])
if six.PY3:
@@ -267,5 +274,5 @@ def pretty_choice_list(l):
def exit(msg=''):
if msg:
print (msg, file=sys.stderr)
print(msg, file=sys.stderr)
sys.exit(1)

View File

@@ -43,4 +43,8 @@ class Lister(Command, lister.Lister):
class ShowOne(Command, show.ShowOne):
pass
def get_parser(self, prog_name, formatter_class=None):
parser = super(ShowOne, self).get_parser(prog_name)
if formatter_class:
parser.formatter_class = formatter_class
return parser

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
@@ -18,9 +17,11 @@
from __future__ import print_function
import argparse
import json
import os
import uuid
import yaml
from oslo_serialization import jsonutils
from oslo_utils import importutils
from watcherclient._i18n import _
@@ -88,7 +89,7 @@ def split_and_deserialize(string):
raise exc.CommandError(_('Attributes must be a list of '
'PATH=VALUE not "%s"') % string)
try:
value = json.loads(value)
value = jsonutils.loads(value)
except ValueError:
pass
@@ -192,3 +193,10 @@ def is_uuid_like(val):
return str(uuid.UUID(val)) == val
except (TypeError, ValueError, AttributeError):
return False
def serialize_file_to_dict(filename):
filename = os.path.expanduser(filename)
with open(filename, "rb") as stream:
scope = yaml.safe_load(stream.read())
return scope

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -53,6 +52,20 @@ A generic error message, given when no more specific message is suitable.
An alias of :py:exc:`watcherclient.common.apiclient.ValidationError`
"""
Conflict = exceptions.Conflict
ConnectionRefused = exceptions.ConnectionRefused
EndpointException = exceptions.EndpointException
EndpointNotFound = exceptions.EndpointNotFound
ServiceUnavailable = exceptions.ServiceUnavailable
class UnsupportedVersion(Exception):
"""Unsupported API Version
Indicates that the user is trying to use an unsupported version of the API.
"""
pass
class AmbiguousAuthSystem(exceptions.ClientException):
"""Could not obtain token and endpoint using provided credentials."""
@@ -86,11 +99,10 @@ def from_response(response, message=None, traceback=None, method=None,
'Content-Type': response.getheader('content-type', "")}
if hasattr(response, 'status_code'):
# NOTE(jiangfei): These modifications allow SessionClient
# to handle faultstring.
# NOTE(hongbin): This allows SessionClient to handle faultstring.
response.json = lambda: {'error': error_body}
if (response.headers['Content-Type'].startswith('text/') and
if (response.headers.get('Content-Type', '').startswith('text/') and
not hasattr(response, 'text')):
# NOTE(clif_h): There seems to be a case in the
# common.apiclient.exceptions module where if the
@@ -100,4 +112,4 @@ def from_response(response, message=None, traceback=None, method=None,
# This is to work around that problem.
response.text = ''
return exceptions.from_response(response, message, url)
return exceptions.from_response(response, method, url)

View File

@@ -13,7 +13,7 @@
import logging
from openstackclient.common import utils
from osc_lib import utils
LOG = logging.getLogger(__name__)
@@ -26,25 +26,16 @@ API_VERSIONS = {
def make_client(instance):
"""Returns an infra-optim service client"""
watcher_client = utils.get_client_class(
"""Returns an infra-optim service client."""
infraoptim_client_class = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('Instantiating infra-optim client: %s', watcher_client)
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
endpoint = instance.get_endpoint_for_service_type(
API_NAME,
region_name=instance._region_name,
interface=instance._interface,
)
client = watcher_client(
endpoint=endpoint,
client = infraoptim_client_class(
os_watcher_api_version=instance._api_version[API_NAME],
session=instance.session,
auth_url=instance._auth_url,
username=instance._username,
password=instance._password,
region_name=instance._region_name,
)

View File

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

View File

@@ -0,0 +1,61 @@
..
Except where otherwise noted, this document is licensed under Creative
Commons Attribution 3.0 License. You can view the license at:
https://creativecommons.org/licenses/by/3.0/
=======
Testing
=======
.. _functional_tests:
Functional tests
================
The following procedure gets you started with Tempest testing but you can also
refer to the `Tempest documentation`_ for more details.
.. _Tempest documentation: https://docs.openstack.org/tempest/latest/
Tempest installation
--------------------
You need to install virtualenv, create a virtual environment and activate it::
$ pip install virtualenv
$ virtualenv watcher-env
$ source 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

@@ -0,0 +1,53 @@
#!/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 jenkins:jenkins $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 jenkins: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 jenkins tox -efunctional
EXIT_CODE=$?
set -e
# Collect and parse result
generate_testr_results
exit $EXIT_CODE

View File

@@ -0,0 +1,108 @@
# 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 re
import shlex
import subprocess
import testtools
import six
from tempest.lib.cli import output_parser
from tempest.lib import exceptions
def execute(cmd, fail_ok=False, merge_stderr=False):
"""Executes specified command for the given action."""
cmdlist = shlex.split(cmd)
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

@@ -0,0 +1,71 @@
# 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

@@ -0,0 +1,122 @@
# 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 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]
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

@@ -0,0 +1,114 @@
# 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', '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', '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

@@ -0,0 +1,89 @@
# 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

@@ -0,0 +1,41 @@
# 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

@@ -0,0 +1,40 @@
# 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

@@ -0,0 +1,45 @@
# 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

@@ -0,0 +1,41 @@
# 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'
list_fields = ['UUID', 'Name', 'Display name', 'Goal']
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)

View File

@@ -1,142 +0,0 @@
# -*- coding: utf-8 -*-
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
from watcherclient.client import get_client
from watcherclient import exceptions as exc
from watcherclient.tests import utils
def fake_get_ksclient(**kwargs):
return utils.FakeKeystone('KSCLIENT_AUTH_TOKEN')
class ClientTest(utils.BaseTestCase):
def test_get_client_with_auth_token_watcher_url(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'watcher_url': 'http://watcher.example.org:6385/',
'os_auth_token': 'USER_AUTH_TOKEN',
}
client = get_client('1', **kwargs)
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://watcher.example.org:6385/',
client.http_client.endpoint)
def test_get_client_no_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
}
client = get_client('1', **kwargs)
self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://localhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_with_region_no_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_region_name': 'REGIONONE',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
}
client = get_client('1', **kwargs)
self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://regionhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_with_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': 'USER_AUTH_TOKEN',
}
client = get_client('1', **kwargs)
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://localhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_with_region_name_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_region_name': 'REGIONONE',
'os_auth_token': 'USER_AUTH_TOKEN',
}
client = get_client('1', **kwargs)
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://regionhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_no_url_and_no_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': '',
'os_auth_token': '',
}
self.assertRaises(exc.AmbiguousAuthSystem, get_client, '1', **kwargs)
# test the alias as well to ensure backwards compatibility
self.assertRaises(exc.AmbigiousAuthSystem, get_client, '1', **kwargs)
def test_ensure_auth_ref_propagated(self):
ksclient = fake_get_ksclient
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
}
client = get_client('1', **kwargs)
self.assertEqual(ksclient().auth_ref, client.http_client.auth_ref)

View File

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

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