Compare commits

...

84 Commits
2.0.0 ... 4.2.0

Author SHA1 Message Date
Zuul
1e8b06243e Merge "Fix create_threshold method when using cost as 0" 2020-12-22 16:20:55 +00:00
Rafael Weingärtner
a15f11a7d2 Fix create_threshold method when using cost as 0
When using 0 as the cost, the `create_threshold` method
throws an exception. That happens because 0 (zero) is evaluated
to False. Therefore, we need to change the validation method to
check if the values are None.

Change-Id: Iedd541c0ad16db0d11d6e6de332eddf880af1698
2020-12-11 10:25:10 -03:00
wuchunyang
c2b5ed9535 Replace deprecated UPPER_CONSTRAINTS_FILE variable
UPPER_CONSTRAINTS_FILE is old name and deprecated
-https://zuul-ci.org/docs/zuul-jobs/python-roles.html#rolevar-tox.tox_constraints_file
This allows to use lower-constraints file as more
readable way instead of UPPER_CONSTRAINTS_FILE=<lower-constraints file>.

Change-Id: I25d4154797da0453f59e310d2392d30ca35e5258
2020-11-27 12:41:36 +00:00
Zuul
bddf634143 Merge "Drop mock from lower-constraints.txt" 2020-09-25 11:55:49 +00:00
Zuul
5442134a5f Merge "bump py37 to py38 in tox.ini" 2020-09-25 11:55:48 +00:00
wangzihao
3ec8f86e5a bump py37 to py38 in tox.ini
in 'victoria' cycle, we should test py38 by default.

Change-Id: Ib01cf2aad3edf31a067a6ac4459529851f8d8ecb
2020-09-22 00:50:20 +00:00
wangzihao
5b35e817b2 Drop mock from lower-constraints.txt
The mock is not needed for py36 and later.

Change-Id: I24afe1f1255ac47dfba300946149732f70f5f399
2020-09-21 11:46:13 +08:00
wangzihao
953c6c9443 Bump hacking min version to 3.0.1
hacking 3.0.1 fix the pinning of flake8 to avoid bringing in a new
version with new checks.

bumping the min version for hacking so that any older hacking versions
which auto adopt the new checks are not used.

Change-Id: I5875f1c0261ff6039773d7daf412102f2c02651f
2020-09-18 10:36:40 +08:00
30e21ddf7b Add Python3 wallaby unit tests
This is an automatically generated patch to ensure unit testing
is in place for all the of the tested runtimes for wallaby.

See also the PTI in governance [1].

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

Change-Id: I4c238eefa02cece34ee8f77a0dee91a5bf698a65
2020-09-08 22:47:17 +00:00
c24de5fe54 Update master for stable/victoria
Add file to the reno documentation build to show release notes for
stable/victoria.

Use pbr instruction to increment the minor version number
automatically so that master versions are higher than the versions on
stable/victoria.

Change-Id: Ifdc6cb5adbb2139a2437d220b231c1c61526e648
Sem-Ver: feature
2020-09-08 22:47:15 +00:00
Justin Ferrieu
2a3dd279dc Add support for GET /v2/dataframes API endpoint to the client
Support for the ``GET /v2/dataframes`` endpoint has been added
to the client. A new ``dataframes get`` CLI command is also available.

Story: 2005890
Task: 36384
Depends-On: https://review.opendev.org/#/c/679636
Change-Id: Idfe93025e0f740906d0f53f33547c7746fc15169
2020-09-08 12:49:29 +00:00
Andreas Jaeger
def535752f Update hacking for Python3
The repo is Python 3 now, so update hacking to version 3.0 which
supports Python 3.

Fix problems found.

Change-Id: I8fed95ecb736e0b6d7c4b63a55553de1539139be
2020-09-07 11:29:21 -03:00
Zuul
d3408517d6 Merge "Remove translation sections from setup.cfg" 2020-09-01 13:43:38 +00:00
Zuul
37df7e1258 Merge "migrate testing to ubuntu focal" 2020-09-01 13:34:30 +00:00
Zuul
e9a6c22cef Merge "Remove six" 2020-08-31 17:58:31 +00:00
melissaml
db10c24e25 Remove translation sections from setup.cfg
These translation sections are not needed anymore, Babel can
generate translation files without them.

Change-Id: I6559348831b9277b70834556adbd9445c5982c79
2020-08-27 08:25:29 -05:00
Zuul
2bcd29dcbd Merge "Cleanup py27 support" 2020-08-24 15:57:36 +00:00
Zuul
d5a99b511f Merge "Fix pygments style" 2020-08-24 14:55:03 +00:00
Zuul
2a475bbb4a Merge "add py38 package metedata" 2020-08-21 16:41:25 +00:00
Zuul
8f9e104f84 Merge "Add Python3 victoria unit tests" 2020-08-21 16:07:40 +00:00
Zuul
a2f3de89b4 Merge "Update master for stable/ussuri" 2020-08-21 15:34:40 +00:00
jiasirui
608cd0262f add py38 package metedata
Change-Id: I08a5505237eabf5c440ed4e3f068b5a834d5917d
2020-08-18 15:43:38 +00:00
Ghanshyam Mann
de3c4924e9 migrate testing to ubuntu focal
As per victoria cycle testing runtime and community goal[1]
we need to migrate upstream CI/CD to Ubuntu Focal(20.04).

Fixing:
- bug#1886296
Bump the pyflakes to 2.1.1 as min version to run pep8 jobs
on py3.8 which is default python vesion in ubuntu focal.

Story: #2007865
Task: #40180

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

Change-Id: I482ac98bc56f0e3cfb8b767f47649da11ed1afab
2020-08-13 17:24:09 +00:00
fuzihao
e88a3fa033 Fix pygments style
New theme of docs (Victoria+) respects pygments_style.
Since we starts using Victoria reqs while being on Ussuri,
this patch ensures proper rendering both in Ussuri and Victoria.

Change-Id: If42b2154a5a28f92d89dde9882afa31f01bc5ac3
2020-05-20 14:51:45 +08:00
jacky06
61dc82cb54 Remove six
We don't need this in a Python 3-only world.

Change-Id: Ibeb506281e88b44d454497d06f9187308859ac9c
2020-05-10 23:08:12 +08:00
Sean McGinnis
e69f9d5452 Use unittest.mock instead of third party mock
Now that we no longer support py27, we can use the standard library
unittest.mock module instead of the third party mock lib.

Change-Id: I9bf0a8fbb7b4f22aa2f5b5ed0836d11cac27552b
Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
2020-04-18 11:57:46 -05:00
73cb650ee4 Add Python3 victoria unit tests
This is an automatically generated patch to ensure unit testing
is in place for all the of the tested runtimes for victoria.

See also the PTI in governance [1].

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

Change-Id: I603335a1099880a00de6e4cfef6393a436ff990b
2020-04-11 18:49:23 +00:00
8bc96e21a9 Update master for stable/ussuri
Add file to the reno documentation build to show release notes for
stable/ussuri.

Use pbr instruction to increment the minor version number
automatically so that master versions are higher than the versions on
stable/ussuri.

Change-Id: I26f6e1f91faec1ce661ecfe2570c524b64687da9
Sem-Ver: feature
2020-04-11 18:49:18 +00:00
Andreas Jaeger
3f97e9844a Cleanup py27 support
Make a few cleanups:
- Remove python 2.7 stanza from setup.py
- Remove obsolete sections from setup.cfg
- Update classifiers

Change-Id: I79e3c540b56b024c7d01e4c916cdd79da9000331
2020-04-04 12:32:35 +02:00
Zuul
f4b1a3f224 Merge "Add support for v2/rating/modules endpoints" 2020-02-10 10:16:26 +00:00
Luka Peschke
9424e67f21 [ussuri][goal] Drop python 2.7 support and testing
This drops python2.7 support for cloudkittyclient. Even if this should be
done between milestone-1 and milestone-2, zuul jobs running on python2 are
currently broken since nova dropped python2.7 support.

Depends-On: https://review.opendev.org/#/c/693631/
Change-Id: I7615601540419e45259291a7bfce1cc038c27986
2020-01-23 13:42:35 +00:00
Luka Peschke
9c3bd770f2 Fix tox environments
Work items:

* Removed the globale-requirements constraint. Since python-cloudkittyclient
  is now part of the global requirements, the upstream requirements
  file can't be used anymore.

* Add cliff to docs requirements.

* Change cloudkittyclient namespace names: Having a dot in a namespace
  name causes the "autoprogram-cliff" to use the "application mode".

Change-Id: I8020d816b3397550fbcbd42cc14a9861bca7ae80
2019-12-17 15:10:17 +01:00
Luka Peschke
d28c5bc4dd Improve HTTP error formatting
This improves formatting for HTTP errors. keystoneauth's HttpErrors
are now caught an re-formatted to include the body of the http response.

Work items:

* Introduce the "http_error_formatter" function decorator, which catches
  an re-formats keystoneauths HttpErrors.

* Introduce the "format_http_errors" class decorator, which applies the
  "http_error_formatter" to all functions of a class.

* Add an "HttpDecoratorMeta" to the "BaseManager" class. This will decorate
  all functions of classes inheriting from "BaseManager" with the
  "http_error_formatter" decorator.

Change-Id: I6735f1fa8d876a87e2b7d4aaa533d5a32b085735
2019-11-28 11:52:40 +01:00
caoyuan
3e7f7a0f5d tox: Keeping going with docs
Sphinx 1.8 introduced [1] the '--keep-going' argument which, as its name
suggests, keeps the build running when it encounters non-fatal errors.
This is exceptionally useful in avoiding a continuous edit-build loop
when undertaking large doc reworks where multiple errors may be
introduced.

[1] https://github.com/sphinx-doc/sphinx/commit/e3483e9b045

Change-Id: Ie373018aafc05f7ea859a73c55163840dee60b56
2019-11-18 15:46:25 +00:00
Luka Peschke
584046761a Adapt functional tests to python3
A test looking for a regex in an argparse error message is failing when ran
on python3. Since this is now the default python runtime for devstack-based
jobs, this updates the message.

Change-Id: Id3372998715f13f7641d11a4a1d9d16629acfd18
Story: 2006896
Task: 37530
2019-11-18 10:28:23 +01:00
kangyufei
be0a9861d0 Switch to Ussuri jobs
Change-Id: I40dbc3c66164725bc4a8af4d0bf9ab481a7d72e7
2019-10-22 13:45:58 +08:00
Quentin Anglade
e599ac9f84 Add support for v2/rating/modules endpoints
This adds support for the new v2 API rating modules endpoints. Unit tests included.

Change-Id: Ie116c518f30128b49c3991f36101db7535af7db1
Story: 2006572
Task: 36680
2019-09-30 16:36:16 +02:00
55b056bc8a Update master for stable/train
Add file to the reno documentation build to show release notes for
stable/train.

Use pbr instruction to increment the minor version number
automatically so that master versions are higher than the versions on
stable/train.

Change-Id: Ia9a85597ccd963eb69f5e04eb1d28d9346f4c31d
Sem-Ver: feature
2019-09-20 16:44:57 +00:00
manchandavishal
296fd22640 Generate PDF documentation
This commit adds a new tox target to build PDF documentation.
It's a part of community goal, see storyboard for more
information.

Change-Id: I6e4f6372ff026a9eb379ee06c683451f58a99976
Story: 2006075
Task: 34812
2019-09-16 16:46:06 +00:00
Justin Ferrieu
c8d7a9e1c5 Add support for POST /v2/dataframes API endpoint to the client
Support for the ``/v2/dataframes`` endpoint has been added to the client.
A new ``dataframes add`` CLI command is also available.

