volume: Add Limit to volume v2 API

Another case of adding a resource from v3 to the legacy v2 API. We
tackled Extension in change I1e4528f5a5d8e2caaaf204792ddcee7267e4c2e9
and Capability in change I98252ddd0dadba2bfa1aaf97b59932a19c396cd4. Now
it's time for Limit.

Note that the API is unchanged between v2 and v3 so this can be a
straightforward copy-paste job. This was determined by inspecting the
cinder code before the v2 API was removed (e.g. commit 'e05b261af~',
file 'cinder/api/views/limits.py').

Change-Id: I5a375d8dee7e68368f2e454851812a3638acf9ee
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2023-10-31 16:46:22 +00:00
parent 7235634f74
commit 5dd7d64769
6 changed files with 361 additions and 0 deletions

View File

@ -33,6 +33,13 @@ Capabilities Operations
:noindex:
:members: get_capabilities
Limits Operations
^^^^^^^^^^^^^^^^^
.. autoclass:: openstack.block_storage.v2._proxy.Proxy
:noindex:
:members: get_limits
Type Operations
^^^^^^^^^^^^^^^

View File

@ -0,0 +1,37 @@
openstack.block_storage.v2.limits
=================================
.. automodule:: openstack.block_storage.v2.limits
The AbsoluteLimit Class
-----------------------
The ``AbsoluteLimit`` class inherits from
:class:`~openstack.resource.Resource`.
.. autoclass:: openstack.block_storage.v2.limits.AbsoluteLimit
:members:
The Limit Class
---------------
The ``Limit`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.block_storage.v2.limits.Limit
:members:
The RateLimit Class
-------------------
The ``RateLimit`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.block_storage.v2.limits.RateLimit
:members:
The RateLimits Class
--------------------
The ``RateLimits`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.block_storage.v2.limits.RateLimits
:members:

View File

@ -14,6 +14,7 @@ from openstack.block_storage import _base_proxy
from openstack.block_storage.v2 import backup as _backup
from openstack.block_storage.v2 import capabilities as _capabilities
from openstack.block_storage.v2 import extension as _extension
from openstack.block_storage.v2 import limits as _limits
from openstack.block_storage.v2 import quota_set as _quota_set
from openstack.block_storage.v2 import snapshot as _snapshot
from openstack.block_storage.v2 import stats as _stats
@ -610,6 +611,23 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
backup = self._get_resource(_backup.Backup, backup)
backup.reset(self, status)
# ====== LIMITS ======
def get_limits(self, project=None):
"""Retrieves limits
:param project: A project to get limits for. The value can be either
the ID of a project or an
:class:`~openstack.identity.v2.project.Project` instance.
:returns: A Limit object, including both
:class:`~openstack.block_storage.v2.limits.AbsoluteLimit` and
:class:`~openstack.block_storage.v2.limits.RateLimit`
:rtype: :class:`~openstack.block_storage.v2.limits.Limit`
"""
params = {}
if project:
params['project_id'] = resource.Resource._get_id(project)
return self._get(_limits.Limit, requires_id=False, **params)
# ====== CAPABILITIES ======
def get_capabilities(self, host):
"""Get a backend's capabilites

View File

@ -0,0 +1,82 @@
# 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 AbsoluteLimit(resource.Resource):
#: Properties
#: The maximum total amount of backups, in gibibytes (GiB).
max_total_backup_gigabytes = resource.Body(
"maxTotalBackupGigabytes", type=int
)
#: The maximum number of backups.
max_total_backups = resource.Body("maxTotalBackups", type=int)
#: The maximum number of snapshots.
max_total_snapshots = resource.Body("maxTotalSnapshots", type=int)
#: The maximum total amount of volumes, in gibibytes (GiB).
max_total_volume_gigabytes = resource.Body(
"maxTotalVolumeGigabytes", type=int
)
#: The maximum number of volumes.
max_total_volumes = resource.Body("maxTotalVolumes", type=int)
#: The total number of backups gibibytes (GiB) used.
total_backup_gigabytes_used = resource.Body(
"totalBackupGigabytesUsed", type=int
)
#: The total number of backups used.
total_backups_used = resource.Body("totalBackupsUsed", type=int)
#: The total number of gibibytes (GiB) used.
total_gigabytes_used = resource.Body("totalGigabytesUsed", type=int)
#: The total number of snapshots used.
total_snapshots_used = resource.Body("totalSnapshotsUsed", type=int)
#: The total number of volumes used.
total_volumes_used = resource.Body("totalVolumesUsed", type=int)
class RateLimit(resource.Resource):
#: Properties
#: Rate limits next availabe time.
next_available = resource.Body("next-available")
#: Integer for rate limits remaining.
remaining = resource.Body("remaining", type=int)
#: Unit of measurement for the value parameter.
unit = resource.Body("unit")
#: Integer number of requests which can be made.
value = resource.Body("value", type=int)
#: An HTTP verb (POST, PUT, etc.).
verb = resource.Body("verb")
class RateLimits(resource.Resource):
#: Properties
#: A list of the specific limits that apply to the ``regex`` and ``uri``.
limits = resource.Body("limit", type=list, list_type=RateLimit)
#: A regex representing which routes this rate limit applies to.
regex = resource.Body("regex")
#: A URI representing which routes this rate limit applies to.
uri = resource.Body("uri")
class Limit(resource.Resource):
resource_key = "limits"
base_path = "/limits"
# capabilities
allow_fetch = True
#: Properties
#: An absolute limits object.
absolute = resource.Body("absolute", type=AbsoluteLimit)
#: Rate-limit volume copy bandwidth, used to mitigate
#: slow down of data access from the instances.
rate = resource.Body("rate", type=list, list_type=RateLimits)

View File

@ -0,0 +1,206 @@
# 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.block_storage.v2 import limits
from openstack.tests.unit import base
ABSOLUTE_LIMIT = {
"totalSnapshotsUsed": 1,
"maxTotalBackups": 10,
"maxTotalVolumeGigabytes": 1000,
"maxTotalSnapshots": 10,
"maxTotalBackupGigabytes": 1000,
"totalBackupGigabytesUsed": 1,
"maxTotalVolumes": 10,
"totalVolumesUsed": 2,
"totalBackupsUsed": 3,
"totalGigabytesUsed": 2,
}
RATE_LIMIT = {
"verb": "POST",
"value": 80,
"remaining": 80,
"unit": "MINUTE",
"next-available": "2021-02-23T22:08:00Z",
}
RATE_LIMITS = {"regex": ".*", "uri": "*", "limit": [RATE_LIMIT]}
LIMIT = {"rate": [RATE_LIMITS], "absolute": ABSOLUTE_LIMIT}
class TestAbsoluteLimit(base.TestCase):
def test_basic(self):
limit_resource = limits.AbsoluteLimit()
self.assertIsNone(limit_resource.resource_key)
self.assertIsNone(limit_resource.resources_key)
self.assertEqual('', limit_resource.base_path)
self.assertFalse(limit_resource.allow_create)
self.assertFalse(limit_resource.allow_fetch)
self.assertFalse(limit_resource.allow_delete)
self.assertFalse(limit_resource.allow_commit)
self.assertFalse(limit_resource.allow_list)
def test_make_absolute_limit(self):
limit_resource = limits.AbsoluteLimit(**ABSOLUTE_LIMIT)
self.assertEqual(
ABSOLUTE_LIMIT['totalSnapshotsUsed'],
limit_resource.total_snapshots_used,
)
self.assertEqual(
ABSOLUTE_LIMIT['maxTotalBackups'], limit_resource.max_total_backups
)
self.assertEqual(
ABSOLUTE_LIMIT['maxTotalVolumeGigabytes'],
limit_resource.max_total_volume_gigabytes,
)
self.assertEqual(
ABSOLUTE_LIMIT['maxTotalSnapshots'],
limit_resource.max_total_snapshots,
)
self.assertEqual(
ABSOLUTE_LIMIT['maxTotalBackupGigabytes'],
limit_resource.max_total_backup_gigabytes,
)
self.assertEqual(
ABSOLUTE_LIMIT['totalBackupGigabytesUsed'],
limit_resource.total_backup_gigabytes_used,
)
self.assertEqual(
ABSOLUTE_LIMIT['maxTotalVolumes'], limit_resource.max_total_volumes
)
self.assertEqual(
ABSOLUTE_LIMIT['totalVolumesUsed'],
limit_resource.total_volumes_used,
)
self.assertEqual(
ABSOLUTE_LIMIT['totalBackupsUsed'],
limit_resource.total_backups_used,
)
self.assertEqual(
ABSOLUTE_LIMIT['totalGigabytesUsed'],
limit_resource.total_gigabytes_used,
)
class TestRateLimit(base.TestCase):
def test_basic(self):
limit_resource = limits.RateLimit()
self.assertIsNone(limit_resource.resource_key)
self.assertIsNone(limit_resource.resources_key)
self.assertEqual('', limit_resource.base_path)
self.assertFalse(limit_resource.allow_create)
self.assertFalse(limit_resource.allow_fetch)
self.assertFalse(limit_resource.allow_delete)
self.assertFalse(limit_resource.allow_commit)
self.assertFalse(limit_resource.allow_list)
def test_make_rate_limit(self):
limit_resource = limits.RateLimit(**RATE_LIMIT)
self.assertEqual(RATE_LIMIT['verb'], limit_resource.verb)
self.assertEqual(RATE_LIMIT['value'], limit_resource.value)
self.assertEqual(RATE_LIMIT['remaining'], limit_resource.remaining)
self.assertEqual(RATE_LIMIT['unit'], limit_resource.unit)
self.assertEqual(
RATE_LIMIT['next-available'], limit_resource.next_available
)
class TestRateLimits(base.TestCase):
def test_basic(self):
limit_resource = limits.RateLimits()
self.assertIsNone(limit_resource.resource_key)
self.assertIsNone(limit_resource.resources_key)
self.assertEqual('', limit_resource.base_path)
self.assertFalse(limit_resource.allow_create)
self.assertFalse(limit_resource.allow_fetch)
self.assertFalse(limit_resource.allow_delete)
self.assertFalse(limit_resource.allow_commit)
self.assertFalse(limit_resource.allow_list)
def _test_rate_limit(self, expected, actual):
self.assertEqual(expected[0]['verb'], actual[0].verb)
self.assertEqual(expected[0]['value'], actual[0].value)
self.assertEqual(expected[0]['remaining'], actual[0].remaining)
self.assertEqual(expected[0]['unit'], actual[0].unit)
self.assertEqual(
expected[0]['next-available'], actual[0].next_available
)
def test_make_rate_limits(self):
limit_resource = limits.RateLimits(**RATE_LIMITS)
self.assertEqual(RATE_LIMITS['regex'], limit_resource.regex)
self.assertEqual(RATE_LIMITS['uri'], limit_resource.uri)
self._test_rate_limit(RATE_LIMITS['limit'], limit_resource.limits)
class TestLimit(base.TestCase):
def test_basic(self):
limit_resource = limits.Limit()
self.assertEqual('limits', limit_resource.resource_key)
self.assertEqual('/limits', limit_resource.base_path)
self.assertTrue(limit_resource.allow_fetch)
self.assertFalse(limit_resource.allow_create)
self.assertFalse(limit_resource.allow_commit)
self.assertFalse(limit_resource.allow_delete)
self.assertFalse(limit_resource.allow_list)
def _test_absolute_limit(self, expected, actual):
self.assertEqual(
expected['totalSnapshotsUsed'], actual.total_snapshots_used
)
self.assertEqual(expected['maxTotalBackups'], actual.max_total_backups)
self.assertEqual(
expected['maxTotalVolumeGigabytes'],
actual.max_total_volume_gigabytes,
)
self.assertEqual(
expected['maxTotalSnapshots'], actual.max_total_snapshots
)
self.assertEqual(
expected['maxTotalBackupGigabytes'],
actual.max_total_backup_gigabytes,
)
self.assertEqual(
expected['totalBackupGigabytesUsed'],
actual.total_backup_gigabytes_used,
)
self.assertEqual(expected['maxTotalVolumes'], actual.max_total_volumes)
self.assertEqual(
expected['totalVolumesUsed'], actual.total_volumes_used
)
self.assertEqual(
expected['totalBackupsUsed'], actual.total_backups_used
)
self.assertEqual(
expected['totalGigabytesUsed'], actual.total_gigabytes_used
)
def _test_rate_limit(self, expected, actual):
self.assertEqual(expected[0]['verb'], actual[0].verb)
self.assertEqual(expected[0]['value'], actual[0].value)
self.assertEqual(expected[0]['remaining'], actual[0].remaining)
self.assertEqual(expected[0]['unit'], actual[0].unit)
self.assertEqual(
expected[0]['next-available'], actual[0].next_available
)
def _test_rate_limits(self, expected, actual):
self.assertEqual(expected[0]['regex'], actual[0].regex)
self.assertEqual(expected[0]['uri'], actual[0].uri)
self._test_rate_limit(expected[0]['limit'], actual[0].limits)
def test_make_limit(self):
limit_resource = limits.Limit(**LIMIT)
self._test_rate_limits(LIMIT['rate'], limit_resource.rate)
self._test_absolute_limit(LIMIT['absolute'], limit_resource.absolute)

View File

@ -14,6 +14,7 @@ from unittest import mock
from openstack.block_storage.v2 import _proxy
from openstack.block_storage.v2 import backup
from openstack.block_storage.v2 import capabilities
from openstack.block_storage.v2 import limits
from openstack.block_storage.v2 import quota_set
from openstack.block_storage.v2 import snapshot
from openstack.block_storage.v2 import stats
@ -290,6 +291,16 @@ class TestBackup(TestVolumeProxy):
)
class TestLimit(TestVolumeProxy):
def test_limits_get(self):
self.verify_get(
self.proxy.get_limits,
limits.Limit,
method_args=[],
expected_kwargs={'requires_id': False},
)
class TestCapabilities(TestVolumeProxy):
def test_capabilites_get(self):
self.verify_get(self.proxy.get_capabilities, capabilities.Capabilities)