dns: Add support for quotas
This patch add quota support for designate(DNS) API - add dns quota resource & proxy methods - add unit & functional test - add docs and release note - fix tld functional test code Change-Id: Id194b6871e2bb80d08b0bb0113bf3fa64e419385 Signed-off-by: choieastsea <choieastsea@gmail.com>
This commit is contained in:

committed by
Stephen Finucane

parent
633c73018e
commit
c826687bbf
@@ -83,6 +83,13 @@ Limit Operations
|
|||||||
:noindex:
|
:noindex:
|
||||||
:members: limits
|
:members: limits
|
||||||
|
|
||||||
|
Quota Operations
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. autoclass:: openstack.dns.v2._proxy.Proxy
|
||||||
|
:noindex:
|
||||||
|
:members: quotas, get_quota, update_quota, delete_quota
|
||||||
|
|
||||||
Service Status Operations
|
Service Status Operations
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@@ -13,5 +13,6 @@ DNS Resources
|
|||||||
v2/tld
|
v2/tld
|
||||||
v2/recordset
|
v2/recordset
|
||||||
v2/limit
|
v2/limit
|
||||||
|
v2/quota
|
||||||
v2/service_status
|
v2/service_status
|
||||||
v2/blacklist
|
v2/blacklist
|
||||||
|
12
doc/source/user/resources/dns/v2/quota.rst
Normal file
12
doc/source/user/resources/dns/v2/quota.rst
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
openstack.dns.v2.quota
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. automodule:: openstack.dns.v2.quota
|
||||||
|
|
||||||
|
The Quota Class
|
||||||
|
---------------
|
||||||
|
|
||||||
|
The ``Quota`` class inherits from :class:`~openstack.resource.Resource`.
|
||||||
|
|
||||||
|
.. autoclass:: openstack.dns.v2.quota.Quota
|
||||||
|
:members:
|
@@ -15,6 +15,7 @@ import typing as ty
|
|||||||
from openstack.dns.v2 import blacklist as _blacklist
|
from openstack.dns.v2 import blacklist as _blacklist
|
||||||
from openstack.dns.v2 import floating_ip as _fip
|
from openstack.dns.v2 import floating_ip as _fip
|
||||||
from openstack.dns.v2 import limit as _limit
|
from openstack.dns.v2 import limit as _limit
|
||||||
|
from openstack.dns.v2 import quota as _quota
|
||||||
from openstack.dns.v2 import recordset as _rs
|
from openstack.dns.v2 import recordset as _rs
|
||||||
from openstack.dns.v2 import service_status as _svc_status
|
from openstack.dns.v2 import service_status as _svc_status
|
||||||
from openstack.dns.v2 import tld as _tld
|
from openstack.dns.v2 import tld as _tld
|
||||||
@@ -34,6 +35,7 @@ class Proxy(proxy.Proxy):
|
|||||||
"blacklist": _blacklist.Blacklist,
|
"blacklist": _blacklist.Blacklist,
|
||||||
"floating_ip": _fip.FloatingIP,
|
"floating_ip": _fip.FloatingIP,
|
||||||
"limits": _limit.Limit,
|
"limits": _limit.Limit,
|
||||||
|
"quota": _quota.Quota,
|
||||||
"recordset": _rs.Recordset,
|
"recordset": _rs.Recordset,
|
||||||
"service_status": _svc_status.ServiceStatus,
|
"service_status": _svc_status.ServiceStatus,
|
||||||
"zone": _zone.Zone,
|
"zone": _zone.Zone,
|
||||||
@@ -699,6 +701,60 @@ class Proxy(proxy.Proxy):
|
|||||||
"""
|
"""
|
||||||
return self._list(_limit.Limit, **query)
|
return self._list(_limit.Limit, **query)
|
||||||
|
|
||||||
|
# ======== Quotas ========
|
||||||
|
def quotas(self, **query):
|
||||||
|
"""Return a generator of quotas
|
||||||
|
|
||||||
|
:param dict query: Optional query parameters to be sent to limit the
|
||||||
|
resources being returned.
|
||||||
|
|
||||||
|
:returns: A generator of quota objects
|
||||||
|
:rtype: :class:`~openstack.dns.v2.quota.Quota`
|
||||||
|
"""
|
||||||
|
return self._list(_quota.Quota, **query)
|
||||||
|
|
||||||
|
def get_quota(self, quota):
|
||||||
|
"""Get a quota
|
||||||
|
|
||||||
|
:param quota: The value can be the ID of a quota or a
|
||||||
|
:class:`~openstack.dns.v2.quota.Quota` instance.
|
||||||
|
The ID of a quota is the same as the project ID for the quota.
|
||||||
|
|
||||||
|
:returns: One :class:`~openstack.dns.v2.quota.Quota`
|
||||||
|
:raises: :class:`~openstack.exceptions.ResourceNotFound`
|
||||||
|
"""
|
||||||
|
return self._get(_quota.Quota, quota)
|
||||||
|
|
||||||
|
def update_quota(self, quota, **attrs):
|
||||||
|
"""Update a quota
|
||||||
|
|
||||||
|
:param quota: Either the ID of a quota or a
|
||||||
|
:class:`~openstack.dns.v2.quota.Quota` instance. The ID of a quota
|
||||||
|
is the same as the project ID for the quota.
|
||||||
|
:param dict attrs: The attributes to update on the quota represented
|
||||||
|
by ``quota``.
|
||||||
|
|
||||||
|
:returns: The updated quota
|
||||||
|
:rtype: :class:`~openstack.dns.v2.quota.Quota`
|
||||||
|
"""
|
||||||
|
return self._update(_quota.Quota, quota, **attrs)
|
||||||
|
|
||||||
|
def delete_quota(self, quota, ignore_missing=True):
|
||||||
|
"""Delete a quota (i.e. reset to the default quota)
|
||||||
|
|
||||||
|
:param quota: The value can be the ID of a quota or a
|
||||||
|
:class:`~openstack.dns.v2.quota.Quota` instance.
|
||||||
|
The ID of a quota is the same as the project ID for the quota.
|
||||||
|
:param bool ignore_missing: When set to ``False``,
|
||||||
|
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
|
||||||
|
the quota does not exist.
|
||||||
|
When set to ``True``, no exception will be set when attempting to
|
||||||
|
delete a nonexistent quota.
|
||||||
|
|
||||||
|
:returns: ``None``
|
||||||
|
"""
|
||||||
|
return self._delete(_quota.Quota, quota, ignore_missing=ignore_missing)
|
||||||
|
|
||||||
# ======== Service Statuses ========
|
# ======== Service Statuses ========
|
||||||
def service_statuses(self):
|
def service_statuses(self):
|
||||||
"""Retrieve a generator of service statuses
|
"""Retrieve a generator of service statuses
|
||||||
|
106
openstack/dns/v2/quota.py
Normal file
106
openstack/dns/v2/quota.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
|
from keystoneauth1 import adapter
|
||||||
|
import typing_extensions as ty_ext
|
||||||
|
|
||||||
|
from openstack.dns.v2 import _base
|
||||||
|
from openstack import resource
|
||||||
|
|
||||||
|
|
||||||
|
class Quota(_base.Resource):
|
||||||
|
"""DNS Quota Resource"""
|
||||||
|
|
||||||
|
base_path = "/quotas"
|
||||||
|
|
||||||
|
# capabilities
|
||||||
|
allow_fetch = True
|
||||||
|
allow_commit = True
|
||||||
|
allow_delete = True
|
||||||
|
allow_list = True
|
||||||
|
commit_method = "PATCH"
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
#: The ID of the project.
|
||||||
|
project = resource.URI("project", alternate_id=True)
|
||||||
|
#: The maximum amount of recordsets allowed in a zone export. *Type: int*
|
||||||
|
api_export_size = resource.Body("api_export_size", type=int)
|
||||||
|
#: The maximum amount of records allowed per recordset. *Type: int*
|
||||||
|
recordset_records = resource.Body("recordset_records", type=int)
|
||||||
|
#: The maximum amount of records allowed per zone. *Type: int*
|
||||||
|
zone_records = resource.Body("zone_records", type=int)
|
||||||
|
#: The maximum amount of recordsets allowed per zone. *Type: int*
|
||||||
|
zone_recordsets = resource.Body("zone_recordsets", type=int)
|
||||||
|
#: The maximum amount of zones allowed per project. *Type: int*
|
||||||
|
zones = resource.Body("zones", type=int)
|
||||||
|
|
||||||
|
def _prepare_request(
|
||||||
|
self,
|
||||||
|
requires_id=True,
|
||||||
|
prepend_key=False,
|
||||||
|
patch=False,
|
||||||
|
base_path=None,
|
||||||
|
params=None,
|
||||||
|
*,
|
||||||
|
resource_request_key=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
_request = super()._prepare_request(
|
||||||
|
requires_id, prepend_key, base_path=base_path
|
||||||
|
)
|
||||||
|
if self.resource_key in _request.body:
|
||||||
|
_body = _request.body[self.resource_key]
|
||||||
|
else:
|
||||||
|
_body = _request.body
|
||||||
|
if "id" in _body:
|
||||||
|
del _body["id"]
|
||||||
|
_request.headers = {'x-auth-sudo-project-id': self.id}
|
||||||
|
return _request
|
||||||
|
|
||||||
|
def fetch(
|
||||||
|
self,
|
||||||
|
session: adapter.Adapter,
|
||||||
|
requires_id: bool = True,
|
||||||
|
base_path: str | None = None,
|
||||||
|
error_message: str | None = None,
|
||||||
|
skip_cache: bool = False,
|
||||||
|
*,
|
||||||
|
resource_response_key: str | None = None,
|
||||||
|
microversion: str | None = None,
|
||||||
|
**params: ty.Any,
|
||||||
|
) -> ty_ext.Self:
|
||||||
|
request = self._prepare_request(
|
||||||
|
requires_id=requires_id,
|
||||||
|
base_path=base_path,
|
||||||
|
)
|
||||||
|
session = self._get_session(session)
|
||||||
|
if microversion is None:
|
||||||
|
microversion = self._get_microversion(session)
|
||||||
|
self.microversion = microversion
|
||||||
|
|
||||||
|
response = session.get(
|
||||||
|
request.url,
|
||||||
|
microversion=microversion,
|
||||||
|
params=params,
|
||||||
|
skip_cache=skip_cache,
|
||||||
|
headers=request.headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._translate_response(
|
||||||
|
response,
|
||||||
|
error_message=error_message,
|
||||||
|
resource_response_key=resource_response_key,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
71
openstack/tests/functional/dns/v2/test_quota.py
Normal file
71
openstack/tests/functional/dns/v2/test_quota.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# 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.tests.functional import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuota(base.BaseFunctionalTest):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.require_service("dns")
|
||||||
|
if not self._operator_cloud_name:
|
||||||
|
self.skip("Operator cloud must be set for this test")
|
||||||
|
|
||||||
|
self.project = self.create_temporary_project()
|
||||||
|
|
||||||
|
def test_quota(self):
|
||||||
|
# set quota
|
||||||
|
|
||||||
|
attrs = {
|
||||||
|
"api_export_size": 1,
|
||||||
|
"recordset_records": 2,
|
||||||
|
"zone_records": 3,
|
||||||
|
"zone_recordsets": 4,
|
||||||
|
"zones": 5,
|
||||||
|
}
|
||||||
|
new_quota = self.operator_cloud.dns.update_quota(
|
||||||
|
self.project.id, **attrs
|
||||||
|
)
|
||||||
|
self.assertEqual(attrs["api_export_size"], new_quota.api_export_size)
|
||||||
|
self.assertEqual(
|
||||||
|
attrs["recordset_records"], new_quota.recordset_records
|
||||||
|
)
|
||||||
|
self.assertEqual(attrs["zone_records"], new_quota.zone_records)
|
||||||
|
self.assertEqual(attrs["zone_recordsets"], new_quota.zone_recordsets)
|
||||||
|
self.assertEqual(attrs["zones"], new_quota.zones)
|
||||||
|
|
||||||
|
# get quota
|
||||||
|
|
||||||
|
expected_keys = [
|
||||||
|
"id",
|
||||||
|
"api_export_size",
|
||||||
|
"recordset_records",
|
||||||
|
"zone_records",
|
||||||
|
"zone_recordsets",
|
||||||
|
"zones",
|
||||||
|
]
|
||||||
|
test_quota = self.operator_cloud.dns.get_quota(self.project.id)
|
||||||
|
for actual_key in test_quota._body.attributes.keys():
|
||||||
|
self.assertIn(actual_key, expected_keys)
|
||||||
|
self.assertEqual(self.project.id, test_quota.id)
|
||||||
|
self.assertEqual(attrs["api_export_size"], test_quota.api_export_size)
|
||||||
|
self.assertEqual(
|
||||||
|
attrs["recordset_records"], test_quota.recordset_records
|
||||||
|
)
|
||||||
|
self.assertEqual(attrs["zone_records"], test_quota.zone_records)
|
||||||
|
self.assertEqual(attrs["zone_recordsets"], test_quota.zone_recordsets)
|
||||||
|
self.assertEqual(attrs["zones"], test_quota.zones)
|
||||||
|
|
||||||
|
# reset quota
|
||||||
|
|
||||||
|
self.operator_cloud.dns.delete_quota(self.project.id)
|
@@ -13,6 +13,7 @@
|
|||||||
from openstack.dns.v2 import _proxy
|
from openstack.dns.v2 import _proxy
|
||||||
from openstack.dns.v2 import blacklist
|
from openstack.dns.v2 import blacklist
|
||||||
from openstack.dns.v2 import floating_ip
|
from openstack.dns.v2 import floating_ip
|
||||||
|
from openstack.dns.v2 import quota
|
||||||
from openstack.dns.v2 import recordset
|
from openstack.dns.v2 import recordset
|
||||||
from openstack.dns.v2 import service_status
|
from openstack.dns.v2 import service_status
|
||||||
from openstack.dns.v2 import tld
|
from openstack.dns.v2 import tld
|
||||||
@@ -424,3 +425,30 @@ class TestDnsTLD(TestDnsProxy):
|
|||||||
|
|
||||||
def test_tld_update(self):
|
def test_tld_update(self):
|
||||||
self.verify_update(self.proxy.update_tld, tld.TLD)
|
self.verify_update(self.proxy.update_tld, tld.TLD)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDnsQuota(TestDnsProxy):
|
||||||
|
def test_quotas(self):
|
||||||
|
self.verify_list(self.proxy.quotas, quota.Quota)
|
||||||
|
|
||||||
|
def test_quota_get(self):
|
||||||
|
self.verify_get(self.proxy.get_quota, quota.Quota)
|
||||||
|
|
||||||
|
def test_quota_update(self):
|
||||||
|
self.verify_update(self.proxy.update_quota, quota.Quota)
|
||||||
|
|
||||||
|
def test_quota_delete(self):
|
||||||
|
self.verify_delete(
|
||||||
|
self.proxy.delete_quota,
|
||||||
|
quota.Quota,
|
||||||
|
False,
|
||||||
|
expected_kwargs={'ignore_missing': False},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_quota_delete_ignore(self):
|
||||||
|
self.verify_delete(
|
||||||
|
self.proxy.delete_quota,
|
||||||
|
quota.Quota,
|
||||||
|
True,
|
||||||
|
expected_kwargs={'ignore_missing': True},
|
||||||
|
)
|
||||||
|
51
openstack/tests/unit/dns/v2/test_quota.py
Normal file
51
openstack/tests/unit/dns/v2/test_quota.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 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.dns.v2 import quota
|
||||||
|
from openstack.tests.unit import base
|
||||||
|
|
||||||
|
IDENTIFIER = "IDENTIFIER"
|
||||||
|
EXAMPLE = {
|
||||||
|
"zones": 10,
|
||||||
|
"zone_recordsets": 500,
|
||||||
|
"zone_records": 500,
|
||||||
|
"recordset_records": 20,
|
||||||
|
"api_export_size": 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuota(base.TestCase):
|
||||||
|
def test_basic(self):
|
||||||
|
sot = quota.Quota()
|
||||||
|
self.assertIsNone(sot.resources_key)
|
||||||
|
self.assertIsNone(sot.resource_key)
|
||||||
|
self.assertEqual("/quotas", sot.base_path)
|
||||||
|
self.assertTrue(sot.allow_fetch)
|
||||||
|
self.assertTrue(sot.allow_commit)
|
||||||
|
self.assertTrue(sot.allow_delete)
|
||||||
|
self.assertTrue(sot.allow_list)
|
||||||
|
self.assertTrue(sot.commit_method, "PATCH")
|
||||||
|
|
||||||
|
def test_make_it(self):
|
||||||
|
sot = quota.Quota(project='FAKE_PROJECT', **EXAMPLE)
|
||||||
|
self.assertEqual(EXAMPLE['zones'], sot.zones)
|
||||||
|
self.assertEqual(EXAMPLE['zone_recordsets'], sot.zone_recordsets)
|
||||||
|
self.assertEqual(EXAMPLE['zone_records'], sot.zone_records)
|
||||||
|
self.assertEqual(EXAMPLE['recordset_records'], sot.recordset_records)
|
||||||
|
self.assertEqual(EXAMPLE['api_export_size'], sot.api_export_size)
|
||||||
|
self.assertEqual('FAKE_PROJECT', sot.project)
|
||||||
|
|
||||||
|
def test_prepare_request(self):
|
||||||
|
body = {'id': 'ABCDEFGH', 'zones': 20}
|
||||||
|
quota_obj = quota.Quota(**body)
|
||||||
|
response = quota_obj._prepare_request()
|
||||||
|
self.assertNotIn('id', response)
|
4
releasenotes/notes/add-dns-quota-49ae659a88eeeab9.yaml
Normal file
4
releasenotes/notes/add-dns-quota-49ae659a88eeeab9.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add quota support for designate(DNS) API.
|
Reference in New Issue
Block a user