Change-Id: I7fe9072d7280f251edc865a653a0b9ed2ab26c90
Story: 2005890
Task: 35970
2019-08-27 11:50:32 +00:00
Luka Peschke
dd4112acea Add functional test jobs for the v2 client
This adds test jobs for the v2 client, in python2 and python3.

Work items:

* Remove the "functional" tox environment and introduce the "functional-v1"
  and "functional-v2" environments.

* Add zuul base jobs for python2 and python 3 testing. Two jobs inherit from
  each of these new jobs: one for the v1 client and one for the v2 client.

* Add "OS_ENDPOINT" to the list of environment variables forwarded to the
  functional test environments in order to ease local testing.

Change-Id: I54a43a1e844e92730afbf87316b9efe73a08d850
2019-08-27 10:13:32 +02:00
pengyuesheng
3010383f10 Add lower-constraints job
create a tox environment for running the unit tests against the lower
bounds of the dependencies.

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

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

Change-Id: Iae676c4bbd00836cc6dce0f083f7aa308bbfc372
2019-08-20 13:07:13 +00:00
Justin Ferrieu
d660bef837 Add support for PUT /v2/scope API endpoint to the client
This allows to reset the state of one or several scopes through the API via
the client library and cli tool.

Change-Id: I69ce9a1c2ee0d8a6dd191a39e5c843e0baa1290f
Story: 2005395
Task: 30794
2019-07-22 11:42:15 +00:00
Luka Peschke
c138f409b1 Add support for /v2/summary to the client
This allows to get a summary through the v2 API endpoint via the client
library and cli tool.

Depends-On: https://review.opendev.org/#/c/660608/
Change-Id: Id63f2419fe3a1eb518a0ffa7ea5fa572b18df651
Story: 2005664
Task: 30960
2019-06-27 14:49:34 +00:00
pengyuesheng
a5a14a5883 Bump openstackdocstheme to 1.30.0
...to pick up many improvements, including the return of table borders.

Change-Id: I6af7c339e139f0980c64927fdd7cf3304b0715c5
2019-06-27 10:24:40 +08:00
Luka Peschke
7e7d25dd78 Add support for /v2/scope API endpoint to the client
This allows to retrieve the state of one or several scopes through the API via
the client library and cli tool.

Change-Id: I53995062fe76100f6dfcc672af482f653cc85bde
Story: 2005395
Task: 30795
Depends-On: https://review.opendev.org/#/c/658073/
2019-06-25 13:52:56 +00:00
pengyuesheng
b29fefaf8d Modify the url of upper_constraints_file
Depends-On: http://lists.openstack.org/pipermail/openstack-discuss/2019-May/006478.html

Change-Id: I6442a6423d3ab4df5dc699ad594eb2244e9ef994
2019-06-18 10:12:34 +08:00
pengyuesheng
301d8cb8ba Blacklist sphinx 2.1.0 (autodoc bug)
See https://github.com/sphinx-doc/sphinx/issues/6440 for upstream details
Depend-On: https://review.opendev.org/#/c/663060/

Change-Id: I3adad072b1acacecec47be076b5fca67fd1a9469
2019-06-17 14:27:44 +08:00
Zuul
b7018031a7 Merge "Add python 3.7 classifier to setup.cfg" 2019-06-13 09:01:26 +00:00
pengyuesheng
0c623ef90c Add python 3.7 classifier to setup.cfg
Change-Id: I9da335c8f7f186aa2bb8c7f4d30febafb0ec0649
2019-06-13 09:22:22 +08:00
pengyuesheng
6d27c9bc0a Use openstack-python3-train-jobs for python3 test runtime
Depends-On:https://review.opendev.org/#/c/641878/
Change-Id: I41112851450e23a05cd7ad4ccdf43bc2ad678386
2019-06-13 09:14:04 +08:00
98k
a3e18a120b Add upper-constraints.txt to releasenotes tox environment
Without these dependencies, the releasenotes build does not actually
work.

Change-Id: Iebb060857df6ec0b582e7635844e8505e09f9a4f
2019-05-20 17:12:50 +02:00
Luka Peschke
573908b345 Replaced openstack.org with opendev.org
Change-Id: I529172550f042eb291713661b6c97a8833db1872
2019-05-16 14:30:03 +02:00
Luka Peschke
d2fb83e64c Adapt the client for the v2 API
This adds a v2 client class allowing to add support for upcoming
v2 API endpoints. The v2 client class implements all v1 endpoints.
The cloudkitty API version can be specified with the
"--os-rating-api-version" option or the "OS_RATING_API_VERSION"
environment variable.

Change-Id: If38730da3baed59c93543a08f8a4989f919611db
2019-05-15 08:59:03 +00:00
Luka Peschke
d77526b42e Fix sphinx for global requirements
Change-Id: I0de0864dbc2e12efa1de6ad0da5f62a9a624b5d4
2019-05-15 10:15:57 +02:00
OpenDev Sysadmins
1f1f811f1d OpenDev Migration Patch
This commit was bulk generated and pushed by the OpenDev sysadmins
as a part of the Git hosting and code review systems migration
detailed in these mailing list posts:

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

Attempts have been made to correct repository namespaces and
hostnames based on simple pattern matching, but it's possible some
were updated incorrectly or missed entirely. Please reach out to us
via the contact information listed at https://opendev.org/ with any
questions you may have.
2019-04-19 19:42:57 +00:00
Ghanshyam Mann
40984c2593 Dropping the py35 testing
All the integration testing has been moved to
Bionic now[1] and py3.5 is not tested runtime for
Train or stable/stein[2].

As per below ML thread, we are good to drop the py35
testing now:
http://lists.openstack.org/pipermail/openstack-discuss/2019-April/005097.html

[1] http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004647.html
[2]
https://governance.openstack.org/tc/reference/runtimes/stein.html
https://governance.openstack.org/tc/reference/runtimes/train.html

Change-Id: Iaa29ad6616ca2cbdfeb492201aecb1aa51e728f3
2019-04-15 09:39:51 +02:00
Zuul
75687951e0 Merge "Adding a python3 functional job" 2019-04-04 08:19:06 +00:00
Luka Peschke
aeebd64928 Adding a python3 functional job
Change-Id: I3bb28edc9d62e8ea622e1061cbfb50168c217f24
2019-03-29 10:08:37 +01:00
Luka Peschke
de96c61985 Fix the rating.get_quotation method
This updates the rating.get_quotation method of the client. Tests on this
method have been added.

Depends-On: https://review.openstack.org/#/c/648062/
Change-Id: Ie2de0162311c2d162c1573042187ac4e628bd966
2019-03-28 14:57:44 +00:00
Luka Peschke
a7e687f740 Asserting 'summary get' returns nothing in functional tests
Since no data is available in devstack in our functional test environment,
we make the assertion that 'summary get' returns nothing. This is prone to
update if more complete test scenarios are implemented.

Change-Id: Ic80e39f0d2a75882762ebd6a0dba46033c9fd7f4
2019-03-28 13:41:45 +01:00
Luka Peschke
89475e2aac Fix releasenotes generation
Change-Id: Id72ea6407350bfccf7443bca9f4880d1324812c9
2019-03-27 10:43:55 +01:00
Zuul
658fd14155 Merge "Update master for stable/stein" 2019-03-21 09:15:43 +00:00
454584fc7e Update master for stable/stein
Add file to the reno documentation build to show release notes for
stable/stein.

Use pbr instruction to increment the minor version number
automatically so that master versions are higher than the versions on
stable/stein.

Change-Id: Ifba1f5deb95cc54e2b0d7a0e5782ad57e9842f62
Sem-Ver: feature
2019-03-18 14:49:55 +00:00
ZhongShengping
613e698de8 add python 3.7 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.7.

See ML discussion here [1] for context.

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

Change-Id: Ib366a82afa36152701947bcdfe9fad680264c858
Story: #2004073
Task: #27407
2019-02-19 17:05:53 +08:00
huang.zhiping
e7a9f20999 Update home-page
Change-Id: I81aebb4ca5f818fac4bd7077390b0283dce6ce18
2019-01-11 11:53:43 +00:00
ZhijunWei
dd1a421b7c Update the bugs link to storyboard
Change-Id: Ib089bdbc75d5a5008081ba0749eb944fa839f095
2018-12-31 12:28:13 +00:00
98k
419ee046f2 Change openstack-dev to openstack-discuss
Mailinglists have been updated. Openstack-discuss replaces openstack-dev.

Change-Id: Id0b5b599072f14824e29d2f896e9ea7c960545ff
2018-12-04 08:01:34 +00:00
taoguo
541d68239e Update http link to https link
Modify http to https.

Change-Id: I9cf0b476dd915725160b75affa54ef7bcbf6dc6c
2018-11-13 16:15:02 +08:00
Zuul
8e528c8e97 Merge "Fix oslo_debug_helper not running" 2018-10-26 13:40:56 +00:00
Zuul
3b49fbd4d0 Merge "Use global-requirements for requirements" 2018-10-26 11:13:37 +00:00
Jeremy Liu
378ee67117 Fix oslo_debug_helper not running
Specify test directory so that tox won't complain
`ImportError: Start directory is not importable`

Change-Id: I16fff5a8a43da50e4db8fbc5ef32cb59b0ba7f24
2018-10-23 13:50:50 +02:00
Luka Peschke
e9a92a2941 Fix "cloudkitty report tenant list" command
This fixes the "cloudkitty report tenant list": command, by transforming each
element of the list returned by CliTenantList's take_action method
into a tuple.

Change-Id: Iba1401b0cb4319a668d449139c8d20fc011cf178
Story: 2004149
Task: 27622
2018-10-23 11:27:13 +02:00
Luka Peschke
1ed287c92a Use global-requirements for requirements
This updates cloudkittyclient's requirement files in order to use
openstack/requirements for constraints. This will help to avoid dependency
conflicts when cloudkittyclient is deployed in an openstack context.

Work items:

* Updated requirements.txt, test-requirements.txt and doc/requirements.txt
  with the `update-requirements` tool provided by openstack/requirements.

* Added a lower-constraints.txt file.

* Added the "check-requirements" zuul job template to the CI.

Change-Id: I12a882ce4d24ade153a64b75852396377ac42ca6
2018-10-22 18:10:19 +02:00
huang.zhiping
63ac84b165 Update min tox version to 2.0
The commands used by constraints need at least tox 2.0.  Update to
reflect reality, which should help with local running of constraints
targets.

Change-Id: I0ff800d84949f1a02b083b4fcf16e9b82f0d9e57
2018-10-21 02:00:40 +00:00
Vieri
1cf5b3aca2 Don't quote {posargs} in tox.ini
Quotes around {posargs} cause the entire string to be combined into one
arg that gets passed to stestr. This prevents passing multiple args
(e.g. '--concurrency=16 some-regex')

Change-Id: Ie33e01abfd695c100033ec1ede8f14fec98d4b36
2018-10-09 13:46:50 +00:00
Luka Peschke
503dd3247a Add documentation jobs
Currently, the client's docs are neither built nor published. This
adds the required jobs.

Change-Id: I4199b6091502c22bf2a718d5baec29ab3f2ec400
2018-09-20 13:40:43 +02:00
Andreas Jaeger
7b9d447eb8 Use openstack-tox-cover template
Use openstack-tox-cover template, this runs the cover job as
non-voting in the check queue only.

Remove jobs and use template instead.

Add cover tox.ini environment - this job never worked before.

