Merge "Add negative testcases for ownership transfer_request and tsigkey"
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index 5f4f728..d85d343 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -13,7 +13,9 @@
# under the License.
from tempest import clients
from tempest import config
+from tempest.lib.auth import KeystoneAuthProvider
from tempest.lib.auth import KeystoneV2AuthProvider
+from tempest.lib.auth import KeystoneV3AuthProvider
from designate_tempest_plugin.services.dns.v1.json.domains_client import \
DomainsClient
@@ -27,7 +29,7 @@
ZoneImportsClient
from designate_tempest_plugin.services.dns.v2.json.blacklists_client import \
BlacklistsClient
-from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
+from designate_tempest_plugin.services.dns.v2.json.quotas_client import \
QuotasClient
from designate_tempest_plugin.services.dns.v2.json.zone_exports_client import \
ZoneExportsClient
@@ -37,6 +39,8 @@
PoolClient
from designate_tempest_plugin.services.dns.v2.json.tld_client import \
TldClient
+from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
+ QuotasClient as AdminQuotaClient
from designate_tempest_plugin.services.dns.query.query_client import \
QueryClient
from designate_tempest_plugin.services.dns.v2.json.transfer_request_client \
@@ -112,11 +116,33 @@
return params
+class ManagerAdmin(clients.Manager):
+
+ def __init__(self, credentials=None, service=None):
+ super(ManagerAdmin, self).__init__(credentials, service)
+ self._init_clients(self._get_params())
+
+ def _init_clients(self, params):
+ self.quotas_client = AdminQuotaClient(**params)
+
+ def _get_params(self):
+ params = dict(self.default_params)
+ params.update({
+ 'auth_provider': self.auth_provider,
+ 'service': CONF.dns.catalog_type,
+ 'region': CONF.identity.region,
+ 'endpoint_type': CONF.dns.endpoint_type,
+ 'build_interval': CONF.dns.build_interval,
+ 'build_timeout': CONF.dns.build_timeout
+ })
+ return params
+
+
class ManagerV2Unauthed(ManagerV2):
def __init__(self, credentials=None, service=None):
super(ManagerV2Unauthed, self).__init__(credentials, service)
- self.auth_provider = KeystoneV2UnauthedProvider(
+ self.auth_provider = self._auth_provider_class()(
credentials=self.auth_provider.credentials,
auth_url=self.auth_provider.auth_client.auth_url,
disable_ssl_certificate_validation=self.auth_provider.dscv,
@@ -125,13 +151,18 @@
)
self._init_clients(self._get_params())
+ def _auth_provider_class(self):
+ if CONF.identity.auth_version == 'v3':
+ return KeystoneV3UnauthedProvider
+ else:
+ return KeystoneV2UnauthedProvider
-class KeystoneV2UnauthedProvider(KeystoneV2AuthProvider):
- """This auth provider will ensure requests are made without a token"""
+
+class BaseUnauthedProvider(KeystoneAuthProvider):
def _decorate_request(self, filters, method, url, headers=None, body=None,
auth_data=None):
- result = super(KeystoneV2UnauthedProvider, self)._decorate_request(
+ result = super(BaseUnauthedProvider, self)._decorate_request(
filters, method, url, headers=headers, body=body,
auth_data=auth_data)
url, headers, body = result
@@ -140,3 +171,15 @@
except KeyError:
pass
return url, headers, body
+
+
+class KeystoneV2UnauthedProvider(KeystoneV2AuthProvider, BaseUnauthedProvider):
+
+ def _decorate_request(self, *args, **kwargs):
+ return BaseUnauthedProvider._decorate_request(self, *args, **kwargs)
+
+
+class KeystoneV3UnauthedProvider(KeystoneV3AuthProvider, BaseUnauthedProvider):
+
+ def _decorate_request(self, *args, **kwargs):
+ return BaseUnauthedProvider._decorate_request(self, *args, **kwargs)
diff --git a/designate_tempest_plugin/config.py b/designate_tempest_plugin/config.py
index 5b99e81..e4e0270 100644
--- a/designate_tempest_plugin/config.py
+++ b/designate_tempest_plugin/config.py
@@ -71,4 +71,11 @@
cfg.BoolOpt('api_v2_root_recordsets',
default=False,
help="Is the v2 root recordsets API enabled."),
+ cfg.BoolOpt('api_v2_quotas',
+ default=False,
+ help="Is the v2 quota API enabled."),
+ cfg.BoolOpt('bug_1573141_fixed',
+ default=False,
+ help="Is https://bugs.launchpad.net/designate/+bug/1573141 "
+ "fixed"),
]
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index 11113b8..7600542 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -81,24 +81,26 @@
def rand_quotas(zones=None, zone_records=None, zone_recordsets=None,
recordset_records=None, api_export_size=None):
- LOG.warn("Leaving `api_export_size` out of quota data due to: "
- "https://bugs.launchpad.net/designate/+bug/1573141")
- return {
- 'quota': {
- 'zones':
- zones or data_utils.rand_int_id(100, 999999),
- 'zone_records':
- zone_records or data_utils.rand_int_id(100, 999999),
- 'zone_recordsets':
- zone_recordsets or data_utils.rand_int_id(100, 999999),
- 'recordset_records':
- recordset_records or data_utils.rand_int_id(100, 999999),
- # https://bugs.launchpad.net/designate/+bug/1573141
- # 'api_export_size':
- # api_export_size or data_utils.rand_int_id(100, 999999),
- }
+ quotas_dict = {
+ 'zones':
+ zones or data_utils.rand_int_id(100, 999999),
+ 'zone_records':
+ zone_records or data_utils.rand_int_id(100, 999999),
+ 'zone_recordsets':
+ zone_recordsets or data_utils.rand_int_id(100, 999999),
+ 'recordset_records':
+ recordset_records or data_utils.rand_int_id(100, 999999),
}
+ if CONF.dns_feature_enabled.bug_1573141_fixed:
+ quotas_dict['api_export_size'] = \
+ api_export_size or data_utils.rand_int_id(100, 999999)
+ else:
+ LOG.warn("Leaving `api_export_size` out of quota data due to: "
+ "https://bugs.launchpad.net/designate/+bug/1573141")
+
+ return quotas_dict
+
def rand_zone_data(name=None, email=None, ttl=None, description=None):
"""Generate random zone data, with optional overrides
diff --git a/designate_tempest_plugin/services/dns/admin/json/quotas_client.py b/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
index c815611..08b1790 100644
--- a/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
+++ b/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
@@ -53,7 +53,9 @@
api_export_size=api_export_size,
)
- resp, body = self._update_request('quotas', project_id, quotas,
+ quotas_dict = {'quota': quotas}
+
+ resp, body = self._update_request('quotas', project_id, quotas_dict,
params=params)
self.expected_success(200, resp.status)
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index ea8fb8f..19dbe45 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -129,17 +129,24 @@
return resp, self.deserialize(resp, body)
- def _show_request(self, resource, uuid, headers=None, params=None):
+ def _show_request(self, resource, uuid, headers=None, params=None,
+ extra_headers=False):
"""Gets a specific object of the specified type.
:param resource: The name of the REST resource, e.g., 'zones'.
:param uuid: Unique identifier of the object in UUID format.
:param params: A Python dict that represents the query paramaters to
include in the request URI.
+ :param extra_headers (bool): Boolean value than indicates if the
+ headers returned by the get_headers()
+ method are to be used but additional
+ headers are needed in the request
+ pass them in as a dict.
:returns: Serialized object as a dictionary.
"""
uri = self.get_uri(resource, uuid=uuid, params=params)
- resp, body = self.get(uri, headers=headers)
+ resp, body = self.get(
+ uri, headers=headers, extra_headers=extra_headers)
self.expected_success(self.SHOW_STATUS_CODES, resp.status)
@@ -179,7 +186,8 @@
return resp, self.deserialize(resp, body)
- def _update_request(self, resource, uuid, data, params=None):
+ def _update_request(self, resource, uuid, data, params=None, headers=None,
+ extra_headers=False):
"""Updates the specified object using PATCH request.
:param resource: The name of the REST resource, e.g., 'zones'
:param uuid: Unique identifier of the object in UUID format.
@@ -188,28 +196,44 @@
is sent as-is.
:param params: A Python dict that represents the query paramaters to
include in the request URI.
+ :param headers (dict): The headers to use for the request.
+ :param extra_headers (bool): Boolean value than indicates if the
+ headers returned by the get_headers()
+ method are to be used but additional
+ headers are needed in the request
+ pass them in as a dict.
:returns: Serialized object as a dictionary.
"""
body = self.serialize(data)
uri = self.get_uri(resource, uuid=uuid, params=params)
- resp, body = self.patch(uri, body=body)
+ resp, body = self.patch(uri, body=body,
+ headers=headers, extra_headers=True)
self.expected_success(self.UPDATE_STATUS_CODES, resp.status)
return resp, self.deserialize(resp, body)
- def _delete_request(self, resource, uuid, params=None):
+ def _delete_request(self, resource, uuid, params=None, headers=None,
+ extra_headers=False):
"""Deletes the specified object.
:param resource: The name of the REST resource, e.g., 'zones'.
:param uuid: The unique identifier of an object in UUID format.
:param params: A Python dict that represents the query paramaters to
include in the request URI.
+ :param headers (dict): The headers to use for the request.
+ :param extra_headers (bool): Boolean value than indicates if the
+ headers returned by the get_headers()
+ method are to be used but additional
+ headers are needed in the request
+ pass them in as a dict.
:returns: A tuple with the server response and the response body.
"""
uri = self.get_uri(resource, uuid=uuid, params=params)
- resp, body = self.delete(uri)
+ resp, body = self.delete(
+ uri, headers=headers, extra_headers=extra_headers)
+
self.expected_success(self.DELETE_STATUS_CODES, resp.status)
if resp.status == 202:
body = self.deserialize(resp, body)
diff --git a/designate_tempest_plugin/services/dns/v2/json/quotas_client.py b/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
new file mode 100644
index 0000000..3522a1d
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
@@ -0,0 +1,101 @@
+# Copyright 2016 Rackspace
+#
+# 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 oslo_log import log as logging
+
+from designate_tempest_plugin import data_utils as dns_data_utils
+from designate_tempest_plugin.services.dns.v2.json import base
+
+LOG = logging.getLogger(__name__)
+
+
+class QuotasClient(base.DnsClientV2Base):
+
+ @base.handle_errors
+ def update_quotas(self, zones=None, zone_records=None,
+ zone_recordsets=None, recordset_records=None,
+ api_export_size=None, project_id=None, params=None,
+ headers=None):
+ """Update the quotas for the project id
+
+ :param zones: The limit on zones per tenant
+ Default: Random Value
+ :param zone_records: The limit on records per zone
+ Default: Random Value
+ :param zone_recordsets: The limit recordsets per zone
+ Default: Random Value
+ :param recordset_records: The limit on records per recordset
+ Default: Random Value
+ :param api_export_size: The limit on size of on exported zone
+ Default: Random Value
+ :param project_id: Apply the quotas to this project id
+ Default: The project id of this client
+ :param params: A Python dict that represents the query paramaters to
+ include in the request URI.
+ :param headers (dict): The headers to use for the request.
+ :return: A tuple with the server response and the created quota.
+ """
+ project_id = project_id or self.tenant_id
+
+ quotas = dns_data_utils.rand_quotas(
+ zones=zones,
+ zone_records=zone_records,
+ zone_recordsets=zone_recordsets,
+ recordset_records=recordset_records,
+ api_export_size=api_export_size,
+ )
+
+ resp, body = self._update_request('quotas', project_id, quotas,
+ params=params, headers=headers,
+ extra_headers=True)
+
+ self.expected_success(200, resp.status)
+
+ return resp, body
+
+ @base.handle_errors
+ def show_quotas(self, project_id=None, params=None, headers=None):
+ """Gets a specific quota.
+
+ :param project_id: Show the quotas of this project id
+ Default: The project id of this client
+ :param params: A Python dict that represents the query paramaters to
+ include in the request URI.
+ :param headers (dict): The headers to use for the request.
+ :return: Serialized quota as a dictionary.
+ """
+ project_id = project_id or self.tenant_id
+ return self._show_request('quotas', project_id, params=params,
+ headers=headers, extra_headers=True)
+
+ @base.handle_errors
+ def delete_quotas(self, project_id=None, params=None, headers=None):
+ """Resets the quotas for the specified project id
+
+ :param project_id: Reset the quotas of this project id
+ Default: The project id of this client
+ :param params: A Python dict that represents the query paramaters to
+ include in the request URI.
+ :param headers (dict): The headers to use for the request.
+ :return: A tuple with the server response and the response body.
+ """
+ project_id = project_id or self.tenant_id
+
+ resp, body = self._delete_request(
+ 'quotas', project_id,
+ params=params, headers=headers,
+ extra_headers=True)
+
+ self.expected_success(204, resp.status)
+
+ return resp, body
diff --git a/designate_tempest_plugin/tests/api/admin/test_quotas.py b/designate_tempest_plugin/tests/api/admin/test_quotas.py
index 762f817..ae49460 100644
--- a/designate_tempest_plugin/tests/api/admin/test_quotas.py
+++ b/designate_tempest_plugin/tests/api/admin/test_quotas.py
@@ -12,17 +12,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
+from tempest import config
from tempest.lib import decorators
from designate_tempest_plugin.tests import base
from designate_tempest_plugin import data_utils as dns_data_utils
LOG = logging.getLogger(__name__)
+CONF = config.CONF
class BaseQuotasTest(base.BaseDnsAdminTest):
- # see: https://bugs.launchpad.net/designate/+bug/1573141
- excluded_keys = ['api_expected_size']
+
+ excluded_keys = []
+
+ def setUp(self):
+ if CONF.dns_feature_enabled.bug_1573141_fixed:
+ self.excluded_keys = ['api_export_size']
+ super(BaseQuotasTest, self).setUp()
class QuotasAdminTest(BaseQuotasTest):
@@ -39,14 +46,14 @@
def test_show_quotas(self):
LOG.info("Updating quotas")
quotas = dns_data_utils.rand_quotas()
- _, body = self.admin_client.update_quotas(**quotas['quota'])
+ _, body = self.admin_client.update_quotas(**quotas)
self.addCleanup(self.admin_client.delete_quotas)
LOG.info("Fetching quotas")
_, body = self.admin_client.show_quotas()
LOG.info("Ensuring the response has all quota types")
- self.assertExpected(quotas['quota'], body['quota'], self.excluded_keys)
+ self.assertExpected(quotas, body['quota'], self.excluded_keys)
@decorators.idempotent_id('33e0affb-5d66-4216-881c-f101a779851a')
def test_delete_quotas(self):
@@ -60,8 +67,8 @@
def test_update_quotas(self):
LOG.info("Updating quotas")
quotas = dns_data_utils.rand_quotas()
- _, body = self.admin_client.update_quotas(**quotas['quota'])
+ _, body = self.admin_client.update_quotas(**quotas)
self.addCleanup(self.admin_client.delete_quotas)
LOG.info("Ensuring the response has all quota types")
- self.assertExpected(quotas['quota'], body['quota'], self.excluded_keys)
+ self.assertExpected(quotas, body['quota'], self.excluded_keys)
diff --git a/designate_tempest_plugin/tests/api/v2/test_quotas.py b/designate_tempest_plugin/tests/api/v2/test_quotas.py
new file mode 100644
index 0000000..9411270
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_quotas.py
@@ -0,0 +1,124 @@
+# Copyright 2016 Rackspace
+#
+# 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 oslo_log import log as logging
+from tempest import config
+from tempest.lib import decorators
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin import data_utils as dns_data_utils
+
+LOG = logging.getLogger(__name__)
+
+
+CONF = config.CONF
+
+
+class QuotasV2Test(base.BaseDnsV2Test):
+
+ credentials = ['primary', 'admin']
+
+ @classmethod
+ def skip_checks(cls):
+ super(QuotasV2Test, cls).skip_checks()
+
+ if not CONF.dns_feature_enabled.api_v2_quotas:
+ skip_msg = ("%s skipped as designate V2 Quotas API is not "
+ "available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(QuotasV2Test, cls).setup_clients()
+
+ cls.quotas_client = cls.os.quotas_client
+ cls.admin_client = cls.os_adm.quotas_client
+
+ @decorators.idempotent_id('1dac991a-9e2e-452c-a47a-26ac37381ec5')
+ def test_show_quotas(self):
+ LOG.info("Updating quotas")
+ quotas = dns_data_utils.rand_quotas()
+ _, body = self.admin_client.update_quotas(**quotas)
+ self.addCleanup(self.admin_client.delete_quotas)
+
+ LOG.info("Fetching quotas")
+ _, body = self.admin_client.show_quotas()
+
+ LOG.info("Ensuring the response has all quota types")
+ self.assertExpected(quotas, body, [])
+
+ @decorators.idempotent_id('0448b089-5803-4ce3-8a6c-5c15ff75a2cc')
+ def test_delete_quotas(self):
+ LOG.info("Deleting quotas")
+ _, body = self.admin_client.delete_quotas()
+
+ LOG.info("Ensuring an empty response body")
+ self.assertEqual(body.strip(), "")
+
+ @decorators.idempotent_id('76d24c87-1b39-4e19-947c-c08e1380dc61')
+ def test_update_quotas(self):
+ LOG.info("Updating quotas")
+ quotas = dns_data_utils.rand_quotas()
+ _, body = self.admin_client.update_quotas(**quotas)
+ self.addCleanup(self.admin_client.delete_quotas)
+
+ LOG.info("Ensuring the response has all quota types")
+ self.assertExpected(quotas, body, [])
+
+ @decorators.idempotent_id('76d24c87-1b39-4e19-947c-c08e1380dc61')
+ def test_update_quotas_other_project(self):
+
+ project_id = self.quotas_client.tenant_id
+
+ LOG.info("Updating quotas for %s ", project_id)
+
+ quotas = dns_data_utils.rand_quotas()
+ request = quotas.copy()
+ request['project_id'] = project_id
+ request['headers'] = {'X-Auth-All-Projects': True}
+ _, body = self.admin_client.update_quotas(**request)
+ self.addCleanup(self.admin_client.delete_quotas, project_id=project_id)
+
+ LOG.info("Ensuring the response has all quota types")
+ self.assertExpected(quotas, body, [])
+
+ _, client_body = self.quotas_client.show_quotas()
+
+ self.assertExpected(quotas, client_body, [])
+
+ @decorators.idempotent_id('21e45d30-dbc1-4173-9d6b-9b6813ef514b')
+ def test_reset_quotas_other_project(self):
+
+ # Use a fake project for this
+ project_id = '21e45d30-dbc1-4173-9d6b-9b6813ef514b'
+
+ _, original_quotas = self.admin_client.show_quotas(
+ project_id=project_id, headers={'X-Auth-All-Projects': True})
+
+ LOG.info("Updating quotas for %s ", project_id)
+
+ quotas = dns_data_utils.rand_quotas()
+ request = quotas.copy()
+ request['project_id'] = project_id
+ request['headers'] = {'X-Auth-All-Projects': True}
+ _, body = self.admin_client.update_quotas(**request)
+ self.addCleanup(self.admin_client.delete_quotas, project_id=project_id)
+
+ self.admin_client.delete_quotas(
+ project_id=project_id,
+ headers={'X-Auth-All-Projects': True})
+
+ _, final_quotas = self.admin_client.show_quotas(
+ project_id=project_id, headers={'X-Auth-All-Projects': True})
+
+ self.assertExpected(original_quotas, final_quotas, [])
diff --git a/designate_tempest_plugin/tests/base.py b/designate_tempest_plugin/tests/base.py
index 9f24d62..c86b38a 100644
--- a/designate_tempest_plugin/tests/base.py
+++ b/designate_tempest_plugin/tests/base.py
@@ -85,8 +85,8 @@
class BaseDnsAdminTest(BaseDnsTest):
"""Base class for DNS Admin API tests."""
- # Use the Designate V2 Client Manager
- client_manager = clients.ManagerV2
+ # Use the Designate Admin Client Manager
+ client_manager = clients.ManagerAdmin
@classmethod
def skip_checks(cls):