Skip to content

Commit c79de8a

Browse files
committed
Paginate over usage list to return all usages
since nova api 2.40 the os-simple-tenant-usage API supports pagination and will by default return a number of entities configured internally in Nova. This means that when there are many enough projects, the single call to usage.list() will not return usages for all projects. This patch effectively copy-pastes the logic to paginate over usage list results from novaclient/v2/shell.py code. Change-Id: I1b639fe386b7b7db3223f6965495094b9d51533a Story: #2005099 Task: #29713
1 parent 55cbbbe commit c79de8a

3 files changed

Lines changed: 76 additions & 1 deletion

File tree

openstackclient/compute/v2/usage.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,47 @@
1515

1616
"""Usage action implementations"""
1717

18+
import collections
1819
import datetime
1920

21+
from novaclient import api_versions
2022
from osc_lib.command import command
2123
from osc_lib import utils
2224
import six
2325

2426
from openstackclient.i18n import _
2527

2628

29+
def _get_usage_marker(usage):
30+
marker = None
31+
if hasattr(usage, 'server_usages') and usage.server_usages:
32+
marker = usage.server_usages[-1]['instance_id']
33+
return marker
34+
35+
36+
def _get_usage_list_marker(usage_list):
37+
marker = None
38+
if usage_list:
39+
marker = _get_usage_marker(usage_list[-1])
40+
return marker
41+
42+
43+
def _merge_usage(usage, next_usage):
44+
usage.server_usages.extend(next_usage.server_usages)
45+
usage.total_hours += next_usage.total_hours
46+
usage.total_memory_mb_usage += next_usage.total_memory_mb_usage
47+
usage.total_vcpus_usage += next_usage.total_vcpus_usage
48+
usage.total_local_gb_usage += next_usage.total_local_gb_usage
49+
50+
51+
def _merge_usage_list(usages, next_usage_list):
52+
for next_usage in next_usage_list:
53+
if next_usage.tenant_id in usages:
54+
_merge_usage(usages[next_usage.tenant_id], next_usage)
55+
else:
56+
usages[next_usage.tenant_id] = next_usage
57+
58+
2759
class ListUsage(command.Lister):
2860
_description = _("List resource usage per project")
2961

@@ -83,7 +115,23 @@ def _format_project(project):
83115
else:
84116
end = now + datetime.timedelta(days=1)
85117

86-
usage_list = compute_client.usage.list(start, end, detailed=True)
118+
if compute_client.api_version < api_versions.APIVersion("2.40"):
119+
usage_list = compute_client.usage.list(start, end, detailed=True)
120+
else:
121+
# If the number of instances used to calculate the usage is greater
122+
# than CONF.api.max_limit, the usage will be split across multiple
123+
# requests and the responses will need to be merged back together.
124+
usages = collections.OrderedDict()
125+
usage_list = compute_client.usage.list(start, end, detailed=True)
126+
_merge_usage_list(usages, usage_list)
127+
marker = _get_usage_list_marker(usage_list)
128+
while marker:
129+
next_usage_list = compute_client.usage.list(
130+
start, end, detailed=True, marker=marker)
131+
marker = _get_usage_list_marker(next_usage_list)
132+
if marker:
133+
_merge_usage_list(usages, next_usage_list)
134+
usage_list = list(usages.values())
87135

88136
# Cache the project list
89137
project_cache = {}

openstackclient/tests/unit/compute/v2/fakes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,7 @@ def create_one_usage(attrs=None):
13041304
'local_gb': 1,
13051305
'memory_mb': 512,
13061306
'name': 'usage-name-' + uuid.uuid4().hex,
1307+
'instance_id': uuid.uuid4().hex,
13071308
'state': 'active',
13081309
'uptime': 3600,
13091310
'vcpus': 1

openstackclient/tests/unit/compute/v2/test_usage.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import datetime
1515

1616
import mock
17+
from novaclient import api_versions
1718

1819
from openstackclient.compute.v2 import usage
1920
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -104,6 +105,31 @@ def test_usage_list_with_options(self):
104105
self.assertEqual(self.columns, columns)
105106
self.assertEqual(tuple(self.data), tuple(data))
106107

108+
def test_usage_list_with_pagination(self):
109+
arglist = []
110+
verifylist = [
111+
('start', None),
112+
('end', None),
113+
]
114+
115+
self.app.client_manager.compute.api_version = api_versions.APIVersion(
116+
'2.40')
117+
self.usage_mock.list.reset_mock()
118+
self.usage_mock.list.side_effect = [self.usages, []]
119+
120+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
121+
122+
columns, data = self.cmd.take_action(parsed_args)
123+
124+
self.projects_mock.list.assert_called_with()
125+
self.usage_mock.list.assert_has_calls([
126+
mock.call(mock.ANY, mock.ANY, detailed=True),
127+
mock.call(mock.ANY, mock.ANY, detailed=True,
128+
marker=self.usages[0]['server_usages'][0]['instance_id'])
129+
])
130+
self.assertEqual(self.columns, columns)
131+
self.assertEqual(tuple(self.data), tuple(data))
132+
107133

108134
class TestUsageShow(TestUsage):
109135

0 commit comments

Comments
 (0)