Change-Id: I9652aed41bc21bafc10e8c4a046d52bfb9681bdc
2018-09-07 14:32:43 +02:00
Zuul
a9eb87c436 Merge "Add `insecure and cacert` options to the client." 2018-09-07 10:00:10 +00:00
Luka Peschke
fff37a84fa Add `insecure and cacert` options to the client.
The client does support SSL authentication through keystoneauth right now. In
CLI mode, this is done through the "--os-cert" and "--os-cacert" options, or
through environment variables.

However, when the client is used as a python library,this is done through
requests' "verify" parameter, which is not very explicit.

This adds two parameters to the client

Change-Id: I68969c658724f53c85c47ab6098a3e2165f5925d
Story: 2003689
Task: 26224
2018-09-06 20:50:59 +02:00
Matthias Bastian
81cdcba4f3 Consider interface and region options with OSC
The --os-interface/OS_INTERFACE and --os-region-name/OS_REGION_NAME
options were considered by cloudkitty CLI but ignored when using the OSC
integration.

Change-Id: I36dc3616fba59c9b2e77da75abe0f76db7ddb7e4
2018-09-06 12:35:59 +02:00
Doug Hellmann
214083c695 add python 3.6 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.6 as part of the python3-first goal.

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

Change-Id: I257a223e215cfb383f600954e612ffd92ab1b6f8
Story: #2002586
Task: #24289
2018-08-31 08:57:48 -04:00
Doug Hellmann
c78ae05005 import zuul job settings from project-config
This is a mechanically generated patch to complete step 1 of moving
the zuul job settings out of project-config and into each project
repository.

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

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

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

Change-Id: I0fc887efcc4196dd3b371b2edae44075eb0b845e
Story: #2002586
Task: #24289
2018-08-31 08:57:42 -04:00
1348fd67e1 Update reno for stable/rocky
Change-Id: Ie70f71c1b702fd77039b4249c3ba562d0b813095
2018-08-08 22:09:57 +00:00
87 changed files with 2074 additions and 160 deletions

View File

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

View File

@@ -1,5 +1,5 @@
- job:
name: cloudkittyclient-devstack-functional
name: cloudkittyclient-devstack-functional-base
parent: devstack
description: |
Job for cloudkittyclient functional tests
@@ -18,20 +18,46 @@
- ^releasenotes/.*$
vars:
devstack_plugins:
cloudkitty: https://git.openstack.org/openstack/cloudkitty
cloudkitty: https://opendev.org/openstack/cloudkitty
devstack_localrc:
CLOUDKITTY_FETCHER: keystone
DEVSTACK_GATE_USE_PYTHON3: "True"
USE_PYTHON3: True
devstack_services:
ck-api: true
horizon: false
tox_install_siblings: false
zuul_work_dir: src/git.openstack.org/openstack/python-cloudkittyclient
tox_envlist: functional
zuul_work_dir: src/opendev.org/openstack/python-cloudkittyclient
- job:
name: cloudkittyclient-devstack-functional-v1-client
parent: cloudkittyclient-devstack-functional-base
vars:
tox_envlist: functional-v1
- job:
name: cloudkittyclient-devstack-functional-v2-client
parent: cloudkittyclient-devstack-functional-base
vars:
tox_envlist: functional-v2
- project:
templates:
- openstack-lower-constraints-jobs
- check-requirements
- openstack-cover-jobs
- openstack-python3-wallaby-jobs
- openstackclient-plugin-jobs
- publish-openstack-docs-pti
check:
jobs:
- cloudkittyclient-devstack-functional:
- cloudkittyclient-devstack-functional-v1-client:
voting: true
- cloudkittyclient-devstack-functional-v2-client:
voting: true
gate:
jobs:
- cloudkittyclient-devstack-functional:
- cloudkittyclient-devstack-functional-v1-client:
voting: true
- cloudkittyclient-devstack-functional-v2-client:
voting: true

View File

@@ -1,16 +1,16 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
https://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
Bugs should be filed on Storyboard, not GitHub:
https://bugs.launchpad.net/cloudkitty
https://storyboard.openstack.org/#!/project/openstack/python-cloudkittyclient

View File

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

View File

View File

@@ -12,13 +12,24 @@
# 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 string import Formatter as StringFormatter
from urllib.parse import urlencode
from six.moves.urllib.parse import urlencode
from cloudkittyclient import utils
class BaseManager(object):
class HttpDecoratorMeta(type):
ignore = ('get_url', )
def __new__(cls, *args, **kwargs):
return utils.format_http_errors(HttpDecoratorMeta.ignore)(
super(HttpDecoratorMeta, cls).__new__(cls, *args, **kwargs)
)
class BaseManager(object, metaclass=HttpDecoratorMeta):
"""Base class for Endpoint Manager objects."""
url = ''

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Objectif Libre
#
# 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 keystoneauth1 import adapter
from keystoneauth1 import session as ks_session
class BaseClient(object):
def __init__(self,
session=None,
adapter_options={},
cacert=None,
insecure=False,
**kwargs):
adapter_options.setdefault('service_type', 'rating')
adapter_options.setdefault('additional_headers', {
'Content-Type': 'application/json',
})
if insecure:
verify_cert = False
else:
if cacert:
verify_cert = cacert
else:
verify_cert = True
self.session = session
if self.session is None:
self.session = ks_session.Session(
verify=verify_cert, **kwargs)
self.api_client = adapter.Adapter(
session=self.session, **adapter_options)

View File

@@ -19,6 +19,7 @@ API_VERSION_OPTION = 'os_rating_api_version'
API_NAME = "rating"
API_VERSIONS = {
"1": "cloudkittyclient.v1.client.Client",
"2": "cloudkittyclient.v2.client.Client",
}
@@ -30,9 +31,19 @@ def make_client(instance):
version,
API_VERSIONS)
instance.setup_auth()
return ck_client(session=instance.session)
adapter_options = dict(
interface=instance.interface,
region_name=instance.region_name,
)
return ck_client(session=instance.session,
adapter_options=adapter_options)
def build_option_parser(parser):
"""Hook to add global options."""
parser.add_argument(
'--rating-api-version', type=int, default=utils.env(
'OS_RATING_API_VERSION',
default=DEFAULT_API_VERSION)
)
return parser

View File

@@ -22,6 +22,7 @@ import os_client_config
from oslo_log import log
from cloudkittyclient import client
from cloudkittyclient.osc import DEFAULT_API_VERSION
from cloudkittyclient import utils
@@ -77,13 +78,25 @@ class CloudKittyShell(cliff.app.App):
'pyscripts-script-update',
]
def _get_api_version(self, args):
# FIXME(peschk_l): This is a hacky way to figure out the client version
# to load. If anybody has a better idea, please fix this.
self.deferred_help = True
parser = self.build_option_parser('CloudKitty CLI client',
utils.get_version())
del self.deferred_help
parsed_args = parser.parse_known_args(args)
return str(parsed_args[0].os_rating_api_version or DEFAULT_API_VERSION)
def __init__(self, args):
self._args = args
self.cloud_config = os_client_config.OpenStackConfig()
super(CloudKittyShell, self).__init__(
description='CloudKitty CLI client',
version=utils.get_version(),
command_manager=CommandManager('cloudkittyclient'),
command_manager=CommandManager('cloudkittyclient_v{}'.format(
self._get_api_version(args[:]),
)),
deferred_help=True,
)
self._client = None
@@ -105,9 +118,6 @@ class CloudKittyShell(cliff.app.App):
description,
version,
argparse_kwargs={'allow_abbrev': False})
parser.add_argument(
'--ck-api-version', type=int, default=1, dest='ck_version',
help='Cloudkitty API version (defaults to 1)')
if 'OS_AUTH_TYPE' not in os.environ.keys() \
and 'OS_PASSWORD' in os.environ.keys():
os.environ['OS_AUTH_TYPE'] = 'password'
@@ -133,9 +143,10 @@ class CloudKittyShell(cliff.app.App):
self.options.os_rating_endpoint_override or
self.options.os_endpoint_override),
)
self._client = client.Client(str(self.options.ck_version),
session=session,
adapter_options=adapter_options)
self._client = client.Client(
str(self.options.os_rating_api_version or DEFAULT_API_VERSION),
session=session,
adapter_options=adapter_options)
return self._client

View File

@@ -24,24 +24,30 @@ from cloudkittyclient.tests import utils
class BaseFunctionalTest(utils.BaseTestCase):
def _run(self, executable, action,
flags='', params='', fmt='-f json', has_output=True):
flags='', params='', fmt='-f json', stdin=None, has_output=True):
if not has_output:
fmt = ''
cmd = ' '.join([executable, flags, action, params, fmt])
cmd = shlex.split(cmd)
p = subprocess.Popen(cmd, env=os.environ.copy(), shell=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
p = subprocess.Popen(
cmd, env=os.environ.copy(), shell=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE if stdin else None,
)
stdout, stderr = p.communicate(input=stdin)
if p.returncode != 0:
raise RuntimeError('"{cmd}" returned {val}: {msg}'.format(
cmd=' '.join(cmd), val=p.returncode, msg=stderr))
return json.loads(stdout) if has_output else None
def openstack(self, action,
flags='', params='', fmt='-f json', has_output=True):
flags='', params='', fmt='-f json',
stdin=None, has_output=True):
return self._run('openstack rating', action,
flags, params, fmt, has_output)
flags, params, fmt, stdin, has_output)
def cloudkitty(self, action,
flags='', params='', fmt='-f json', has_output=True):
return self._run('cloudkitty', action, flags, params, fmt, has_output)
flags='', params='', fmt='-f json',
stdin=None, has_output=True):
return self._run('cloudkitty', action, flags, params, fmt,
stdin, has_output)

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkCollectorTest(base.BaseFunctionalTest):

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkHashmapTest(base.BaseFunctionalTest):

View File

@@ -15,7 +15,7 @@
#
import jsonpath_rw_ext as jp
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkInfoTest(base.BaseFunctionalTest):

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkPyscriptTest(base.BaseFunctionalTest):

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkRatingTest(base.BaseFunctionalTest):

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkReportTest(base.BaseFunctionalTest):
@@ -22,8 +22,8 @@ class CkReportTest(base.BaseFunctionalTest):
self.runner = self.cloudkitty
def test_get_summary(self):
resp = self.runner('summary get')[0]
self.assertEqual(resp['Resource Type'], 'ALL')
resp = self.runner('summary get')
self.assertEqual(len(resp), 0)
def test_get_summary_with_groupby(self):
resp = self.runner('summary get', params='-g res_type tenant_id')

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.tests.functional.v1 import base
from cloudkittyclient.tests.functional import base
class CkStorageTest(base.BaseFunctionalTest):

View File

