From 53b23d52177ee561ef587f6db33722157d18763e Mon Sep 17 00:00:00 2001 From: Diana Clarke Date: Wed, 7 Dec 2016 03:11:46 +0000 Subject: [PATCH] Microversion 2.40 - Simple tenant usage pagination Add optional parameters 'limit' and 'marker' to the os-simple-tenant-usage endpoints for pagaination. /os-simple-tenant-usage?limit={limit}&marker={instance_uuid} /os-simple-tenant-usage/{tenant}?limit={limit}&marker={instance_uuid} Implements blueprint paginate-simple-tenant-usage Depends-on: Ic8e9f869f1b855f968967bedbf77542f287f26c0 Change-Id: If99db6933de012b71cf2c982075f08b3e664361e --- novaclient/__init__.py | 2 +- .../tests/functional/v2/legacy/test_usage.py | 74 +++++++++++++++++++ novaclient/tests/functional/v2/test_usage.py | 38 ++++++++++ novaclient/tests/unit/v2/fakes.py | 74 +++++++++++++++++++ novaclient/tests/unit/v2/test_shell.py | 54 +++++++++++++- novaclient/tests/unit/v2/test_usage.py | 49 ++++++++++++ novaclient/v2/shell.py | 70 ++++++++++++++++-- novaclient/v2/usage.py | 66 +++++++++++++++-- .../microversion-v2_40-484adba0806b08bf.yaml | 4 + 9 files changed, 414 insertions(+), 17 deletions(-) create mode 100644 novaclient/tests/functional/v2/legacy/test_usage.py create mode 100644 novaclient/tests/functional/v2/test_usage.py create mode 100644 releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 85cadcf62..956e139e6 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.39") +API_MAX_VERSION = api_versions.APIVersion("2.40") diff --git a/novaclient/tests/functional/v2/legacy/test_usage.py b/novaclient/tests/functional/v2/legacy/test_usage.py new file mode 100644 index 000000000..e37693d1b --- /dev/null +++ b/novaclient/tests/functional/v2/legacy/test_usage.py @@ -0,0 +1,74 @@ +# 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 datetime + +from novaclient.tests.functional import base + + +class TestUsageCLI(base.ClientTestBase): + + COMPUTE_API_VERSION = '2.1' + + def _get_num_servers_from_usage_output(self): + output = self.nova('usage') + servers = self._get_column_value_from_single_row_table( + output, 'Servers') + return int(servers) + + def _get_num_servers_by_tenant_from_usage_output(self): + tenant_id = self._get_project_id(self.cli_clients.tenant_name) + output = self.nova('usage --tenant=%s' % tenant_id) + servers = self._get_column_value_from_single_row_table( + output, 'Servers') + return int(servers) + + def test_usage(self): + before = self._get_num_servers_from_usage_output() + self._create_server('some-server') + after = self._get_num_servers_from_usage_output() + self.assertGreater(after, before) + + def test_usage_tenant(self): + before = self._get_num_servers_by_tenant_from_usage_output() + self._create_server('some-server') + after = self._get_num_servers_by_tenant_from_usage_output() + self.assertGreater(after, before) + + +class TestUsageClient(base.ClientTestBase): + + COMPUTE_API_VERSION = '2.1' + + def _create_servers_in_time_window(self): + start = datetime.datetime.now() + self._create_server('some-server') + self._create_server('another-server') + end = datetime.datetime.now() + return start, end + + def test_get(self): + start, end = self._create_servers_in_time_window() + tenant_id = self._get_project_id(self.cli_clients.tenant_name) + usage = self.client.usage.get(tenant_id, start=start, end=end) + self.assertEqual(tenant_id, usage.tenant_id) + self.assertGreaterEqual(len(usage.server_usages), 2) + + def test_list(self): + start, end = self._create_servers_in_time_window() + tenant_id = self._get_project_id(self.cli_clients.tenant_name) + usages = self.client.usage.list(start=start, end=end, detailed=True) + tenant_ids = [usage.tenant_id for usage in usages] + self.assertIn(tenant_id, tenant_ids) + for usage in usages: + if usage.tenant_id == tenant_id: + self.assertGreaterEqual(len(usage.server_usages), 2) diff --git a/novaclient/tests/functional/v2/test_usage.py b/novaclient/tests/functional/v2/test_usage.py new file mode 100644 index 000000000..1a19ad935 --- /dev/null +++ b/novaclient/tests/functional/v2/test_usage.py @@ -0,0 +1,38 @@ +# 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 novaclient.tests.functional.v2.legacy import test_usage + + +class TestUsageCLI_V240(test_usage.TestUsageCLI): + + COMPUTE_API_VERSION = '2.40' + + +class TestUsageClient_V240(test_usage.TestUsageClient): + + COMPUTE_API_VERSION = '2.40' + + def test_get(self): + start, end = self._create_servers_in_time_window() + tenant_id = self._get_project_id(self.cli_clients.tenant_name) + usage = self.client.usage.get( + tenant_id, start=start, end=end, limit=1) + self.assertEqual(tenant_id, usage.tenant_id) + self.assertEqual(1, len(usage.server_usages)) + + def test_list(self): + start, end = self._create_servers_in_time_window() + usages = self.client.usage.list( + start=start, end=end, detailed=True, limit=1) + self.assertEqual(1, len(usages)) + self.assertEqual(1, len(usages[0].server_usages)) diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 8fbe3fdc4..e6f749a51 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -69,6 +69,7 @@ class FakeSessionClient(base_client.SessionClient): def __init__(self, *args, **kwargs): self.callstack = [] + self.visited = [] self.auth = mock.Mock() self.session = mock.Mock() self.service_type = 'service_type' @@ -139,12 +140,21 @@ class FakeSessionClient(base_client.SessionClient): v2_image = True callback = callback.replace('get_v2_', 'get_') + simulate_pagination_next_links = [ + 'get_os_simple_tenant_usage', + 'get_os_simple_tenant_usage_tenant_id', + ] + if callback in simulate_pagination_next_links: + while callback in self.visited: + callback += '_next' + if not hasattr(self, callback): raise AssertionError('Called unknown API method: %s %s, ' 'expected fakes method name: %s' % (method, url, callback)) # Note the call + self.visited.append(callback) self.callstack.append((method, url, kwargs.get('body'))) status, headers, body = getattr(self, callback)(**kwargs) @@ -1551,6 +1561,8 @@ class FakeSessionClient(base_client.SessionClient): six.u('name'): six.u('f15image1'), six.u('tenant_id'): six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('instance_id'): + six.u('f079e394-1111-457b-b350-bb5ecc685cdd'), six.u('vcpus'): 1, six.u('memory_mb'): 512, six.u('state'): six.u('active'), @@ -1560,6 +1572,37 @@ class FakeSessionClient(base_client.SessionClient): six.u('start'): six.u('2011-12-25 19:48:41.750687'), six.u('total_local_gb_usage'): 0.0}]}) + def get_os_simple_tenant_usage_next(self, **kw): + return (200, FAKE_RESPONSE_HEADERS, + {six.u('tenant_usages'): [{ + six.u('total_memory_mb_usage'): 25451.762807466665, + six.u('total_vcpus_usage'): 49.71047423333333, + six.u('total_hours'): 49.71047423333333, + six.u('tenant_id'): + six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('stop'): six.u('2012-01-22 19:48:41.750722'), + six.u('server_usages'): [{ + six.u('hours'): 49.71047423333333, + six.u('uptime'): 27035, + six.u('local_gb'): 0, + six.u('ended_at'): None, + six.u('name'): six.u('f15image1'), + six.u('tenant_id'): + six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('instance_id'): + six.u('f079e394-2222-457b-b350-bb5ecc685cdd'), + six.u('vcpus'): 1, + six.u('memory_mb'): 512, + six.u('state'): six.u('active'), + six.u('flavor'): six.u('m1.tiny'), + six.u('started_at'): + six.u('2012-01-20 18:06:06.479998')}], + six.u('start'): six.u('2011-12-25 19:48:41.750687'), + six.u('total_local_gb_usage'): 0.0}]}) + + def get_os_simple_tenant_usage_next_next(self, **kw): + return (200, FAKE_RESPONSE_HEADERS, {six.u('tenant_usages'): []}) + def get_os_simple_tenant_usage_tenantfoo(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {six.u('tenant_usage'): { @@ -1576,6 +1619,8 @@ class FakeSessionClient(base_client.SessionClient): six.u('name'): six.u('f15image1'), six.u('tenant_id'): six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('instance_id'): + six.u('f079e394-1111-457b-b350-bb5ecc685cdd'), six.u('vcpus'): 1, six.u('memory_mb'): 512, six.u('state'): six.u('active'), six.u('flavor'): six.u('m1.tiny'), @@ -1597,6 +1642,8 @@ class FakeSessionClient(base_client.SessionClient): six.u('ended_at'): None, six.u('name'): six.u('f15image1'), six.u('tenant_id'): six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('instance_id'): + six.u('f079e394-1111-457b-b350-bb5ecc685cdd'), six.u('vcpus'): 1, six.u('memory_mb'): 512, six.u('state'): six.u('active'), six.u('flavor'): six.u('m1.tiny'), @@ -1617,6 +1664,8 @@ class FakeSessionClient(base_client.SessionClient): six.u('ended_at'): None, six.u('name'): six.u('f15image1'), six.u('tenant_id'): six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('instance_id'): + six.u('f079e394-1111-457b-b350-bb5ecc685cdd'), six.u('vcpus'): 1, six.u('memory_mb'): 512, six.u('state'): six.u('active'), six.u('flavor'): six.u('m1.tiny'), @@ -1624,6 +1673,31 @@ class FakeSessionClient(base_client.SessionClient): six.u('start'): six.u('2011-12-25 19:48:41.750687'), six.u('total_local_gb_usage'): 0.0}}) + def get_os_simple_tenant_usage_tenant_id_next(self, **kw): + return (200, {}, {six.u('tenant_usage'): { + six.u('total_memory_mb_usage'): 25451.762807466665, + six.u('total_vcpus_usage'): 49.71047423333333, + six.u('total_hours'): 49.71047423333333, + six.u('tenant_id'): six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('stop'): six.u('2012-01-22 19:48:41.750722'), + six.u('server_usages'): [{ + six.u('hours'): 49.71047423333333, + six.u('uptime'): 27035, six.u('local_gb'): 0, + six.u('ended_at'): None, + six.u('name'): six.u('f15image1'), + six.u('tenant_id'): six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('instance_id'): + six.u('f079e394-2222-457b-b350-bb5ecc685cdd'), + six.u('vcpus'): 1, six.u('memory_mb'): 512, + six.u('state'): six.u('active'), + six.u('flavor'): six.u('m1.tiny'), + six.u('started_at'): six.u('2012-01-20 18:06:06.479998')}], + six.u('start'): six.u('2011-12-25 19:48:41.750687'), + six.u('total_local_gb_usage'): 0.0}}) + + def get_os_simple_tenant_usage_tenant_id_next_next(self, **kw): + return (200, {}, {six.u('tenant_usage'): {}}) + # # Aggregates # diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index bcfee1e9a..6a27175da 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -50,7 +50,7 @@ class ShellFixture(fixtures.Fixture): def tearDown(self): # For some method like test_image_meta_bad_action we are # testing a SystemExit to be thrown and object self.shell has - # no time to get instantatiated which is OK in this case, so + # no time to get instantiated which is OK in this case, so # we make sure the method is there before launching it. if hasattr(self.shell, 'cs'): self.shell.cs.clear_callstack() @@ -1852,12 +1852,36 @@ class ShellTest(utils.TestCase): {'removeFloatingIp': {'address': '11.0.0.1'}}) def test_usage_list(self): - self.run_command('usage-list --start 2000-01-20 --end 2005-02-01') + cmd = 'usage-list --start 2000-01-20 --end 2005-02-01' + stdout, _stderr = self.run_command(cmd) self.assert_called('GET', '/os-simple-tenant-usage?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00&' + 'detailed=1') + # Servers, RAM MB-Hours, CPU Hours, Disk GB-Hours + self.assertIn('1 | 25451.76 | 49.71 | 0.00', stdout) + + def test_usage_list_stitch_together_next_results(self): + cmd = 'usage-list --start 2000-01-20 --end 2005-02-01' + stdout, _stderr = self.run_command(cmd, api_version='2.40') + self.assert_called('GET', + '/os-simple-tenant-usage?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00&' + 'detailed=1', pos=0) + markers = [ + 'f079e394-1111-457b-b350-bb5ecc685cdd', + 'f079e394-2222-457b-b350-bb5ecc685cdd', + ] + for pos, marker in enumerate(markers): + self.assert_called('GET', + '/os-simple-tenant-usage?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00&' + 'marker=%s&detailed=1' % (marker), pos=pos + 1) + # Servers, RAM MB-Hours, CPU Hours, Disk GB-Hours + self.assertIn('2 | 50903.53 | 99.42 | 0.00', stdout) def test_usage_list_no_args(self): timeutils.set_time_override(datetime.datetime(2005, 2, 1, 0, 0)) @@ -1870,12 +1894,34 @@ class ShellTest(utils.TestCase): 'detailed=1') def test_usage(self): - self.run_command('usage --start 2000-01-20 --end 2005-02-01 ' - '--tenant test') + cmd = 'usage --start 2000-01-20 --end 2005-02-01 --tenant test' + stdout, _stderr = self.run_command(cmd) self.assert_called('GET', '/os-simple-tenant-usage/test?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00') + # Servers, RAM MB-Hours, CPU Hours, Disk GB-Hours + self.assertIn('1 | 25451.76 | 49.71 | 0.00', stdout) + + def test_usage_stitch_together_next_results(self): + cmd = 'usage --start 2000-01-20 --end 2005-02-01' + stdout, _stderr = self.run_command(cmd, api_version='2.40') + self.assert_called('GET', + '/os-simple-tenant-usage/tenant_id?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00', pos=0) + markers = [ + 'f079e394-1111-457b-b350-bb5ecc685cdd', + 'f079e394-2222-457b-b350-bb5ecc685cdd', + ] + for pos, marker in enumerate(markers): + self.assert_called('GET', + '/os-simple-tenant-usage/tenant_id?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00&' + 'marker=%s' % (marker), pos=pos + 1) + # Servers, RAM MB-Hours, CPU Hours, Disk GB-Hours + self.assertIn('2 | 50903.53 | 99.42 | 0.00', stdout) def test_usage_no_tenant(self): self.run_command('usage --start 2000-01-20 --end 2005-02-01') diff --git a/novaclient/tests/unit/v2/test_usage.py b/novaclient/tests/unit/v2/test_usage.py index e67178b41..900842334 100644 --- a/novaclient/tests/unit/v2/test_usage.py +++ b/novaclient/tests/unit/v2/test_usage.py @@ -73,3 +73,52 @@ class UsageTest(utils.TestCase): 'GET', "/os-simple-tenant-usage/tenantfoo?start=%s&end=%s" % (start, stop)) + + +class UsageV40Test(UsageTest): + def setUp(self): + super(UsageV40Test, self).setUp() + self.cs.api_version = api_versions.APIVersion('2.40') + + def test_usage_list_with_paging(self): + now = datetime.datetime.now() + usages = self.cs.usage.list(now, now, marker='some-uuid', limit=3) + self.assert_request_id(usages, fakes.FAKE_REQUEST_ID_LIST) + + self.cs.assert_called( + 'GET', + '/os-simple-tenant-usage?' + + ('start=%s&' % now.isoformat()) + + ('end=%s&' % now.isoformat()) + + ('limit=3&marker=some-uuid&detailed=0')) + for u in usages: + self.assertIsInstance(u, usage.Usage) + + def test_usage_list_detailed_with_paging(self): + now = datetime.datetime.now() + usages = self.cs.usage.list( + now, now, detailed=True, marker='some-uuid', limit=3) + self.assert_request_id(usages, fakes.FAKE_REQUEST_ID_LIST) + + self.cs.assert_called( + 'GET', + '/os-simple-tenant-usage?' + + ('start=%s&' % now.isoformat()) + + ('end=%s&' % now.isoformat()) + + ('limit=3&marker=some-uuid&detailed=1')) + for u in usages: + self.assertIsInstance(u, usage.Usage) + + def test_usage_get_with_paging(self): + now = datetime.datetime.now() + u = self.cs.usage.get( + 'tenantfoo', now, now, marker='some-uuid', limit=3) + self.assert_request_id(u, fakes.FAKE_REQUEST_ID_LIST) + + self.cs.assert_called( + 'GET', + '/os-simple-tenant-usage/tenantfoo?' + + ('start=%s&' % now.isoformat()) + + ('end=%s&' % now.isoformat()) + + ('limit=3&marker=some-uuid')) + self.assertIsInstance(u, usage.Usage) diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index b104ff8a1..704e96705 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -19,6 +19,7 @@ from __future__ import print_function import argparse +import collections import datetime import functools import getpass @@ -3453,6 +3454,36 @@ def do_limits(cs, args): _print_absolute_limits(limits.absolute) +def _get_usage_marker(usage): + marker = None + if hasattr(usage, 'server_usages') and usage.server_usages: + marker = usage.server_usages[-1]['instance_id'] + return marker + + +def _get_usage_list_marker(usage_list): + marker = None + if usage_list: + marker = _get_usage_marker(usage_list[-1]) + return marker + + +def _merge_usage(usage, next_usage): + usage.server_usages.extend(next_usage.server_usages) + usage.total_hours += next_usage.total_hours + usage.total_memory_mb_usage += next_usage.total_memory_mb_usage + usage.total_vcpus_usage += next_usage.total_vcpus_usage + usage.total_local_gb_usage += next_usage.total_local_gb_usage + + +def _merge_usage_list(usages, next_usage_list): + for next_usage in next_usage_list: + if next_usage.tenant_id in usages: + _merge_usage(usages[next_usage.tenant_id], next_usage) + else: + usages[next_usage.tenant_id] = next_usage + + @utils.arg( '--start', metavar='', @@ -3490,7 +3521,23 @@ def do_usage_list(cs, args): setattr(u, simplerows[3], "%.2f" % u.total_vcpus_usage) setattr(u, simplerows[4], "%.2f" % u.total_local_gb_usage) - usage_list = cs.usage.list(start, end, detailed=True) + if cs.api_version < api_versions.APIVersion('2.40'): + usage_list = cs.usage.list(start, end, detailed=True) + else: + # If the number of instances used to calculate the usage is greater + # than CONF.api.max_limit, the usage will be split across multiple + # requests and the responses will need to be merged back together. + usages = collections.OrderedDict() + usage_list = cs.usage.list(start, end, detailed=True) + _merge_usage_list(usages, usage_list) + marker = _get_usage_list_marker(usage_list) + while marker: + next_usage_list = cs.usage.list( + start, end, detailed=True, marker=marker) + marker = _get_usage_list_marker(next_usage_list) + if marker: + _merge_usage_list(usages, next_usage_list) + usage_list = list(usages.values()) print(_("Usage from %(start)s to %(end)s:") % {'start': start.strftime(dateformat), @@ -3542,14 +3589,27 @@ def do_usage(cs, args): setattr(u, simplerows[3], "%.2f" % u.total_local_gb_usage) if args.tenant: - usage = cs.usage.get(args.tenant, start, end) + tenant_id = args.tenant else: if isinstance(cs.client, client.SessionClient): auth = cs.client.auth - project_id = auth.get_auth_ref(cs.client.session).project_id - usage = cs.usage.get(project_id, start, end) + tenant_id = auth.get_auth_ref(cs.client.session).project_id else: - usage = cs.usage.get(cs.client.tenant_id, start, end) + tenant_id = cs.client.tenant_id + + if cs.api_version < api_versions.APIVersion('2.40'): + usage = cs.usage.get(tenant_id, start, end) + else: + # If the number of instances used to calculate the usage is greater + # than CONF.api.max_limit, the usage will be split across multiple + # requests and the responses will need to be merged back together. + usage = cs.usage.get(tenant_id, start, end) + marker = _get_usage_marker(usage) + while marker: + next_usage = cs.usage.get(tenant_id, start, end, marker=marker) + marker = _get_usage_marker(next_usage) + if marker: + _merge_usage(usage, next_usage) print(_("Usage from %(start)s to %(end)s:") % {'start': start.strftime(dateformat), diff --git a/novaclient/v2/usage.py b/novaclient/v2/usage.py index dacf87b55..abbeca692 100644 --- a/novaclient/v2/usage.py +++ b/novaclient/v2/usage.py @@ -17,6 +17,7 @@ Usage interface. import oslo_utils +from novaclient import api_versions from novaclient import base @@ -46,7 +47,19 @@ class UsageManager(base.ManagerWithFind): Manage :class:`Usage` resources. """ resource_class = Usage + usage_prefix = 'os-simple-tenant-usage' + def _usage_query(self, start, end, marker=None, limit=None, detailed=None): + query = "?start=%s&end=%s" % (start.isoformat(), end.isoformat()) + if limit: + query = "%s&limit=%s" % (query, int(limit)) + if marker: + query = "%s&marker=%s" % (query, marker) + if detailed is not None: + query = "%s&detailed=%s" % (query, int(bool(detailed))) + return query + + @api_versions.wraps("2.0", "2.39") def list(self, start, end, detailed=False): """ Get usage for all tenants @@ -57,11 +70,31 @@ class UsageManager(base.ManagerWithFind): instance whose usage is part of the report :rtype: list of :class:`Usage`. """ - return self._list( - "/os-simple-tenant-usage?start=%s&end=%s&detailed=%s" % - (start.isoformat(), end.isoformat(), int(bool(detailed))), - "tenant_usages") + query_string = self._usage_query(start, end, detailed=detailed) + url = '/%s%s' % (self.usage_prefix, query_string) + return self._list(url, 'tenant_usages') + @api_versions.wraps("2.40") + def list(self, start, end, detailed=False, marker=None, limit=None): + """ + Get usage for all tenants + + :param start: :class:`datetime.datetime` Start date in UTC + :param end: :class:`datetime.datetime` End date in UTC + :param detailed: Whether to include information about each + instance whose usage is part of the report + :param marker: Begin returning usage data for instances that appear + later in the instance list than that represented by + this instance UUID (optional). + :param limit: Maximum number of instances to include in the usage + (optional). + :rtype: list of :class:`Usage`. + """ + query_string = self._usage_query(start, end, marker, limit, detailed) + url = '/%s%s' % (self.usage_prefix, query_string) + return self._list(url, 'tenant_usages') + + @api_versions.wraps("2.0", "2.39") def get(self, tenant_id, start, end): """ Get usage for a specific tenant. @@ -71,6 +104,25 @@ class UsageManager(base.ManagerWithFind): :param end: :class:`datetime.datetime` End date in UTC :rtype: :class:`Usage` """ - return self._get("/os-simple-tenant-usage/%s?start=%s&end=%s" % - (tenant_id, start.isoformat(), end.isoformat()), - "tenant_usage") + query_string = self._usage_query(start, end) + url = '/%s/%s%s' % (self.usage_prefix, tenant_id, query_string) + return self._get(url, 'tenant_usage') + + @api_versions.wraps("2.40") + def get(self, tenant_id, start, end, marker=None, limit=None): + """ + Get usage for a specific tenant. + + :param tenant_id: Tenant ID to fetch usage for + :param start: :class:`datetime.datetime` Start date in UTC + :param end: :class:`datetime.datetime` End date in UTC + :param marker: Begin returning usage data for instances that appear + later in the instance list than that represented by + this instance UUID (optional). + :param limit: Maximum number of instances to include in the usage + (optional). + :rtype: :class:`Usage` + """ + query_string = self._usage_query(start, end, marker, limit) + url = '/%s/%s%s' % (self.usage_prefix, tenant_id, query_string) + return self._get(url, 'tenant_usage') diff --git a/releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml b/releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml new file mode 100644 index 000000000..8b856ac65 --- /dev/null +++ b/releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added microversion v2.40 which introduces pagination support for usage + with the help of new optional parameters 'limit' and 'marker'.