From c138f409b1f54bb2ea4528b4e61757ab19a989b9 Mon Sep 17 00:00:00 2001 From: Luka Peschke Date: Wed, 29 May 2019 13:56:49 +0200 Subject: [PATCH] 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 --- cloudkittyclient/osc.py | 2 +- cloudkittyclient/shell.py | 14 +++- .../tests/functional/v2/test_summary.py | 35 ++++++++++ cloudkittyclient/tests/unit/v2/base.py | 2 + .../tests/unit/v2/test_summary.py | 39 +++++++++++ cloudkittyclient/v2/client.py | 2 + cloudkittyclient/v2/summary.py | 52 +++++++++++++++ cloudkittyclient/v2/summary_cli.py | 62 ++++++++++++++++++ doc/source/api_reference/index.rst | 15 ++--- .../api_reference/{ => v1}/collector.rst | 0 doc/source/api_reference/v1/index.rst | 12 ++++ doc/source/api_reference/{ => v1}/info.rst | 0 doc/source/api_reference/{ => v1}/rating.rst | 0 doc/source/api_reference/{ => v1}/report.rst | 0 doc/source/api_reference/{ => v1}/storage.rst | 0 doc/source/api_reference/v2/index.rst | 16 +++++ doc/source/api_reference/v2/scope.rst | 6 ++ doc/source/api_reference/v2/summary.rst | 6 ++ doc/source/cli_reference.rst | 16 ++++- doc/source/conf.py | 5 ++ doc/source/usage.rst | 62 ++++++++++++++++++ ...d-support-v2-summary-7c1ff903f21f057b.yaml | 6 ++ setup.cfg | 65 +++++++++++++++++-- 23 files changed, 397 insertions(+), 20 deletions(-) create mode 100644 cloudkittyclient/tests/functional/v2/test_summary.py create mode 100644 cloudkittyclient/tests/unit/v2/test_summary.py create mode 100644 cloudkittyclient/v2/summary.py create mode 100644 cloudkittyclient/v2/summary_cli.py rename doc/source/api_reference/{ => v1}/collector.rst (100%) create mode 100644 doc/source/api_reference/v1/index.rst rename doc/source/api_reference/{ => v1}/info.rst (100%) rename doc/source/api_reference/{ => v1}/rating.rst (100%) rename doc/source/api_reference/{ => v1}/report.rst (100%) rename doc/source/api_reference/{ => v1}/storage.rst (100%) create mode 100644 doc/source/api_reference/v2/index.rst create mode 100644 doc/source/api_reference/v2/scope.rst create mode 100644 doc/source/api_reference/v2/summary.rst create mode 100644 releasenotes/notes/add-support-v2-summary-7c1ff903f21f057b.yaml diff --git a/cloudkittyclient/osc.py b/cloudkittyclient/osc.py index 6376a58..d534f7f 100644 --- a/cloudkittyclient/osc.py +++ b/cloudkittyclient/osc.py @@ -14,7 +14,7 @@ from osc_lib import utils -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_rating_api_version' API_NAME = "rating" API_VERSIONS = { diff --git a/cloudkittyclient/shell.py b/cloudkittyclient/shell.py index 408a71a..8e3abbf 100644 --- a/cloudkittyclient/shell.py +++ b/cloudkittyclient/shell.py @@ -78,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 diff --git a/cloudkittyclient/tests/functional/v2/test_summary.py b/cloudkittyclient/tests/functional/v2/test_summary.py new file mode 100644 index 0000000..e217790 --- /dev/null +++ b/cloudkittyclient/tests/functional/v2/test_summary.py @@ -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 diff --git a/cloudkittyclient/tests/unit/v2/base.py b/cloudkittyclient/tests/unit/v2/base.py index c5239b7..f82d42b 100644 --- a/cloudkittyclient/tests/unit/v2/base.py +++ b/cloudkittyclient/tests/unit/v2/base.py @@ -14,6 +14,7 @@ from cloudkittyclient.tests import utils from cloudkittyclient.v2 import scope +from cloudkittyclient.v2 import summary class BaseAPIEndpointTestCase(utils.BaseTestCase): @@ -22,3 +23,4 @@ class BaseAPIEndpointTestCase(utils.BaseTestCase): super(BaseAPIEndpointTestCase, self).setUp() self.api_client = utils.FakeHTTPClient() self.scope = scope.ScopeManager(self.api_client) + self.summary = summary.SummaryManager(self.api_client) diff --git a/cloudkittyclient/tests/unit/v2/test_summary.py b/cloudkittyclient/tests/unit/v2/test_summary.py new file mode 100644 index 0000000..5383001 --- /dev/null +++ b/cloudkittyclient/tests/unit/v2/test_summary.py @@ -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') diff --git a/cloudkittyclient/v2/client.py b/cloudkittyclient/v2/client.py index 68f595a..fd8abd4 100644 --- a/cloudkittyclient/v2/client.py +++ b/cloudkittyclient/v2/client.py @@ -15,6 +15,7 @@ # from cloudkittyclient.v1 import client 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 @@ -36,3 +37,4 @@ class Client(client.Client): ) self.scope = scope.ScopeManager(self.api_client) + self.summary = summary.SummaryManager(self.api_client) diff --git a/cloudkittyclient/v2/summary.py b/cloudkittyclient/v2/summary.py new file mode 100644 index 0000000..2880bda --- /dev/null +++ b/cloudkittyclient/v2/summary.py @@ -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() diff --git a/cloudkittyclient/v2/summary_cli.py b/cloudkittyclient/v2/summary_cli.py new file mode 100644 index 0000000..6bc87d3 --- /dev/null +++ b/cloudkittyclient/v2/summary_cli.py @@ -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'] diff --git a/doc/source/api_reference/index.rst b/doc/source/api_reference/index.rst index e9e9c17..0c90b24 100644 --- a/doc/source/api_reference/index.rst +++ b/doc/source/api_reference/index.rst @@ -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 diff --git a/doc/source/api_reference/collector.rst b/doc/source/api_reference/v1/collector.rst similarity index 100% rename from doc/source/api_reference/collector.rst rename to doc/source/api_reference/v1/collector.rst diff --git a/doc/source/api_reference/v1/index.rst b/doc/source/api_reference/v1/index.rst new file mode 100644 index 0000000..7ad233a --- /dev/null +++ b/doc/source/api_reference/v1/index.rst @@ -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: + + ./* diff --git a/doc/source/api_reference/info.rst b/doc/source/api_reference/v1/info.rst similarity index 100% rename from doc/source/api_reference/info.rst rename to doc/source/api_reference/v1/info.rst diff --git a/doc/source/api_reference/rating.rst b/doc/source/api_reference/v1/rating.rst similarity index 100% rename from doc/source/api_reference/rating.rst rename to doc/source/api_reference/v1/rating.rst diff --git a/doc/source/api_reference/report.rst b/doc/source/api_reference/v1/report.rst similarity index 100% rename from doc/source/api_reference/report.rst rename to doc/source/api_reference/v1/report.rst diff --git a/doc/source/api_reference/storage.rst b/doc/source/api_reference/v1/storage.rst similarity index 100% rename from doc/source/api_reference/storage.rst rename to doc/source/api_reference/v1/storage.rst diff --git a/doc/source/api_reference/v2/index.rst b/doc/source/api_reference/v2/index.rst new file mode 100644 index 0000000..725ce4e --- /dev/null +++ b/doc/source/api_reference/v2/index.rst @@ -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: + + ./* diff --git a/doc/source/api_reference/v2/scope.rst b/doc/source/api_reference/v2/scope.rst new file mode 100644 index 0000000..c24adcb --- /dev/null +++ b/doc/source/api_reference/v2/scope.rst @@ -0,0 +1,6 @@ +================= +scope (/v2/scope) +================= + +.. automodule:: cloudkittyclient.v2.scope + :members: diff --git a/doc/source/api_reference/v2/summary.rst b/doc/source/api_reference/v2/summary.rst new file mode 100644 index 0000000..e608637 --- /dev/null +++ b/doc/source/api_reference/v2/summary.rst @@ -0,0 +1,6 @@ +===================== +summary (/v2/summary) +===================== + +.. automodule:: cloudkittyclient.v2.summary + :members: diff --git a/doc/source/cli_reference.rst b/doc/source/cli_reference.rst index 856f9bf..350192f 100644 --- a/doc/source/cli_reference.rst +++ b/doc/source/cli_reference.rst @@ -2,6 +2,18 @@ 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: scope state get + +.. autoprogram-cliff:: cloudkittyclient.v2 + :command: summary get diff --git a/doc/source/conf.py b/doc/source/conf.py index fb4c3b2..64f8579 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -25,6 +25,11 @@ extensions = [ '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 # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 20f7e9d..3c51e5c 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -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 ============== @@ -59,6 +114,13 @@ Else, use it the same way as any other OpenStack client:: >>> 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``:: diff --git a/releasenotes/notes/add-support-v2-summary-7c1ff903f21f057b.yaml b/releasenotes/notes/add-support-v2-summary-7c1ff903f21f057b.yaml new file mode 100644 index 0000000..173cd03 --- /dev/null +++ b/releasenotes/notes/add-support-v2-summary-7c1ff903f21f057b.yaml @@ -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. diff --git a/setup.cfg b/setup.cfg index 2088fd0..40862c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -87,9 +87,8 @@ openstack.rating.v1 = openstack.rating.v2 = 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_module_get = cloudkittyclient.v1.rating:CliModuleGet @@ -142,10 +141,7 @@ openstack.rating.v2 = rating_pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript rating_pyscript_delete = cloudkittyclient.v1.rating.pyscripts_cli:CliDeleteScript - -cloudkittyclient = - scope_state_get = cloudkittyclient.v2.scope_cli:CliScopeStateGet - +cloudkittyclient.v1 = total_get = cloudkittyclient.v1.report_cli:CliTotalGet summary_get = cloudkittyclient.v1.report_cli:CliSummaryGet report_tenant_list = cloudkittyclient.v1.report_cli:CliTenantList @@ -201,6 +197,63 @@ cloudkittyclient = pyscript_update = cloudkittyclient.v1.rating.pyscripts_cli:CliUpdateScript 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 = cloudkitty-noauth = cloudkittyclient.auth:CloudKittyNoAuthLoader