@@ -0,0 +1,174 @@
# Copyright 2019 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import os
import uuid
from cloudkittyclient.tests.functional import base
class CkDataframesTest(base.BaseFunctionalTest):
dataframes_data = """
{
"dataframes": [
{
"period": {
"begin": "20190723T122810Z",
"end": "20190723T132810Z"
},
"usage": {
"metric_one": [
{
"vol": {
"unit": "GiB",
"qty": 1.2
},
"rating": {
"price": 0.04
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
],
"metric_two": [
{
"vol": {
"unit": "MB",
"qty": 200.4
},
"rating": {
"price": 0.06
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
]
}
},
{
"period": {
"begin": "20190823T122810Z",
"end": "20190823T132810Z"
},
"usage": {
"metric_one": [
{
"vol": {
"unit": "GiB",
"qty": 2.4
},
"rating": {
"price": 0.08
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
],
"metric_two": [
{
"vol": {
"unit": "MB",
"qty": 400.8
},
"rating": {
"price": 0.12
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
]
}
}
]
}
"""
def __init__(self, *args, **kwargs):
super(CkDataframesTest, self).__init__(*args, **kwargs)
self.runner = self.cloudkitty
def setUp(self):
super(CkDataframesTest, self).setUp()
self.fixture_file_name = '{}.json'.format(uuid.uuid4())
with open(self.fixture_file_name, 'w') as f:
f.write(self.dataframes_data)
def tearDown(self):
files = os.listdir('.')
if self.fixture_file_name in files:
os.remove(self.fixture_file_name)
super(CkDataframesTest, self).tearDown()
def test_dataframes_add_with_no_args(self):
self.assertRaisesRegexp(
RuntimeError,
'error: the following arguments are required: datafile',
self.runner,
'dataframes add',
fmt='',
has_output=False,
)
def test_dataframes_add(self):
self.runner(
'dataframes add {}'.format(self.fixture_file_name),
fmt='',
has_output=False,
)
def test_dataframes_add_with_hyphen_stdin(self):
with open(self.fixture_file_name, 'r') as f:
self.runner(
'dataframes add -',
fmt='',
stdin=f.read().encode(),
has_output=False,
)
def test_dataframes_get(self):
# TODO(jferrieu): functional tests will be added in another
# patch for `dataframes get`
pass
class OSCDataframesTest(CkDataframesTest):
def __init__(self, *args, **kwargs):
super(OSCDataframesTest, self).__init__(*args, **kwargs)
self.runner = self.openstack

View File

@@ -0,0 +1,41 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient.tests.functional import base
class CkScopeTest(base.BaseFunctionalTest):
def __init__(self, *args, **kwargs):
super(CkScopeTest, self).__init__(*args, **kwargs)
self.runner = self.cloudkitty
def test_scope_state_get(self):
return True
# FIXME(peschk_l): Uncomment and update this once there is a way to set
# the state of a scope through the client
# resp = self.runner('scope state get')
def test_scope_state_reset(self):
return True
# FIXME(jferrieu): Uncomment and update this once there is a way to set
# the state of a scope through the client
# resp = self.runner('scope state reset')
class OSCScopeTest(CkScopeTest):
def __init__(self, *args, **kwargs):
super(OSCScopeTest, self).__init__(*args, **kwargs)
self.runner = self.openstack

View File

@@ -0,0 +1,35 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient.tests.functional import base
class CkSummaryTest(base.BaseFunctionalTest):
def __init__(self, *args, **kwargs):
super(CkSummaryTest, self).__init__(*args, **kwargs)
self.runner = self.cloudkitty
def test_summary_get(self):
return True
# FIXME(peschk_l): Uncomment and update this once there is a way to set
# the state of a summary through the client
# resp = self.runner('summary get')
class OSCSummaryTest(CkSummaryTest):
def __init__(self, *args, **kwargs):
super(OSCSummaryTest, self).__init__(*args, **kwargs)
self.runner = self.openstack

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from unittest import mock
from cloudkittyclient import exc
from cloudkittyclient.tests.unit.v1 import base

View File

@@ -0,0 +1,48 @@
# Copyright 2019 Objectif Libre
#
# 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 decimal
from cloudkittyclient import exc
from cloudkittyclient.tests.unit.v1 import base
class TestRating(base.BaseAPIEndpointTestCase):
def test_quote_request(self):
res_data = [{'usage': {
'instance': [{
'vol': {'unit': 'undef', 'qty': '1'},
'rating': {'price': decimal.Decimal(1)},
'desc': {
'disk_total_display': 1,
'image_id': 'c43a3e7d-c4e6-45d6-8c8d-e2832a45bc0a',
'ram': 64,
'ephemeral': 0,
'vcpus': 1,
'source_type': 'image',
'disk_total': 1,
'flavor_id': '42',
'flavor': 'm1.nano',
'disk': 1,
'source_val': 'c43a3e7d-c4e6-45d6-8c8d-e2832a45bc0a'}
}]
}}]
self.rating.get_quotation(res_data=res_data)
self.api_client.post.assert_called_once_with(
'/v1/rating/quote/', json={'resources': res_data})
def test_get_quotation_no_res_data(self):
self.assertRaises(exc.ArgumentRequired, self.rating.get_quotation)

View File

@@ -0,0 +1,63 @@
# Copyright 2018 Objectif Libre
#
# 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 collections
from unittest import mock
from cloudkittyclient.tests.unit.v1 import base
from cloudkittyclient.v1 import report_cli
class TestReportCli(base.BaseAPIEndpointTestCase):
def test_report_tenant_list(self):
class DummyAPIClient(object):
def get_tenants(*args, **kwargs):
return ['ee530dfc-319a-438f-9d43-346cfef501d6',
'91743a9a-688b-4526-b568-7b501531176c',
'4468704c-972e-4cfd-a342-9b71c493b79b']
class ClientWrap(object):
report = DummyAPIClient()
class DummyParsedArgs(object):
def __init__(self):
self.begin = '2042-01-01T00:00:00'
self.end = '2042-12-01T00:00:00'
class DummyCliTenantList(report_cli.CliTenantList):
def __init__(self):
pass
def __get_client_from_osc(*args):
return ClientWrap()
parsed_args = DummyParsedArgs()
cli_class_instance = DummyCliTenantList()
with mock.patch('cloudkittyclient.utils.get_client_from_osc',
new=__get_client_from_osc):
# NOTE(peschk_l): self is only used used to get a client so just we
# just override __init__ in order to skip class instanciation. In
# python3 we could just have passed None
result = report_cli.CliTenantList.take_action(
cli_class_instance, parsed_args)
assert len(result) == 2
assert result[0] == ('Tenant ID', )
assert isinstance(result[1], collections.Iterable)
for res in result[1]:
assert isinstance(res, collections.Iterable)

View File

@@ -0,0 +1,31 @@
# Copyright 2019 objectif Libre
#
# 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 cloudkittyclient.tests import utils
from cloudkittyclient.v2 import dataframes
from cloudkittyclient.v2.rating import modules
from cloudkittyclient.v2 import scope
from cloudkittyclient.v2 import summary
class BaseAPIEndpointTestCase(utils.BaseTestCase):
def setUp(self):
super(BaseAPIEndpointTestCase, self).setUp()
self.api_client = utils.FakeHTTPClient()
self.dataframes = dataframes.DataframesManager(self.api_client)
self.scope = scope.ScopeManager(self.api_client)
self.summary = summary.SummaryManager(self.api_client)
self.rating = modules.RatingManager(self.api_client)

View File

@@ -0,0 +1,172 @@
# Copyright 2019 Objectif Libre
#
# 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
from collections import OrderedDict
from cloudkittyclient import exc
from cloudkittyclient.tests.unit.v2 import base
class TestDataframes(base.BaseAPIEndpointTestCase):
dataframes_data = """
{
"dataframes": [
{
"period": {
"begin": "20190723T122810Z",
"end": "20190723T132810Z"
},
"usage": {
"metric_one": [
{
"vol": {
"unit": "GiB",
"qty": 1.2
},
"rating": {
"price": 0.04
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
],
"metric_two": [
{
"vol": {
"unit": "MB",
"qty": 200.4
},
"rating": {
"price": 0.06
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
]
}
},
{
"period": {
"begin": "20190823T122810Z",
"end": "20190823T132810Z"
},
"usage": {
"metric_one": [
{
"vol": {
"unit": "GiB",
"qty": 2.4
},
"rating": {
"price": 0.08
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
],
"metric_two": [
{
"vol": {
"unit": "MB",
"qty": 400.8
},
"rating": {
"price": 0.12
},
"groupby": {
"group_one": "one",
"group_two": "two"
},
"metadata": {
"attr_one": "one",
"attr_two": "two"
}
}
]
}
}
]
}
"""
def test_add_dataframes_with_string(self):
self.dataframes.add_dataframes(
dataframes=self.dataframes_data,
)
self.api_client.post.assert_called_once_with(
'/v2/dataframes',
data=self.dataframes_data,
)
def test_add_dataframes_with_json_object(self):
json_data = json.loads(self.dataframes_data)
self.dataframes.add_dataframes(
dataframes=json_data,
)
self.api_client.post.assert_called_once_with(
'/v2/dataframes',
data=json.dumps(json_data),
)
def test_add_dataframes_with_neither_string_nor_object_raises_exc(self):
self.assertRaises(
exc.InvalidArgumentError,
self.dataframes.add_dataframes,
dataframes=[open],
)
def test_add_dataframes_with_no_args_raises_exc(self):
self.assertRaises(
exc.ArgumentRequired,
self.dataframes.add_dataframes)
def test_get_dataframes(self):
self.dataframes.get_dataframes()
self.api_client.get.assert_called_once_with('/v2/dataframes')
def test_get_dataframes_with_pagination_args(self):
self.dataframes.get_dataframes(offset=10, limit=10)
try:
self.api_client.get.assert_called_once_with(
'/v2/dataframes?limit=10&offset=10')
except AssertionError:
self.api_client.get.assert_called_once_with(
'/v2/dataframes?offset=10&limit=10')
def test_get_dataframes_filters(self):
self.dataframes.get_dataframes(
filters=OrderedDict([('one', 'two'), ('three', 'four')]))
self.api_client.get.assert_called_once_with(
'/v2/dataframes?filters=one%3Atwo%2Cthree%3Afour')

View File

@@ -0,0 +1,38 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient.tests.unit.v2 import base
class TestRating(base.BaseAPIEndpointTestCase):
def test_get_modules(self):
self.rating.get_module()
self.api_client.get.assert_called_once_with('/v2/rating/modules')
def test_get_one_module(self):
self.rating.get_module(module_id="moduleidtest")
self.api_client.get.assert_called_once_with(
'/v2/rating/modules/moduleidtest')
def test_update_one_module(self):
self.rating.update_module(module_id="moduleidtest",
enabled=False, priority=42)
self.api_client.put.assert_called_once_with(
'/v2/rating/modules/moduleidtest',
json={
'enabled': False,
'priority': 42,
})

View File

@@ -0,0 +1,88 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient import exc
from cloudkittyclient.tests.unit.v2 import base
import datetime
class TestScope(base.BaseAPIEndpointTestCase):
def test_get_scope(self):
self.scope.get_scope_state()
self.api_client.get.assert_called_once_with('/v2/scope')
def test_get_scope_with_args(self):
self.scope.get_scope_state(offset=10, limit=10)
try:
self.api_client.get.assert_called_once_with(
'/v2/scope?limit=10&offset=10')
except AssertionError:
self.api_client.get.assert_called_once_with(
'/v2/scope?offset=10&limit=10')
def test_reset_scope_with_args(self):
self.scope.reset_scope_state(
state=datetime.datetime(2019, 5, 7),
all_scopes=True)
self.api_client.put.assert_called_once_with(
'/v2/scope',
json={
'state': datetime.datetime(2019, 5, 7),
'all_scopes': True,
})
def test_reset_scope_with_list_args(self):
self.scope.reset_scope_state(
state=datetime.datetime(2019, 5, 7),
scope_id=['id1', 'id2'],
all_scopes=False)
self.api_client.put.assert_called_once_with(
'/v2/scope',
json={
'state': datetime.datetime(2019, 5, 7),
'scope_id': 'id1,id2',
})
def test_reset_scope_strips_none_and_false_args(self):
self.scope.reset_scope_state(
state=datetime.datetime(2019, 5, 7),
all_scopes=False,
scope_key=None,
scope_id=['id1', 'id2'])
self.api_client.put.assert_called_once_with(
'/v2/scope',
json={
'state': datetime.datetime(2019, 5, 7),
'scope_id': 'id1,id2',
})
def test_reset_scope_with_no_args_raises_exc(self):
self.assertRaises(
exc.ArgumentRequired,
self.scope.reset_scope_state)
def test_reset_scope_with_lacking_args_raises_exc(self):
self.assertRaises(
exc.ArgumentRequired,
self.scope.reset_scope_state,
state=datetime.datetime(2019, 5, 7))
def test_reset_scope_with_both_args_raises_exc(self):
self.assertRaises(
exc.InvalidArgumentError,
self.scope.reset_scope_state,
state=datetime.datetime(2019, 5, 7),
scope_id=['id1', 'id2'],
all_scopes=True)

