Merge "compute: Add support for os-simple-tenant-usages API"

This commit is contained in:
Zuul 2022-08-13 12:39:13 +00:00 committed by Gerrit Code Review
commit 6469429498
4 changed files with 289 additions and 12 deletions

View File

@ -31,6 +31,7 @@ from openstack.compute.v2 import server_ip
from openstack.compute.v2 import server_migration as _server_migration
from openstack.compute.v2 import server_remote_console as _src
from openstack.compute.v2 import service as _service
from openstack.compute.v2 import usage as _usage
from openstack.compute.v2 import volume_attachment as _volume_attachment
from openstack import exceptions
from openstack.identity.v3 import project as _project
@ -609,10 +610,8 @@ class Proxy(proxy.Proxy):
:class:`~openstack.compute.v2.limits.RateLimits`
:rtype: :class:`~openstack.compute.v2.limits.Limits`
"""
res = self._get_resource(
limits.Limits, None)
return res.fetch(
self, **query)
res = self._get_resource(limits.Limits, None)
return res.fetch(self, **query)
# ========== Servers ==========
@ -1865,6 +1864,46 @@ class Proxy(proxy.Proxy):
return self._get(_server_diagnostics.ServerDiagnostics,
server_id=server_id, requires_id=False)
# ========== Project usage ============
def usages(self, start=None, end=None, **query):
"""Get project usages.
:param datetime.datetime start: Usage range start date.
:param datetime.datetime end: Usage range end date.
:param dict query: Additional query parameters to use.
:returns: A list of compute ``Usage`` objects.
"""
if start is not None:
query['start'] = start
if end is not None:
query['end'] = end
return self._list(_usage.Usage, **query)
def get_usage(self, project, start=None, end=None, **query):
"""Get usage for a single project.
:param project: ID or instance of
:class:`~openstack.identity.project.Project` of the project for
which the usage should be retrieved.
:param datetime.datetime start: Usage range start date.
:param datetime.datetime end: Usage range end date.
:param dict query: Additional query parameters to use.
:returns: A compute ``Usage`` object.
"""
project = self._get_resource(_project.Project, project)
if start is not None:
query['start'] = start
if end is not None:
query['end'] = end
res = self._get_resource(_usage.Usage, project.id)
return res.fetch(self, **query)
# ========== Server consoles ==========
def create_server_remote_console(self, server, **attrs):
@ -1953,11 +1992,9 @@ class Proxy(proxy.Proxy):
"""
project = self._get_resource(_project.Project, project)
res = self._get_resource(
_quota_set.QuotaSet, None, project_id=project.id)
if not query:
query = {}
return res.fetch(
self, usage=usage, **query)
_quota_set.QuotaSet, None, project_id=project.id,
)
return res.fetch(self, usage=usage, **query)
def get_quota_set_defaults(self, project):
"""Show QuotaSet defaults for the project
@ -1972,9 +2009,9 @@ class Proxy(proxy.Proxy):
"""
project = self._get_resource(_project.Project, project)
res = self._get_resource(
_quota_set.QuotaSet, None, project_id=project.id)
return res.fetch(
self, base_path='/os-quota-sets/defaults')
_quota_set.QuotaSet, None, project_id=project.id,
)
return res.fetch(self, base_path='/os-quota-sets/defaults')
def revert_quota_set(self, project, **query):
"""Reset Quota for the project/user.

View File

@ -0,0 +1,102 @@
# 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 openstack import resource
class ServerUsage(resource.Resource):
resource_key = None
resources_key = None
# Capabilities
allow_create = False
allow_fetch = False
allow_delete = False
allow_list = False
allow_commit = False
# Properties
#: The duration that the server exists (in hours).
hours = resource.Body('hours')
#: The display name of a flavor.
flavor = resource.Body('flavor')
#: The UUID of the server.
instance_id = resource.Body('instance_id')
#: The server name.
name = resource.Body('name')
#: The UUID of the project in a multi-tenancy cloud.
project_id = resource.Body('tenant_id')
#: The memory size of the server (in MiB).
memory_mb = resource.Body('memory_mb')
#: The sum of the root disk size of the server and the ephemeral disk size
#: of it (in GiB).
local_gb = resource.Body('local_gb')
#: The number of virtual CPUs that the server uses.
vcpus = resource.Body('vcpus')
#: The date and time when the server was launched.
started_at = resource.Body('started_at')
#: The date and time when the server was deleted.
ended_at = resource.Body('ended_at')
#: The VM state.
state = resource.Body('state')
#: The uptime of the server.
uptime = resource.Body('uptime')
class Usage(resource.Resource):
resource_key = 'tenant_usage'
resources_key = 'tenant_usages'
base_path = '/os-simple-tenant-usage'
# Capabilities
allow_create = False
allow_fetch = True
allow_delete = False
allow_list = True
allow_commit = False
# TODO(stephenfin): Add 'start', 'end'. These conflict with the body
# responses though.
_query_mapping = resource.QueryParameters(
"detailed",
"limit",
"marker",
"start",
"end",
)
# Properties
#: The UUID of the project in a multi-tenancy cloud.
project_id = resource.Body('tenant_id')
#: A list of the server usage objects.
server_usages = resource.Body(
'server_usages', type=list, list_type=ServerUsage,
)
#: Multiplying the server disk size (in GiB) by hours the server exists,
#: and then adding that all together for each server.
total_local_gb_usage = resource.Body('total_local_gb_usage')
#: Multiplying the number of virtual CPUs of the server by hours the server
#: exists, and then adding that all together for each server.
total_vcpus_usage = resource.Body('total_vcpus_usage')
#: Multiplying the server memory size (in MiB) by hours the server exists,
#: and then adding that all together for each server.
total_memory_mb_usage = resource.Body('total_memory_mb_usage')
#: The total duration that servers exist (in hours).
total_hours = resource.Body('total_hours')
#: The beginning time to calculate usage statistics on compute and storage
#: resources.
start = resource.Body('start')
#: The ending time to calculate usage statistics on compute and storage
#: resources.
stop = resource.Body('stop')
_max_microversion = '2.75'

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from unittest import mock
from openstack.compute.v2 import _proxy
@ -30,6 +31,7 @@ from openstack.compute.v2 import server_ip
from openstack.compute.v2 import server_migration
from openstack.compute.v2 import server_remote_console
from openstack.compute.v2 import service
from openstack.compute.v2 import usage
from openstack import resource
from openstack.tests.unit import test_proxy_base
@ -1105,6 +1107,43 @@ class TestCompute(TestComputeProxy):
method_args=["value", {'id': 'id', 'name': 'sg'}],
expected_args=[self.proxy, 'sg'])
def test_usages(self):
self.verify_list(self.proxy.usages, usage.Usage)
def test_usages__with_kwargs(self):
now = datetime.datetime.utcnow()
start = now - datetime.timedelta(weeks=4)
end = end = now + datetime.timedelta(days=1)
self.verify_list(
self.proxy.usages,
usage.Usage,
method_kwargs={'start': start, 'end': end},
expected_kwargs={'start': start, 'end': end},
)
def test_get_usage(self):
self._verify(
"openstack.compute.v2.usage.Usage.fetch",
self.proxy.get_usage,
method_args=['value'],
method_kwargs={},
expected_args=[self.proxy],
expected_kwargs={},
)
def test_get_usage__with_kwargs(self):
now = datetime.datetime.utcnow()
start = now - datetime.timedelta(weeks=4)
end = end = now + datetime.timedelta(days=1)
self._verify(
"openstack.compute.v2.usage.Usage.fetch",
self.proxy.get_usage,
method_args=['value'],
method_kwargs={'start': start, 'end': end},
expected_args=[self.proxy],
expected_kwargs={'start': start, 'end': end},
)
def test_create_server_remote_console(self):
self.verify_create(
self.proxy.create_server_remote_console,

View File

@ -0,0 +1,99 @@
# 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 openstack.compute.v2 import usage
from openstack.tests.unit import base
EXAMPLE = {
"tenant_id": "781c9299e68d4b7c80ef52712889647f",
"server_usages": [
{
"hours": 79.51840531333333,
"flavor": "m1.tiny",
"instance_id": "76638c30-d199-4c2e-8154-7dea963bfe2f",
"name": "test-server",
"tenant_id": "781c9299e68d4b7c80ef52712889647f",
"memory_mb": 512,
"local_gb": 1,
"vcpus": 1,
"started_at": "2022-05-16T10:35:31.000000",
"ended_at": None,
"state": "active",
"uptime": 286266,
}
],
"total_local_gb_usage": 79.51840531333333,
"total_vcpus_usage": 79.51840531333333,
"total_memory_mb_usage": 40713.423520426666,
"total_hours": 79.51840531333333,
"start": "2022-04-21T18:06:47.064959",
"stop": "2022-05-19T18:06:37.259128",
}
class TestUsage(base.TestCase):
def test_basic(self):
sot = usage.Usage()
self.assertEqual('tenant_usage', sot.resource_key)
self.assertEqual('tenant_usages', sot.resources_key)
self.assertEqual('/os-simple-tenant-usage', sot.base_path)
self.assertTrue(sot.allow_fetch)
self.assertTrue(sot.allow_list)
def test_make_it(self):
sot = usage.Usage(**EXAMPLE)
self.assertEqual(EXAMPLE['tenant_id'], sot.project_id)
self.assertEqual(
EXAMPLE['total_local_gb_usage'],
sot.total_local_gb_usage,
)
self.assertEqual(EXAMPLE['total_vcpus_usage'], sot.total_vcpus_usage)
self.assertEqual(
EXAMPLE['total_memory_mb_usage'],
sot.total_memory_mb_usage,
)
self.assertEqual(EXAMPLE['total_hours'], sot.total_hours)
self.assertEqual(EXAMPLE['start'], sot.start)
self.assertEqual(EXAMPLE['stop'], sot.stop)
# now do the embedded objects
self.assertIsInstance(sot.server_usages, list)
self.assertEqual(1, len(sot.server_usages))
ssot = sot.server_usages[0]
self.assertIsInstance(ssot, usage.ServerUsage)
self.assertEqual(EXAMPLE['server_usages'][0]['hours'], ssot.hours)
self.assertEqual(EXAMPLE['server_usages'][0]['flavor'], ssot.flavor)
self.assertEqual(
EXAMPLE['server_usages'][0]['instance_id'], ssot.instance_id
)
self.assertEqual(EXAMPLE['server_usages'][0]['name'], ssot.name)
self.assertEqual(
EXAMPLE['server_usages'][0]['tenant_id'], ssot.project_id
)
self.assertEqual(
EXAMPLE['server_usages'][0]['memory_mb'], ssot.memory_mb
)
self.assertEqual(
EXAMPLE['server_usages'][0]['local_gb'], ssot.local_gb
)
self.assertEqual(EXAMPLE['server_usages'][0]['vcpus'], ssot.vcpus)
self.assertEqual(
EXAMPLE['server_usages'][0]['started_at'], ssot.started_at
)
self.assertEqual(
EXAMPLE['server_usages'][0]['ended_at'], ssot.ended_at
)
self.assertEqual(EXAMPLE['server_usages'][0]['state'], ssot.state)
self.assertEqual(EXAMPLE['server_usages'][0]['uptime'], ssot.uptime)