summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-12-20 00:25:26 +0000
committerGerrit Code Review <review@openstack.org>2016-12-20 00:25:26 +0000
commit9388fd1d89a565ce01ebfbb182da43bc085f79c1 (patch)
tree36e6f499f8734f73748f293df0438ae08852521b
parent2e04cb29d70524a3f11bcd3c785e1aabae7b9a69 (diff)
parent53b23d52177ee561ef587f6db33722157d18763e (diff)
downloadpython-novaclient-9388fd1d89a565ce01ebfbb182da43bc085f79c1.tar.gz
Merge "Microversion 2.40 - Simple tenant usage pagination"
-rw-r--r--novaclient/__init__.py2
-rw-r--r--novaclient/tests/functional/v2/legacy/test_usage.py74
-rw-r--r--novaclient/tests/functional/v2/test_usage.py38
-rw-r--r--novaclient/tests/unit/v2/fakes.py74
-rw-r--r--novaclient/tests/unit/v2/test_shell.py54
-rw-r--r--novaclient/tests/unit/v2/test_usage.py49
-rw-r--r--novaclient/v2/shell.py70
-rw-r--r--novaclient/v2/usage.py66
-rw-r--r--releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml4
9 files changed, 414 insertions, 17 deletions
diff --git a/novaclient/__init__.py b/novaclient/__init__.py
index 85cadcf6..956e139e 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 00000000..e37693d1
--- /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 00000000..1a19ad93
--- /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 8fbe3fdc..e6f749a5 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,36 @@ 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'),
+ 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(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'),
@@ -1560,6 +1600,9 @@ 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_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,30 @@ 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'),
+ 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(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'),
@@ -1624,6 +1695,9 @@ 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_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 bcfee1e9..6a27175d 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 e67178b4..90084233 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 b104ff8a..704e9670 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='<start>',
@@ -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 dacf87b5..abbeca69 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 00000000..8b856ac6
--- /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'.