View File

@@ -0,0 +1,39 @@
# Copyright 2019 Objectif Libre
#
# 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 collections import OrderedDict
from cloudkittyclient.tests.unit.v2 import base
class TestSummary(base.BaseAPIEndpointTestCase):
def test_get_summary(self):
self.summary.get_summary()
self.api_client.get.assert_called_once_with('/v2/summary')
def test_get_summary_with_pagination_args(self):
self.summary.get_summary(offset=10, limit=10)
try:
self.api_client.get.assert_called_once_with(
'/v2/summary?limit=10&offset=10')
except AssertionError:
self.api_client.get.assert_called_once_with(
'/v2/summary?offset=10&limit=10')
def test_get_summary_filters(self):
self.summary.get_summary(
filters=OrderedDict([('one', 'two'), ('three', 'four')]))
self.api_client.get.assert_called_once_with(
'/v2/summary?filters=one%3Atwo%2Cthree%3Afour')

View File

@@ -12,12 +12,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import fixtures
import testtools
from keystoneauth1 import adapter
from keystoneauth1 import session
import mock
class BaseTestCase(testtools.TestCase):

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -13,8 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import inspect
import sys
import pbr.version
from keystoneauth1.exceptions import http
from oslo_utils import timeutils
@@ -56,3 +59,44 @@ def list_to_cols(list_obj, cols):
for item in list_obj:
values.append(dict_to_cols(item, cols))
return values
def http_error_formatter(func):
"""This decorator catches Http Errors and re-formats them"""
def wrap(*args, **kwargs):
try:
return func(*args, **kwargs)
except http.HttpError as e:
raise http.HttpError(message=e.response.text,
http_status=e.http_status)
return wrap
def format_http_errors(ignore):
"""Applies ``http_error_formatter`` to all methods of a class.
:param ignore: List of function names to ignore
:type ignore: iterable
"""
def wrap(cls):
# If you want pretty errors, use python3.
# __qualname__ does not exist in python 2
if sys.version_info.major < 3:
return cls
def predicate(item):
# This avoids decorating functions of parent classes
return (inspect.isfunction(item)
and item.__name__ not in ignore
and not item.__name__.startswith('_')
and cls.__name__ in item.__qualname__)
for name, func in inspect.getmembers(cls, predicate):
setattr(cls, name, http_error_formatter(func))
return cls
return wrap

View File

@@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from keystoneauth1 import adapter
from keystoneauth1 import session as ks_session
from cloudkittyclient.common import client
from cloudkittyclient.v1 import collector
from cloudkittyclient.v1 import info
from cloudkittyclient.v1 import rating
@@ -23,17 +21,22 @@ from cloudkittyclient.v1 import report
from cloudkittyclient.v1 import storage
class Client(object):
class Client(client.BaseClient):
def __init__(self, session=None, adapter_options={}, **kwargs):
adapter_options.setdefault('service_type', 'rating')
def __init__(self,
session=None,
adapter_options={},
cacert=None,
insecure=False,
**kwargs):
super(Client, self).__init__(
session=session,
adapter_options=adapter_options,
cacert=cacert,
insecure=insecure,
**kwargs
)
self.session = session
if self.session is None:
self.session = ks_session.Session(**kwargs)
self.api_client = adapter.Adapter(
session=self.session, **adapter_options)
self.info = info.InfoManager(self.api_client)
self.collector = collector.CollectorManager(self.api_client)
self.rating = rating.RatingManager(self.api_client)

View File

@@ -15,8 +15,8 @@
#
from oslo_log import log
from cloudkittyclient.common import base
from cloudkittyclient import exc
from cloudkittyclient.v1 import base
LOG = log.getLogger(__name__)

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.v1 import base
from cloudkittyclient.common import base
class InfoManager(base.BaseManager):

View File

@@ -15,9 +15,9 @@
#
from cliff import lister
from cloudkittyclient.common import base
from cloudkittyclient import exc
from cloudkittyclient import utils
from cloudkittyclient.v1 import base
from cloudkittyclient.v1.rating import hashmap
from cloudkittyclient.v1.rating import pyscripts
@@ -78,8 +78,10 @@ class RatingManager(base.BaseManager):
"""
if not kwargs.get('res_data', None):
raise exc.ArgumentRequired("'res_data' argument is required.")
url = self.get_url('quote')
return self.api_client.post(url, kwargs['res_data'])
url = self.get_url('quote', {})
body = {'resources': kwargs['res_data']}
return self.api_client.post(url, json=body).json()
class CliModuleGet(lister.Lister):

View File

@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.common import base
from cloudkittyclient import exc
from cloudkittyclient.v1 import base
class HashmapManager(base.BaseManager):
@@ -373,7 +373,7 @@ class HashmapManager(base.BaseManager):
:type level: str
"""
for arg in ['cost', 'level']:
if not kwargs.get(arg):
if kwargs.get(arg) is None:
raise exc.ArgumentRequired(
"'{}' argument is required".format(arg))
if not kwargs.get('service_id') and not kwargs.get('field_id'):

View File

@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.common import base
from cloudkittyclient import exc
from cloudkittyclient.v1 import base
class PyscriptManager(base.BaseManager):

View File

@@ -15,7 +15,7 @@
#
from oslo_log import log
from cloudkittyclient.v1 import base
from cloudkittyclient.common import base
LOG = log.getLogger(__name__)

View File

@@ -114,8 +114,11 @@ class CliTenantList(lister.Lister):
raise exc.InvalidArgumentError(
'Invalid timestamp "{}"'.format(value))
client = utils.get_client_from_osc(self)
return (('Tenant ID', ),
(client.report.get_tenants(**vars(parsed_args)), ))
tenants = client.report.get_tenants(**vars(parsed_args))
output = []
for tenant in tenants:
output.append((tenant, ))
return (('Tenant ID', ), output)
def get_parser(self, prog_name):
parser = super(CliTenantList, self).get_parser(prog_name)

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from cloudkittyclient.v1 import base
from cloudkittyclient.common import base
class StorageManager(base.BaseManager):

View File

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Objectif Libre
#
# 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 cloudkittyclient.v1 import client
from cloudkittyclient.v2 import dataframes
from cloudkittyclient.v2.rating import modules
from cloudkittyclient.v2 import scope
from cloudkittyclient.v2 import summary
# NOTE(peschk_l) v2 client needs to implement v1 until the v1 API has been
# completely ported to v2
class Client(client.Client):
def __init__(self,
session=None,
adapter_options={},
cacert=None,
insecure=False,
**kwargs):
super(Client, self).__init__(
session=session,
adapter_options=adapter_options,
cacert=cacert,
insecure=insecure,
**kwargs
)
self.dataframes = dataframes.DataframesManager(self.api_client)
self.scope = scope.ScopeManager(self.api_client)
self.summary = summary.SummaryManager(self.api_client)
self.rating = modules.RatingManager(self.api_client)

View File

@@ -0,0 +1,77 @@
# Copyright 2019 Objectif Libre
#
# 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
from cloudkittyclient.common import base
from cloudkittyclient import exc
class DataframesManager(base.BaseManager):
"""Class used to handle /v2/dataframes endpoint"""
url = '/v2/dataframes'
def add_dataframes(self, **kwargs):
"""Add DataFrames to the storage backend. Returns nothing.
:param dataframes: List of dataframes to add to the storage backend.
:type dataframes: list of dataframes
"""
dataframes = kwargs.get('dataframes')
if not dataframes:
raise exc.ArgumentRequired("'dataframes' argument is required")
if not isinstance(dataframes, str):
try:
dataframes = json.dumps(dataframes)
except TypeError:
raise exc.InvalidArgumentError(
"'dataframes' must be either a string"
"or a JSON serializable object.")
url = self.get_url(None, kwargs)
return self.api_client.post(
url,
data=dataframes,
)
def get_dataframes(self, **kwargs):
"""Returns a paginated list of DataFrames.
This support filters and datetime framing.
:param offset: Index of the first dataframe that should be returned.
:type offset: int
:param limit: Maximal number of dataframes to return.
:type limit: int
:param filters: Optional dict of filters to select data on.
:type filters: dict
:param begin: Start of the period to gather data from
:type begin: datetime.datetime
:param end: End of the period to gather data from
:type end: datetime.datetime
"""
kwargs['filters'] = ','.join(
'{}:{}'.format(k, v) for k, v in
(kwargs.get('filters', None) or {}).items()
)
authorized_args = [
'offset', 'limit', 'filters', 'begin', 'end']
url = self.get_url(None, kwargs, authorized_args=authorized_args)
return self.api_client.get(url).json()

View File

@@ -0,0 +1,116 @@
# Copyright 2019 Objectif Libre
#
# 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 argparse
from cliff import command
from cliff import lister
from oslo_utils import timeutils
from cloudkittyclient import utils
class CliDataframesAdd(command.Command):
"""Add one or several DataFrame objects to the storage backend."""
def get_parser(self, prog_name):
parser = super(CliDataframesAdd, self).get_parser(prog_name)
parser.add_argument(
'datafile',
type=argparse.FileType('r'),
help="File formatted as a JSON object having a DataFrame list"
"under a 'dataframes' key."
"'-' (hyphen) can be specified for using stdin.",
)
return parser
def take_action(self, parsed_args):
with parsed_args.datafile as dfile:
dataframes = dfile.read()
utils.get_client_from_osc(self).dataframes.add_dataframes(
dataframes=dataframes,
)
class CliDataframesGet(lister.Lister):
"""Get dataframes from the storage backend."""
columns = [
('begin', 'Begin'),
('end', 'End'),
('metric', 'Metric Type'),
('unit', 'Unit'),
('qty', 'Quantity'),
('price', 'Price'),
('groupby', 'Group By'),
('metadata', 'Metadata'),
]
def get_parser(self, prog_name):
parser = super(CliDataframesGet, self).get_parser(prog_name)
def filter_(elem):
if len(elem.split(':')) != 2:
raise TypeError
return str(elem)
parser.add_argument('--offset', type=int, default=0,
help='Index of the first dataframe')
parser.add_argument('--limit', type=int, default=100,
help='Maximal number of dataframes')
parser.add_argument('--filter', type=filter_, action='append',
help="Optional filter, in 'key:value' format. Can "
"be specified several times.")
parser.add_argument('-b', '--begin', type=timeutils.parse_isotime,
help="Start of the period to query, in iso8601 "
"format. Example: 2019-05-01T00:00:00Z.")
parser.add_argument('-e', '--end', type=timeutils.parse_isotime,
help="End of the period to query, in iso8601 "
"format. Example: 2019-06-01T00:00:00Z.")
return parser
def take_action(self, parsed_args):
filters = dict(elem.split(':') for elem in (parsed_args.filter or []))
dataframes = utils.get_client_from_osc(self).dataframes.get_dataframes(
offset=parsed_args.offset,
limit=parsed_args.limit,
begin=parsed_args.begin,
end=parsed_args.end,
filters=filters,
).get('dataframes', [])
def format_(d):
return ' '.join([
'{}="{}"'.format(k, v) for k, v in (d or {}).items()])
values = []
for df in dataframes:
period = df['period']
usage = df['usage']
for metric_type, points in usage.items():
for point in points:
values.append([
period['begin'],
period['end'],
metric_type,
point['vol']['unit'],
point['vol']['qty'],
point['rating']['price'],
format_(point.get('groupby', {})),
format_(point.get('metadata', {})),
])
return [col[1] for col in self.columns], values

