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
This commit is contained in:
Luka Peschke
2019-05-29 13:56:49 +02:00
parent a5a14a5883
commit c138f409b1
23 changed files with 397 additions and 20 deletions

View File

@@ -14,7 +14,7 @@
from osc_lib import utils from osc_lib import utils
DEFAULT_API_VERSION = '2' DEFAULT_API_VERSION = '1'
API_VERSION_OPTION = 'os_rating_api_version' API_VERSION_OPTION = 'os_rating_api_version'
API_NAME = "rating" API_NAME = "rating"
API_VERSIONS = { API_VERSIONS = {

View File

@@ -78,13 +78,25 @@ class CloudKittyShell(cliff.app.App):
'pyscripts-script-update', '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): def __init__(self, args):
self._args = args self._args = args
self.cloud_config = os_client_config.OpenStackConfig() self.cloud_config = os_client_config.OpenStackConfig()
super(CloudKittyShell, self).__init__( super(CloudKittyShell, self).__init__(
description='CloudKitty CLI client', description='CloudKitty CLI client',
version=utils.get_version(), version=utils.get_version(),
command_manager=CommandManager('cloudkittyclient'), command_manager=CommandManager('cloudkittyclient.v{}'.format(
self._get_api_version(args[:]),
)),
deferred_help=True, deferred_help=True,
) )
self._client = None self._client = None

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

@@ -14,6 +14,7 @@
from cloudkittyclient.tests import utils from cloudkittyclient.tests import utils
from cloudkittyclient.v2 import scope from cloudkittyclient.v2 import scope
from cloudkittyclient.v2 import summary
class BaseAPIEndpointTestCase(utils.BaseTestCase): class BaseAPIEndpointTestCase(utils.BaseTestCase):
@@ -22,3 +23,4 @@ class BaseAPIEndpointTestCase(utils.BaseTestCase):
super(BaseAPIEndpointTestCase, self).setUp() super(BaseAPIEndpointTestCase, self).setUp()
self.api_client = utils.FakeHTTPClient() self.api_client = utils.FakeHTTPClient()
self.scope = scope.ScopeManager(self.api_client) self.scope = scope.ScopeManager(self.api_client)
self.summary = summary.SummaryManager(self.api_client)

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

@@ -15,6 +15,7 @@
# #
from cloudkittyclient.v1 import client from cloudkittyclient.v1 import client
from cloudkittyclient.v2 import scope 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 # NOTE(peschk_l) v2 client needs to implement v1 until the v1 API has been
@@ -36,3 +37,4 @@ class Client(client.Client):
) )
self.scope = scope.ScopeManager(self.api_client) self.scope = scope.ScopeManager(self.api_client)
self.summary = summary.SummaryManager(self.api_client)

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

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

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,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,18 @@
CLI Reference CLI Reference
============= =============
.. autoprogram-cliff:: cloudkittyclient V1 Client
=========
.. autoprogram-cliff:: cloudkittyclient.v1
:application: cloudkitty :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: scope state get
.. autoprogram-cliff:: cloudkittyclient.v2
:command: summary get

View File

@@ -25,6 +25,11 @@ extensions = [
'openstackdocstheme', 'openstackdocstheme',
] ]
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 # autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles. # text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable # execute "export SPHINX_DEBUG=1" in your terminal to disable

View File

@@ -2,6 +2,61 @@
Usage 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 Python library
============== ==============
@@ -59,6 +114,13 @@ Else, use it the same way as any other OpenStack client::
>>> client = ck_client.Client( >>> client = ck_client.Client(
'1', auth=auth, insecure=False, cacert='/path/to/ca') '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 When using the ``cloudkitty`` CLI client with keystone authentication, the
auth plugin to use should automagically be detected. If not, you can specify 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``:: the auth plugin to use with ``--os-auth-type/--os-auth-plugin``::

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

@@ -87,9 +87,8 @@ openstack.rating.v1 =
openstack.rating.v2 = openstack.rating.v2 =
rating_scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet rating_scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet
rating_summary_get = cloudkittyclient.v2.summary_cli:CliSummaryGet
rating_total_get = cloudkittyclient.v1.report_cli:CliTotalGet
rating_summary_get = cloudkittyclient.v1.report_cli:CliSummaryGet
rating_report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList rating_report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList
rating_module_get = cloudkittyclient.v1.rating:CliModuleGet rating_module_get = cloudkittyclient.v1.rating:CliModuleGet
@@ -142,10 +141,7 @@ openstack.rating.v2 =
rating_pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript rating_pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript
rating_pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript rating_pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript
cloudkittyclient.v1 =
cloudkittyclient =
scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet
total_get = cloudkittyclient.v1.report_cli:CliTotalGet total_get = cloudkittyclient.v1.report_cli:CliTotalGet
summary_get = cloudkittyclient.v1.report_cli:CliSummaryGet summary_get = cloudkittyclient.v1.report_cli:CliSummaryGet
report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList
@@ -201,6 +197,63 @@ cloudkittyclient =
pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript
pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript
cloudkittyclient.v2 =
scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet
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
dataframes_get = cloudkittyclient.v1.storage_cli:CliGetDataframes
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 = keystoneauth1.plugin =
cloudkitty-noauth = cloudkittyclient.auth:CloudKittyNoAuthLoader cloudkitty-noauth = cloudkittyclient.auth:CloudKittyNoAuthLoader