Use the compute SDK in usage commands

Update usage list and usage show to use the compute component of the
OpenStack SDK instead of directly using the nova interface.

Change-Id: I1c4d2247c9c1a577ed9efad7e8332e7c9b974ad5
This commit is contained in:
Daniel Wilson 2022-10-18 00:30:55 -04:00
parent ccd9356550
commit ce4cbeab67
2 changed files with 48 additions and 66 deletions

View File

@ -15,12 +15,10 @@
"""Usage action implementations""" """Usage action implementations"""
import collections
import datetime import datetime
import functools import functools
from cliff import columns as cliff_columns from cliff import columns as cliff_columns
from novaclient import api_versions
from osc_lib.command import command from osc_lib.command import command
from osc_lib import utils from osc_lib import utils
@ -58,7 +56,7 @@ class ProjectColumn(cliff_columns.FormattableColumn):
class CountColumn(cliff_columns.FormattableColumn): class CountColumn(cliff_columns.FormattableColumn):
def human_readable(self): def human_readable(self):
return len(self._value) return len(self._value) if self._value is not None else None
class FloatColumn(cliff_columns.FormattableColumn): class FloatColumn(cliff_columns.FormattableColumn):
@ -69,7 +67,7 @@ class FloatColumn(cliff_columns.FormattableColumn):
def _formatters(project_cache): def _formatters(project_cache):
return { return {
'tenant_id': functools.partial( 'project_id': functools.partial(
ProjectColumn, project_cache=project_cache), ProjectColumn, project_cache=project_cache),
'server_usages': CountColumn, 'server_usages': CountColumn,
'total_memory_mb_usage': FloatColumn, 'total_memory_mb_usage': FloatColumn,
@ -102,10 +100,10 @@ def _merge_usage(usage, next_usage):
def _merge_usage_list(usages, next_usage_list): def _merge_usage_list(usages, next_usage_list):
for next_usage in next_usage_list: for next_usage in next_usage_list:
if next_usage.tenant_id in usages: if next_usage.project_id in usages:
_merge_usage(usages[next_usage.tenant_id], next_usage) _merge_usage(usages[next_usage.project_id], next_usage)
else: else:
usages[next_usage.tenant_id] = next_usage usages[next_usage.project_id] = next_usage
class ListUsage(command.Lister): class ListUsage(command.Lister):
@ -138,9 +136,9 @@ class ListUsage(command.Lister):
else: else:
return project return project
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
columns = ( columns = (
"tenant_id", "project_id",
"server_usages", "server_usages",
"total_memory_mb_usage", "total_memory_mb_usage",
"total_vcpus_usage", "total_vcpus_usage",
@ -154,36 +152,25 @@ class ListUsage(command.Lister):
"Disk GB-Hours" "Disk GB-Hours"
) )
dateformat = "%Y-%m-%d" date_cli_format = "%Y-%m-%d"
date_api_format = "%Y-%m-%dT%H:%M:%S"
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
if parsed_args.start: if parsed_args.start:
start = datetime.datetime.strptime(parsed_args.start, dateformat) start = datetime.datetime.strptime(
parsed_args.start, date_cli_format)
else: else:
start = now - datetime.timedelta(weeks=4) start = now - datetime.timedelta(weeks=4)
if parsed_args.end: if parsed_args.end:
end = datetime.datetime.strptime(parsed_args.end, dateformat) end = datetime.datetime.strptime(parsed_args.end, date_cli_format)
else: else:
end = now + datetime.timedelta(days=1) end = now + datetime.timedelta(days=1)
if compute_client.api_version < api_versions.APIVersion("2.40"): usage_list = list(compute_client.usages(
usage_list = compute_client.usage.list(start, end, detailed=True) start=start.strftime(date_api_format),
else: end=end.strftime(date_api_format),
# If the number of instances used to calculate the usage is greater detailed=True))
# 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 = compute_client.usage.list(start, end, detailed=True)
_merge_usage_list(usages, usage_list)
marker = _get_usage_list_marker(usage_list)
while marker:
next_usage_list = compute_client.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())
# Cache the project list # Cache the project list
project_cache = {} project_cache = {}
@ -196,8 +183,8 @@ class ListUsage(command.Lister):
if parsed_args.formatter == 'table' and len(usage_list) > 0: if parsed_args.formatter == 'table' and len(usage_list) > 0:
self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % {
"start": start.strftime(dateformat), "start": start.strftime(date_cli_format),
"end": end.strftime(dateformat), "end": end.strftime(date_cli_format),
}) })
return ( return (
@ -239,17 +226,19 @@ class ShowUsage(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.identity
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
dateformat = "%Y-%m-%d" date_cli_format = "%Y-%m-%d"
date_api_format = "%Y-%m-%dT%H:%M:%S"
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
if parsed_args.start: if parsed_args.start:
start = datetime.datetime.strptime(parsed_args.start, dateformat) start = datetime.datetime.strptime(
parsed_args.start, date_cli_format)
else: else:
start = now - datetime.timedelta(weeks=4) start = now - datetime.timedelta(weeks=4)
if parsed_args.end: if parsed_args.end:
end = datetime.datetime.strptime(parsed_args.end, dateformat) end = datetime.datetime.strptime(parsed_args.end, date_cli_format)
else: else:
end = now + datetime.timedelta(days=1) end = now + datetime.timedelta(days=1)
@ -262,19 +251,21 @@ class ShowUsage(command.ShowOne):
# Get the project from the current auth # Get the project from the current auth
project = self.app.client_manager.auth_ref.project_id project = self.app.client_manager.auth_ref.project_id
usage = compute_client.usage.get(project, start, end) usage = compute_client.get_usage(
project=project, start=start.strftime(date_api_format),
end=end.strftime(date_api_format))
if parsed_args.formatter == 'table': if parsed_args.formatter == 'table':
self.app.stdout.write(_( self.app.stdout.write(_(
"Usage from %(start)s to %(end)s on project %(project)s: \n" "Usage from %(start)s to %(end)s on project %(project)s: \n"
) % { ) % {
"start": start.strftime(dateformat), "start": start.strftime(date_cli_format),
"end": end.strftime(dateformat), "end": end.strftime(date_cli_format),
"project": project, "project": project,
}) })
columns = ( columns = (
"tenant_id", "project_id",
"server_usages", "server_usages",
"total_memory_mb_usage", "total_memory_mb_usage",
"total_vcpus_usage", "total_vcpus_usage",

View File

@ -11,11 +11,8 @@
# under the License. # under the License.
# #
import datetime
from unittest import mock from unittest import mock
from novaclient import api_versions
from openstackclient.compute.v2 import usage as usage_cmds from openstackclient.compute.v2 import usage as usage_cmds
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@ -26,8 +23,9 @@ class TestUsage(compute_fakes.TestComputev2):
def setUp(self): def setUp(self):
super(TestUsage, self).setUp() super(TestUsage, self).setUp()
self.usage_mock = self.app.client_manager.compute.usage self.app.client_manager.sdk_connection = mock.Mock()
self.usage_mock.reset_mock() self.app.client_manager.sdk_connection.compute = mock.Mock()
self.sdk_client = self.app.client_manager.sdk_connection.compute
self.projects_mock = self.app.client_manager.identity.projects self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock() self.projects_mock.reset_mock()
@ -38,7 +36,7 @@ class TestUsageList(TestUsage):
project = identity_fakes.FakeProject.create_one_project() project = identity_fakes.FakeProject.create_one_project()
# Return value of self.usage_mock.list(). # Return value of self.usage_mock.list().
usages = compute_fakes.FakeUsage.create_usages( usages = compute_fakes.FakeUsage.create_usages(
attrs={'tenant_id': project.name}, count=1) attrs={'project_id': project.name}, count=1)
columns = ( columns = (
"Project", "Project",
@ -49,7 +47,7 @@ class TestUsageList(TestUsage):
) )
data = [( data = [(
usage_cmds.ProjectColumn(usages[0].tenant_id), usage_cmds.ProjectColumn(usages[0].project_id),
usage_cmds.CountColumn(usages[0].server_usages), usage_cmds.CountColumn(usages[0].server_usages),
usage_cmds.FloatColumn(usages[0].total_memory_mb_usage), usage_cmds.FloatColumn(usages[0].total_memory_mb_usage),
usage_cmds.FloatColumn(usages[0].total_vcpus_usage), usage_cmds.FloatColumn(usages[0].total_vcpus_usage),
@ -59,7 +57,7 @@ class TestUsageList(TestUsage):
def setUp(self): def setUp(self):
super(TestUsageList, self).setUp() super(TestUsageList, self).setUp()
self.usage_mock.list.return_value = self.usages self.sdk_client.usages.return_value = self.usages
self.projects_mock.list.return_value = [self.project] self.projects_mock.list.return_value = [self.project]
# Get the command object to test # Get the command object to test
@ -97,9 +95,9 @@ class TestUsageList(TestUsage):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.list.assert_called_with() self.projects_mock.list.assert_called_with()
self.usage_mock.list.assert_called_with( self.sdk_client.usages.assert_called_with(
datetime.datetime(2016, 11, 11, 0, 0), start='2016-11-11T00:00:00',
datetime.datetime(2016, 12, 20, 0, 0), end='2016-12-20T00:00:00',
detailed=True) detailed=True)
self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.columns, columns)
@ -112,20 +110,13 @@ class TestUsageList(TestUsage):
('end', None), ('end', None),
] ]
self.app.client_manager.compute.api_version = api_versions.APIVersion(
'2.40')
self.usage_mock.list.reset_mock()
self.usage_mock.list.side_effect = [self.usages, []]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.list.assert_called_with() self.projects_mock.list.assert_called_with()
self.usage_mock.list.assert_has_calls([ self.sdk_client.usages.assert_has_calls([
mock.call(mock.ANY, mock.ANY, detailed=True), mock.call(start=mock.ANY, end=mock.ANY, detailed=True)
mock.call(mock.ANY, mock.ANY, detailed=True,
marker=self.usages[0]['server_usages'][0]['instance_id'])
]) ])
self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.columns, columns)
self.assertCountEqual(tuple(self.data), tuple(data)) self.assertCountEqual(tuple(self.data), tuple(data))
@ -136,7 +127,7 @@ class TestUsageShow(TestUsage):
project = identity_fakes.FakeProject.create_one_project() project = identity_fakes.FakeProject.create_one_project()
# Return value of self.usage_mock.list(). # Return value of self.usage_mock.list().
usage = compute_fakes.FakeUsage.create_one_usage( usage = compute_fakes.FakeUsage.create_one_usage(
attrs={'tenant_id': project.name}) attrs={'project_id': project.name})
columns = ( columns = (
'Project', 'Project',
@ -147,7 +138,7 @@ class TestUsageShow(TestUsage):
) )
data = ( data = (
usage_cmds.ProjectColumn(usage.tenant_id), usage_cmds.ProjectColumn(usage.project_id),
usage_cmds.CountColumn(usage.server_usages), usage_cmds.CountColumn(usage.server_usages),
usage_cmds.FloatColumn(usage.total_memory_mb_usage), usage_cmds.FloatColumn(usage.total_memory_mb_usage),
usage_cmds.FloatColumn(usage.total_vcpus_usage), usage_cmds.FloatColumn(usage.total_vcpus_usage),
@ -157,7 +148,7 @@ class TestUsageShow(TestUsage):
def setUp(self): def setUp(self):
super(TestUsageShow, self).setUp() super(TestUsageShow, self).setUp()
self.usage_mock.get.return_value = self.usage self.sdk_client.get_usage.return_value = self.usage
self.projects_mock.get.return_value = self.project self.projects_mock.get.return_value = self.project
# Get the command object to test # Get the command object to test
@ -199,10 +190,10 @@ class TestUsageShow(TestUsage):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.usage_mock.get.assert_called_with( self.sdk_client.get_usage.assert_called_with(
self.project.id, project=self.project.id,
datetime.datetime(2016, 11, 11, 0, 0), start='2016-11-11T00:00:00',
datetime.datetime(2016, 12, 20, 0, 0)) end='2016-12-20T00:00:00')
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data) self.assertEqual(self.data, data)