View File

View File

@@ -0,0 +1,59 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient import exc
from cloudkittyclient.v1.client import rating
class RatingManager(rating.RatingManager):
"""Class used to handle /v2/rating/modules endpoint"""
url = '/v2/rating/modules'
def get_module(self, **kwargs):
"""Returns the given module.
If module_id is not specified, returns the list of loaded modules.
:param module_id: ID of the module on which you want information.
:type module_id: str
"""
module_id = kwargs.get('module_id', None)
if module_id is not None:
url = "{}/{}".format(self.url, module_id)
else:
url = self.url
return self.api_client.get(url).json()
def update_module(self, **kwargs):
"""Update the given module.
:param module_id: Id of the module to update.
:type module_id: str
:param enabled: Set to True to enable the module, False to disable it.
:type enabled: bool
:param priority: New priority of the module.
:type priority: int
"""
if not kwargs.get('module_id', None):
raise exc.ArgumentRequired("'module_id' argument is required.")
mutable_fields = ['enabled', 'priority']
changes = {}
for key, value in kwargs.items():
if value is not None and key in mutable_fields:
changes[key] = value
self.api_client.put("{}/{}".format(self.url, kwargs['module_id']),
json=changes)
return self.get_module(**kwargs)

View File

@@ -0,0 +1,29 @@
# Copyright 2019 Objectif Libre
#
# 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 cliff import lister
from cloudkittyclient import utils
class CliModuleList(lister.Lister):
"""Get loaded rating modules list"""
def get_parser(self, prog_name):
parser = super(CliModuleList, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
resp = utils.get_client_from_osc(self).ratingmodules.get_modules_list()
return resp['modules']

View File

@@ -0,0 +1,102 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient.common import base
from cloudkittyclient import exc
class ScopeManager(base.BaseManager):
"""Class used to handle /v2/scope endpoint"""
url = '/v2/scope'
def get_scope_state(self, **kwargs):
"""Returns a paginated list of scopes along with their state.
Some optional filters can be provided.
:param offset: Index of the first scope that should be returned.
:type offset: int
:param limit: Maximal number of scopes to return.
:type limit: int
:param collector: Optional collector to filter on.
:type collector: str or list of str
:param fetcher: Optional fetcher to filter on.
:type fetcher: str or list of str
:param scope_id: Optional scope_id to filter on.
:type scope_id: str or list of str
:param scope_key: Optional scope_key to filter on.
:type scope_key: str or list of str
"""
for key in ('collector', 'fetcher', 'scope_id', 'scope_key'):
if key in kwargs.keys():
if isinstance(kwargs[key], list):
kwargs[key] = ','.join(kwargs[key])
authorized_args = [
'offset', 'limit', 'collector', 'fetcher', 'scope_id', 'scope_key']
url = self.get_url(None, kwargs, authorized_args=authorized_args)
return self.api_client.get(url).json()
def reset_scope_state(self, **kwargs):
"""Returns nothing.
Some optional filters can be provided.
The all_scopes and the scope_id options are mutually exclusive and one
must be provided.
:param state: datetime object from which the state will be reset
:type state: datetime.datetime
:param all_scopes: Whether all scopes must be reset
:type all_scopes: bool
:param collector: Optional collector to filter on.
:type collector: str or list of str
:param fetcher: Optional fetcher to filter on.
:type fetcher: str or list of str
:param scope_id: Optional scope_id to filter on.
:type scope_id: str or list of str
:param scope_key: Optional scope_key to filter on.
:type scope_key: str or list of str
"""
if not kwargs.get('state'):
raise exc.ArgumentRequired("'state' argument is required")
if not kwargs.get('all_scopes') and not kwargs.get('scope_id'):
raise exc.ArgumentRequired(
"You must specify either 'scope_id' or 'all_scopes'")
if kwargs.get('all_scopes') and kwargs.get('scope_id'):
raise exc.InvalidArgumentError(
"You can't specify both 'scope_id' and 'all_scopes'")
for key in ('collector', 'fetcher', 'scope_id', 'scope_key'):
if key in kwargs.keys():
if isinstance(kwargs[key], list):
kwargs[key] = ','.join(kwargs[key])
body = dict(
state=kwargs.get('state'),
scope_id=kwargs.get('scope_id'),
scope_key=kwargs.get('scope_key'),
collector=kwargs.get('collector'),
fetcher=kwargs.get('fetcher'),
all_scopes=kwargs.get('all_scopes'),
)
# Stripping None and False values
body = dict(filter(lambda elem: bool(elem[1]), body.items()))
url = self.get_url(None, kwargs)
return self.api_client.put(url, json=body)

View File

@@ -0,0 +1,98 @@
# Copyright 2019 Objectif Libre
#
# 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 cliff import command
from cliff import lister
from oslo_utils import timeutils
from cloudkittyclient import utils
class CliScopeStateGet(lister.Lister):
"""Get information about current state of several scopes."""
info_columns = [
('scope_id', 'Scope ID'),
('scope_key', 'Scope Key'),
('collector', 'Collector'),
('fetcher', 'Fetcher'),
('state', 'State')
]
def get_parser(self, prog_name):
parser = super(CliScopeStateGet, self).get_parser(prog_name)
for col in self.info_columns[:-1]:
parser.add_argument(
'--' + col[0].replace('_', '-'), type=str,
action='append', help='Optional filter on ' + col[1])
parser.add_argument('--offset', type=int, default=0,
help='Index of the first scope')
parser.add_argument('--limit', type=int, default=100,
help='Maximal number of scopes')
return parser
def take_action(self, parsed_args):
resp = utils.get_client_from_osc(self).scope.get_scope_state(
offset=parsed_args.offset,
limit=parsed_args.limit,
collector=parsed_args.collector,
fetcher=parsed_args.fetcher,
scope_id=parsed_args.scope_id,
scope_key=parsed_args.scope_key,
)
values = utils.list_to_cols(resp['results'], self.info_columns)
return [col[1] for col in self.info_columns], values
class CliScopeStateReset(command.Command):
"""Reset the state of several scopes."""
info_columns = [
('scope_id', 'Scope ID'),
('scope_key', 'Scope Key'),
('collector', 'Collector'),
('fetcher', 'Fetcher'),
]
def get_parser(self, prog_name):
parser = super(CliScopeStateReset, self).get_parser(prog_name)
for col in self.info_columns:
parser.add_argument(
'--' + col[0].replace('_', '-'), type=str,
action='append', help='Optional filter on ' + col[1])
parser.add_argument(
'-a', '--all-scopes',
action='store_true',
help="Target all scopes at once")
parser.add_argument(
'state',
type=timeutils.parse_isotime,
help="State iso8601 datetime to which the state should be set. "
"Example: 2019-06-01T00:00:00Z.")
return parser
def take_action(self, parsed_args):
utils.get_client_from_osc(self).scope.reset_scope_state(
collector=parsed_args.collector,
fetcher=parsed_args.fetcher,
scope_id=parsed_args.scope_id,
scope_key=parsed_args.scope_key,
all_scopes=parsed_args.all_scopes,
state=parsed_args.state,
)

View File

@@ -0,0 +1,52 @@
# Copyright 2019 Objectif Libre
#
# 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 cloudkittyclient.common import base
class SummaryManager(base.BaseManager):
url = '/v2/summary'
def get_summary(self, **kwargs):
"""Returns a paginated list of summaries.
This support filters along with custom grouping.
:param offset: Index of the first scope that should be returned.
:type offset: int
:param limit: Maximal number of scopes to return.
:type limit: int
:param filters: Optional dict of filters to select data on.
:type filters: dict
:param groupby: Optional list of attributes to group data on.
:type groupby: str or list of str.
:param begin: Start of the period to gather data from
:type begin: datetime.datetime
:param end: End of the period to gather data from
:type end: datetime.datetime
"""
if 'groupby' in kwargs.keys() and isinstance(kwargs['groupby'], list):
kwargs['groupby'] = ','.join(kwargs['groupby'])
kwargs['filters'] = ','.join(
'{}:{}'.format(k, v) for k, v in
(kwargs.get('filters', None) or {}).items()
)
authorized_args = [
'offset', 'limit', 'filters', 'groupby', 'begin', 'end']
url = self.get_url(None, kwargs, authorized_args=authorized_args)
return self.api_client.get(url).json()

View File

@@ -0,0 +1,62 @@
# Copyright 2019 Objectif Libre
#
# 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 cliff import lister
from oslo_utils import timeutils
from cloudkittyclient import utils
class CliSummaryGet(lister.Lister):
"""Get a summary for a given period."""
def get_parser(self, prog_name):
parser = super(CliSummaryGet, self).get_parser(prog_name)
def filter_(elem):
if len(elem.split(':')) != 2:
raise TypeError
return str(elem)
parser.add_argument('--offset', type=int, default=0,
help='Index of the first element')
parser.add_argument('--limit', type=int, default=100,
help='Maximal number of elements')
parser.add_argument('-g', '--groupby', type=str, action='append',
help='Attribute to group the summary by. Can be '
'specified several times')
parser.add_argument('--filter', type=filter_, action='append',
help="Optional filter, in 'key:value' format. Can "
"be specified several times.")
parser.add_argument('-b', '--begin', type=timeutils.parse_isotime,
help="Start of the period to query, in iso8601 "
"format. Example: 2019-05-01T00:00:00Z.")
parser.add_argument('-e', '--end', type=timeutils.parse_isotime,
help="End of the period to query, in iso8601 "
"format. Example: 2019-06-01T00:00:00Z.")
return parser
def take_action(self, parsed_args):
filters = dict(elem.split(':') for elem in (parsed_args.filter or []))
resp = utils.get_client_from_osc(self).summary.get_summary(
offset=parsed_args.offset,
limit=parsed_args.limit,
begin=parsed_args.begin,
end=parsed_args.end,
filters=filters,
groupby=parsed_args.groupby,
)
columns = [c.replace('_', ' ').capitalize() for c in resp['columns']]
return columns, resp['results']

View File

@@ -2,6 +2,8 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
openstackdocstheme>=1.18.1 # Apache-2.0
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
reno>=2.5.0 # Apache2
openstackdocstheme>=1.30.0 # Apache-2.0
sphinx>=1.8.0,!=2.1.0 # BSD
sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD
reno>=2.5.0 # Apache-2.0
cliff>=2.11.0 # Apache-2.0

View File

@@ -1,15 +1,10 @@
=============
Api Reference
API Reference
=============
A ``client.Client`` instance has the following submodules (each one
corresponding to an API endpoint):
.. toctree::
:maxdepth: 2
:maxdepth: 1
:glob:
report
info
rating
collector
storage
v1/index
v2/index

View File

@@ -0,0 +1,12 @@
=======================
V1 API client reference
=======================
A ``client.v1.Client`` instance has the following submodules (each one
corresponding to an API endpoint):
.. toctree::
:maxdepth: 1
:glob:
./*

View File

@@ -0,0 +1,6 @@
===========================
dataframes (/v2/dataframes)
===========================
.. automodule:: cloudkittyclient.v2.dataframes
:members:

View File

@@ -0,0 +1,16 @@
=======================
V2 API client reference
=======================
In addition to the modules available in a ``client.v1.Client`` instance, a
``client.v2.Client`` instance has the following submodules (each one
corresponding to an API endpoint):
.. note:: Some modules of the ``client.v2.Client`` replace v1 modules with
their v2 alternative (for example) ``summary``.
.. toctree::
:maxdepth: 1
:glob:
./*

View File

@@ -0,0 +1,6 @@
=================
scope (/v2/scope)
=================
.. automodule:: cloudkittyclient.v2.scope
:members:

View File

@@ -0,0 +1,6 @@
=====================
summary (/v2/summary)
=====================
.. automodule:: cloudkittyclient.v2.summary
:members:

View File

@@ -2,6 +2,20 @@
CLI Reference
=============
.. autoprogram-cliff:: cloudkittyclient
V1 Client
=========
.. autoprogram-cliff:: cloudkittyclient_v1
:application: cloudkitty
:ignored: --format, --column, --max-width, --fit-width, --print-empty, --format-config-file, --noindent, --quote, --sort-column
V2 Client
=========
.. autoprogram-cliff:: cloudkittyclient_v2
:command: dataframes add
.. autoprogram-cliff:: cloudkittyclient_v2
:command: scope state get
.. autoprogram-cliff:: cloudkittyclient_v2
:command: summary get

View File

@@ -23,6 +23,14 @@ extensions = [
'cliff.sphinxext',
'sphinx.ext.autodoc',
'openstackdocstheme',
'sphinxcontrib.rsvgconverter',
]
autoprogram_cliff_application = 'cloudkitty'
autoprogram_cliff_ignored = [
"--format", "--column", "--max-width", "--fit-width", "--print-empty",
"--format-config-file", "--noindent", "--quote", "--sort-column",
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
@@ -52,7 +60,7 @@ add_function_parentheses = True
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = 'native'
# -- Options for HTML output --------------------------------------------------
@@ -69,14 +77,30 @@ html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# -- Options for LaTeX output ---------------------------------------------
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
latex_use_xindy = False
latex_domain_indices = False
latex_elements = {
'makeindex': '',
'printindex': '',
'preamble': r'\setcounter{tocdepth}{3}',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
# NOTE: Specify toctree_only=True for a better document structure of
# the generated PDF file.
latex_documents = [
('index',
'%s.tex' % project,
'doc-%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
u'OpenStack Foundation', 'howto', True),
]
# Example configuration for intersphinx: refer to the Python standard library.

View File

@@ -2,6 +2,61 @@
Usage
=====
CLI
===
Authentication
--------------
The CloudKitty client can either be used through the standalone CLI executable
(``cloudkitty``) or through the OpenStack Client module (``openstack rating``).
When using CloudKitty in standalone mode (ie without Keystone authentication),
the API endpoint and the auth method must be specified:
.. code-block:: shell
cloudkitty --os-endpoint http://cloudkitty-api:8889 --os-auth-type cloudkitty-noauth module list
These options can also be specified as environment variables:
.. code-block:: shell
export OS_ENDPOINT=http://cloudkitty-api:8889
export OS_AUTH_TYPE=cloudkitty-noauth
cloudkitty module list
The exact same options apply when using the OpenStack Client plugin:
.. code-block:: shell
# EITHER
openstack rating --os-endpoint http://cloudkitty-api:8889 --os-auth-type cloudkitty-noauth module list
# OR
export OS_ENDPOINT=http://cloudkitty-api:8889
export OS_AUTH_TYPE=cloudkitty-noauth
openstack rating module list
Version
-------
Two versions of the client exist: v1 and v2. The v2 version adds support for
v2 API endpoints. The default API version is 1. You can specify which API
version you want to use via a CLI option:
.. code-block:: shell
# EITHER
cloudkitty --os-rating-api-version 2 summary get
# OR
export OS_RATING_API_VERSION=2
cloudkitty summary get
Again, the option can also be provided to the OSC plugin, both via the CLI
flag or the environment variable.
Python library
==============
@@ -49,6 +104,23 @@ Else, use it the same way as any other OpenStack client::
u'res_type': u'ALL',
u'tenant_id': u'bea6a24f77e946b0a92dca7c78b7870b'}]}
.. warning::
If you want to use SSL with the client as a python library, you need to
provide a cert to keystone's session object. Else, two additional options
are available if you provide an ``auth`` object to the client: ``insecure``
and ``cacert``::
>>> client = ck_client.Client(
'1', auth=auth, insecure=False, cacert='/path/to/ca')
If you want to use the v2 API, you have to specify it at client instanciation
.. code-block:: python
c = ck_client.Client('2', session=session)
When using the ``cloudkitty`` CLI client with keystone authentication, the
auth plugin to use should automagically be detected. If not, you can specify
the auth plugin to use with ``--os-auth-type/--os-auth-plugin``::

23
lower-constraints.txt Normal file
View File

@@ -0,0 +1,23 @@
# requirements
pbr==5.5.1 # Apache-2.0
cliff==3.5.0 # Apache-2.0
keystoneauth1==4.3.0 # Apache-2.0
oslo.utils==4.7.0 # Apache-2.0
oslo.log==4.4.0 # Apache-2.0
PyYAML==5.3.1 # MIT
jsonpath-rw-ext==1.2.0 # Apache-2.0
os-client-config==2.1.0 # Apache-2.0
osc-lib==2.3.0 # Apache-2.0
# test-requirements.txt
pyflakes==2.1.1
coverage==4.0 # Apache-2.0
python-subunit==1.4.0 # Apache-2.0/BSD
oslotest==1.10.0 # Apache-2.0
stestr==2.0 # Apache-2.0
python-openstackclient==3.14 # Apache-2.0
# doc/requirements.txt
openstackdocstheme==1.30.0 # Apache-2.0
sphinx==1.6.2 # BSD
reno==2.5.0 # Apache2

View File

@@ -0,0 +1,8 @@
---
upgrade:
- |
The client has been adapted to allow adding support for v2 API
endpoints. The v2 client class implements all v1 endpoints, but v1
endpoints ported to v2 will be overriden. The API version to use can be
specified through the ``--os-rating-api-version`` option or the
``OS_RATING_API_VERSION``.

View File

@@ -0,0 +1,5 @@
---
features:
- |
Support for the ``/v2/dataframes`` endpoint has been added to the client.
A new ``dataframes add`` CLI command is also available.

View File

@@ -0,0 +1,5 @@
---
features:
- |
Support for the ``GET /v2/dataframes`` endpoint has been added
to the client. A new ``dataframes get`` CLI command is also available.

View File

@@ -0,0 +1,6 @@
---
features:
- |
Support for the ``/v2/summary`` endpoint has been added to the client. The
``summary get`` CLI command as well as the ``client.summary`` object in
the python library have been overriden in case the v2 API is used.

View File

@@ -0,0 +1,7 @@
---
upgrade:
- |
Python 2.7 support has been dropped. The last release of
``cloudkittyclient`` to support python 2.7 is OpenStack Train (3.1.0).
The minimum version of Python now supported by ``cloudkittyclient``
is Python 3.6.

View File

@@ -0,0 +1,8 @@
---
fixes:
- |
Fix `create_threshold` method when using cost as 0.
When using 0 as the cost, the `create_threshold` method
throws an exception. That happens because 0 (zero) is evaluated
to False. Therefore, we need to change the validation method to
check if the values are None.

View File

@@ -0,0 +1,5 @@
---
fixes:
- |
The ``rating.get_quotation`` method of the client has been fixed: the json
body has been updated to match the API reference.

View File

@@ -16,7 +16,6 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
import pbr.version
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -26,8 +25,8 @@ import pbr.version
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'oslosphinx',
'reno.sphinxext',
'openstackdocstheme',
]
# Add any paths that contain templates here, relative to this directory.
@@ -46,16 +45,12 @@ master_doc = 'index'
project = u'Cloudkitty Client Release Notes'
copyright = u'2016, Cloudkitty developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
cloudkittyclient_version = pbr.version.VersionInfo('python-cloudkittyclient')
# Release notes are version independent.
# The short X.Y version.
version = cloudkittyclient_version.canonical_version_string()
version = ''
# The full version, including alpha/beta/rc tags.
release = cloudkittyclient_version.version_string_with_vcs()
release = ''
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -87,7 +82,7 @@ exclude_patterns = []
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = 'native'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
@@ -100,7 +95,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View File

@@ -8,6 +8,11 @@ Contents
:maxdepth: 2
unreleased
victoria
ussuri
train
stein
rocky
queens
Indices and tables

View File

@@ -0,0 +1,6 @@
===================================
Rocky Series Release Notes
===================================
.. release-notes::
:branch: stable/rocky

View File

@@ -0,0 +1,6 @@
===================================
Stein Series Release Notes
===================================
.. release-notes::
:branch: stable/stein

View File

@@ -0,0 +1,6 @@
==========================
Train Series Release Notes
==========================
.. release-notes::
:branch: stable/train

View File

@@ -0,0 +1,6 @@
===========================
Ussuri Series Release Notes
===========================
.. release-notes::
:branch: stable/ussuri

View File

@@ -0,0 +1,6 @@
=============================
Victoria Series Release Notes
=============================
.. release-notes::
:branch: stable/victoria

View File

@@ -2,12 +2,12 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
cliff>=2.11.0,<3.0 # Apache-2.0
keystoneauth1>=3.4.0,<4.0 # Apache-2.0
oslo.utils>=3.35,<4.0 # Apache-2.0
oslo.log>=3.36,<4.0 # Apache-2.0
PyYAML>=3.12,<4.0 # MIT
jsonpath-rw-ext>=1.0 # Apache-2.0
six>=1.11,<2.0 # MIT
os-client-config>=1.29.0 # Apache-2.0
pbr>=5.5.1 # Apache-2.0
cliff>=3.5.0 # Apache-2.0
keystoneauth1>=4.3.0 # Apache-2.0
oslo.utils>=4.7.0 # Apache-2.0
oslo.log>=4.4.0 # Apache-2.0
PyYAML>=5.3.1 # MIT
jsonpath-rw-ext>=1.2.0 # Apache-2.0
os-client-config>=2.1.0 # Apache-2.0
osc-lib>=2.3.0 # Apache-2.0

152
setup.cfg
View File

@@ -4,8 +4,9 @@ summary = API client of cloudkitty, Rating as a Service project.
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/python-cloudkittyclient/latest/
python-requires = >=3.6
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -13,10 +14,12 @@ classifier =
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
[files]
packages =
@@ -84,8 +87,67 @@ openstack.rating.v1 =
rating_pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript
rating_pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript
openstack.rating.v2 =
rating_dataframes_get = cloudkittyclient.v2.dataframes_cli:CliDataframesGet
rating_dataframes_add = cloudkittyclient.v2.dataframes_cli:CliDataframesAdd
cloudkittyclient =
rating_scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet
rating_scope_state_reset = cloudkittyclient.v2.scope_cli:CliScopeStateReset
rating_summary_get = cloudkittyclient.v2.summary_cli:CliSummaryGet
rating_report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList
rating_module_get = cloudkittyclient.v1.rating:CliModuleGet
rating_module_list = cloudkittyclient.v1.rating:CliModuleList
rating_module_enable = cloudkittyclient.v1.rating:CliModuleEnable
rating_module_disable = cloudkittyclient.v1.rating:CliModuleDisable
rating_module_set_priority = cloudkittyclient.v1.rating:CliModuleSetPriority
rating_info_config_get = cloudkittyclient.v1.info_cli:CliInfoConfigGet
rating_info_metric_get = cloudkittyclient.v1.info_cli:CliInfoMetricGet
rating_info_metric_list = cloudkittyclient.v1.info_cli:CliInfoMetricList
rating_hashmap_mapping-types_list = cloudkittyclient.v1.rating.hashmap_cli:CliGetMappingTypes
rating_hashmap_service_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetService
rating_hashmap_service_list = cloudkittyclient.v1.rating.hashmap_cli:CliListService
rating_hashmap_service_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateService
rating_hashmap_service_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteService
rating_hashmap_field_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetField
rating_hashmap_field_list = cloudkittyclient.v1.rating.hashmap_cli:CliListField
rating_hashmap_field_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateField
rating_hashmap_field_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteField
rating_hashmap_mapping_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetMapping
rating_hashmap_mapping_list = cloudkittyclient.v1.rating.hashmap_cli:CliListMapping
rating_hashmap_mapping_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateMapping
rating_hashmap_mapping_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteMapping
rating_hashmap_mapping_update = cloudkittyclient.v1.rating.hashmap_cli:CliUpdateMapping
rating_hashmap_group_list = cloudkittyclient.v1.rating.hashmap_cli:CliListGroup
rating_hashmap_group_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateGroup
rating_hashmap_group_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteGroup
rating_hashmap_group_mappings_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetGroupMappings
rating_hashmap_group_thresholds_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetGroupThresholds
rating_hashmap_threshold_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetThreshold
rating_hashmap_threshold_list = cloudkittyclient.v1.rating.hashmap_cli:CliListThreshold
rating_hashmap_threshold_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateThreshold
rating_hashmap_threshold_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteThreshold
rating_hashmap_threshold_update = cloudkittyclient.v1.rating.hashmap_cli:CliUpdateThreshold
rating_collector-mapping_get = cloudkittyclient.v1.collector_cli:CliCollectorMappingGet
rating_collector-mapping_list = cloudkittyclient.v1.collector_cli:CliCollectorMappingList
rating_collector-mapping_create = cloudkittyclient.v1.collector_cli:CliCollectorMappingCreate
rating_collector-mapping_delete = cloudkittyclient.v1.collector_cli:CliCollectorMappingDelete
rating_collector_state_get = cloudkittyclient.v1.collector_cli:CliCollectorGetState
rating_collector_enable = cloudkittyclient.v1.collector_cli:CliCollectorEnable
rating_collector_disable = cloudkittyclient.v1.collector_cli:CliCollectorDisable
rating_pyscript_create = cloudkittyclient.v1.rating.pyscripts_cli:CliCreateScript
rating_pyscript_list = cloudkittyclient.v1.rating.pyscripts_cli:CliListScripts
rating_pyscript_get = cloudkittyclient.v1.rating.pyscripts_cli:CliGetScript
rating_pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript
rating_pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript
cloudkittyclient_v1 =
total_get = cloudkittyclient.v1.report_cli:CliTotalGet
summary_get = cloudkittyclient.v1.report_cli:CliSummaryGet
report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList
@@ -141,25 +203,69 @@ cloudkittyclient =
pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript
pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript
cloudkittyclient_v2 =
dataframes_add = cloudkittyclient.v2.dataframes_cli:CliDataframesAdd
dataframes_get = cloudkittyclient.v2.dataframes_cli:CliDataframesGet
scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet
scope_state_reset = cloudkittyclient.v2.scope_cli:CliScopeStateReset
summary_get = cloudkittyclient.v2.summary_cli:CliSummaryGet
report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList
module_get = cloudkittyclient.v1.rating:CliModuleGet
module_list = cloudkittyclient.v1.rating:CliModuleList
module_enable = cloudkittyclient.v1.rating:CliModuleEnable
module_disable = cloudkittyclient.v1.rating:CliModuleDisable
module_set_priority = cloudkittyclient.v1.rating:CliModuleSetPriority
info_config_get = cloudkittyclient.v1.info_cli:CliInfoConfigGet
info_metric_get = cloudkittyclient.v1.info_cli:CliInfoMetricGet
info_metric_list = cloudkittyclient.v1.info_cli:CliInfoMetricList
hashmap_mapping-types_list = cloudkittyclient.v1.rating.hashmap_cli:CliGetMappingTypes
hashmap_service_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetService
hashmap_service_list = cloudkittyclient.v1.rating.hashmap_cli:CliListService
hashmap_service_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateService
hashmap_service_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteService
hashmap_field_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetField
hashmap_field_list = cloudkittyclient.v1.rating.hashmap_cli:CliListField
hashmap_field_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateField
hashmap_field_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteField
hashmap_mapping_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetMapping
hashmap_mapping_list = cloudkittyclient.v1.rating.hashmap_cli:CliListMapping
hashmap_mapping_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateMapping
hashmap_mapping_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteMapping
hashmap_mapping_update = cloudkittyclient.v1.rating.hashmap_cli:CliUpdateMapping
hashmap_group_list = cloudkittyclient.v1.rating.hashmap_cli:CliListGroup
hashmap_group_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateGroup
hashmap_group_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteGroup
hashmap_group_mappings_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetGroupMappings
hashmap_group_thresholds_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetGroupThresholds
hashmap_threshold_get = cloudkittyclient.v1.rating.hashmap_cli:CliGetThreshold
hashmap_threshold_list = cloudkittyclient.v1.rating.hashmap_cli:CliListThreshold
hashmap_threshold_create = cloudkittyclient.v1.rating.hashmap_cli:CliCreateThreshold
hashmap_threshold_delete = cloudkittyclient.v1.rating.hashmap_cli:CliDeleteThreshold
hashmap_threshold_update = cloudkittyclient.v1.rating.hashmap_cli:CliUpdateThreshold
collector-mapping_get = cloudkittyclient.v1.collector_cli:CliCollectorMappingGet
collector-mapping_list = cloudkittyclient.v1.collector_cli:CliCollectorMappingList
collector-mapping_create = cloudkittyclient.v1.collector_cli:CliCollectorMappingCreate
collector-mapping_delete = cloudkittyclient.v1.collector_cli:CliCollectorMappingDelete
collector_state_get = cloudkittyclient.v1.collector_cli:CliCollectorGetState
collector_enable = cloudkittyclient.v1.collector_cli:CliCollectorEnable
collector_disable = cloudkittyclient.v1.collector_cli:CliCollectorDisable
pyscript_create = cloudkittyclient.v1.rating.pyscripts_cli:CliCreateScript
pyscript_list = cloudkittyclient.v1.rating.pyscripts_cli:CliListScripts
pyscript_get = cloudkittyclient.v1.rating.pyscripts_cli:CliGetScript
pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript
pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript
keystoneauth1.plugin =
cloudkitty-noauth = cloudkittyclient.auth:CloudKittyNoAuthLoader
cliff.formatter.list =
df-to-csv = cloudkittyclient.format:DataframeToCsvFormatter
[upload_sphinx]
upload-dir = doc/build/html
[compile_catalog]
directory = cloudkittyclient/locale
domain = python-cloudkittyclient
[update_catalog]
domain = python-cloudkittyclient
output_dir = cloudkittyclient/locale
input_file = cloudkittyclient/locale/python-cloudkittyclient.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = cloudkittyclient/locale/python-cloudkittyclient.pot

View File

@@ -13,17 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)

View File

@@ -2,11 +2,15 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
hacking>=3.0.1,<3.1.0 # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
# remove this pyflakes from here once you bump the
# hacking to 3.2.0 or above. hacking 3.2.0 takes
# care of pyflakes version compatibilty.
pyflakes>=2.1.1
coverage>=4.0,!=4.4 # Apache-2.0
python-subunit>=1.4.0 # Apache-2.0/BSD
oslotest>=1.10.0 # Apache-2.0
stestr>=2.0 # Apache-2.0
mock>=2.0 # BSD
python-openstackclient>=3.14 # Apache-2.0

66
tox.ini
View File

@@ -1,43 +1,67 @@
[tox]
minversion = 1.6
envlist = py35,py27,pypy,pep8
minversion = 3.1.1
envlist = py36,py38,pep8
skipsdist = True
ignore_basepython_conflict = True
[testenv]
basepython = python3
usedevelop = True
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -U {opts} {packages}
install_command = pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
[testenv:debug]
basepython = python3
commands = oslo_debug_helper {posargs}
[testenv:cover]
setenv =
VIRTUAL_ENV={envdir}
PYTHON=coverage run --source cloudkittyclient --parallel-mode
commands =
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
coverage report
[testenv:functional]
passenv = OS_CLOUD OS_PROJECT_DOMAIN_ID OS_USER_DOMAIN_ID OS_PROJECT_DOMAIN_NAME OS_USER_DOMAIN_NAME OS_PROJECT_NAME OS_IDENTITY_API_VERSION OS_PASSWORD OS_AUTH_TYPE OS_AUTH_URL OS_USERNAME
commands = stestr run --concurrency=1 --test-path ./cloudkittyclient/tests/functional
[testenv:debug]
commands = oslo_debug_helper -t cloudkittyclient/tests {posargs}
[testenv:functional-v1]
passenv = OS_CLOUD OS_PROJECT_DOMAIN_ID OS_USER_DOMAIN_ID OS_PROJECT_DOMAIN_NAME OS_USER_DOMAIN_NAME OS_PROJECT_NAME OS_IDENTITY_API_VERSION OS_PASSWORD OS_AUTH_TYPE OS_AUTH_URL OS_USERNAME OS_ENDPOINT
setenv = OS_RATING_API_VERSION=1
commands = stestr run --concurrency=1 --test-path ./cloudkittyclient/tests/functional/v1
[testenv:functional-v2]
passenv = OS_CLOUD OS_PROJECT_DOMAIN_ID OS_USER_DOMAIN_ID OS_PROJECT_DOMAIN_NAME OS_USER_DOMAIN_NAME OS_PROJECT_NAME OS_IDENTITY_API_VERSION OS_PASSWORD OS_AUTH_TYPE OS_AUTH_URL OS_USERNAME OS_ENDPOINT
setenv = OS_RATING_API_VERSION=2
commands = stestr run --concurrency=1 --test-path ./cloudkittyclient/tests/functional/v2
[testenv:pep8]
basepython = python3
commands = flake8
[testenv:venv]
basepython = python3
commands = {posargs}
[testenv:docs]
basepython = python3
deps = -r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
commands = sphinx-build --keep-going -b html doc/source doc/build/html
[testenv:pdf-docs]
envdir = {toxworkdir}/docs
deps = {[testenv:docs]deps}
whitelist_externals =
make
commands =
sphinx-build --keep-going -b latex doc/source doc/build/pdf
make -C doc/build/pdf
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
ignore = E123,E125,W503,W504
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes
@@ -45,6 +69,14 @@ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes
import_exceptions = cloudkittyclient.i18n
[testenv:releasenotes]
basepython = python3
deps = -r{toxinidir}/doc/requirements.txt
commands = sphinx-build -a -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
-r{toxinidir}/doc/requirements.txt
commands =
sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html
[testenv:lower-constraints]
deps =
-c{toxinidir}/lower-constraints.txt
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt