From a2b08ee1b19ad034afa4fb784fdd0c4c6b6ae652 Mon Sep 17 00:00:00 2001 From: Arkady Shtempler Date: Tue, 12 Apr 2022 18:02:24 +0300 Subject: [PATCH] Recordset scenario test suite changes 1) Re-factoring: replace underscores by index, use constants 2) Moving "test_create_soa_record_not_permitted" from Scenario to API 3) New test: "test_delete_ns_record_not_permitted" Primary user is not able to delete NS type recordset 4) New test: "test_update_records_propagated_to_backends" Update recordset TTL (all types except NS and SOA) and make sure that the updated TTL is propagated to the backends 5) Adding backend validation check to the existing test: "test_create_and_delete_records_on_existing_zone" 6) Remove "NS" type record from "recordset_data.json" Reason: the test was bogus because it was creating an NS record for a sub-zone Change-Id: I169b3666a941ac61ac56619cdbe0f947340f669f --- .../services/dns/v2/json/recordset_client.py | 29 +++++-- .../tests/api/v2/test_recordset.py | 53 +++++++++++- designate_tempest_plugin/tests/base.py | 1 + .../tests/scenario/v2/recordset_data.json | 5 -- .../tests/scenario/v2/test_recordsets.py | 81 +++++++++++++------ 5 files changed, 132 insertions(+), 37 deletions(-) diff --git a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py index 44407053..ed078179 100644 --- a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py +++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py @@ -35,8 +35,9 @@ class RecordsetClient(base.DnsClientV2Base): @base.handle_errors def create_recordset(self, zone_uuid, recordset_data, - params=None, wait_until=False, headers=None): + params=None, headers=None, wait_until=False): """Create a recordset for the specified zone. + :param zone_uuid: Unique identifier of the zone in UUID format.. :param recordset_data: A dictionary that represents the recordset data. @@ -45,20 +46,28 @@ class RecordsetClient(base.DnsClientV2Base): :param headers (dict): The headers to use for the request. :return: A tuple with the server response and the created zone. """ - resp, body = self._create_request( - "/zones/{0}/recordsets".format(zone_uuid), params=params, - data=recordset_data, headers=headers) + if headers: + resp, body = self._create_request( + "/zones/{0}/recordsets".format(zone_uuid), params=params, + data=recordset_data, extra_headers=True, headers=headers) + else: + resp, body = self._create_request( + "/zones/{0}/recordsets".format(zone_uuid), params=params, + data=recordset_data) + # Create Recordset should Return a HTTP 202 self.expected_success(202, resp.status) + if wait_until: waiters.wait_for_recordset_status( self, zone_uuid, body['id'], wait_until, headers=headers) + return resp, body @base.handle_errors def update_recordset(self, zone_uuid, recordset_uuid, - recordet_data, params=None, - headers=None, extra_headers=None): + recordset_data, params=None, + headers=None, extra_headers=None, wait_until=False): """Update the recordset related to the specified zone. :param zone_uuid: Unique identifier of the zone in UUID format. :param recordset_uuid: Unique identifier of the recordset in UUID @@ -73,17 +82,23 @@ class RecordsetClient(base.DnsClientV2Base): method are to be used but additional headers are needed in the request pass them in as a dict. + :param wait_until: Block until the recordset reaches the + desired status :return: A tuple with the server response and the created zone. """ resp, body = self._put_request( 'zones/{0}/recordsets'.format(zone_uuid), recordset_uuid, - data=recordet_data, params=params, + data=recordset_data, params=params, headers=headers, extra_headers=extra_headers) # Update Recordset should Return a HTTP 202, or a 200 if the recordset # is already active self.expected_success([200, 202], resp.status) + if wait_until: + waiters.wait_for_recordset_status( + self, zone_uuid, body['id'], wait_until) + return resp, body @base.handle_errors diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py index 72495d31..37735372 100644 --- a/designate_tempest_plugin/tests/api/v2/test_recordset.py +++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py @@ -1063,7 +1063,7 @@ class AdminManagedRecordsetTest(BaseRecordsetsTest): lib_exc.BadRequest, 'bad_request', 400, self.admin_client.update_recordset, zone['id'], recordset['id'], - recordet_data=dns_data_utils.rand_ns_records(), + recordset_data=dns_data_utils.rand_ns_records(), headers=sudo_managed_headers, extra_headers=True) if recordset['type'] == 'SOA': @@ -1071,6 +1071,55 @@ class AdminManagedRecordsetTest(BaseRecordsetsTest): lib_exc.BadRequest, 'bad_request', 400, self.admin_client.update_recordset, zone['id'], recordset['id'], - recordet_data=dns_data_utils.rand_soa_recordset( + recordset_data=dns_data_utils.rand_soa_recordset( zone['name']), headers=sudo_managed_headers, extra_headers=True) + + +class RecordsetsManagedRecordsNegativeTest(BaseRecordsetsTest): + + credentials = ["admin", "system_admin", "primary"] + + @classmethod + def setup_clients(cls): + super(RecordsetsManagedRecordsNegativeTest, cls).setup_clients() + if CONF.enforce_scope.designate: + cls.admin_client = cls.os_system_admin.dns_v2.RecordsetClient() + cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient() + else: + cls.admin_client = cls.os_admin.dns_v2.RecordsetClient() + cls.admin_tld_client = cls.os_admin.dns_v2.TldClient() + cls.zone_client = cls.os_primary.dns_v2.ZonesClient() + cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient() + + @decorators.idempotent_id('083fa738-bb1b-11ec-b581-201e8823901f') + def test_delete_ns_record_not_permitted(self): + LOG.info('Get NS type recordset ID') + recordsets = self.recordset_client.list_recordset( + self.zone['id'])[1]['recordsets'] + for recordset in recordsets: + if recordset['type'] == 'NS': + ns_record_id = recordset['id'] + break + + LOG.info('Primary user tries to delete NS Recordset') + self.assertRaises( + lib_exc.Forbidden, + self.recordset_client.delete_recordset, + self.zone['id'], ns_record_id, headers=self.managed_records) + + @decorators.idempotent_id('1e78a742-66ee-11ec-8dc3-201e8823901f') + def test_create_soa_record_not_permitted(self): + soa_record = ("s1.devstack.org. admin.example.net. 1510721487 3510" + " 600 86400 3600") + LOG.info('Primary tries to create a Recordset on ' + 'the existing zone') + self.assertRaises( + lib_exc.BadRequest, + self.recordset_client.create_recordset, + self.zone['id'], soa_record) + LOG.info('Admin tries to create a Recordset on the existing zone') + self.assertRaises( + lib_exc.BadRequest, + self.admin_client.create_recordset, + self.zone['id'], soa_record) diff --git a/designate_tempest_plugin/tests/base.py b/designate_tempest_plugin/tests/base.py index 091c3cab..d5cdb6b4 100644 --- a/designate_tempest_plugin/tests/base.py +++ b/designate_tempest_plugin/tests/base.py @@ -177,6 +177,7 @@ class BaseDnsV2Test(BaseDnsTest): """Base class for DNS V2 API tests.""" all_projects_header = {'X-Auth-All-Projects': True} + managed_records = {'x-designate-edit-managed-records': True} @classmethod def skip_checks(cls): diff --git a/designate_tempest_plugin/tests/scenario/v2/recordset_data.json b/designate_tempest_plugin/tests/scenario/v2/recordset_data.json index 31687221..6dda9869 100644 --- a/designate_tempest_plugin/tests/scenario/v2/recordset_data.json +++ b/designate_tempest_plugin/tests/scenario/v2/recordset_data.json @@ -54,11 +54,6 @@ "type": "SPF", "records": ["\"v=spf1; a -all\""] }, - "NS": { - "name": "NS_Record", - "type": "NS", - "records": ["ns1.example.org."] - }, "PTR_IPV4": { "name": "PTR_Record_IPV4", "type": "PTR", diff --git a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py index e58a54c7..029854a4 100644 --- a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py +++ b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py @@ -9,6 +9,9 @@ # 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 time + from oslo_log import log as logging from tempest import config from tempest.lib.common.utils import test_utils @@ -17,9 +20,11 @@ from tempest.lib import exceptions as lib_exc import ddt from designate_tempest_plugin.tests import base +from designate_tempest_plugin.common import constants as const from designate_tempest_plugin import data_utils as dns_data_utils from designate_tempest_plugin.common import waiters - +from designate_tempest_plugin.services.dns.query.query_client \ + import SingleQueryClient LOG = logging.getLogger(__name__) @@ -50,7 +55,7 @@ class RecordsetsTest(base.BaseDnsV2Test): zone_id = CONF.dns.zone_id if zone_id: LOG.info('Retrieve info from a zone') - _, zone = cls.client.show_zone(zone_id) + zone = cls.client.show_zone(zone_id)[1] else: # Make sure we have an allowed TLD available tld_name = dns_data_utils.rand_zone_name(name="RecordsetsTest") @@ -93,8 +98,8 @@ class RecordsetsTest(base.BaseDnsV2Test): } LOG.info('Create a Recordset on the existing zone') - _, recordset = self.recordset_client.create_recordset( - self.zone['id'], recordset_data) + recordset = self.recordset_client.create_recordset( + self.zone['id'], recordset_data)[1] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.recordset_client.delete_recordset, self.zone['id'], recordset['id']) @@ -108,8 +113,8 @@ class RecordsetsTest(base.BaseDnsV2Test): 'ACTIVE') LOG.info('Delete the recordset') - _, body = self.recordset_client.delete_recordset(self.zone['id'], - recordset['id']) + body = self.recordset_client.delete_recordset( + self.zone['id'], recordset['id'])[1] LOG.info('Ensure we respond with DELETE+PENDING') self.assertEqual('DELETE', body['action']) @@ -120,20 +125,50 @@ class RecordsetsTest(base.BaseDnsV2Test): lambda: self.recordset_client.show_recordset( self.zone['id'], recordset['id'])) - @decorators.idempotent_id('1e78a742-66ee-11ec-8dc3-201e8823901f') - def test_create_soa_record_not_permitted(self): - # SOA record is automatically created for a zone, no user - # should be able to create a SOA record. - soa_record = ("s1.devstack.org. admin.example.net. 1510721487 3510" - " 600 86400 3600") - LOG.info('Primary tries to create a Recordset on ' - 'the existing zone') - self.assertRaises( - lib_exc.BadRequest, - self.recordset_client.create_recordset, - self.zone['id'], soa_record) - LOG.info('Admin tries to create a Recordset on the existing zone') - self.assertRaises( - lib_exc.BadRequest, - self.admin_client.create_recordset, - self.zone['id'], soa_record) + @decorators.attr(type='slow') + @decorators.idempotent_id('cbf756b0-ba64-11ec-93d4-201e8823901f') + @ddt.file_data("recordset_data.json") + def test_update_records_propagated_to_backends(self, name, type, records): + if name: + recordset_name = name + "." + self.zone['name'] + else: + recordset_name = self.zone['name'] + + orig_ttl = 666 + updated_ttl = 777 + recordset_data = { + 'name': recordset_name, + 'type': type, + 'records': records, + 'ttl': orig_ttl + } + + LOG.info('Create a Recordset on the existing zone') + recordset = self.recordset_client.create_recordset( + self.zone['id'], recordset_data, wait_until=const.ACTIVE)[1] + self.addCleanup(test_utils.call_and_ignore_notfound_exc, + self.recordset_client.delete_recordset, + self.zone['id'], recordset['id']) + + LOG.info('Update a Recordset on the existing zone') + recordset_data['ttl'] = updated_ttl + self.recordset_client.update_recordset( + self.zone['id'], recordset['id'], + recordset_data, wait_until=const.ACTIVE) + + LOG.info('Per Nameserver "dig" for a record until either:' + ' updated TTL is detected or build timeout has reached') + for ns in config.CONF.dns.nameservers: + start = time.time() + while True: + ns_obj = SingleQueryClient(ns, config.CONF.dns.query_timeout) + ns_record = ns_obj.query( + self.zone['name'], rdatatype=recordset_data['type']) + if str(updated_ttl) in str(ns_record): + return + if time.time() - start >= config.CONF.dns.build_timeout: + raise lib_exc.TimeoutException( + 'Failed, updated TTL:{} for the record was not' + ' detected on Nameserver:{} within a timeout of:{}' + ' seconds.'.format( + updated_ttl, ns, config.CONF.dns.build_timeout))