From a84e3194adf72fb2eb87f0c563065bb089b0d192 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 22 May 2020 19:10:50 +0300 Subject: [PATCH] Add tempest tests for shared zones This patch adds API and scenario test coverage for the shard zones feature. Author: Igor Malinovskiy Co-Authored-By: Sergey Drozdov Co-Authored-By: Michael Johnson Change-Id: I53a1e4676c5bbb63bee0c4bb91eac03c95dd3a3c Partial-Bug: #1714088 Depends-On: https://review.opendev.org/726334 --- .../services/dns/v2/__init__.py | 7 +- .../dns/v2/json/shared_zones_client.py | 75 +++++ .../tests/api/v2/test_shared_zones.py | 282 ++++++++++++++++++ .../tests/api/v2/test_zones.py | 47 ++- .../tests/scenario/v2/test_shared_zones.py | 198 ++++++++++++ requirements.txt | 1 + 6 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 designate_tempest_plugin/services/dns/v2/json/shared_zones_client.py create mode 100644 designate_tempest_plugin/tests/api/v2/test_shared_zones.py create mode 100644 designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py diff --git a/designate_tempest_plugin/services/dns/v2/__init__.py b/designate_tempest_plugin/services/dns/v2/__init__.py index ed86ffb8..acc26d28 100644 --- a/designate_tempest_plugin/services/dns/v2/__init__.py +++ b/designate_tempest_plugin/services/dns/v2/__init__.py @@ -19,6 +19,7 @@ from .json.ptr_client import PtrClient from .json.quotas_client import QuotasClient from .json.recordset_client import RecordsetClient from .json.service_client import ServiceClient +from .json.shared_zones_client import SharedZonesClient from .json.tld_client import TldClient from .json.transfer_accepts_client import TransferAcceptClient from .json.transfer_request_client import TransferRequestClient @@ -30,6 +31,6 @@ from .json.api_version_client import ApiVersionClient __all__ = ['BlacklistsClient', 'DesignateLimitClient', 'PoolClient', 'PtrClient', 'QuotasClient', 'RecordsetClient', 'ServiceClient', - 'TldClient', 'TransferAcceptClient', 'TransferRequestClient', - 'TsigkeyClient', 'ZonesClient', 'ZoneExportsClient', - 'ZoneImportsClient', 'ApiVersionClient'] + 'SharedZonesClient', 'TldClient', 'TransferAcceptClient', + 'TransferRequestClient', 'TsigkeyClient', 'ZonesClient', + 'ZoneExportsClient', 'ZoneImportsClient', 'ApiVersionClient'] diff --git a/designate_tempest_plugin/services/dns/v2/json/shared_zones_client.py b/designate_tempest_plugin/services/dns/v2/json/shared_zones_client.py new file mode 100644 index 00000000..4215c57a --- /dev/null +++ b/designate_tempest_plugin/services/dns/v2/json/shared_zones_client.py @@ -0,0 +1,75 @@ +# Copyright 2020 Cloudification GmbH. All rights reserved. +# +# 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 designate_tempest_plugin.services.dns.v2.json import base + + +class SharedZonesClient(base.DnsClientV2Base): + + @base.handle_errors + def create_zone_share(self, zone_id, target_project_id): + """Create a new zone share for a project ID. + + :param zone_id: Zone UUID to share + :param target_project_id: Project ID that will gain access to specified + zone + :return: Zone share dict + """ + resp, body = self._create_request( + 'zones/{}/shares'.format(zone_id), + data={'target_project_id': target_project_id}) + + # Endpoint should Return a HTTP 201 + self.expected_success(201, resp.status) + + return resp, body + + @base.handle_errors + def show_zone_share(self, zone_id, zone_share_id): + """Get the zone share object + + :param zone_id: Zone UUID for the share + :param zone_share_id: The zone share ID + :return: Zone share dict + """ + return self._show_request('zones/{}/shares'.format(zone_id), + zone_share_id) + + @base.handle_errors + def list_zone_shares(self, zone_id, params=None, headers=None): + """List zone shares + + :param zone_id: Zone UUID to query for the shares + :param params: A Python dict that represents the query parameters to + include in the request URI. + :return: Zone shares list. + """ + return self._list_request('zones/{}/shares'.format(zone_id), + params=params, headers=headers) + + @base.handle_errors + def delete_zone_share(self, zone_id, zone_share_id): + """Deletes the zone share + + :param zone_id: Zone UUID for the share + :param zone_share_id: The zone share ID + :return: None + """ + resp, body = self._delete_request('zones/{}/shares'.format(zone_id), + zone_share_id) + + # Endpoint should Return a HTTP 204 - No Content + self.expected_success(204, resp.status) + + return resp, body diff --git a/designate_tempest_plugin/tests/api/v2/test_shared_zones.py b/designate_tempest_plugin/tests/api/v2/test_shared_zones.py new file mode 100644 index 00000000..90394001 --- /dev/null +++ b/designate_tempest_plugin/tests/api/v2/test_shared_zones.py @@ -0,0 +1,282 @@ +# Copyright 2020 Cloudification GmbH. All rights reserved. +# +# 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 oslo_utils import uuidutils +from oslo_utils import versionutils +from tempest import config +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +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 BaseSharedZoneTest(base.BaseDnsV2Test): + + credentials = ['admin', 'system_admin', 'system_reader', 'primary', 'alt', + 'project_reader', 'project_member', ['demo', 'member']] + + excluded_keys = ['links'] + + @classmethod + def resource_setup(cls): + super(BaseSharedZoneTest, cls).resource_setup() + + if not versionutils.is_compatible('2.1', cls.api_version, + same_major=False): + raise cls.skipException( + 'The shared zones API tests require Designate API version ' + '2.1 or newer. Skipping Shared Zones API tests.') + + # Make sure we have an allowed TLD available + tld_name = dns_data_utils.rand_zone_name(name="APISharedZoneTest") + cls.tld_name = f".{tld_name}" + cls.class_tld = cls.admin_tld_client.create_tld(tld_name=tld_name[:-1]) + + # All the shared zone tests need a zone, create one to share + zone_name = dns_data_utils.rand_zone_name(name="TestZone", + suffix=cls.tld_name) + LOG.info('Create a zone: %s', zone_name) + cls.zone = cls.zones_client.create_zone(name=zone_name)[1] + + @classmethod + def resource_cleanup(cls): + cls.zones_client.delete_zone( + cls.zone['id'], ignore_errors=lib_exc.NotFound) + cls.admin_tld_client.delete_tld(cls.class_tld[1]['id']) + super(BaseSharedZoneTest, cls).resource_cleanup() + + @classmethod + def setup_clients(cls): + super(BaseSharedZoneTest, cls).setup_clients() + + if CONF.enforce_scope.designate: + cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient() + else: + cls.admin_tld_client = cls.os_admin.dns_v2.TldClient() + cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient() + cls.demo_zone_client = cls.os_demo.dns_v2.ZonesClient() + cls.share_zone_client = cls.os_primary.dns_v2.SharedZonesClient() + cls.alt_share_zone_client = cls.os_alt.dns_v2.SharedZonesClient() + + +class SharedZonesTest(BaseSharedZoneTest): + + @classmethod + def setup_credentials(cls): + # Do not create network resources for these test. + cls.set_network_resources() + super(SharedZonesTest, cls).setup_credentials() + + @decorators.idempotent_id('982a7780-a460-4c13-97df-b4855bf19c7b') + def test_create_zone_share(self): + # Test RBAC + expected_allowed = ['os_admin', 'os_primary', 'os_alt'] + if CONF.dns_feature_enabled.enforce_new_defaults: + expected_allowed.append('os_system_admin') + expected_allowed.append('os_project_member') + self.check_CUD_RBAC_enforcement( + 'SharedZonesClient', 'create_zone_share', expected_allowed, True, + self.zone['id'], self.alt_zone_client.project_id) + + # Test a basic API create a zone share + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + self.assertTrue(uuidutils.is_uuid_like(shared_zone['id'])) + self.assertEqual(self.zone['id'], shared_zone['zone_id']) + self.assertEqual(self.share_zone_client.project_id, + shared_zone['project_id']) + self.assertEqual(self.alt_zone_client.project_id, + shared_zone['target_project_id']) + self.assertIsNotNone(shared_zone['created_at']) + self.assertIsNone(shared_zone['updated_at']) + self.assertIsNotNone(shared_zone['links']) + + @decorators.idempotent_id('0edecb9b-4890-433c-8195-0935271efc9a') + def test_show_shared_zone(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + # Test RBAC + expected_allowed = ['os_admin', 'os_primary', 'os_alt'] + if CONF.dns_feature_enabled.enforce_new_defaults: + expected_allowed.append('os_system_admin') + expected_allowed.append('os_system_reader') + expected_allowed.append('os_project_member') + expected_allowed.append('os_project_reader') + self.check_CUD_RBAC_enforcement( + 'SharedZonesClient', 'show_zone_share', expected_allowed, True, + self.zone['id'], shared_zone['id']) + + # Test show zone share + LOG.info('Fetch the zone share') + body = self.share_zone_client.show_zone_share(self.zone['id'], + shared_zone['id'])[1] + + LOG.info('Ensure the fetched response matches the zone share') + self.assertExpected(shared_zone, body, self.excluded_keys) + + @decorators.idempotent_id('a18a8577-9d02-492a-a869-4ff7d6f4f89b') + def test_delete_zone_share(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id'], + ignore_errors=lib_exc.NotFound) + + # Test RBAC + expected_allowed = ['os_admin', 'os_primary'] + if CONF.dns_feature_enabled.enforce_new_defaults: + expected_allowed.append('os_system_admin') + expected_allowed.append('os_project_member') + self.check_CUD_RBAC_enforcement( + 'SharedZonesClient', 'delete_zone_share', expected_allowed, True, + self.zone['id'], shared_zone['id']) + + # Test zone share delete + LOG.info('Delete zone share') + self.share_zone_client.delete_zone_share(self.zone['id'], + shared_zone['id']) + + LOG.info('Ensure the zone share was deleted') + self.assertRaises(lib_exc.NotFound, + self.share_zone_client.show_zone_share, + self.zone['id'], shared_zone['id']) + + @decorators.idempotent_id('707bfa4f-f15b-4486-ba5c-0e5991f0f3a5') + def test_list_zone_shares(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + # Test RBAC + expected_allowed = ['os_admin', 'os_primary', 'os_alt'] + if CONF.dns_feature_enabled.enforce_new_defaults: + expected_allowed.append('os_system_admin') + expected_allowed.append('os_system_reader') + expected_allowed.append('os_project_member') + expected_allowed.append('os_project_reader') + self.check_CUD_RBAC_enforcement( + 'SharedZonesClient', 'list_zone_shares', expected_allowed, True, + self.zone['id']) + + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.demo_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('List zone shares') + body = self.share_zone_client.list_zone_shares(self.zone['id'])[1] + + self.assertEqual(2, len(body['shared_zones'])) + targets = [] + for share in body['shared_zones']: + targets.append(share['target_project_id']) + self.assertIn(self.alt_zone_client.project_id, targets) + self.assertIn(self.demo_zone_client.project_id, targets) + + +class NegativeSharedZonesTest(BaseSharedZoneTest): + + @classmethod + def setup_credentials(cls): + # Do not create network resources for these test. + cls.set_network_resources() + super(NegativeSharedZonesTest, cls).setup_credentials() + + @decorators.idempotent_id('4389a12b-8609-493c-9640-d3c67b625022') + def test_target_project_cannot_delete_zone(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('Ensure target project cannot delete zone') + self.assertRaises(lib_exc.NotFound, + self.alt_zone_client.delete_zone, + self.zone['id']) + + @decorators.idempotent_id('5c5e8551-1398-447d-a490-9cf1b16de129') + def test_target_project_cannot_show_zone(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('Ensure target project cannot show zone') + self.assertRaises(lib_exc.NotFound, + self.alt_zone_client.show_zone, + self.zone['id']) + + @decorators.idempotent_id('ab9bf257-ea5d-4362-973e-767055a316dd') + def test_target_project_cannot_list_zone(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('Ensure target project cannot see the zone in list zones') + body = self.alt_zone_client.list_zones()[1] + self.assertEqual([], body['zones']) + + @decorators.idempotent_id('f4354b5c-8dbb-4bb9-8025-f65f8f2b21fb') + def test_target_project_cannot_update_zone(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('Ensure target project cannot update the zone') + self.assertRaises(lib_exc.NotFound, + self.alt_zone_client.update_zone, + self.zone['id'], ttl=5) + + @decorators.idempotent_id('4389a12b-8609-493c-9640-d3c67b625022') + def test_target_project_share_permissions(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('Ensure target project cannot share shared zone') + self.assertRaises( + lib_exc.NotFound, + self.alt_share_zone_client.create_zone_share, + self.zone['id'], + self.demo_zone_client.project_id) + + @decorators.idempotent_id('abc0f820-ae27-4e85-8f00-0b8e8abf3ae9') + def test_target_project_cannot_subzone(self): + shared_zone = self.share_zone_client.create_zone_share( + self.zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + self.zone['id'], shared_zone['id']) + + LOG.info('Ensure target project cannot create sub-zones') + sub_zone_name = "test.{}".format(self.zone['name']) + self.assertRaises( + lib_exc.Forbidden, + self.alt_zone_client.create_zone, + name=sub_zone_name) diff --git a/designate_tempest_plugin/tests/api/v2/test_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py index aa7edd88..83b8b315 100644 --- a/designate_tempest_plugin/tests/api/v2/test_zones.py +++ b/designate_tempest_plugin/tests/api/v2/test_zones.py @@ -13,6 +13,7 @@ # under the License. import uuid from oslo_log import log as logging +from oslo_utils import versionutils from tempest import config from tempest.lib import decorators from tempest.lib.common.utils import data_utils @@ -20,7 +21,6 @@ from tempest.lib import exceptions as lib_exc from designate_tempest_plugin.common import constants as const - from designate_tempest_plugin import data_utils as dns_data_utils from designate_tempest_plugin.tests import base @@ -72,6 +72,8 @@ class ZonesTest(BaseZonesTest): else: cls.pool_client = cls.os_admin.dns_v2.PoolClient() cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient() + cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient() + cls.share_zone_client = cls.os_primary.dns_v2.SharedZonesClient() @decorators.idempotent_id('9d2e20fc-e56f-4a62-9c61-9752a9ec615c') def test_create_zones(self): @@ -218,6 +220,49 @@ class ZonesTest(BaseZonesTest): self.assertEqual(const.DELETE, body['action']) self.assertEqual(const.PENDING, body['status']) + @decorators.idempotent_id('bf2ee5c1-67b5-47dc-9902-ddb5b0e03e37') + def test_delete_zone_with_shares(self): + + if not versionutils.is_compatible('2.1', self.api_version, + same_major=False): + raise self.skipException( + 'Zone share tests require Designate API version 2.1 or newer. ' + 'Skipping test_delete_zone_with_shares test.') + + LOG.info('Create a zone') + zone_name = dns_data_utils.rand_zone_name( + name="delete_zones_with_shares", suffix=self.tld_name) + zone = self.zones_client.create_zone(name=zone_name)[1] + self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'], + ignore_errors=lib_exc.NotFound) + + shared_zone = self.share_zone_client.create_zone_share( + zone['id'], self.alt_zone_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + zone['id'], shared_zone['id'], + ignore_errors=lib_exc.NotFound) + + LOG.info('Attempt to delete the zone with shares') + self.assertRaises(lib_exc.BadRequest, self.zones_client.delete_zone, + zone['id']) + + LOG.info('Make sure the zone share is still present') + check_share = self.share_zone_client.show_zone_share( + zone['id'], shared_zone['id'])[1] + self.assertEqual(shared_zone['id'], check_share['id']) + + LOG.info('Delete the zone using delete-shares') + body = self.zones_client.delete_zone( + zone['id'], headers={'x-designate-delete-shares': True})[1] + + LOG.info('Ensure we respond with DELETE+PENDING') + self.assertEqual(const.DELETE, body['action']) + self.assertEqual(const.PENDING, body['status']) + + self.assertRaises(lib_exc.NotFound, + self.share_zone_client.show_zone_share, + zone['id'], shared_zone['id']) + @decorators.idempotent_id('5bfa3cfe-5bc8-443b-bf48-cfba44cbb247') def test_list_zones(self): LOG.info('Create a zone') diff --git a/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py b/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py new file mode 100644 index 00000000..2438da0a --- /dev/null +++ b/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py @@ -0,0 +1,198 @@ +# Copyright 2023 Red Hat +# +# 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 oslo_utils import versionutils +from tempest import config +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +from designate_tempest_plugin import data_utils as dns_data_utils +from designate_tempest_plugin.tests import base + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class SharedZonesTest(base.BaseDnsV2Test): + credentials = ['primary', 'admin', 'system_admin', 'alt', + ['demo', 'member']] + + @classmethod + def setup_clients(cls): + super(SharedZonesTest, cls).setup_clients() + if CONF.enforce_scope.designate: + cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient() + else: + cls.admin_tld_client = cls.os_admin.dns_v2.TldClient() + cls.share_zone_client = cls.os_primary.dns_v2.SharedZonesClient() + cls.rec_client = cls.os_primary.dns_v2.RecordsetClient() + cls.alt_rec_client = cls.os_alt.dns_v2.RecordsetClient() + cls.demo_rec_client = cls.os_demo.dns_v2.RecordsetClient() + + @classmethod + def resource_setup(cls): + super(SharedZonesTest, cls).resource_setup() + + if not versionutils.is_compatible('2.1', cls.api_version, + same_major=False): + raise cls.skipException( + 'The shared zones scenario tests require Designate API ' + 'version 2.1 or newer. Skipping Shared Zones scenario tests.') + + # Make sure we have an allowed TLD available + tld_name = dns_data_utils.rand_zone_name(name='SharedZonesTest') + cls.tld_name = f'.{tld_name}' + cls.class_tld = cls.admin_tld_client.create_tld(tld_name=tld_name[:-1]) + + @classmethod + def resource_cleanup(cls): + cls.admin_tld_client.delete_tld(cls.class_tld[1]['id']) + super(SharedZonesTest, cls).resource_cleanup() + + @decorators.attr(type='slow') + @decorators.idempotent_id('b0fad45d-25ec-49b9-89a8-10b0e3c8b14c') + def test_zone_share_CRUD_recordset(self): + # Create a zone to share with the alt credential + zone_name = dns_data_utils.rand_zone_name(name='TestZone', + suffix=self.tld_name) + LOG.info('Create a zone: %s', zone_name) + zone = self.zones_client.create_zone(name=zone_name)[1] + self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'], + ignore_errors=lib_exc.NotFound) + + recordset_data = dns_data_utils.rand_recordset_data( + record_type='A', zone_name=zone['name']) + + # Check that the alt user has no access to the zone before the share + self.assertRaises(lib_exc.NotFound, + self.alt_rec_client.create_recordset, + zone['id'], recordset_data) + + # Check that the demo user has no access to the zone before the share + self.assertRaises(lib_exc.NotFound, + self.demo_rec_client.create_recordset, + zone['id'], recordset_data) + + # Share the zone with the alt credential + shared_zone = self.share_zone_client.create_zone_share( + zone['id'], self.alt_rec_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + zone['id'], shared_zone['id']) + + # Check that the demo user has no access to the zone after the share + self.assertRaises(lib_exc.NotFound, + self.demo_rec_client.create_recordset, + zone['id'], recordset_data) + + # Check that the alt user can create a recordset on the shared zone + recordset = self.alt_rec_client.create_recordset(zone['id'], + recordset_data)[1] + self.addCleanup(self.wait_recordset_delete, self.alt_rec_client, + zone['id'], recordset['id'], ignore_errors=lib_exc.NotFound) + + # Check that the demo user cannot see the alt recordset + self.assertRaises(lib_exc.NotFound, + self.demo_rec_client.show_recordset, + zone['id'], recordset['id']) + + # Check that the alt user can see the alt recordset + show_recordset = self.alt_rec_client.show_recordset( + zone['id'], recordset['id'])[1] + + self.assertEqual(recordset['id'], show_recordset['id']) + + # Check that the zone owner can see the alt recordset + show_recordset = self.rec_client.show_recordset(zone['id'], + recordset['id'])[1] + + self.assertEqual(recordset['id'], show_recordset['id']) + + recordset_data = { + 'ttl': dns_data_utils.rand_ttl(start=recordset['ttl'] + 1) + } + + # Check that the demo user cannot update the recordset created by alt + self.assertRaises(lib_exc.NotFound, + self.demo_rec_client.update_recordset, + zone['id'], recordset['id'], recordset_data) + + # Check that the alt user can update a recordset on the shared zone + update = self.alt_rec_client.update_recordset(zone['id'], + recordset['id'], recordset_data)[1] + + self.assertNotEqual(recordset['ttl'], update['ttl']) + + recordset_data = { + 'ttl': dns_data_utils.rand_ttl(start=update['ttl'] + 1) + } + + # Check that the zone owner can update a recordset on the shared zone + primary_update = self.rec_client.update_recordset(zone['id'], + recordset['id'], recordset_data)[1] + + self.assertNotEqual(update['ttl'], primary_update['ttl']) + + # Check that the demo user cannot delete the alt recordset + self.assertRaises(lib_exc.NotFound, + self.demo_rec_client.delete_recordset, + zone['id'], recordset['id']) + + # Check that the alt user can delete it's recordset + self.alt_rec_client.delete_recordset(zone['id'], recordset['id']) + + LOG.info('Ensure successful deletion of Recordset') + self.assertRaises(lib_exc.NotFound, + self.alt_rec_client.show_recordset, + zone['id'], recordset['id']) + + @decorators.attr(type='slow') + @decorators.idempotent_id('de03b4d3-3ccf-4291-a920-89e2694bba22') + def test_zone_owner_can_delete_shared_recordset(self): + # Create a zone to share with the alt credential + zone_name = dns_data_utils.rand_zone_name(name='TestZone', + suffix=self.tld_name) + LOG.info('Create a zone: %s', zone_name) + zone = self.zones_client.create_zone(name=zone_name)[1] + self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'], + ignore_errors=lib_exc.NotFound) + + recordset_data = dns_data_utils.rand_recordset_data( + record_type='A', zone_name=zone['name']) + + # Share the zone with the alt credential + shared_zone = self.share_zone_client.create_zone_share( + zone['id'], self.alt_rec_client.project_id)[1] + self.addCleanup(self.share_zone_client.delete_zone_share, + zone['id'], shared_zone['id']) + + # Check that the alt user can create a recordset on the shared zone + recordset = self.alt_rec_client.create_recordset(zone['id'], + recordset_data)[1] + self.addCleanup(self.wait_recordset_delete, self.alt_rec_client, + zone['id'], recordset['id'], ignore_errors=lib_exc.NotFound) + + # Check that the alt user can see the alt recordset + show_recordset = self.alt_rec_client.show_recordset( + zone['id'], recordset['id'])[1] + + self.assertEqual(recordset['id'], show_recordset['id']) + + # Check that the zone owner can delete the recordset + self.rec_client.delete_recordset(zone['id'], recordset['id']) + + LOG.info('Ensure successful deletion of Recordset') + self.assertRaises(lib_exc.NotFound, + self.alt_rec_client.show_recordset, + zone['id'], recordset['id']) diff --git a/requirements.txt b/requirements.txt index d9c89bf1..2ba3e1a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,6 @@ dnspython>=1.16.0 # http://www.dnspython.org/LICENSE ddt>=1.0.1 # MIT oslo.serialization>=2.25.0 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0