Merge "Remove reference to 'all-plugin' tox environment"
diff --git a/.zuul.yaml b/.zuul.yaml
index b3c1bbf..87612e8 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -16,18 +16,6 @@
     nodeset: openstack-single-node-focal
     override-checkout: stable/yoga
 
-- job:
-    name: designate-bind9-stable-xena
-    parent: designate-bind9
-    nodeset: openstack-single-node-focal
-    override-checkout: stable/xena
-
-- job:
-    name: designate-bind9-stable-wallaby
-    parent: designate-bind9
-    nodeset: openstack-single-node-focal
-    override-checkout: stable/wallaby
-
 - project:
     templates:
       - designate-devstack-jobs
@@ -40,6 +28,4 @@
         - designate-bind9-stable-antelope
         - designate-bind9-stable-zed
         - designate-bind9-stable-yoga
-        - designate-bind9-stable-xena
-        - designate-bind9-stable-wallaby
         - neutron-tempest-plugin-designate-scenario
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index d56d0de..75a7d41 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -209,11 +209,12 @@
 
 def rand_ns_records():
     ns_zone = rand_zone_name()
-    records = []
+    ns_records = []
+    # Make sure we don't have equal priority here which causes test failures
+    # when doing sorted comparisons
     for i in range(0, 2):
-        records.append("ns%s.%s" % (i, ns_zone))
-    ns_records = [{"hostname": x, "priority": random.randint(1, 999)}
-                  for x in records]
+        ns_records.append({"hostname": "ns%s.%s" % (i, ns_zone),
+                           "priority": (random.randint(1, 999) + i)})
     return ns_records
 
 
diff --git a/designate_tempest_plugin/services/dns/query/query_client.py b/designate_tempest_plugin/services/dns/query/query_client.py
index a9e9723..95fcb61 100644
--- a/designate_tempest_plugin/services/dns/query/query_client.py
+++ b/designate_tempest_plugin/services/dns/query/query_client.py
@@ -15,6 +15,7 @@
 import dns.exception
 import dns.query
 from tempest import config
+from oslo_utils import netutils
 
 CONF = config.CONF
 
@@ -78,7 +79,7 @@
 
     @classmethod
     def from_str(self, nameserver):
-        if ':' in nameserver:
-            ip, port = nameserver.rsplit(':', 1)
-            return Nameserver(ip, int(port))
+        ip, port = netutils.parse_host_port(nameserver)
+        if port:
+            return Nameserver(ip, port)
         return Nameserver(nameserver)
diff --git a/designate_tempest_plugin/tests/api/v2/invalid_mx_dataset.json b/designate_tempest_plugin/tests/api/v2/invalid_mx_dataset.json
deleted file mode 100644
index 100995a..0000000
--- a/designate_tempest_plugin/tests/api/v2/invalid_mx_dataset.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "empty_preference": {"pref": ""},
-  "minus_zero_preference": {"pref": "-0"},
-  "minus_one_preference": {"pref": "-1"}
-}
diff --git a/designate_tempest_plugin/tests/api/v2/invalid_sshfp_dataset.json b/designate_tempest_plugin/tests/api/v2/invalid_sshfp_dataset.json
deleted file mode 100644
index 133b460..0000000
--- a/designate_tempest_plugin/tests/api/v2/invalid_sshfp_dataset.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "minus_zero_algorithm": {"algo": "-0", "finger": null},
-  "minus_zero_fingerprint": {"algo": null, "finger": "-0"},
-  "minus_one_algorithm": {"algo": "-1", "finger": null},
-  "minus_one_fingerprint": {"algo": null, "finger": "-1"}
-}
diff --git a/designate_tempest_plugin/tests/api/v2/invalid_txt_dataset.json b/designate_tempest_plugin/tests/api/v2/invalid_txt_dataset.json
deleted file mode 100644
index 449d90c..0000000
--- a/designate_tempest_plugin/tests/api/v2/invalid_txt_dataset.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "trailing_slash": {"data": "\\"},
-  "trailing_double_slash": {"data": "\\\\"},
-  "trailing_slash_after_text": {"data": "testtext\\"}
-}
diff --git a/designate_tempest_plugin/tests/api/v2/recordset_data_invalid.json b/designate_tempest_plugin/tests/api/v2/recordset_data_invalid.json
deleted file mode 100644
index 413698f..0000000
--- a/designate_tempest_plugin/tests/api/v2/recordset_data_invalid.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "CNAME multiple": {
-        "name": "www",
-        "type": "CNAME",
-        "records": ["target1.example.org.", "target2.example.org."]
-    },
-    "CNAME at Apex": {
-        "name": null,
-        "type": "CNAME",
-        "records": ["target1.example.org."]
-    }
-}
diff --git a/designate_tempest_plugin/tests/api/v2/recordset_wildcard_data.json b/designate_tempest_plugin/tests/api/v2/recordset_wildcard_data.json
deleted file mode 100644
index c56727d..0000000
--- a/designate_tempest_plugin/tests/api/v2/recordset_wildcard_data.json
+++ /dev/null
@@ -1,53 +0,0 @@
-{
-    "A at APEX": {
-        "name": "*",
-        "type": "A",
-        "records": ["192.0.2.1", "192.0.2.2", "192.0.2.3"]
-    },
-    "A under APEX": {
-        "name": "*.sub",
-        "type": "A",
-        "records": ["192.0.2.1", "192.0.2.2", "192.0.2.3"]
-    },
-    "AAAA at APEX": {
-        "name": "*",
-        "type": "AAAA",
-        "records": ["2001:db8::1", "2001:db8::1", "2001:db8::"]
-    },
-    "AAAA under APEX": {
-        "name": "*.sub",
-        "type": "AAAA",
-        "records": ["2001:db8::1", "2001:db8::1", "2001:db8::"]
-    },
-    "MX at APEX": {
-        "name": "*",
-        "type": "MX",
-        "records": ["10 mail1.example.org.",
-                    "20 mail2.example.org."]
-    },
-    "MX under APEX": {
-        "name": "*.sub",
-        "type": "MX",
-        "records": ["10 mail.example.org."]
-    },
-    "SPF at APEX": {
-        "name": "*",
-        "type": "SPF",
-        "records": ["\"v=spf1; a -all\""]
-    },
-    "SPF under APEX": {
-        "name": "*.sub",
-        "type": "SPF",
-        "records": ["\"v=spf1; a -all\""]
-    },
-    "TXT at APEX": {
-        "name": "*",
-        "type": "TXT",
-        "records": ["\"Can you read me?\""]
-    },
-    "TXT under APEX": {
-        "name": "*.sub",
-        "type": "TXT",
-        "records": ["\"Can you read me?\""]
-    }
-}
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
index 2249a68..a7cb4bc 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -16,7 +16,6 @@
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.common.utils import data_utils
-import ddt
 
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin.common import constants as const
@@ -63,7 +62,6 @@
         super(BaseRecordsetsTest, cls).resource_cleanup()
 
 
