Merge "volume: Add Limit to volume v2 API"
This commit is contained in:
@@ -33,6 +33,13 @@ Capabilities Operations
|
|||||||
:noindex:
|
:noindex:
|
||||||
:members: get_capabilities
|
:members: get_capabilities
|
||||||
|
|
||||||
|
Limits Operations
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. autoclass:: openstack.block_storage.v2._proxy.Proxy
|
||||||
|
:noindex:
|
||||||
|
:members: get_limits
|
||||||
|
|
||||||
Type Operations
|
Type Operations
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
37
doc/source/user/resources/block_storage/v2/limits.rst
Normal file
37
doc/source/user/resources/block_storage/v2/limits.rst
Normal 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:
|
@@ -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 backup as _backup
|
||||||
from openstack.block_storage.v2 import capabilities as _capabilities
|
from openstack.block_storage.v2 import capabilities as _capabilities
|
||||||
from openstack.block_storage.v2 import extension as _extension
|
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 quota_set as _quota_set
|
||||||
from openstack.block_storage.v2 import snapshot as _snapshot
|
from openstack.block_storage.v2 import snapshot as _snapshot
|
||||||
from openstack.block_storage.v2 import stats as _stats
|
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 = self._get_resource(_backup.Backup, backup)
|
||||||
backup.reset(self, status)
|
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 ======
|
# ====== CAPABILITIES ======
|
||||||
def get_capabilities(self, host):
|
def get_capabilities(self, host):
|
||||||
"""Get a backend's capabilites
|
"""Get a backend's capabilites
|
||||||
|
82
openstack/block_storage/v2/limits.py
Normal file
82
openstack/block_storage/v2/limits.py
Normal 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)
|
206
openstack/tests/unit/block_storage/v2/test_limits.py
Normal file
206
openstack/tests/unit/block_storage/v2/test_limits.py
Normal 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)
|
@@ -14,6 +14,7 @@ from unittest import mock
|
|||||||
from openstack.block_storage.v2 import _proxy
|
from openstack.block_storage.v2 import _proxy
|
||||||
from openstack.block_storage.v2 import backup
|
from openstack.block_storage.v2 import backup
|
||||||
from openstack.block_storage.v2 import capabilities
|
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 quota_set
|
||||||
from openstack.block_storage.v2 import snapshot
|
from openstack.block_storage.v2 import snapshot
|
||||||
from openstack.block_storage.v2 import stats
|
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):
|
class TestCapabilities(TestVolumeProxy):
|
||||||
def test_capabilites_get(self):
|
def test_capabilites_get(self):
|
||||||
self.verify_get(self.proxy.get_capabilities, capabilities.Capabilities)
|
self.verify_get(self.proxy.get_capabilities, capabilities.Capabilities)
|
||||||
|
Reference in New Issue
Block a user