-@ddt.ddt
 class RecordsetsTest(BaseRecordsetsTest):
 
     credentials = ["admin", "system_admin", "system_reader", "primary", "alt",
@@ -190,9 +188,7 @@
         self._test_create_recordset_type(
             "www", "TXT", ["\"Any Old Text Goes Here\""])
 
-    @decorators.idempotent_id('69f002e5-6511-43d3-abae-7abdd45ae03e')
-    @ddt.file_data("recordset_wildcard_data.json")
-    def test_create_wildcard_recordset(self, name, type, records):
+    def _test_create_wildcard_recordset(self, name, type, records):
         if name is not None:
             recordset_name = name + "." + self.zone['name']
 
@@ -215,6 +211,56 @@
         LOG.info('Ensure we respond with PENDING')
         self.assertEqual(const.PENDING, body['status'])
 
+    @decorators.idempotent_id('69f002e5-6511-43d3-abae-7abdd45ae03e')
+    def test_create_wildcard_recordset_A_at_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*", "A", ["192.0.2.1", "192.0.2.2", "192.0.2.3"])
+
+    @decorators.idempotent_id('d97ee452-0dc3-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_A_under_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*.sub", "A", ["192.0.2.1", "192.0.2.2", "192.0.2.3"])
+
+    @decorators.idempotent_id('1b3c1cc0-0dc4-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_AAAA_at_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*", "AAAA", ["2001:db8::1", "2001:db8::1", "2001:db8::"])
+
+    @decorators.idempotent_id('928e735e-0dc4-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_AAAA_under_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*.sub", "AAAA", ["2001:db8::1", "2001:db8::1", "2001:db8::"])
+
+    @decorators.idempotent_id('d96138f2-0dc4-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_MX_at_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*", "MX", ["10 mail1.example.org.", "20 mail2.example.org."])
+
+    @decorators.idempotent_id('ff273c94-0dc4-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_MX_under_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*.sub", "MX", ["10 mail.example.org."])
+
+    @decorators.idempotent_id('3097f16a-0dc5-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_SPF_at_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*", "SPF", ["\"v=spf1; a -all\""])
+
+    @decorators.idempotent_id('50b3f390-0dc5-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_SPF_under_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*.sub", "SPF", ["\"v=spf1; a -all\""])
+
+    @decorators.idempotent_id('5b73981c-0dc5-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_TXT_at_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*", "TXT", ["\"Can you read me?\""])
+
+    @decorators.idempotent_id('5b73981c-0dc5-11ee-8b75-201e8823901f')
+    def test_create_wildcard_recordset_TXT_under_APEX(self):
+        self._test_create_wildcard_recordset(
+            "*.sub", "TXT", ["\"Can you read me?\""])
+
     @decorators.idempotent_id('5964f730-5546-46e6-9105-5030e9c492b2')
     def test_list_recordsets(self):
         recordset_data = dns_data_utils.rand_recordset_data(
@@ -565,7 +611,6 @@
             lambda: self.client.show_recordset(zone['id'], record['id']))
 
 
-@ddt.ddt
 class RecordsetsNegativeTest(BaseRecordsetsTest):
 
     credentials = ["admin", "system_admin", "primary", "alt"]
@@ -582,9 +627,7 @@
         cls.client = cls.os_primary.dns_v2.RecordsetClient()
         cls.alt_client = cls.os_alt.dns_v2.RecordsetClient()
 
-    @decorators.idempotent_id('98c94f8c-217a-4056-b996-b1f856d0753e')
-    @ddt.file_data("recordset_data_invalid.json")
-    def test_create_recordset_invalid(self, name, type, records):
+    def _test_create_recordset_invalid(self, name, type, records):
         if name is not None:
             recordset_name = name + "." + self.zone['name']
 
@@ -602,6 +645,16 @@
             lambda: self.client.create_recordset(
                 self.zone['id'], recordset_data))
 
+    @decorators.idempotent_id('98c94f8c-217a-4056-b996-b1f856d0753e')
+    def test_create_recordset_invalid_CNAME_multiple(self):
+        self._test_create_recordset_invalid(
+            'www', 'CNAME', ["target1.example.org.", "target2.example.org."])
+
+    @decorators.idempotent_id('43385ce8-0dd3-11ee-8b75-201e8823901f')
+    def test_create_recordset_invalid_CNAME_at_Apex(self):
+        self._test_create_recordset_invalid(
+            None, 'CNAME', ["target1.example.org."])
+
     @decorators.idempotent_id('b6dad57e-5ce9-4fa5-8d66-aebbcd23b4ad')
     def test_get_nonexistent_recordset(self):
         LOG.info('Attempt to get an invalid Recordset')
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py b/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
index 534e107..e61e7fa 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
@@ -13,7 +13,7 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 """
-import ddt
+from oslo_log import log as logging
 
 from tempest import config
 from tempest.lib import decorators
@@ -25,6 +25,7 @@
 
 
 CONF = config.CONF
+LOG = logging.getLogger(__name__)
 RECORDSETS_DATASET = [
     'A',
     'AAAA',
@@ -35,9 +36,25 @@
     'SSHFP',
     'TXT',
 ]
+INVALID_TXT_DATASET = {
+    "trailing_slash": {"data": "\\"},
+    "trailing_double_slash": {"data": "\\\\"},
+    "trailing_slash_after_text": {"data": "testtext\\"}}
+VALID_TXT_DATASET = {
+    "slash_with_one_trailing_space": {"data": "\"\\ \""},
+    "slash_with_many_trailing_space": {"data": "\"\\    \""},
+    "text_with_slash_and_trailing_space": {"data": "\"the txts    \""}}
+INVALID_MX_DATASET = {
+    "empty_preference": {"pref": ""},
+    "minus_zero_preference": {"pref": "-0"},
+    "minus_one_preference": {"pref": "-1"}}
+INVALID_SSHFP_DATASET = {
+    "minus_zero_algorithm": {"algo": "-0", "finger": None},
+    "minus_zero_fingerprint": {"algo": None, "finger": "-0"},
+    "minus_one_algorithm": {"algo": "-1", "finger": None},
+    "minus_one_fingerprint": {"algo": None, "finger": "-1"}}
 
 
-@ddt.ddt
 class RecordsetValidationTest(base.BaseDnsV2Test):
 
     credentials = ["admin", "primary", "system_admin"]
@@ -88,39 +105,35 @@
         return body
 
     @decorators.idempotent_id('c5ef87e2-cb79-4758-b968-18eef2c251df')
-    @ddt.data(*RECORDSETS_DATASET)
-    def test_create_invalid(self, rtype):
-        data = ["b0rk"]
-
-        for i in data:
-            model = dns_data_utils.make_rand_recordset(
-                self.zone['name'], rtype)
-            model['data'] = i
-
-            self.assertRaisesDns(
-                exceptions.BadRequest, 'invalid_object', 400,
-                self.recordset_client.create_recordset,
-                self.zone['id'], model
-            )
+    def test_create_invalid(self):
+        for rtype in RECORDSETS_DATASET:
+            data = ["b0rk"]
+            for i in data:
+                model = dns_data_utils.make_rand_recordset(
+                    self.zone['name'], rtype)
+                model['data'] = i
+                self.assertRaisesDns(
+                    exceptions.BadRequest, 'invalid_object', 400,
+                    self.recordset_client.create_recordset,
+                    self.zone['id'], model
+                )
 
     @decorators.idempotent_id('1164c826-dceb-4557-9a22-7d65c4a4f5f4')
-    @ddt.data(*RECORDSETS_DATASET)
-    def test_update_invalid(self, rtype):
-        data = ["b0rk"]
-
-        post_model = dns_data_utils.make_rand_recordset(
-            self.zone['name'], rtype)
-        recordset = self.create_recordset(post_model)
-
-        for i in data:
-            model = dns_data_utils.make_rand_recordset(
+    def test_update_invalid(self):
+        for rtype in RECORDSETS_DATASET:
+            data = ["b0rk"]
+            post_model = dns_data_utils.make_rand_recordset(
                 self.zone['name'], rtype)
-            model['data'] = i
-            self.assertRaisesDns(
-                exceptions.BadRequest, 'invalid_object', 400,
-                self.recordset_client.update_recordset,
-                self.zone['id'], recordset['id'], model
-            )
+            recordset = self.create_recordset(post_model)
+            for i in data:
+                model = dns_data_utils.make_rand_recordset(
+                    self.zone['name'], rtype)
+                model['data'] = i
+                self.assertRaisesDns(
+                    exceptions.BadRequest, 'invalid_object', 400,
+                    self.recordset_client.update_recordset,
+                    self.zone['id'], recordset['id'], model
+                )
 
     @decorators.idempotent_id('61da1015-291f-43d1-a1a8-345cff12d201')
     def test_cannot_create_wildcard_NS_recordset(self):
@@ -147,57 +160,68 @@
         )
 
     @decorators.idempotent_id('22a9544b-2382-4ed2-ba12-4dbaedb8e880')
-    @ddt.file_data("invalid_txt_dataset.json")
-    def test_cannot_create_TXT_with(self, data):
-        post_model = dns_data_utils.rand_txt_recordset(self.zone['name'], data)
-        self.assertRaisesDns(
-            exceptions.BadRequest, 'invalid_object', 400,
-            self.recordset_client.create_recordset,
-            self.zone['id'], post_model
-        )
+    def test_cannot_create_TXT_with(self):
+        for key, data in INVALID_TXT_DATASET.items():
+            LOG.info('Tested INVALID_TXT_DATASET: {}'.format(key))
+            post_model = dns_data_utils.rand_txt_recordset(
+                self.zone['name'], data['data'])
+            self.assertRaisesDns(
+                exceptions.BadRequest, 'invalid_object', 400,
+                self.recordset_client.create_recordset,
+                self.zone['id'], post_model
+            )
 
     @decorators.idempotent_id('03e4f811-0c37-4ce2-8b16-662c824f8f18')
-    @ddt.file_data("valid_txt_dataset.json")
-    def test_create_TXT_with(self, data):
-        post_model = dns_data_utils.rand_txt_recordset(self.zone['name'], data)
+    def test_create_TXT_with(self):
+        for key, data in VALID_TXT_DATASET.items():
+            LOG.info('Tested VALID_TXT_DATASET: {}'.format(key))
+        post_model = dns_data_utils.rand_txt_recordset(
+            self.zone['name'], data['data'])
         recordset = self.create_recordset(post_model)
 
         waiters.wait_for_recordset_status(
             self.recordset_client, self.zone['id'], recordset['id'], 'ACTIVE')
 
     @decorators.idempotent_id('775b3db5-ec60-4dd7-85d2-f05a9c544978')
-    @ddt.file_data("valid_txt_dataset.json")
-    def test_create_SPF_with(self, data):
-        post_model = dns_data_utils.rand_spf_recordset(self.zone['name'], data)
-        recordset = self.create_recordset(post_model)
+    def test_create_SPF_with(self):
+        for key, data in VALID_TXT_DATASET.items():
+            LOG.info('Tested VALID_TXT_DATASET: {}'.format(key))
+            post_model = dns_data_utils.rand_spf_recordset(
+                self.zone['name'], data['data'])
+            recordset = self.create_recordset(post_model)
 
-        waiters.wait_for_recordset_status(
-            self.recordset_client, self.zone['id'], recordset['id'], 'ACTIVE')
+            waiters.wait_for_recordset_status(
+                self.recordset_client, self.zone['id'],
+                recordset['id'], 'ACTIVE')
 
     @decorators.idempotent_id('7fa7783f-1624-4122-bfb2-6cfbf7a5b49b')
-    @ddt.file_data("invalid_mx_dataset.json")
-    def test_cannot_create_MX_with(self, pref):
-        post_model = dns_data_utils.rand_mx_recordset(
-            self.zone['name'], pref=pref
-        )
+    def test_cannot_create_MX_with(self):
+        for key, pref in INVALID_MX_DATASET.items():
+            LOG.info('Tested INVALID_MX_DATASET: {}'.format(key))
 
-        self.assertRaisesDns(
-            exceptions.BadRequest, 'invalid_object', 400,
-            self.recordset_client.create_recordset,
-            self.zone['id'], post_model,
-        )
+            post_model = dns_data_utils.rand_mx_recordset(
+                self.zone['name'], pref=pref['pref']
+            )
+
+            self.assertRaisesDns(
+                exceptions.BadRequest, 'invalid_object', 400,
+                self.recordset_client.create_recordset,
+                self.zone['id'], post_model,
+            )
 
     @decorators.idempotent_id('3016f998-4e4a-4712-b15a-4e8dfbc5a60b')
-    @ddt.data("invalid_sshfp_dataset.json")
-    def test_cannot_create_SSHFP_with(self, algo=None, finger=None):
-        post_model = dns_data_utils.rand_sshfp_recordset(
-            zone_name=self.zone['name'],
-            algorithm_number=algo,
-            fingerprint_type=finger,
-        )
+    def test_cannot_create_SSHFP_with(self):
+        for key, data in INVALID_SSHFP_DATASET.items():
+            LOG.info('Tested INVALID_SSHFP_DATASET: {}'.format(key))
 
-        self.assertRaisesDns(
-            exceptions.BadRequest, 'invalid_object', 400,
-            self.recordset_client.create_recordset,
-            self.zone['id'], post_model,
-        )
+            post_model = dns_data_utils.rand_sshfp_recordset(
+                zone_name=self.zone['name'],
+                algorithm_number=data['algo'],
+                fingerprint_type=data['finger'],
+            )
+
+            self.assertRaisesDns(
+                exceptions.BadRequest, 'invalid_object', 400,
+                self.recordset_client.create_recordset,
+                self.zone['id'], post_model,
+            )
diff --git a/designate_tempest_plugin/tests/api/v2/test_shared_zones.py b/designate_tempest_plugin/tests/api/v2/test_shared_zones.py
index e6a5dd0..78d6233 100644
--- a/designate_tempest_plugin/tests/api/v2/test_shared_zones.py
+++ b/designate_tempest_plugin/tests/api/v2/test_shared_zones.py
@@ -215,33 +215,10 @@
                         self.zone['id'], shared_zone['id'])
 
         LOG.info('Ensure target project cannot delete zone')
-        self.assertRaises(lib_exc.NotFound,
+        self.assertRaises(lib_exc.Forbidden,
                           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(
@@ -250,7 +227,7 @@
                         self.zone['id'], shared_zone['id'])
 
         LOG.info('Ensure target project cannot update the zone')
-        self.assertRaises(lib_exc.NotFound,
+        self.assertRaises(lib_exc.Forbidden,
                           self.alt_zone_client.update_zone,
                           self.zone['id'], ttl=5)
 
@@ -263,7 +240,7 @@
 
         LOG.info('Ensure target project cannot share shared zone')
         self.assertRaises(
-            lib_exc.NotFound,
+            lib_exc.Forbidden,
             self.alt_share_zone_client.create_zone_share,
             self.zone['id'],
             self.demo_zone_client.project_id)
diff --git a/designate_tempest_plugin/tests/api/v2/test_unauthed.py b/designate_tempest_plugin/tests/api/v2/test_unauthed.py
index aaf8043..b151a04 100644
--- a/designate_tempest_plugin/tests/api/v2/test_unauthed.py
+++ b/designate_tempest_plugin/tests/api/v2/test_unauthed.py
@@ -14,7 +14,6 @@
 from oslo_log import log as logging
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
-import ddt
 
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin import clients
@@ -22,7 +21,6 @@
 LOG = logging.getLogger(__name__)
 
 
-@ddt.ddt
 class TestDnsUnauthed(base.BaseDnsV2Test):
 
     client_manager = clients.ManagerV2Unauthed
@@ -43,10 +41,154 @@
         cls.pool_client = cls.os_primary.pool_client
         cls.blacklists_client = cls.os_primary.blacklists_client
 
-    @decorators.idempotent_id('0f7a6d20-f6f3-4937-8fe6-7a9851227d98')
-    @ddt.file_data('unauthed_data.json')
-    def test_unauthed(self, client, method, args=None):
+    def _test_unauthed(self, client, method, args=None):
         client = getattr(self, client)
         method = getattr(client, method)
         args = args or []
         self.assertRaises(lib_exc.Unauthorized, method, *args)
+
+    @decorators.idempotent_id('b18827ac-de92-11ed-8334-201e8823901f')
+    def test_list_zones(self):
+        self._test_unauthed('zones_client', 'list_zones')
+
+    @decorators.idempotent_id('f60c32ce-de92-11ed-8334-201e8823901f')
+    def test_show_zone(self):
+        self._test_unauthed(
+            'zones_client', 'show_zone',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('56e899c0-de93-11ed-8334-201e8823901f')
+    def test_create_zone(self):
+        self._test_unauthed(
+            'zones_client', 'create_zone',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('5765af6e-de93-11ed-8334-201e8823901f')
+    def test_update_zone(self):
+        self._test_unauthed(
+            'zones_client', 'update_zone',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('57b5cef4-de93-11ed-8334-201e8823901f')
+    def test_delete_zone(self):
+        self._test_unauthed(
+            'zones_client', 'delete_zone',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('05099b62-de94-11ed-8334-201e8823901f')
+    def test_list_recordsets(self):
+        self._test_unauthed(
+            'recordset_client', 'list_recordset',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('0573ca32-de94-11ed-8334-201e8823901f')
+    def test_show_recordset(self):
+        self._test_unauthed(
+            'recordset_client', 'show_recordset',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90",
+             "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('05c0236e-de94-11ed-8334-201e8823901f')
+    def test_create_recordset(self):
+        self._test_unauthed(
+            'recordset_client', 'create_recordset',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90",
+             "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('0600f628-de94-11ed-8334-201e8823901f')
+    def test_update_recordset(self):
+        self._test_unauthed(
+            'recordset_client', 'update_recordset',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90",
+             "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90", {}])
+
+    @decorators.idempotent_id('063c95b6-de94-11ed-8334-201e8823901f')
+    def test_delete_recordset(self):
+        self._test_unauthed(
+            'recordset_client', 'delete_recordset',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90",
+             "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('ee9dae6c-de94-11ed-8334-201e8823901f')
+    def test_list_tlds(self):
+        self._test_unauthed('tld_client', 'list_tlds')
+
+    @decorators.idempotent_id('eef1e5f4-de94-11ed-8334-201e8823901f')
+    def test_show_tld(self):
+        self._test_unauthed(
+            'tld_client', 'show_tld',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('ef3ae024-de94-11ed-8334-201e8823901f')
+    def test_create_tld(self):
+        self._test_unauthed(
+            'tld_client', 'create_tld',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('ef7cfda6-de94-11ed-8334-201e8823901f')
+    def test_update_tld(self):
+        self._test_unauthed(
+            'tld_client', 'update_tld',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('efb982e4-de94-11ed-8334-201e8823901f')
+    def test_delete_tld(self):
+        self._test_unauthed(
+            'tld_client', 'delete_tld',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('654e7596-de95-11ed-8334-201e8823901f')
+    def test_list_blacklists(self):
+        self._test_unauthed('blacklists_client', 'list_blacklists')
+
+    @decorators.idempotent_id('658ea9cc-de95-11ed-8334-201e8823901f')
+    def test_show_blacklist(self):
+        self._test_unauthed(
+            'blacklists_client', 'show_blacklist',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('65cbc2ee-de95-11ed-8334-201e8823901f')
+    def test_create_blacklist(self):
+        self._test_unauthed(
+            'blacklists_client', 'create_blacklist',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('66032676-de95-11ed-8334-201e8823901f')
+    def test_update_blacklist(self):
+        self._test_unauthed(
+            'blacklists_client', 'update_blacklist',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('66321184-de95-11ed-8334-201e8823901f')
+    def test_delete_blacklist(self):
+        self._test_unauthed(
+            'blacklists_client', 'delete_blacklist',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('c7048d66-de95-11ed-8334-201e8823901f')
+    def test_list_pools(self):
+        self._test_unauthed('pool_client', 'list_pools')
+
+    @decorators.idempotent_id('c74581cc-de95-11ed-8334-201e8823901f')
+    def test_show_pool(self):
+        self._test_unauthed(
+            'pool_client', 'show_pool',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('c77d62f4-de95-11ed-8334-201e8823901f')
+    def test_create_pool(self):
+        self._test_unauthed(
+            'pool_client', 'create_pool',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('c7ada040-de95-11ed-8334-201e8823901f')
+    def test_update_pool(self):
+        self._test_unauthed(
+            'pool_client', 'update_pool',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
+
+    @decorators.idempotent_id('c7e07682-de95-11ed-8334-201e8823901f')
+    def test_delete_pool(self):
+        self._test_unauthed(
+            'pool_client', 'delete_pool',
+            ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"])
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py
index d99f31b..5109327 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones.py
@@ -183,6 +183,35 @@
             'ZonesClient', 'show_zone', expected_allowed, False, zone['id'],
             headers={'x-auth-sudo-project-id': self.zones_client.project_id})
 
+    @decorators.idempotent_id('81bff0fb-a5d1-4c64-84db-56ca751c17fc')
+    def test_show_shared_zone(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_show_shared_zone test.')
+
+        LOG.info('Create a zone')
+        zone_name = dns_data_utils.rand_zone_name(
+            name="show_shared_zone", 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'])
+
+        LOG.info('Share the zone with alt')
+        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'])
+
+        LOG.info('Fetch the zone as alt')
+        body = self.alt_zone_client.show_zone(zone['id'])[1]
+
+        # Account for the zone now being shared
+        zone['shared'] = True
+
+        LOG.info('Ensure the fetched response matches the created zone')
+        self.assertExpected(zone, body, self.excluded_keys)
+
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('a4791906-6cd6-4d27-9f15-32273db8bb3d')
     def test_delete_zone(self):
@@ -274,7 +303,7 @@
         LOG.info('List zones')
         body = self.zones_client.list_zones()[1]
 
-        # TODO(kiall): We really want to assert that out newly created zone is
+        # TODO(kiall): We really want to assert that our newly created zone is
         #              present in the response.
         self.assertGreater(len(body['zones']), 0)
 
@@ -307,6 +336,48 @@
             'ZonesClient', 'list_zones', expected_allowed, [zone['id']],
             headers={'x-auth-sudo-project-id': self.zones_client.project_id})
 
+    @decorators.idempotent_id('ad2eed2f-6335-4bc0-87b2-7df7fc4cd82d')
+    def test_list_shared_zone(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_list_shared_zone test.')
+
+        LOG.info('Create zone 1')
+        zone_name = dns_data_utils.rand_zone_name(
+            name="list_shared_zone_1", 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'])
+
+        LOG.info('Create zone 2')
+        zone_2_name = dns_data_utils.rand_zone_name(
+            name="list_shared_zone_2", suffix=self.tld_name)
+        zone_2 = self.zones_client.create_zone(name=zone_2_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone_2['id'])
+
+        LOG.info('Share zone 2 with alt')
+        shared_zone = self.share_zone_client.create_zone_share(
+            zone_2['id'], self.alt_zone_client.project_id)[1]
+        self.addCleanup(self.share_zone_client.delete_zone_share,
+                        zone_2['id'], shared_zone['id'])
+
+        LOG.info('List zones')
+        body = self.zones_client.list_zones()[1]
+
+        # Check that primary can see all of the zones
+        zone_ids = [item['id'] for item in body['zones']]
+        self.assertIn(zone['id'], zone_ids)
+        self.assertIn(zone_2['id'], zone_ids)
+
+        LOG.info('List zones as alt')
+        body = self.alt_zone_client.list_zones()[1]
+
+        # Make sure alt can only see the zone that was shared with alt
+        zone_ids = [item['id'] for item in body['zones']]
+        self.assertNotIn(zone['id'], zone_ids)
+        self.assertIn(zone_2['id'], zone_ids)
+
     @decorators.idempotent_id('123f51cb-19d5-48a9-aacc-476742c02141')
     def test_update_zone(self):
         LOG.info('Create a zone')
diff --git a/designate_tempest_plugin/tests/api/v2/unauthed_data.json b/designate_tempest_plugin/tests/api/v2/unauthed_data.json
deleted file mode 100644
index c4faf80..0000000
--- a/designate_tempest_plugin/tests/api/v2/unauthed_data.json
+++ /dev/null
@@ -1,127 +0,0 @@
-{
-    "list_zones": {
-        "client": "zones_client",
-        "method": "list_zones"
-    },
-    "show_zone": {
-        "client": "zones_client",
-        "method": "show_zone",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "create_zone": {
-        "client": "zones_client",
-        "method": "create_zone",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "update_zone": {
-        "client": "zones_client",
-        "method": "update_zone",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "delete_zone": {
-        "client": "zones_client",
-        "method": "delete_zone",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-
-    "list_recordsets": {
-        "client": "recordset_client",
-        "method": "list_recordset",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "show_recordset": {
-        "client": "recordset_client",
-        "method": "show_recordset",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90", "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "create_recordset": {
-        "client": "recordset_client",
-        "method": "create_recordset",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90", "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "update_recordset": {
-        "client": "recordset_client",
-        "method": "update_recordset",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90", "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90", {}]
-    },
-    "delete_recordset": {
-        "client": "recordset_client",
-        "method": "delete_recordset",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90", "6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-
-    "list_tlds": {
-        "client": "tld_client",
-        "method": "list_tlds"
-    },
-    "show_tld": {
-        "client": "tld_client",
-        "method": "show_tld",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "create_tld": {
-        "client": "tld_client",
-        "method": "create_tld",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "update_tld": {
-        "client": "tld_client",
-        "method": "update_tld",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "delete_tld": {
-        "client": "tld_client",
-        "method": "delete_tld",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-
-    "list_blacklists": {
-        "client": "blacklists_client",
-        "method": "list_blacklists"
-    },
-    "show_blacklist": {
-        "client": "blacklists_client",
-        "method": "show_blacklist",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "create_blacklist": {
-        "client": "blacklists_client",
-        "method": "create_blacklist",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "update_blacklist": {
-        "client": "blacklists_client",
-        "method": "update_blacklist",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "delete_blacklist": {
-        "client": "blacklists_client",
-        "method": "delete_blacklist",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-
-    "list_pools": {
-        "client": "pool_client",
-        "method": "list_pools"
-    },
-    "show_pool": {
-        "client": "pool_client",
-        "method": "show_pool",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "create_pool": {
-        "client": "pool_client",
-        "method": "create_pool",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "update_pool": {
-        "client": "pool_client",
-        "method": "update_pool",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    },
-    "delete_pool": {
-        "client": "pool_client",
-        "method": "delete_pool",
-        "args": ["6ef3b7f2-df39-43ef-9f37-ce2bc424ab90"]
-    }
-}
diff --git a/designate_tempest_plugin/tests/api/v2/valid_txt_dataset.json b/designate_tempest_plugin/tests/api/v2/valid_txt_dataset.json
deleted file mode 100644
index 3abcf10..0000000
--- a/designate_tempest_plugin/tests/api/v2/valid_txt_dataset.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "slash_with_one_trailing_space": {"data": "\"\\ \""},
-  "slash_with_many_trailing_space": {"data": "\"\\    \""},
-  "text_with_slash_and_trailing_space": {"data": "\"the txts    \""}
-}
diff --git a/designate_tempest_plugin/tests/rbac_utils.py b/designate_tempest_plugin/tests/rbac_utils.py
index fa66410..aa8bb6a 100644
--- a/designate_tempest_plugin/tests/rbac_utils.py
+++ b/designate_tempest_plugin/tests/rbac_utils.py
@@ -302,7 +302,7 @@
 
             self.assertEqual(expected_count, len(result_objs),
                              message='Credential {} saw {} objects when {} '
-                             'was expected.'.format(cred, len(result),
+                             'was expected.'.format(cred, len(result_objs),
                                                     expected_count))
 
     def check_list_IDs_RBAC_enforcement(
diff --git a/designate_tempest_plugin/tests/scenario/v2/recordset_data.json b/designate_tempest_plugin/tests/scenario/v2/recordset_data.json
deleted file mode 100644
index 6dda986..0000000
--- a/designate_tempest_plugin/tests/scenario/v2/recordset_data.json
+++ /dev/null
@@ -1,79 +0,0 @@
-{
-    "A": {
-        "name": "www",
-        "type": "A",
-        "records": ["192.0.2.1", "192.0.2.2", "192.0.2.3"]
-    },
-    "AAAA": {
-        "name": "www",
-        "type": "AAAA",
-        "records": ["2001:db8::1", "2001:db8::1", "2001:db8::"]
-    },
-    "SRV TCP": {
-        "name": "_sip._tcp",
-        "type": "SRV",
-        "records": ["10 60 5060 server1.example.com.",
-                    "20 60 5060 server2.example.com.",
-                    "20 30 5060 server3.example.com."]
-    },
-    "SRV UDP": {
-        "name": "_sip._udp",
-        "type": "SRV",
-        "records": ["10 60 5060 server1.example.com.",
-                    "10 60 5060 server2.example.com.",
-                    "20 30 5060 server3.example.com."]
-    },
-    "CNAME": {
-        "name": "alias-of-target",
-        "type": "CNAME",
-        "records": ["target.example.org."]
-    },
-    "MX at APEX": {
-        "name": null,
-        "type": "MX",
-        "records": ["10 mail1.example.org.",
-                    "20 mail2.example.org."]
-    },
-    "MX under APEX": {
-        "name": "under",
-        "type": "MX",
-        "records": ["10 mail.example.org."]
-    },
-    "SSHFP": {
-        "name": "www",
-        "type": "SSHFP",
-        "records": ["2 1 123456789abcdef67890123456789abcdef67890"]
-    },
-    "TXT": {
-        "name": "www",
-        "type": "TXT",
-        "records": ["\"Any Old Text Goes Here\""]
-    },
-    "SPF": {
-        "name": "*.sub",
-        "type": "SPF",
-        "records": ["\"v=spf1; a -all\""]
-    },
-    "PTR_IPV4": {
-        "name": "PTR_Record_IPV4",
-        "type": "PTR",
-        "records": ["34.216.184.93.in-addr.arpa."]
-    },
-   "PTR_IPV6":{
-       "name":"PTR_Record_IPV6",
-       "type":"PTR",
-       "records":[
-           "6.4.9.1.8.c.5.2.3.9.8.1.8.4.2.0.1.0.0.0.0.2.2.0.0.0.8.2.6.0.6.2.ip6.arpa."
-      ]
-   },
-   "CAA_Record": {
-       "name": "CAA_Record",
-       "type": "CAA",
-       "records": ["0 issue letsencrypt.org"]
-    },
-   "NAPTR_Record": {
-       "name": "NAPTR_Record",
-       "type": "NAPTR",
-       "records": ["0 0 S SIP+D2U !^.*$!sip:customer-service@example.com! _sip._udp.example.com."]
-    }
-}
\ No newline at end of file
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_classless_ptr.py b/designate_tempest_plugin/tests/scenario/v2/test_classless_ptr.py
new file mode 100644
index 0000000..c149893
--- /dev/null
+++ b/designate_tempest_plugin/tests/scenario/v2/test_classless_ptr.py
@@ -0,0 +1,227 @@
+# Copyright 2022 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.
+import time
+
+from oslo_utils import versionutils
+from tempest import config
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin.services.dns.query.query_client import (
+    SingleQueryClient)
+
+CONF = config.CONF
+
+
+# This test suite is intended to test RFC 2317 classless in-addr.arpa
+# delegation scenarios.
+class ClasslessPTRTest(base.BaseDnsV2Test):
+
+    credentials = ['primary', 'admin', 'system_admin', 'alt']
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these tests.
+        cls.set_network_resources()
+        super(ClasslessPTRTest, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(ClasslessPTRTest, 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.zone_client = cls.os_primary.dns_v2.ZonesClient()
+        cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
+        cls.alt_rec_client = cls.os_alt.dns_v2.RecordsetClient()
+        cls.share_zone_client = cls.os_primary.dns_v2.SharedZonesClient()
+
+    @classmethod
+    def resource_setup(cls):
+        super(ClasslessPTRTest, cls).resource_setup()
+
+        # Make sure we have an allowed TLD available
+        cls.tld_name = '0.192.in-addr-arpa'
+        cls.class_tld = cls.admin_tld_client.create_tld(tld_name=cls.tld_name)
+
+    @classmethod
+    def resource_cleanup(cls):
+        cls.admin_tld_client.delete_tld(cls.class_tld[1]['id'])
+        super(ClasslessPTRTest, cls).resource_cleanup()
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('f2d9596c-ce87-4dfd-9bb4-ad2430fd3fe6')
+    def test_classless_ptr_delegation(self):
+        # Create full subnet zone
+        zone_name = f'2.{self.tld_name}.'
+        zone = self.zone_client.create_zone(name=zone_name,
+                                            wait_until='ACTIVE')[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'],
+                        ignore_errors=lib_exc.NotFound)
+
+        # Create the delegated zone
+        delegated_zone_name = f'1-3.2.{self.tld_name}.'
+        delegated_zone = self.zone_client.create_zone(
+            name=delegated_zone_name, wait_until='ACTIVE')[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client,
+                        delegated_zone['id'], ignore_errors=lib_exc.NotFound)
+
+        # Create the PTR record in the delegated zone
+        ptr_recordset_data = {
+            'name': f'1.1-3.2.{self.tld_name}.',
+            'type': 'PTR',
+            'records': ['www.example.org.']
+        }
+        ptr_recordset = self.recordset_client.create_recordset(
+            delegated_zone['id'], ptr_recordset_data, wait_until='ACTIVE')[1]
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.recordset_client.delete_recordset,
+                        delegated_zone['id'], ptr_recordset['id'])
+
+        # Create the CNAME record
+        cname_recordset_data = {
+            'name': f'1.2.{self.tld_name}.',
+            'type': 'CNAME',
+            'records': [f'1.1-3.2.{self.tld_name}.']
+        }
+        cname_recordset = self.recordset_client.create_recordset(
+            zone['id'], cname_recordset_data, wait_until='ACTIVE')[1]
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.recordset_client.delete_recordset,
+                        zone['id'], cname_recordset['id'])
+
+        # Check for a CNAME record
+        if config.CONF.dns.nameservers:
+            ns = config.CONF.dns.nameservers[0]
+            start = time.time()
+            while True:
+                ns_obj = SingleQueryClient(ns, config.CONF.dns.query_timeout)
+                ns_record = ns_obj.query(
+                    cname_recordset['name'],
+                    rdatatype=cname_recordset_data['type'])
+                if cname_recordset_data['records'][0] in str(ns_record):
+                    break
+                if time.time() - start >= config.CONF.dns.build_timeout:
+                    raise lib_exc.TimeoutException(
+                        'Failed, CNAME record was not detected on '
+                        'Nameserver:{} within a timeout of:{}'
+                        ' seconds.'.format(ns, config.CONF.dns.build_timeout))
+
+        # Check for a PTR record
+        if config.CONF.dns.nameservers:
+            ns = config.CONF.dns.nameservers[0]
+            start = time.time()
+            while True:
+                ns_obj = SingleQueryClient(ns, config.CONF.dns.query_timeout)
+                ns_record = ns_obj.query(
+                    ptr_recordset['name'],
+                    rdatatype=ptr_recordset_data['type'])
+                if ptr_recordset_data['records'][0] in str(ns_record):
+                    break
+                if time.time() - start >= config.CONF.dns.build_timeout:
+                    raise lib_exc.TimeoutException(
+                        'Failed, PTR record was not detected on '
+                        'Nameserver:{} within a timeout of:{}'
+                        ' seconds.'.format(ns, config.CONF.dns.build_timeout))
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('0110e7b1-9582-410e-b3d5-bd38a1265222')
+    def test_classless_ptr_delegation_shared_zone(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_classless_ptr_delegation_shared_zone test.')
+
+        # Create full subnet zone
+        zone_name = f'2.{self.tld_name}.'
+        zone = self.zone_client.create_zone(name=zone_name,
+                                            wait_until='ACTIVE')[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'],
+                        ignore_errors=lib_exc.NotFound)
+
+        # Create the delegated zone
+        delegated_zone_name = f'1-3.2.{self.tld_name}.'
+        delegated_zone = self.zone_client.create_zone(
+            name=delegated_zone_name, wait_until='ACTIVE')[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client,
+                        delegated_zone['id'], ignore_errors=lib_exc.NotFound)
+
+        # Create the CNAME record
+        cname_recordset_data = {
+            'name': f'1.2.{self.tld_name}.',
+            'type': 'CNAME',
+            'records': [f'1.1-3.2.{self.tld_name}.']
+        }
+        cname_recordset = self.recordset_client.create_recordset(
+            zone['id'], cname_recordset_data, wait_until='ACTIVE')[1]
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.recordset_client.delete_recordset,
+                        zone['id'], cname_recordset['id'])
+
+        # Share the zone with the alt credential
+        shared_zone = self.share_zone_client.create_zone_share(
+            delegated_zone['id'], self.alt_rec_client.project_id)[1]
+        self.addCleanup(self.share_zone_client.delete_zone_share,
+                        delegated_zone['id'], shared_zone['id'])
+
+        # Create the PTR record in the delegated zone as the alt project
+        ptr_recordset_data = {
+            'name': f'1.1-3.2.{self.tld_name}.',
+            'type': 'PTR',
+            'records': ['www.example.org.']
+        }
+        ptr_recordset = self.alt_rec_client.create_recordset(
+            delegated_zone['id'], ptr_recordset_data, wait_until='ACTIVE')[1]
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.alt_rec_client.delete_recordset,
+                        delegated_zone['id'], ptr_recordset['id'])
+
+        # Check for a CNAME record
+        if config.CONF.dns.nameservers:
+            ns = config.CONF.dns.nameservers[0]
+            start = time.time()
+            while True:
+                ns_obj = SingleQueryClient(ns, config.CONF.dns.query_timeout)
+                ns_record = ns_obj.query(
+                    cname_recordset['name'],
+                    rdatatype=cname_recordset_data['type'])
+                if cname_recordset_data['records'][0] in str(ns_record):
+                    break
+                if time.time() - start >= config.CONF.dns.build_timeout:
+                    raise lib_exc.TimeoutException(
+                        'Failed, CNAME record was not detected on '
+                        'Nameserver:{} within a timeout of:{}'
+                        ' seconds.'.format(ns, config.CONF.dns.build_timeout))
+
+        # Check for a PTR record
+        if config.CONF.dns.nameservers:
+            ns = config.CONF.dns.nameservers[0]
+            start = time.time()
+            while True:
+                ns_obj = SingleQueryClient(ns, config.CONF.dns.query_timeout)
+                ns_record = ns_obj.query(
+                    ptr_recordset['name'],
+                    rdatatype=ptr_recordset_data['type'])
+                if ptr_recordset_data['records'][0] in str(ns_record):
+                    break
+                if time.time() - start >= config.CONF.dns.build_timeout:
+                    raise lib_exc.TimeoutException(
+                        'Failed, PTR record was not detected on '
+                        'Nameserver:{} within a timeout of:{}'
+                        ' seconds.'.format(ns, config.CONF.dns.build_timeout))
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
index a84077b..07ddde0 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
@@ -17,7 +17,7 @@
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
-import ddt
+import testtools
 
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin.common import constants as const
@@ -31,7 +31,6 @@
 CONF = config.CONF
 
 
-@ddt.ddt
 class RecordsetsTest(base.BaseDnsV2Test):
 
     credentials = ["admin", "system_admin", "primary"]
@@ -79,11 +78,8 @@
         cls.admin_tld_client.delete_tld(cls.class_tld[1]['id'])
         super(RecordsetsTest, cls).resource_cleanup()
 
-    @decorators.attr(type='slow')
-    @decorators.idempotent_id('4664ed66-9ff1-45f2-9e60-d4913195c505')
-    @ddt.file_data("recordset_data.json")
-    def test_create_and_delete_records_on_existing_zone(self, name,
-                                                        type, records):
+    def _test_create_and_delete_records_on_existing_zone(
+            self, name, type, records):
         if name is not None:
             recordset_name = name + "." + self.zone['name']
 
@@ -106,10 +102,12 @@
         LOG.info('Ensure we respond with PENDING')
         self.assertEqual('PENDING', recordset['status'])
 
-        LOG.info('Wait until the recordset is active')
+        LOG.info('Wait until the recordset is active and propagated')
         waiters.wait_for_recordset_status(self.recordset_client,
                                           self.zone['id'], recordset['id'],
                                           'ACTIVE')
+        waiters.wait_for_query(
+            self.query_client, recordset_data['name'], type)
 
         LOG.info('Delete the recordset')
         body = self.recordset_client.delete_recordset(
@@ -119,15 +117,111 @@
         self.assertEqual('DELETE', body['action'])
         self.assertEqual('PENDING', body['status'])
 
-        LOG.info('Ensure successful deletion of Recordset')
+        LOG.info('Ensure successful deletion of Recordset from:'
+                 ' Designate and Backends')
         self.assertRaises(lib_exc.NotFound,
                           lambda: self.recordset_client.show_recordset(
                               self.zone['id'], recordset['id']))
+        waiters.wait_for_query(
+            self.query_client, recordset_data['name'], type, found=False)
 
     @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):
+    @decorators.idempotent_id('4664ed66-9ff1-45f2-9e60-d4913195c505')
+    def test_create_and_delete_records_on_existing_zone_01_A(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "www", "A", ["192.0.2.1", "192.0.2.2", "192.0.2.3"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('cecd9f20-0b62-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_02_AAAA(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "www", "AAAA", ["2001:db8::1", "2001:db8::1", "2001:db8::"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('f5368d7a-0b62-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_03_SRV(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "_sip._tcp", "SRV", ["10 60 5060 server1.example.com.",
+                    "20 60 5060 server2.example.com.",
+                    "20 30 5060 server3.example.com."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('74ff9efc-0b63-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_04_SRV(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "_sip._udp", "SRV", ["10 60 5060 server1.example.com.",
+                    "10 60 5060 server2.example.com.",
+                    "20 30 5060 server3.example.com."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('82a14a2e-0b63-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_05_CNAME(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "alias-of-target", "CNAME", ["target.example.org."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('ae7a295e-0b63-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_06_MX(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            None, "MX", ["10 mail1.example.org.",
+                    "20 mail2.example.org."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('f9aa8512-0b64-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_07_MX(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "under", "MX", ["10 mail.example.org."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('fa6cbd12-0b64-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_08_SSHFP(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "www", "SSHFP", ["2 1 123456789abcdef67890123456789abcdef67890"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('fa124a1c-0b64-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_09_TXT(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "www", "TXT", ["\"Any Old Text Goes Here\""])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('3e347c28-0b66-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_10_SPF(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "*.sub", "SPF", ["\"v=spf1; a -all\""])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('88f6c2ac-0b66-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_11_PTR(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "PTR_Record_IPV4", "PTR", ["34.216.184.93.in-addr.arpa."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('b9591eea-0b66-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_12_PTR(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "PTR_Record_IPV6", "PTR",
+            ["6.4.9.1.8.c.5.2.3.9.8.1.8.4.2.0.1.0.0.0.0.2.2.0.0.0.8"
+             ".2.6.0.6.2.ip6.arpa."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('c98cd9b4-0b66-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_13_CAA(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "CAA_Record", "CAA", ["0 issue letsencrypt.org"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('f78d6e8c-0b66-11ee-bbcc-201e8823901f')
+    def test_create_and_delete_records_on_existing_zone_14_NAPTR(self):
+        self._test_create_and_delete_records_on_existing_zone(
+            "NAPTR_Record", "NAPTR",
+            ["0 0 S SIP+D2U !^.*$!sip:customer-service@example"
+             ".com! _sip._udp.example.com."])
+
+    @testtools.skipUnless(
+        config.CONF.dns.nameservers,
+        "Config option dns.nameservers is missing or empty")
+    def _test_update_records_propagated_to_backends(self, name, type, records):
         if name:
             recordset_name = name + "." + self.zone['name']
         else:
@@ -171,3 +265,98 @@
                         ' detected on Nameserver:{} within a timeout of:{}'
                         ' seconds.'.format(
                             updated_ttl, ns, config.CONF.dns.build_timeout))
+
+    # These tests were unrolled from DDT to allow accurate tracking by
+    # idempotent_id's. The naming convention for the tests has been preserved.
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('cbf756b0-ba64-11ec-93d4-201e8823901f')
+    def test_update_records_propagated_to_backends_01_A(self):
+        self._test_update_records_propagated_to_backends(
+            "www", "A", ["192.0.2.1", "192.0.2.2", "192.0.2.3"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('258f7f57-9a74-4e72-bbfb-c709c411af14')
+    def test_update_records_propagated_to_backends_02_AAAA(self):
+        self._test_update_records_propagated_to_backends(
+            "www", "AAAA", ["2001:db8::1", "2001:db8::1", "2001:db8::"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('304adbc5-668a-457e-9496-8efd20b8ae82')
+    def test_update_records_propagated_to_backends_03_SRV_TCP(self):
+        self._test_update_records_propagated_to_backends(
+            "_sip._tcp", "SRV", ["10 60 5060 server1.example.com.",
+                                 "20 60 5060 server2.example.com.",
+                                 "20 30 5060 server3.example.com."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('bd1283b3-423c-4bb9-8c4f-a205f31f1c2d')
+    def test_update_records_propagated_to_backends_04_SRV_UDP(self):
+        self._test_update_records_propagated_to_backends(
+            "_sip._udp", "SRV", ["10 60 5060 server1.example.com.",
+                                 "10 60 5060 server2.example.com.",
+                                 "20 30 5060 server3.example.com."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('8b53ae20-d096-4651-a6cf-efd7c98ae8d1')
+    def test_update_records_propagated_to_backends_05_CNAME(self):
+        self._test_update_records_propagated_to_backends(
+            "alias-of-target", "CNAME", ["target.example.org."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('0fd0046a-ac5a-468d-94b3-8a6bde790589')
+    def test_update_records_propagated_to_backends_06_MX_at_APEX(self):
+        self._test_update_records_propagated_to_backends(
+            None, "MX", ["10 mail1.example.org.",
+                         "20 mail2.example.org."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('31176def-3f95-459d-8bdd-b9994335b2d9')
+    def test_update_records_propagated_to_backends_07_MX_under_APEX(self):
+        self._test_update_records_propagated_to_backends(
+            "under", "MX", ["10 mail.example.org."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('0009d787-c590-4149-9f30-082195326fad')
+    def test_update_records_propagated_to_backends_08_SSHFP(self):
+        self._test_update_records_propagated_to_backends(
+            "www", "SSHFP", ["2 1 123456789abcdef67890123456789abcdef67890"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('af7cec16-dfad-4071-aa05-cafa60bf12a5')
+    def test_update_records_propagated_to_backends_09_TXT(self):
+        self._test_update_records_propagated_to_backends(
+            "www", "TXT", ["\"Any Old Text Goes Here\""])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('b3fd1f77-c318-4ab0-b18d-34611e51e9e4')
+    def test_update_records_propagated_to_backends_10_SPF(self):
+        self._test_update_records_propagated_to_backends(
+            "*.sub", "SPF", ["\"v=spf1; a -all\""])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('c310b94b-f3a5-4d26-bab6-2529e6f29fbf')
+    def test_update_records_propagated_to_backends_11_PTR_IPV4(self):
+        self._test_update_records_propagated_to_backends(
+            "PTR_Record_IPV4", "PTR", ["34.216.184.93.in-addr.arpa."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('3e31e406-621f-4f89-b401-b6f38aa63347')
+    def test_update_records_propagated_to_backends_12_PTR_IPV6(self):
+        self._test_update_records_propagated_to_backends(
+            "PTR_Record_IPV6", "PTR",
+            ["6.4.9.1.8.c.5.2.3.9.8.1.8.4.2.0.1.0.0.0.0.2.2.0.0.0.8.2.6.0.6.2"
+             ".ip6.arpa."])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('6fd96280-fb62-4eaf-81f9-609cdb7c126e')
+    def test_update_records_propagated_to_backends_13_CAA_Record(self):
+        self._test_update_records_propagated_to_backends(
+            "CAA_Record", "CAA", ["0 issue letsencrypt.org"])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('45a11efe-bee3-4896-ab7c-daee1cb5eb3a')
+    def test_update_records_propagated_to_backends_14_NAPTR_Record(self):
+        self._test_update_records_propagated_to_backends(
+            "NAPTR_Record", "NAPTR",
+            ["0 0 S SIP+D2U !^.*$!sip:customer-service@example.com! "
+             "_sip._udp.example.com."])
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py b/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py
index 8da7d7c..75aa3c3 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py
@@ -294,6 +294,76 @@
         self.addCleanup(self.share_zone_client.delete_zone_share,
                         zone_id, shared_zone['id'])
 
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('c5d83684-18cb-11ee-a872-201e8823901f')
+    def test_list_zones_shared_with_more_then_two_projects(self):
+        # Create a zone to share with the alt credentialzones_client
+        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]
+        zone_id = zone['id']
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'],
+                        ignore_errors=lib_exc.NotFound)
+
+        # 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'])
+
+        # Share the zone with the demo credential
+        shared_zone = self.share_zone_client.create_zone_share(
+            zone['id'], self.demo_rec_client.project_id)[1]
+        self.addCleanup(self.share_zone_client.delete_zone_share,
+                        zone['id'], shared_zone['id'])
+
+        zones = self.zones_client.list_zones()[1]['zones']
+        zones_ids = [zone['id'] for zone in zones]
+        self.assertEqual(
+            1, zones_ids.count(zone_id),
+            'Failed, ID:{} counted in zones listed:{} must be one'.format(
+                zone_id, zones))
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('78b77c6c-18cf-11ee-a872-201e8823901f')
+    def test_create_recordset_for_zone_shared_with_two_projects(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)
+
+        # 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_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        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)
+
+        # Share the zone with the demo credential
+        shared_zone = self.share_zone_client.create_zone_share(
+            zone['id'], self.demo_rec_client.project_id)[1]
+        self.addCleanup(self.share_zone_client.delete_zone_share,
+                        zone['id'], shared_zone['id'])
+
+        # Check that the demo user can create a recordset on the shared zone
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        recordset = self.demo_rec_client.create_recordset(
+            zone['id'], recordset_data)[1]
+        self.addCleanup(self.wait_recordset_delete, self.demo_rec_client,
+            zone['id'], recordset['id'], ignore_errors=lib_exc.NotFound)
+
 
 class SharedZonesTestNegative(base.BaseDnsV2Test):
     credentials = ['primary', 'admin', 'system_admin', 'alt',
@@ -357,7 +427,7 @@
         zone = self._create_shared_zone(
             'test_alt_create_export_for_shared_zone')[0]
         self.assertRaises(
-            lib_exc.NotFound,
+            lib_exc.Forbidden,
             self.alt_export_client.create_zone_export, zone['id'])
 
     @decorators.attr(type='slow')
@@ -414,7 +484,7 @@
         zone = self._create_shared_zone(
             'test_alt_shows_shared_zones_nameservers')[0]
         self.assertRaises(
-            lib_exc.NotFound,
+            lib_exc.Forbidden,
             self.alt_zone_client.show_zone_nameservers, zone['id'])
 
     @decorators.attr(type='slow')
@@ -425,7 +495,7 @@
             'test_alt_transfers_shared_zone')[0]
         # Alt creates a zone transfer_request
         self.assertRaises(
-            lib_exc.NotFound,
+            lib_exc.Forbidden,
             self.alt_transfer_client.create_transfer_request, zone['id'])
 
     @decorators.attr(type='slow')
diff --git a/requirements.txt b/requirements.txt
index 2ba3e1a..497e29e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,6 @@
 # process, which may cause wedges in the gate later.
 
 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