Merge "Adds backend propagation check for E2E recordset scenario test"
diff --git a/.zuul.yaml b/.zuul.yaml
index 31a06dd..87612e8 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,4 +1,10 @@
 - job:
+    name: designate-bind9-stable-antelope
+    parent: designate-bind9
+    nodeset: openstack-single-node-focal
+    override-checkout: stable/2023.1
+
+- job:
     name: designate-bind9-stable-zed
     parent: designate-bind9
     nodeset: openstack-single-node-focal
@@ -10,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
@@ -31,8 +25,7 @@
       - release-notes-jobs-python3
     check:
       jobs:
+        - 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/README.rst b/README.rst
index e1a2d04..902b26d 100644
--- a/README.rst
+++ b/README.rst
@@ -23,15 +23,25 @@
 Running the tests
 -----------------
 
+From the tempest directory, setup the tempest virtual environment for the
+designate tempest plugin::
+
+    $ tox -e venv-tempest -- pip3 install -e <path to designate-tempest-plugin>
+
+For example, when using a typical devstack setup::
+
+    $ cd /opt/stack/tempest
+    $ tox -e venv-tempest -- pip3 install -e /opt/stack/designate-tempest-plugin
+
 To run all tests from this plugin, install designate into your environment
 and from the tempest repo, run::
 
-    $ tox -e all-plugin -- designate
+    $ tox -e all -- designate
 
 To run a single test case, run with the test case name, for example::
 
-    $ tox -e all-plugin -- designate_tempest_plugin.tests.api.v2.test_zones.ZonesAdminTest.test_get_other_tenant_zone
+    $ tox -e all -- designate_tempest_plugin.tests.api.v2.test_zones.ZonesTest.test_create_zones
 
 To run all tempest tests including this plugin, run::
 
-    $ tox -e all-plugin
+    $ tox -e all
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index c6eed6f..012f8a3 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -15,16 +15,16 @@
 from tempest import config
 from tempest.lib import auth
 
-from designate_tempest_plugin.services.dns.v2.json.blacklists_client import \
-    BlacklistsClient
-from designate_tempest_plugin.services.dns.v2.json.pool_client import \
-    PoolClient
-from designate_tempest_plugin.services.dns.v2.json.recordset_client import \
-    RecordsetClient
-from designate_tempest_plugin.services.dns.v2.json.tld_client import \
-    TldClient
-from designate_tempest_plugin.services.dns.v2.json.zones_client import \
-    ZonesClient
+from designate_tempest_plugin.services.dns.v2.json.blacklists_client import (
+    BlacklistsClient)
+from designate_tempest_plugin.services.dns.v2.json.pool_client import (
+    PoolClient)
+from designate_tempest_plugin.services.dns.v2.json.recordset_client import (
+    RecordsetClient)
+from designate_tempest_plugin.services.dns.v2.json.tld_client import (
+    TldClient)
+from designate_tempest_plugin.services.dns.v2.json.zones_client import (
+    ZonesClient)
 
 
 CONF = config.CONF
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index d148685..d56d0de 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -95,8 +95,8 @@
     }
 
     if CONF.dns_feature_enabled.bug_1573141_fixed:
-        quotas_dict['api_export_size'] = \
-            api_export_size or data_utils.rand_int_id(100, 999999)
+        quotas_dict['api_export_size'] = (
+            api_export_size or data_utils.rand_int_id(100, 999999))
     else:
         LOG.warning("Leaving `api_export_size` out of quota data due to: "
                     "https://bugs.launchpad.net/designate/+bug/1573141")
@@ -190,8 +190,7 @@
                          **kwargs):
     algorithm_number = algorithm_number or 2
     fingerprint_type = fingerprint_type or 1
-    fingerprint = fingerprint or \
-        "123456789abcdef67890123456789abcdef67890"
+    fingerprint = fingerprint or "123456789abcdef67890123456789abcdef67890"
 
     data = "%s %s %s" % (algorithm_number, fingerprint_type, fingerprint)
     return rand_recordset_data('SSHFP', zone_name, records=[data], **kwargs)
diff --git a/designate_tempest_plugin/hacking/__init__.py b/designate_tempest_plugin/hacking/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/designate_tempest_plugin/hacking/__init__.py
diff --git a/designate_tempest_plugin/hacking/checks.py b/designate_tempest_plugin/hacking/checks.py
new file mode 100644
index 0000000..53de95c
--- /dev/null
+++ b/designate_tempest_plugin/hacking/checks.py
@@ -0,0 +1,187 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# Copyright (c) 2012, Cloudscaling
+#
+# 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 re
+
+from hacking import core
+import pycodestyle
+
+# D701: Default parameter value is a mutable type
+# D702: Log messages require translation
+# D703: Found use of _() without explicit import of _!
+# D704: Found import of %s. This oslo library has been graduated!
+# D705: timeutils.utcnow() must be used instead of datetime.%s()
+# D706: Don't translate debug level logs
+# D707: basestring is not Python3-compatible, use str instead.
+# D708: Do not use xrange. Use range for large loops.
+# D709: LOG.audit is deprecated, please use LOG.info!
+# D710: LOG.warn() is not allowed. Use LOG.warning()
+# D711: Don't use backslashes for line continuation.
+
+UNDERSCORE_IMPORT_FILES = []
+
+
+mutable_default_argument_check = re.compile(
+    r"^\s*def .+\((.+=\{\}|.+=\[\])")
+string_translation = re.compile(r"[^_]*_\(\s*('|\")")
+translated_log = re.compile(
+    r"(.)*LOG\.(audit|error|info|warn|warning|critical|exception)"
+    r"\(\s*_\(\s*('|\")")
+underscore_import_check = re.compile(r"(.)*import _(.)*")
+# We need this for cases where they have created their own _ function.
+custom_underscore_check = re.compile(r"(.)*_\s*=\s*(.)*")
+graduated_oslo_libraries_import_re = re.compile(
+    r"^\s*(?:import|from) designate\.openstack\.common\.?.*?"
+    r"(gettextutils|rpc)"
+    r".*?")
+no_line_continuation_backslash_re = re.compile(r'.*(\\)\n')
+
+
+@core.flake8ext
+def mutable_default_arguments(physical_line, logical_line, filename):
+    if pycodestyle.noqa(physical_line):
+        return
+
+    if mutable_default_argument_check.match(logical_line):
+        yield (0, "D701: Default parameter value is a mutable type")
+
+
+@core.flake8ext
+def no_translate_debug_logs(logical_line, filename):
+    """Check for 'LOG.debug(_('
+    As per our translation policy,
+    https://wiki.openstack.org/wiki/LoggingStandards#Log_Translation
+    we shouldn't translate debug level logs.
+    * This check assumes that 'LOG' is a logger.
+    * Use filename so we can start enforcing this in specific folders instead
+      of needing to do so all at once.
+    N319
+    """
+    if logical_line.startswith("LOG.debug(_("):
+        yield(0, "D706: Don't translate debug level logs")
+
+
+@core.flake8ext
+def check_explicit_underscore_import(logical_line, filename):
+    """Check for explicit import of the _ function
+
+    We need to ensure that any files that are using the _() function
+    to translate logs are explicitly importing the _ function.  We
+    can't trust unit test to catch whether the import has been
+    added so we need to check for it here.
+    """
+    # Build a list of the files that have _ imported.  No further
+    # checking needed once it is found.
+    if filename in UNDERSCORE_IMPORT_FILES:
+        pass
+    elif (underscore_import_check.match(logical_line) or
+          custom_underscore_check.match(logical_line)):
+        UNDERSCORE_IMPORT_FILES.append(filename)
+    elif (translated_log.match(logical_line) or
+         string_translation.match(logical_line)):
+        yield(0, "D703: Found use of _() without explicit import of _!")
+
+
+@core.flake8ext
+def no_import_graduated_oslo_libraries(logical_line, filename):
+    """Check that we don't continue to use o.c. oslo libraries after graduation
+
+    After a library graduates from oslo-incubator, as we make the switch, we
+    should ensure we don't continue to use the oslo-incubator versions.
+
+    In many cases, it's not possible to immediately remove the code from the
+    openstack/common folder due to dependency issues.
+    """
+    # We can't modify oslo-incubator code, so ignore it here.
+    if "designate/openstack/common" in filename:
+        return
+
+    matches = graduated_oslo_libraries_import_re.match(logical_line)
+    if matches:
+        yield(0, "D704: Found import of %s. This oslo library has been "
+                 "graduated!" % matches.group(1))
+
+
+@core.flake8ext
+def use_timeutils_utcnow(logical_line, filename):
+    # tools are OK to use the standard datetime module
+    if "/tools/" in filename:
+        return
+
+    msg = "D705: timeutils.utcnow() must be used instead of datetime.%s()"
+
+    datetime_funcs = ['now', 'utcnow']
+    for f in datetime_funcs:
+        pos = logical_line.find('datetime.%s' % f)
+        if pos != -1:
+            yield (pos, msg % f)
+
+
+@core.flake8ext
+def check_no_basestring(logical_line):
+    if re.search(r"\bbasestring\b", logical_line):
+        msg = ("D707: basestring is not Python3-compatible, use "
+               "str instead.")
+        yield(0, msg)
+
+
+@core.flake8ext
+def check_python3_xrange(logical_line):
+    if re.search(r"\bxrange\s*\(", logical_line):
+        yield(0, "D708: Do not use xrange. Use range for "
+                 "large loops.")
+
+
+@core.flake8ext
+def check_no_log_audit(logical_line):
+    """Ensure that we are not using LOG.audit messages
+    Plans are in place going forward as discussed in the following
+    spec (https://review.opendev.org/#/c/132552/) to take out
+    LOG.audit messages. Given that audit was a concept invented
+    for OpenStack we can enforce not using it.
+    """
+    if "LOG.audit(" in logical_line:
+        yield(0, "D709: LOG.audit is deprecated, please use LOG.info!")
+
+
+@core.flake8ext
+def check_no_log_warn(logical_line):
+    """Disallow 'LOG.warn('
+
+    D710
+    """
+    if logical_line.startswith('LOG.warn('):
+        yield(0, "D710:Use LOG.warning() rather than LOG.warn()")
+
+
+@core.flake8ext
+def check_line_continuation_no_backslash(logical_line, tokens):
+    """D711 - Don't use backslashes for line continuation.
+
+    :param logical_line: The logical line to check. Not actually used.
+    :param tokens: List of tokens to check.
+    :returns: None if the tokens don't contain any issues, otherwise a tuple
+              is yielded that contains the offending index in the logical
+              line and a message describe the check validation failure.
+    """
+    backslash = None
+    for token_type, text, start, end, orig_line in tokens:
+        m = no_line_continuation_backslash_re.match(orig_line)
+        if m:
+            backslash = (start[0], m.start(1))
+            break
+
+    if backslash is not None:
+        msg = 'D711 Backslash line continuations not allowed'
+        yield backslash, msg
diff --git a/designate_tempest_plugin/services/dns/admin/__init__.py b/designate_tempest_plugin/services/dns/admin/__init__.py
index 2b43e84..a3b6ac0 100644
--- a/designate_tempest_plugin/services/dns/admin/__init__.py
+++ b/designate_tempest_plugin/services/dns/admin/__init__.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
-    QuotasClient
+from designate_tempest_plugin.services.dns.admin.json.quotas_client import (
+    QuotasClient)
 
 __all__ = ['QuotasClient']
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index 6ac9a85..dc3eb6f 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -276,3 +276,41 @@
             body = self.deserialize(resp, body)
 
         return resp, body
+
+    def get_max_api_version(self):
+        """Get the maximum version available on the API endpoint.
+        :return: Maximum version string available on the endpoint.
+        """
+        response, body = self.get('/')
+        self.expected_success(200, response.status)
+
+        versions_list = json.loads(body)['versions']
+
+        # Handle the legacy version document format
+        if 'values' in versions_list:
+            versions_list = versions_list['values']
+
+        current_versions = (version for version in versions_list if
+                            version['status'] == 'CURRENT')
+        max_version = '0.0'
+        for version in current_versions:
+
+            ver_string = version['id']
+            if ver_string.startswith("v"):
+                ver_string = ver_string[1:]
+
+            ver_split = list(map(int, ver_string.split('.')))
+            max_split = list(map(int, max_version.split('.')))
+
+            if len(ver_split) > 2:
+                raise lib_exc.InvalidAPIVersionString(version=ver_string)
+
+            if ver_split[0] > max_split[0] or (
+                    ver_split[0] == max_split[0] and
+                    ver_split[1] >= max_split[1]):
+                max_version = ver_string
+
+        if max_version == '0.0':
+            raise lib_exc.InvalidAPIVersionString(version=max_version)
+
+        return max_version
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/services/dns/v2/__init__.py b/designate_tempest_plugin/services/dns/v2/__init__.py
index 44cc403..acc26d2 100644
--- a/designate_tempest_plugin/services/dns/v2/__init__.py
+++ b/designate_tempest_plugin/services/dns/v2/__init__.py
@@ -12,38 +12,25 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from designate_tempest_plugin.services.dns.v2.json.blacklists_client import \
-    BlacklistsClient
-from designate_tempest_plugin.services.dns.v2.json.designate_limit_client \
-    import DesignateLimitClient
-from designate_tempest_plugin.services.dns.v2.json.pool_client import \
-    PoolClient
-from designate_tempest_plugin.services.dns.v2.json.ptr_client \
-    import PtrClient
-from designate_tempest_plugin.services.dns.v2.json.quotas_client import \
-    QuotasClient
-from designate_tempest_plugin.services.dns.v2.json.recordset_client import \
-    RecordsetClient
-from designate_tempest_plugin.services.dns.v2.json.service_client import \
-    ServiceClient
-from designate_tempest_plugin.services.dns.v2.json.tld_client import TldClient
-from designate_tempest_plugin.services.dns.v2.json.transfer_accepts_client \
-    import TransferAcceptClient
-from designate_tempest_plugin.services.dns.v2.json.transfer_request_client \
-    import TransferRequestClient
-from designate_tempest_plugin.services.dns.v2.json.tsigkey_client import \
-    TsigkeyClient
-from designate_tempest_plugin.services.dns.v2.json.zones_client import \
-    ZonesClient
-from designate_tempest_plugin.services.dns.v2.json.zone_exports_client import \
-    ZoneExportsClient
-from designate_tempest_plugin.services.dns.v2.json.zone_imports_client import \
-    ZoneImportsClient
-from designate_tempest_plugin.services.dns.v2.json.api_version_client import \
-    ApiVersionClient
+from .json.blacklists_client import BlacklistsClient
+from .json.designate_limit_client import DesignateLimitClient
+from .json.pool_client import PoolClient
+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
+from .json.tsigkey_client import TsigkeyClient
+from .json.zones_client import ZonesClient
+from .json.zone_exports_client import ZoneExportsClient
+from .json.zone_imports_client import ZoneImportsClient
+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 0000000..d908b38
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/shared_zones_client.py
@@ -0,0 +1,80 @@
+# 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, headers=None):
+        """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
+        :param headers: (dict): The headers to use for the request.
+        :return: Zone share dict
+        """
+        resp, body = self._create_request(
+            'zones/{}/shares'.format(zone_id),
+            data={'target_project_id': target_project_id}, headers=headers,
+            extra_headers=True)
+
+        # 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, headers=None):
+        """Get the zone share object
+
+        :param zone_id: Zone UUID for the share
+        :param zone_share_id: The zone share ID
+        :param headers: (dict): The headers to use for the request.
+        :return: Zone share dict
+        """
+        return self._show_request('zones/{}/shares'.format(zone_id),
+                                  zone_share_id, headers=headers)
+
+    @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.
+        :param headers: (dict): The headers to use for the request.
+        :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, headers=None):
+        """Deletes the zone share
+
+        :param zone_id: Zone UUID for the share
+        :param zone_share_id: The zone share ID
+        :param headers: (dict): The headers to use for the request.
+        :return: None
+        """
+        resp, body = self._delete_request('zones/{}/shares'.format(zone_id),
+                                          zone_share_id, headers=headers)
+
+        # Endpoint should Return a HTTP 204 - No Content
+        self.expected_success(204, resp.status)
+
+        return resp, body
diff --git a/designate_tempest_plugin/services/dns/v2/json/zones_client.py b/designate_tempest_plugin/services/dns/v2/json/zones_client.py
index 4cb6016..a995af8 100644
--- a/designate_tempest_plugin/services/dns/v2/json/zones_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/zones_client.py
@@ -48,7 +48,7 @@
             Default: PRIMARY
         :param primaries: List of Primary nameservers. Required for SECONDARY
             Default: None
-        :param params: A Python dict that represents the query paramaters to
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
         :param project_id: When specified, overrides the project ID the zone
                            will be associated with.
@@ -102,7 +102,7 @@
     def show_zone(self, uuid, params=None, headers=None):
         """Gets a specific zone.
         :param uuid: Unique identifier of the zone in UUID format.
-        :param params: A Python dict that represents the query paramaters to
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
         :param headers (dict): The headers to use for the request.
         :return: Serialized zone as a dictionary.
@@ -114,7 +114,7 @@
     def show_zone_nameservers(self, zone_uuid, params=None, headers=None):
         """Gets list of Zone Name Servers
         :param zone_uuid: Unique identifier of the zone in UUID format.
-        :param params: A Python dict that represents the query paramaters to
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
         :param headers (dict): The headers to use for the request.
         :return: Serialized nameservers as a list.
@@ -126,7 +126,7 @@
     @base.handle_errors
     def list_zones(self, params=None, headers=None):
         """Gets a list of zones.
-        :param params: A Python dict that represents the query paramaters to
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
         :param headers (dict): The headers to use for the request.
         :return: Serialized zones as a list.
@@ -134,14 +134,20 @@
         return self._list_request('zones', params=params, headers=headers)
 
     @base.handle_errors
-    def delete_zone(self, uuid, params=None, headers=None):
+    def delete_zone(self, uuid, params=None, headers=None, delete_shares=None):
         """Deletes a zone having the specified UUID.
         :param uuid: The unique identifier of the zone.
-        :param params: A Python dict that represents the query paramaters to
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
         :param headers (dict): The headers to use for the request.
+        :param delete_shares: if set, delete-shares modifier will be activated
         :return: A tuple with the server response and the response body.
         """
+        if delete_shares:
+            if headers:
+                headers['x-designate-delete-shares'] = True
+            else:
+                headers = {'x-designate-delete-shares': True}
         resp, body = self._delete_request(
             'zones', uuid, params=params, headers=headers)
 
@@ -163,7 +169,7 @@
         :param description: A description of the zone.
             Default: Random Value
         :param wait_until: Block until the zone reaches the desiered status
-        :param params: A Python dict that represents the query paramaters to
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
         :param headers (dict): The headers to use for the request.
         :return: A tuple with the server response and the updated zone.
diff --git a/designate_tempest_plugin/tests/api/v2/test_blacklists.py b/designate_tempest_plugin/tests/api/v2/test_blacklists.py
index ecb5ce5..48b3028 100644
--- a/designate_tempest_plugin/tests/api/v2/test_blacklists.py
+++ b/designate_tempest_plugin/tests/api/v2/test_blacklists.py
@@ -58,9 +58,7 @@
 
         self.assertExpected(blacklist, body, self.excluded_keys)
 
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin']
+        expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_CUD_RBAC_enforcement('BlacklistsClient', 'create_blacklist',
                                         expected_allowed, False)
@@ -100,9 +98,10 @@
         LOG.info('Ensure the fetched response matches the created blacklist')
         self.assertExpected(blacklist, body, self.excluded_keys)
 
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin', 'os_system_reader']
+        else:
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'BlacklistsClient', 'show_blacklist', expected_allowed, False,
@@ -121,9 +120,7 @@
         # A blacklist delete returns an empty body
         self.assertEqual(body.strip(), b"")
 
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin']
+        expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_CUD_RBAC_enforcement(
             'BlacklistsClient', 'delete_blacklist', expected_allowed, False,
@@ -141,9 +138,10 @@
         # TODO(pglass): Assert that the created blacklist is in the response
         self.assertGreater(len(body['blacklists']), 0)
 
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader']
+        if CONF.enforce_scope.designate:
+            expected_allowed = ['os_system_admin']
+        else:
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_IDs_RBAC_enforcement(
             'BlacklistsClient', 'list_blacklists',
@@ -168,9 +166,7 @@
         self.assertEqual(pattern, body['pattern'])
         self.assertEqual(description, body['description'])
 
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin']
+        expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_CUD_RBAC_enforcement(
             'BlacklistsClient', 'update_blacklist', expected_allowed, False,
@@ -179,7 +175,7 @@
 
 class TestBlacklistNotFoundAdmin(BaseBlacklistsTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
@@ -225,7 +221,7 @@
 
 class TestBlacklistInvalidIdAdmin(BaseBlacklistsTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
diff --git a/designate_tempest_plugin/tests/api/v2/test_enabled_api_version.py b/designate_tempest_plugin/tests/api/v2/test_enabled_api_version.py
index 814802f..4eacfb9 100644
--- a/designate_tempest_plugin/tests/api/v2/test_enabled_api_version.py
+++ b/designate_tempest_plugin/tests/api/v2/test_enabled_api_version.py
@@ -79,12 +79,13 @@
 
             LOG.info('Received enabled API versions for {} '
                      'user are:{}'.format(user, versions))
-            for item in versions:
-                enabled_ids = [
-                    item['id'] for key in item.keys() if key == 'id']
+
+            enabled_ids = {item['id'] for item in versions}
             LOG.info('Enabled versions IDs are:{}'.format(enabled_ids))
-            possible_options = [['v1'], ['v2'], ['v1', 'v2'], ['v2.0']]
-            self.assertIn(
-                enabled_ids, possible_options,
-                'Failed, received version: {} is not in possible options'
-                ' list:{}'.format(enabled_ids, possible_options))
+
+            # Expect at least one of these to be present in the API
+            base_versions = {'v1', 'v2', 'v2.0'}
+            self.assertFalse(
+                enabled_ids.isdisjoint(base_versions),
+                'Failed, at least one base API version: {} was not found in '
+                'the API version list: {}'.format(base_versions, enabled_ids))
diff --git a/designate_tempest_plugin/tests/api/v2/test_pool.py b/designate_tempest_plugin/tests/api/v2/test_pool.py
index 144f0d6..27f6e9d 100644
--- a/designate_tempest_plugin/tests/api/v2/test_pool.py
+++ b/designate_tempest_plugin/tests/api/v2/test_pool.py
@@ -102,10 +102,10 @@
         # TODO(johnsom) Test reader roles once this bug is fixed.
         #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         # TODO(johnsom) The pools API seems inconsistent with the requirement
         #               of the all-projects header.
@@ -195,7 +195,7 @@
 
 class TestPoolNotFoundAdmin(BasePoolTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
@@ -241,7 +241,7 @@
 
 class TestPoolInvalidIdAdmin(BasePoolTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
@@ -288,7 +288,7 @@
 
 class TestPoolAdminNegative(BasePoolTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
index 3773537..2249a68 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -53,11 +53,11 @@
         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.zone_client.create_zone(name=zone_name)[1]
+        cls.zone = cls.zones_client.create_zone(name=zone_name)[1]
 
     @classmethod
     def resource_cleanup(cls):
-        cls.zone_client.delete_zone(
+        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(BaseRecordsetsTest, cls).resource_cleanup()
@@ -86,7 +86,6 @@
             cls.admin_zone_client = cls.os_admin.dns_v2.ZonesClient()
         cls.client = cls.os_primary.dns_v2.RecordsetClient()
         cls.alt_client = cls.os_alt.dns_v2.RecordsetClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
 
     @decorators.attr(type='smoke')
@@ -234,10 +233,11 @@
 
         self.assertGreater(len(body), 0)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_reader',
+                                     'os_project_member'])
 
         self.check_list_show_RBAC_enforcement(
             'RecordsetClient', 'list_recordset', expected_allowed, True,
@@ -245,6 +245,9 @@
 
         # Test that users who should see the zone, can see it.
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_reader',
+                                     'os_project_member'])
 
         self.check_list_IDs_RBAC_enforcement(
             'RecordsetClient', 'list_recordset',
@@ -283,20 +286,21 @@
         LOG.info('Ensure the fetched response matches the expected one')
         self.assertExpected(body, record, self.excluded_keys)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_member',
+                                     'os_project_reader'])
 
         self.check_list_show_RBAC_enforcement(
             'RecordsetClient', 'show_recordset', expected_allowed, True,
             self.zone['id'], recordset_id)
 
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'RecordsetClient', 'show_recordset', expected_allowed, True,
@@ -322,7 +326,7 @@
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'RecordsetClient', 'delete_recordset', expected_allowed, True,
@@ -375,7 +379,7 @@
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'RecordsetClient', 'update_recordset', expected_allowed, True,
@@ -384,7 +388,7 @@
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'RecordsetClient', 'update_recordset', expected_allowed, False,
@@ -537,9 +541,9 @@
         LOG.info('Create a Zone')
         zone_name = dns_data_utils.rand_zone_name(name="TestZone",
                                               suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name,
-                                            wait_until=const.ACTIVE)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until=const.ACTIVE)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a Recordset')
         recordset_data = dns_data_utils.rand_recordset_data(
@@ -548,13 +552,13 @@
             zone['id'], recordset_data, wait_until=const.ACTIVE)[1]
 
         LOG.info("Delete a Zone and wait till it's done")
-        body = self.zone_client.delete_zone(zone['id'])[1]
+        body = self.zones_client.delete_zone(zone['id'])[1]
         LOG.info('Ensure we respond with DELETE+PENDING')
         self.assertEqual(const.DELETE, body['action'])
         self.assertEqual(const.PENDING, body['status'])
 
         LOG.info('Ensure successful deletion of Zone')
-        waiters.wait_for_zone_404(self.zone_client, zone['id'])
+        waiters.wait_for_zone_404(self.zones_client, zone['id'])
 
         LOG.info('Ensure successful deletion of Recordset')
         self.assertRaises(lib_exc.NotFound,
@@ -577,7 +581,6 @@
         super(RecordsetsNegativeTest, cls).setup_clients()
         cls.client = cls.os_primary.dns_v2.RecordsetClient()
         cls.alt_client = cls.os_alt.dns_v2.RecordsetClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('98c94f8c-217a-4056-b996-b1f856d0753e')
     @ddt.file_data("recordset_data_invalid.json")
@@ -705,7 +708,6 @@
     def setup_clients(cls):
         super(RootRecordsetsTests, cls).setup_clients()
         cls.client = cls.os_primary.dns_v2.RecordsetClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @classmethod
     def skip_checks(cls):
@@ -752,8 +754,8 @@
         LOG.info('Create another zone')
         zone_name = dns_data_utils.rand_zone_name(name="list-filter",
                                               suffix=self.tld_name)
-        zone2 = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone2['id'])
+        zone2 = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone2['id'])
 
         LOG.info('Create another Recordset')
         recordset_data = dns_data_utils.rand_recordset_data(
@@ -786,10 +788,10 @@
         LOG.info('List recordsets')
         zone_name = dns_data_utils.rand_zone_name(name="zone_names",
                                               suffix=self.tld_name)
-        alt_zone = self.zone_client.create_zone(
+        alt_zone = self.zones_client.create_zone(
             name=zone_name, wait_until=const.ACTIVE)[1]
         self.addCleanup(self.wait_zone_delete,
-                        self.zone_client,
+                        self.zones_client,
                         alt_zone['id'])
 
         body = self.client.list_zones_recordsets()[1]
@@ -821,7 +823,6 @@
             cls.admin_client = cls.os_admin.dns_v2.RecordsetClient()
         cls.client = cls.os_primary.dns_v2.RecordsetClient()
         cls.alt_client = cls.os_alt.dns_v2.RecordsetClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
 
     def _create_client_recordset(self, clients_list):
@@ -835,12 +836,12 @@
                 # Create a zone and wait till it's ACTIVE
                 zone_name = dns_data_utils.rand_zone_name(name="primary",
                                                       suffix=self.tld_name)
-                zone = self.zone_client.create_zone(name=zone_name)[1]
+                zone = self.zones_client.create_zone(name=zone_name)[1]
                 self.addCleanup(self.wait_zone_delete,
-                                self.zone_client,
+                                self.zones_client,
                                 zone['id'])
                 waiters.wait_for_zone_status(
-                    self.zone_client, zone['id'], const.ACTIVE)
+                    self.zones_client, zone['id'], const.ACTIVE)
 
                 # Create a recordset and wait till it's ACTIVE
                 recordset_data = dns_data_utils.rand_recordset_data(
@@ -919,8 +920,8 @@
         zone_name = dns_data_utils.rand_zone_name(suffix=self.tld_name)
 
         LOG.info('Create a zone as a default user')
-        zone = self.zone_client.create_zone(name='a.b.' + zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name='a.b.' + zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         rrset_data = dns_data_utils.rand_recordset_data(
             record_type='A', zone_name=zone_name)
@@ -1034,7 +1035,6 @@
         else:
             cls.admin_client = cls.os_admin.dns_v2.RecordsetClient()
         cls.client = cls.os_primary.dns_v2.RecordsetClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('84164ff4-8e68-11ec-983f-201e8823901f')
     def test_admin_updates_soa_and_ns_recordsets(self):
@@ -1047,11 +1047,11 @@
         LOG.info('Primary user creates a Zone')
         zone_name = dns_data_utils.rand_zone_name(name="update_soa_ns",
                                               suffix=self.tld_name)
-        zone = self.zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name,
             description='Zone for "managed recordsets update" test',
             wait_until=const.ACTIVE)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         recordsets = self.admin_client.list_recordset(
             zone['id'], headers=sudo_header)[1]['recordsets']
 
@@ -1089,7 +1089,6 @@
         else:
             cls.admin_client = cls.os_admin.dns_v2.RecordsetClient()
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
 
     @decorators.idempotent_id('083fa738-bb1b-11ec-b581-201e8823901f')
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 63568c8..534e107 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
@@ -61,7 +61,6 @@
         else:
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
         cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
-        cls.zones_client = cls.os_primary.dns_v2.ZonesClient()
 
     @property
     def zone(self):
diff --git a/designate_tempest_plugin/tests/api/v2/test_service_statuses.py b/designate_tempest_plugin/tests/api/v2/test_service_statuses.py
index c1f634b..0b63f21 100644
--- a/designate_tempest_plugin/tests/api/v2/test_service_statuses.py
+++ b/designate_tempest_plugin/tests/api/v2/test_service_statuses.py
@@ -73,10 +73,10 @@
             "services: {}".format(services_statuses_tup))
 
         # Test RBAC
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin', 'os_system_reader']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ServiceClient', 'list_statuses', expected_allowed, False)
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 0000000..78d6233
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_shared_zones.py
@@ -0,0 +1,564 @@
+# 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.common.utils import data_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 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, delete_shares=True)
+        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()
+            cls.adm_shr_client = cls.os_system_admin.dns_v2.SharedZonesClient()
+        else:
+            cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
+            cls.adm_shr_client = cls.os_admin.dns_v2.SharedZonesClient()
+        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_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_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.Forbidden,
+                          self.alt_zone_client.delete_zone,
+                          self.zone['id'])
+
+    @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.Forbidden,
+                          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.Forbidden,
+            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)
+
+    @decorators.idempotent_id('957ba3f8-c250-11ed-a8b1-201e8823901f')
+    def test_share_zone_with_yourself_is_not_allowed(self):
+        with self.assertRaisesDns(lib_exc.BadRequest, 'bad_request', 400):
+            self.share_zone_client.create_zone_share(
+                zone_id=self.zone['id'],
+                target_project_id=self.share_zone_client.project_id)
+
+
+class AdminSharedZonesTest(BaseSharedZoneTest):
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(AdminSharedZonesTest, cls).setup_credentials()
+
+    @decorators.idempotent_id('2bb7bcb2-b824-11ed-9e56-201e8823901f')
+    def test_create_zone_share_all_projects_header(self):
+        LOG.info(
+            'Admin user creates shared zone for Alt tenant '
+            'using "x-auth-all-projects" header')
+        # Scoped tokens do not have a project ID, work around that here
+        if CONF.enforce_scope.designate:
+            headers = self.all_projects_header.copy()
+            headers.update(
+                {'x-auth-sudo-project-id': self.share_zone_client.project_id})
+        else:
+            headers = self.all_projects_header
+
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=headers)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=self.all_projects_header)
+        self.assertTrue(uuidutils.is_uuid_like(shared_zone['id']))
+        self.assertEqual(self.zone['id'], shared_zone['zone_id'])
+        if CONF.enforce_scope.designate:
+            self.assertEqual(self.share_zone_client.project_id,
+                             shared_zone['project_id'])
+        else:
+            self.assertEqual(self.adm_shr_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('f26cd3ac-b8fa-11ed-b4ca-201e8823901f')
+    def test_create_zone_share_sudo_project_header(self):
+        LOG.info(
+            'Admin user creates shared zone for Alt tenant '
+            'using "x-auth-sudo-project-id" header')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=sudo_header)
+        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('ce2688e8-b90a-11ed-b4ca-201e8823901f')
+    def test_show_shared_zone_all_projects_header(self):
+        LOG.info(
+            'Admin user creates shared zone for Alt tenant'
+            ' using "x-auth-all-projects" header')
+        # Scoped tokens do not have a project ID, work around that here
+        if CONF.enforce_scope.designate:
+            headers = self.all_projects_header.copy()
+            headers.update(
+                {'x-auth-sudo-project-id': self.share_zone_client.project_id})
+        else:
+            headers = self.all_projects_header
+
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=headers)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=self.all_projects_header)
+
+        LOG.info('Admin user shows shared zone and validates its content')
+        body = self.adm_shr_client.show_zone_share(
+            self.zone['id'], shared_zone['id'],
+            headers=self.all_projects_header)[1]
+        self.assertExpected(shared_zone, body, self.excluded_keys)
+
+    @decorators.idempotent_id('46f7db22-b90c-11ed-b4ca-201e8823901f')
+    def test_delete_zone_share_sudo_project_header(self):
+        LOG.info(
+            'Admin user creates shared zone for Alt tenant'
+            ' using "x-auth-sudo-project-id" header')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=sudo_header,
+            ignore_errors=lib_exc.NotFound)
+
+        LOG.info('As Admin delete zone share and ensure it was deleted')
+        self.adm_shr_client.delete_zone_share(
+            self.zone['id'], shared_zone['id'], headers=sudo_header)
+        self.assertRaises(lib_exc.NotFound,
+            self.adm_shr_client.show_zone_share,
+            self.zone['id'], shared_zone['id'], headers=sudo_header)
+
+    @decorators.idempotent_id('2eedfd60-b90f-11ed-b4ca-201e8823901f')
+    def test_list_zone_shares_all_projects_header(self):
+        LOG.info(
+            "Admin user shares Primary's zone with Alt tenant"
+            " using 'x-auth-all-projects' header")
+        # Scoped tokens do not have a project ID, work around that here
+        if CONF.enforce_scope.designate:
+            headers = self.all_projects_header.copy()
+            headers.update(
+                {'x-auth-sudo-project-id': self.share_zone_client.project_id})
+        else:
+            headers = self.all_projects_header
+
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=headers)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=self.all_projects_header)
+
+        LOG.info(
+            "Admin user shares Primary's zone with Demo tenant"
+            " using 'x-auth-all-projects' header")
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.demo_zone_client.project_id,
+            headers=headers)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=self.all_projects_header)
+
+        LOG.info('Admin user lists zone shares')
+        body = self.adm_shr_client.list_zone_shares(
+            self.zone['id'], headers=self.all_projects_header)[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 AdminSharedZonesTestNegative(BaseSharedZoneTest):
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(AdminSharedZonesTestNegative, cls).setup_credentials()
+
+    @decorators.idempotent_id('595ae1fc-bce4-11ed-baf2-201e8823901f')
+    def test_create_zone_share_invalid_project_id(self):
+        LOG.info(
+            'Admin user tries to create shared zone, using non existing '
+            'project ID id in "x-auth-sudo-project-id" header')
+        sudo_header = {
+            'x-auth-sudo-project-id': data_utils.rand_uuid()}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.create_zone_share,
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)
+
+    @decorators.idempotent_id('aa42d82e-bcf6-11ed-baf2-201e8823901f')
+    def test_create_zone_share_invalid_zone_id(self):
+        LOG.info(
+            'Admin user tries to create shared zone, using non existing '
+            'zone ID and "x-auth-sudo-project-id" header')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.alt_zone_client.project_id}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.create_zone_share,
+            data_utils.rand_uuid(), self.alt_zone_client.project_id,
+            headers=sudo_header)
+
+    @decorators.idempotent_id('9e7202ba-bd94-11ed-80f5-201e8823901f')
+    def test_show_shared_zone_invalid_shared_zone_id(self):
+        LOG.info('Admin tries to show shared zone using not '
+                 'existing shared zone ID')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.alt_zone_client.project_id}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.show_zone_share,
+            self.zone['id'], data_utils.rand_uuid(), headers=sudo_header)
+
+    @decorators.idempotent_id('8852329c-bd95-11ed-80f5-201e8823901f')
+    def test_show_shared_zone_invalid_project_id(self):
+        LOG.info('Admin tries to show shared zone '
+                 'using not existing project ID')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=sudo_header)
+        sudo_header_invalid_project_id = {
+            'x-auth-sudo-project-id': data_utils.rand_uuid()}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.show_zone_share,
+            self.zone['id'], shared_zone['id'],
+            headers=sudo_header_invalid_project_id)
+
+    @decorators.idempotent_id('871e7e1c-bd9a-11ed-80f5-201e8823901f')
+    @decorators.skip_because(bug="2009819")
+    def test_list_zone_shares_invalid_zone_id(self):
+        LOG.info('Admin user tries to list shared zone '
+                 'using not existing zone ID')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.list_zone_shares,
+            data_utils.rand_uuid(), headers=sudo_header)
+
+    @decorators.idempotent_id('e71068c8-bdb1-11ed-80f5-201e8823901f')
+    @ decorators.skip_because(bug="2009819")
+    def test_list_zone_shares_invalid_project_id(self):
+        LOG.info('Admin user tries to list shared zone using '
+                 'not existing project ID')
+        sudo_header = {
+            'x-auth-sudo-project-id': data_utils.rand_uuid()}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.list_zone_shares,
+            self.zone['id'], headers=sudo_header)
+
+    @decorators.idempotent_id('7136b430-bdb2-11ed-80f5-201e8823901f')
+    def test_delete_zone_share_invalid_project_id(self):
+        LOG.info('Admin user creates shared zone for Alt user')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=sudo_header,
+            ignore_errors=lib_exc.NotFound)
+        LOG.info('Admin user tries to delete the shared zone '
+                 'using non existing project ID')
+        invalid_sudo_header = {
+            'x-auth-sudo-project-id': data_utils.rand_uuid()}
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.delete_zone_share,
+            self.zone['id'], shared_zone['id'],
+            headers=invalid_sudo_header)
+
+    @decorators.idempotent_id('d44c65e2-bdc1-11ed-80f5-201e8823901f')
+    def test_delete_zone_share_invalid_shared_zone_id(self):
+        LOG.info('Admin user creates shared zone for Alt user')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=sudo_header,
+            ignore_errors=lib_exc.NotFound)
+        LOG.info('Admin user tries to delete the shared zone '
+                 'using non existing shared zone ID')
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.delete_zone_share,
+            self.zone['id'], data_utils.rand_uuid(),
+            headers=sudo_header)
+
+    @decorators.idempotent_id('06de2342-bdc2-11ed-80f5-201e8823901f')
+    def test_delete_zone_share_invalid_zone_id(self):
+        LOG.info('Admin user creates shared zone for Alt user')
+        sudo_header = {
+            'x-auth-sudo-project-id': self.share_zone_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            self.zone['id'], self.alt_zone_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, self.zone['id'],
+            shared_zone['id'], headers=sudo_header,
+            ignore_errors=lib_exc.NotFound)
+        LOG.info('Admin user tries to delete the shared zone '
+                 'using non zone ID')
+        self.assertRaises(
+            lib_exc.NotFound, self.adm_shr_client.delete_zone_share,
+            data_utils.rand_uuid(), shared_zone['id'],
+            headers=sudo_header)
diff --git a/designate_tempest_plugin/tests/api/v2/test_tld.py b/designate_tempest_plugin/tests/api/v2/test_tld.py
index 16711e4..a74edc1 100644
--- a/designate_tempest_plugin/tests/api/v2/test_tld.py
+++ b/designate_tempest_plugin/tests/api/v2/test_tld.py
@@ -49,7 +49,6 @@
         else:
             cls.admin_client = cls.os_admin.dns_v2.TldClient()
         cls.primary_client = cls.os_primary.dns_v2.TldClient()
-        cls.primary_zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @classmethod
     def _generate_tld_name(cls, test_name):
@@ -149,10 +148,10 @@
         self.assertExpected(tld, body, self.excluded_keys)
 
         # Test RBAC
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin', 'os_system_reader']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'TldClient', 'show_tld', expected_allowed, False, tld['id'])
@@ -192,10 +191,10 @@
         self.assertGreater(len(body['tlds']), 0)
 
         # Test RBAC
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader']
+        if CONF.enforce_scope.designate:
+            expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_IDs_RBAC_enforcement(
             'TldClient', 'list_tlds', expected_allowed, [tld['id']],
@@ -240,7 +239,7 @@
 
 class TestTldNotFoundAdmin(BaseTldTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
@@ -286,7 +285,7 @@
 
 class TestTldInvalidIdAdmin(BaseTldTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
diff --git a/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py b/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py
index 7696291..94c661a 100644
--- a/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py
+++ b/designate_tempest_plugin/tests/api/v2/test_transfer_accepts.py
@@ -67,7 +67,6 @@
         super(TransferAcceptTest, cls).setup_clients()
 
         # Primary clients
-        cls.prm_zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.prm_request_client = cls.os_primary.dns_v2.TransferRequestClient()
         cls.prm_accept_client = cls.os_primary.dns_v2.TransferAcceptClient()
 
@@ -95,8 +94,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_accept", suffix=self.tld_name)
-        zone = self.prm_zone_client.create_zone(name=zone_name,
-                                                wait_until='ACTIVE')[1]
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
         self.addCleanup(
             self.wait_zone_delete, self.admin_zone_client, zone['id'],
             headers=self.all_projects_header,
@@ -122,6 +121,9 @@
         expected_allowed = ['os_admin', 'os_primary', 'os_alt']
         if CONF.dns_feature_enabled.enforce_new_defaults:
             expected_allowed.append('os_system_admin')
+            # Note: system_reader is allowed because this API RBAC is based
+            #       on the target project ID. It will return a 401 instead of
+            #       a 403.
             expected_allowed.append('os_system_reader')
             expected_allowed.append('os_project_member')
             expected_allowed.append('os_project_reader')
@@ -142,8 +144,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(name="show_transfer_accept",
                                               suffix=self.tld_name)
-        zone = self.prm_zone_client.create_zone(name=zone_name,
-                                                wait_until='ACTIVE')[1]
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
         self.addCleanup(
             self.wait_zone_delete, self.admin_zone_client, zone['id'],
             headers=self.all_projects_header,
@@ -175,20 +177,21 @@
                  'created transfer_accept')
         self.assertExpected(transfer_accept, body, self.excluded_keys)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_member',
+                                     'os_project_reader'])
 
         self.check_list_show_RBAC_enforcement(
             'TransferAcceptClient', 'show_transfer_accept', expected_allowed,
             True, transfer_accept['id'])
 
         # Test RBAC with x-auth-all-projects
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'TransferAcceptClient', 'show_transfer_accept', expected_allowed,
@@ -200,8 +203,8 @@
         LOG.info('Create a Primary zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="ownership_transferred_zone", suffix=self.tld_name)
-        zone = self.prm_zone_client.create_zone(name=zone_name,
-                                                wait_until='ACTIVE')[1]
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
         self.addCleanup(
             self.wait_zone_delete, self.admin_zone_client, zone['id'],
             headers=self.all_projects_header,
@@ -248,8 +251,8 @@
             LOG.info('Create a Primary zone')
             zone_name = dns_data_utils.rand_zone_name(
                 name="list_transfer_accepts", suffix=self.tld_name)
-            zone = self.prm_zone_client.create_zone(name=zone_name,
-                                                    wait_until='ACTIVE')[1]
+            zone = self.zones_client.create_zone(name=zone_name,
+                                                 wait_until='ACTIVE')[1]
             self.addCleanup(
                 self.wait_zone_delete, self.admin_zone_client, zone['id'],
                 headers=self.all_projects_header,
@@ -276,24 +279,22 @@
             self.assertEqual('COMPLETE', transfer_accept['status'])
             transfer_request_ids.append(transfer_accept['id'])
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC - Users that are allowed to call list, but should get
         #             zero zones.
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader']
+        if CONF.enforce_scope.designate:
+            expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_RBAC_enforcement_count(
             'TransferAcceptClient', 'list_transfer_accept',
             expected_allowed, 0)
 
         # Test that users who should see the zone, can see it.
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_IDs_RBAC_enforcement(
             'TransferAcceptClient', 'list_transfer_accept',
@@ -353,8 +354,8 @@
         LOG.info('Create a zone as primary tenant')
         zone_name = dns_data_utils.rand_zone_name(
             name="show_transfer_accept_impersonate", suffix=self.tld_name)
-        zone = self.prm_zone_client.create_zone(name=zone_name,
-                                                wait_until='ACTIVE')[1]
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
 
         # In case when something goes wrong with the test and E2E
         # scenario fails for some reason, we'll use Admin tenant
@@ -402,10 +403,10 @@
             self.wait_zone_delete, self.alt_zone_client, zone['id'])
 
         # Test RBAC with x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'TransferAcceptClient', 'show_transfer_accept', expected_allowed,
@@ -427,7 +428,6 @@
     @classmethod
     def setup_clients(cls):
         super(TransferAcceptTestNegative, cls).setup_clients()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.request_client = cls.os_primary.dns_v2.TransferRequestClient()
         cls.client = cls.os_primary.dns_v2.TransferAcceptClient()
 
@@ -436,9 +436,9 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_accept_invalid_key", suffix=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'])
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone transfer_request')
         transfer_request = self.request_client.create_transfer_request(
@@ -463,9 +463,9 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_accept_deleted_id", suffix=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'])
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone transfer_request')
         transfer_request = self.request_client.create_transfer_request(
diff --git a/designate_tempest_plugin/tests/api/v2/test_transfer_request.py b/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
index caf1876..20c68ed 100644
--- a/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
+++ b/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
@@ -71,7 +71,6 @@
                                 TransferRequestClient())
         else:
             cls.admin_client = cls.os_admin.dns_v2.TransferRequestClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
         cls.client = cls.os_primary.dns_v2.TransferRequestClient()
         cls.alt_client = cls.os_alt.dns_v2.TransferRequestClient()
@@ -81,8 +80,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_request", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary', 'os_alt']
@@ -107,8 +106,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_request_scoped", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         transfer_request_data = dns_data_utils.rand_transfer_request_data(
             target_project_id=self.os_alt.credentials.project_id)
@@ -127,8 +126,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_request_empty", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         LOG.info('Create a zone transfer_request')
         transfer_request = self.client.create_transfer_request_empty_body(
             zone['id'])[1]
@@ -143,8 +142,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="show_transfer_request", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone transfer_request')
         transfer_request = self.client.create_transfer_request(zone['id'])[1]
@@ -158,25 +157,22 @@
                  'created transfer_request')
         self.assertExpected(transfer_request, body, self.excluded_keys)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
         # Note: The create service client does not define a target project
         #       ID, so everyone should be able to see it.
-        expected_allowed = ['os_admin', 'os_primary', 'os_alt']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.extend(['os_system_admin', 'os_system_reader',
-                                     'os_project_member', 'os_project_reader'])
+        expected_allowed = ['os_admin', 'os_primary', 'os_alt',
+                            'os_system_admin', 'os_system_reader',
+                            'os_project_member', 'os_project_reader']
 
         self.check_list_show_RBAC_enforcement(
             'TransferRequestClient', 'show_transfer_request', expected_allowed,
             True, transfer_request['id'])
 
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'TransferRequestClient', 'show_transfer_request', expected_allowed,
@@ -194,8 +190,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="show_transfer_request_impersonate", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone transfer_request')
         transfer_request = self.client.create_transfer_request(zone['id'])[1]
@@ -225,8 +221,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_transfer_request_as_target", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         transfer_request_data = dns_data_utils.rand_transfer_request_data(
             target_project_id=self.os_alt.credentials.project_id)
@@ -246,14 +242,13 @@
                                               "project_id"]
         self.assertExpected(transfer_request, body, excluded_keys)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC when a transfer target project is specified.
-        expected_allowed = ['os_primary', 'os_alt']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+        if CONF.enforce_scope.designate:
+            expected_allowed = ['os_primary', 'os_alt',
+                                'os_system_admin', 'os_project_member']
         else:
-            expected_allowed.append('os_admin')
+            expected_allowed = ['os_primary', 'os_alt', 'os_admin',
+                                'os_system_admin', 'os_project_member']
 
         self.check_list_show_RBAC_enforcement(
             'TransferRequestClient', 'show_transfer_request', expected_allowed,
@@ -264,8 +259,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="delete_transfer_request", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a transfer_request')
         transfer_request = self.client.create_transfer_request(zone['id'])[1]
@@ -293,8 +288,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_transfer_request", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone transfer_request')
         transfer_request = self.client.create_transfer_request(zone['id'])[1]
@@ -306,14 +301,10 @@
 
         self.assertGreater(len(body['transfer_requests']), 0)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC - Users that are allowed to call list, but should get
         #             zero zones.
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader',
-                                'os_admin', 'os_project_member',
-                                'os_project_reader']
+            expected_allowed = ['os_system_admin', 'os_admin']
         else:
             expected_allowed = ['os_alt']
 
@@ -333,9 +324,9 @@
         LOG.info('Create a Primary zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_transfer_request_all_projects", suffix=self.tld_name)
-        primary_zone = self.zone_client.create_zone(name=zone_name)[1]
+        primary_zone = self.zones_client.create_zone(name=zone_name)[1]
         self.addCleanup(self.wait_zone_delete,
-                        self.zone_client, primary_zone['id'])
+                        self.zones_client, primary_zone['id'])
 
         LOG.info('Create an Alt zone')
         alt_zone_name = dns_data_utils.rand_zone_name(
@@ -395,9 +386,9 @@
         LOG.info('Create a Primary zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_transfer_request_impersonate", suffix=self.tld_name)
-        primary_zone = self.zone_client.create_zone(name=zone_name)[1]
+        primary_zone = self.zones_client.create_zone(name=zone_name)[1]
         self.addCleanup(self.wait_zone_delete,
-                        self.zone_client, primary_zone['id'])
+                        self.zones_client, primary_zone['id'])
 
         LOG.info('Create an Alt zone')
         alt_zone_name = dns_data_utils.rand_zone_name(
@@ -441,8 +432,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="update_transfer_request", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone transfer_request')
         transfer_request = self.client.create_transfer_request(zone['id'])[1]
@@ -462,7 +453,7 @@
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'TransferRequestClient', 'update_transfer_request',
diff --git a/designate_tempest_plugin/tests/api/v2/test_tsigkey.py b/designate_tempest_plugin/tests/api/v2/test_tsigkey.py
index 95a4e21..926797f 100644
--- a/designate_tempest_plugin/tests/api/v2/test_tsigkey.py
+++ b/designate_tempest_plugin/tests/api/v2/test_tsigkey.py
@@ -72,7 +72,6 @@
             cls.admin_client = cls.os_admin.dns_v2.TsigkeyClient()
             cls.pool_admin_client = cls.os_admin.dns_v2.PoolClient()
 
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.primary_client = cls.os_primary.dns_v2.TsigkeyClient()
 
     @decorators.idempotent_id('e7b484e3-7ed5-4840-89d7-1e696986f8e4')
@@ -80,8 +79,8 @@
         LOG.info('Create a resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_tsigkey_for_zone", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         tsigkey_data = {
                         "name": dns_data_utils.rand_zone_name(
@@ -137,8 +136,8 @@
         LOG.info('Create a resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_tsigkey", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         LOG.info('Create a tsigkey')
         tsigkey = self.admin_client.create_tsigkey(resource_id=zone['id'])[1]
         self.addCleanup(self.admin_client.delete_tsigkey, tsigkey['id'])
@@ -146,9 +145,11 @@
         self.assertGreater(len(body['tsigkeys']), 0)
 
         # Test RBAC
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader']
+        if CONF.enforce_scope.designate:
+            expected_allowed = ['os_system_admin']
+        else:
+            expected_allowed = ['os_admin', 'os_system_admin']
+
         self.check_list_IDs_RBAC_enforcement(
             'TsigkeyClient', 'list_tsigkeys', expected_allowed,
             [tsigkey['id']])
@@ -159,9 +160,9 @@
             LOG.info('As Primary user create a zone: {} '.format(i))
             zone_name = dns_data_utils.rand_zone_name(
                 name="list_tsigkey_limit", suffix=self.tld_name)
-            zone = self.zone_client.create_zone(name=zone_name)[1]
+            zone = self.zones_client.create_zone(name=zone_name)[1]
             self.addCleanup(
-                self.wait_zone_delete, self.zone_client, zone['id'])
+                self.wait_zone_delete, self.zones_client, zone['id'])
             LOG.info('As Admin user create a tsigkey: {} '.format(i))
             tsigkey = self.admin_client.create_tsigkey(
                 resource_id=zone['id'])[1]
@@ -183,9 +184,9 @@
                      'for {}'.format(name))
             zone_name = dns_data_utils.rand_zone_name(
                 name="list_tsigkey_marker", suffix=self.tld_name)
-            zone = self.zone_client.create_zone(name=zone_name)[1]
+            zone = self.zones_client.create_zone(name=zone_name)[1]
             self.addCleanup(
-                self.wait_zone_delete, self.zone_client, zone['id'])
+                self.wait_zone_delete, self.zones_client, zone['id'])
             LOG.info('As Admin user create "{}" tsigkey'.format(name))
             tsigkey = self.admin_client.create_tsigkey(
                 resource_id=zone['id'], name=name)[1]
@@ -233,9 +234,9 @@
             LOG.info('As Primary user create a zone for: {} '.format(name))
             zone_name = dns_data_utils.rand_zone_name(
                 name="list_tsigkey_sort", suffix=self.tld_name)
-            zone = self.zone_client.create_zone(name=zone_name)[1]
+            zone = self.zones_client.create_zone(name=zone_name)[1]
             self.addCleanup(
-                self.wait_zone_delete, self.zone_client, zone['id'])
+                self.wait_zone_delete, self.zones_client, zone['id'])
             LOG.info('As Admin user create a tsigkey: {} '.format(name))
             tsigkey = self.admin_client.create_tsigkey(
                 resource_id=zone['id'], name=name)[1]
@@ -289,8 +290,8 @@
         LOG.info('As Primary user create a zone for: {} '.format(tsigkey_name))
         zone_name = dns_data_utils.rand_zone_name(
             name="list_tsigkey_filter_name", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         LOG.info('As Admin user create a tsigkey: {} '.format(tsigkey_name))
         tsigkey = self.admin_client.create_tsigkey(
             resource_id=zone['id'], name=tsigkey_name)[1]
@@ -328,8 +329,8 @@
         LOG.info('Create tsigkey for a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_tsigkey_filter_scope", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         zone_tsigkey = self.admin_client.create_tsigkey(
             resource_id=zone['id'], scope='ZONE')[1]
         self.addCleanup(self.admin_client.delete_tsigkey, zone_tsigkey['id'])
@@ -396,8 +397,8 @@
         LOG.info('Create a resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="show_tsigkey", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a tsigkey')
         tsigkey = self.admin_client.create_tsigkey(resource_id=zone['id'])[1]
@@ -410,9 +411,10 @@
         self.assertExpected(tsigkey, body, self.excluded_keys)
 
         # Test RBAC
-        expected_allowed = ['os_admin']
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin', 'os_system_reader']
+        else:
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'TsigkeyClient', 'show_tsigkey', expected_allowed, True,
@@ -423,8 +425,8 @@
         LOG.info('Create a resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="update_tsigkey", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a tsigkey')
         tsigkey = self.admin_client.create_tsigkey(resource_id=zone['id'])[1]
@@ -457,8 +459,8 @@
         LOG.info('Create a resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="delete_tsigkey", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a tsigkey')
         tsigkey = self.admin_client.create_tsigkey(resource_id=zone['id'])[1]
@@ -488,7 +490,7 @@
 
 class TestTsigkeyNotFoundAdmin(BaseTsigkeyTest):
 
-    credentials = ["admin", "system_admin"]
+    credentials = ["admin", "system_admin", "primary"]
 
     @classmethod
     def setup_credentials(cls):
@@ -551,7 +553,6 @@
         else:
             cls.admin_client = cls.os_admin.dns_v2.TsigkeyClient()
             cls.pool_admin_client = cls.os_admin.dns_v2.PoolClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('2a8dfc75-9884-4b1c-8f1f-ed835d96f2fe')
     def test_show_tsigkey_invalid_uuid(self):
@@ -585,8 +586,8 @@
     def test_create_tsigkey_for_zone_invalid_algorithm(self):
         zone_name = dns_data_utils.rand_zone_name(
             name="create_tsigkey_invalid_algo", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         tsigkey_data = {
                         "name": dns_data_utils.rand_zone_name('Example_Key'),
                         "algorithm": "zababun",
@@ -605,8 +606,8 @@
         LOG.info('Create a zone resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_tsigkey_invalid_name", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         tsigkey_data = {
                         "name": dns_data_utils.rand_zone_name(
                             'Example_Key') * 1000,
@@ -627,8 +628,8 @@
         LOG.info('Create a zone resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_tsigkey_empty_secret", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         tsigkey_data = {
                         "name": dns_data_utils.rand_zone_name('Example_Key'),
                         "algorithm": "hmac-sha256",
@@ -647,8 +648,8 @@
         LOG.info('Create a zone resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_tsigkey_invalid_scope", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         tsigkey_data = {
                         "name": dns_data_utils.rand_zone_name('Example_Key'),
                         "algorithm": "hmac-sha256",
@@ -667,8 +668,8 @@
         LOG.info('Create a resource')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_tsigkey_invalide_zone_id", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         tsigkey_data = {
                         "name": dns_data_utils.rand_zone_name('Example_Key'),
                         "algorithm": "hmac-sha256",
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_zone_tasks.py b/designate_tempest_plugin/tests/api/v2/test_zone_tasks.py
index 159d75e..4523c53 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zone_tasks.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zone_tasks.py
@@ -24,8 +24,8 @@
 from designate_tempest_plugin import data_utils as dns_data_utils
 from designate_tempest_plugin.tests import base
 
-from designate_tempest_plugin.services.dns.query.query_client \
-    import SingleQueryClient
+from designate_tempest_plugin.services.dns.query.query_client import (
+    SingleQueryClient)
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -76,7 +76,6 @@
             cls.admin_client = cls.os_system_admin.dns_v2.ZonesClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.ZonesClient()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_client = cls.os_alt.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('287e2cd0-a0e7-11eb-b962-74e5f9e2a801')
@@ -87,16 +86,18 @@
         LOG.info('Create a PRIMARY zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="zone_abandon", suffix=self.tld_name)
-        pr_zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, pr_zone['id'])
-        waiters.wait_for_zone_status(self.client, pr_zone['id'], 'ACTIVE')
+        pr_zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client,
+                        pr_zone['id'])
+        waiters.wait_for_zone_status(self.zones_client, pr_zone['id'],
+                                     'ACTIVE')
 
         LOG.info('Ensure we respond with CREATE+PENDING')
         self.assertEqual('CREATE', pr_zone['action'])
         self.assertEqual('PENDING', pr_zone['status'])
 
         LOG.info('Fetch the zone')
-        self.client.show_zone(pr_zone['id'])
+        self.zones_client.show_zone(pr_zone['id'])
 
         LOG.info('Check that the zone was created on Nameserver/BIND')
         waiters.wait_for_query(self.query_client, pr_zone['name'], "SOA")
@@ -118,7 +119,7 @@
             headers={'x-auth-sudo-project-id': pr_zone['project_id']})
 
         LOG.info('Wait for the zone to become 404/NotFound in Designate')
-        waiters.wait_for_zone_404(self.client, pr_zone['id'])
+        waiters.wait_for_zone_404(self.zones_client, pr_zone['id'])
 
         LOG.info('Check that the zone is still exists in Nameserver/BIND')
         waiters.wait_for_query(
@@ -133,16 +134,18 @@
         LOG.info('Create a PRIMARY zone and add to the cleanup')
         zone_name = dns_data_utils.rand_zone_name(
             name="zone_abandon_forbidden", suffix=self.tld_name)
-        pr_zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, pr_zone['id'])
-        waiters.wait_for_zone_status(self.client, pr_zone['id'], 'ACTIVE')
+        pr_zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client,
+                        pr_zone['id'])
+        waiters.wait_for_zone_status(self.zones_client, pr_zone['id'],
+                                     'ACTIVE')
 
         LOG.info('Ensure we respond with CREATE+PENDING')
         self.assertEqual('CREATE', pr_zone['action'])
         self.assertEqual('PENDING', pr_zone['status'])
 
         LOG.info('Fetch the zone')
-        self.client.show_zone(pr_zone['id'])
+        self.zones_client.show_zone(pr_zone['id'])
 
         LOG.info('Check that the zone was created on Nameserver/BIND')
         waiters.wait_for_query(self.query_client, pr_zone['name'], "SOA")
@@ -150,7 +153,7 @@
         LOG.info('Abandon a zone as primary client, Expected: should '
                  'fail with: 403 forbidden')
         self.assertRaises(
-            lib_exc.Forbidden, self.client.abandon_zone,
+            lib_exc.Forbidden, self.zones_client.abandon_zone,
             zone_id=pr_zone['id'])
 
 
@@ -170,7 +173,6 @@
             cls.admin_client = cls.os_system_admin.dns_v2.ZonesClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.ZonesClient()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_client = cls.os_alt.dns_v2.ZonesClient()
 
     def _query_nameserver(self, nameserver, query_timeout,
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py
index 3ab2601..5109327 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones.py
@@ -11,8 +11,8 @@
 # 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 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 +20,6 @@
 
 
 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
 
@@ -71,8 +70,9 @@
             cls.pool_client = cls.os_system_admin.dns_v2.PoolClient()
         else:
             cls.pool_client = cls.os_admin.dns_v2.PoolClient()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         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):
@@ -80,25 +80,25 @@
         LOG.info('Create a PRIMARY zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_zones_primary", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Ensure we respond with CREATE+PENDING')
         self.assertEqual(const.CREATE, zone['action'])
         self.assertEqual(const.PENDING, zone['status'])
 
         # Get the Name Servers (hosts) created in PRIMARY zone
-        nameservers = self.client.show_zone_nameservers(zone['id'])[1]
+        nameservers = self.zones_client.show_zone_nameservers(zone['id'])[1]
         nameservers = [dic['hostname'] for dic in nameservers['nameservers']]
 
         # Create a SECONDARY zone
         LOG.info('Create a SECONDARY zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_zones_secondary", suffix=self.tld_name)
-        zone = self.client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name, zone_type=const.SECONDARY_ZONE_TYPE,
             primaries=nameservers)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Ensure we respond with CREATE+PENDING')
         self.assertEqual(const.CREATE, zone['action'])
@@ -120,7 +120,7 @@
 
         self.check_CUD_RBAC_enforcement(
             'ZonesClient', 'create_zone', expected_allowed, False,
-            project_id=self.client.project_id)
+            project_id=self.zones_client.project_id)
 
     @decorators.idempotent_id('ec150c22-f52e-11eb-b09b-74e5f9e2a801')
     def test_create_zone_validate_recordsets_created(self):
@@ -128,9 +128,9 @@
         LOG.info('Create a PRIMARY zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_zone_validate_recordsets", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name,
+        zone = self.zones_client.create_zone(name=zone_name,
                                        wait_until=const.ACTIVE)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Ensure we respond with CREATE+PENDING')
         self.assertEqual(const.CREATE, zone['action'])
@@ -152,35 +152,65 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="show_zones", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Fetch the zone')
-        body = self.client.show_zone(zone['id'])[1]
+        body = self.zones_client.show_zone(zone['id'])[1]
 
         LOG.info('Ensure the fetched response matches the created zone')
         self.assertExpected(zone, body, self.excluded_keys)
 
-        # TODO(johnsom) Test reader roles once this bug is fixed.
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test with no extra header overrides (all_projects, sudo-project-id)
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_member',
+                                     'os_project_reader'])
 
         self.check_list_show_RBAC_enforcement(
             'ZonesClient', 'show_zone', expected_allowed, True, zone['id'])
 
         # Test with x-auth-all-projects and x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ZonesClient', 'show_zone', expected_allowed, False, zone['id'],
             headers=self.all_projects_header)
         self.check_list_show_RBAC_enforcement(
             'ZonesClient', 'show_zone', expected_allowed, False, zone['id'],
-            headers={'x-auth-sudo-project-id': self.client.project_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')
@@ -188,14 +218,14 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="delete_zones", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'],
+        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)
 
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement('ZonesClient', 'delete_zone',
                                         expected_allowed, True, zone['id'])
@@ -203,45 +233,84 @@
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement('ZonesClient', 'delete_zone',
                                         expected_allowed, False, zone['id'],
                                         headers=self.all_projects_header)
         self.check_CUD_RBAC_enforcement(
             'ZonesClient', 'delete_zone', expected_allowed, False, zone['id'],
-            headers={'x-auth-sudo-project-id': self.client.project_id})
+            headers={'x-auth-sudo-project-id': self.zones_client.project_id})
 
         LOG.info('Delete the zone')
-        body = self.client.delete_zone(zone['id'])[1]
+        body = self.zones_client.delete_zone(zone['id'])[1]
 
         LOG.info('Ensure we respond with DELETE+PENDING')
         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'], 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')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_zones", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('List zones')
-        body = self.client.list_zones()[1]
+        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)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC - Users that are allowed to call list, but should get
         #             zero zones.
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader',
-                                'os_admin', 'os_project_member',
-                                'os_project_reader']
+            expected_allowed = ['os_system_admin', 'os_admin']
         else:
             expected_allowed = ['os_alt']
 
@@ -265,21 +334,63 @@
             headers=self.all_projects_header)
         self.check_list_IDs_RBAC_enforcement(
             'ZonesClient', 'list_zones', expected_allowed, [zone['id']],
-            headers={'x-auth-sudo-project-id': self.client.project_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')
         zone_name = dns_data_utils.rand_zone_name(
             name="update_zone", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         # Generate a random description
         description = data_utils.rand_name()
 
         LOG.info('Update the zone')
-        zone = self.client.update_zone(
+        zone = self.zones_client.update_zone(
             zone['id'], description=description)[1]
 
         LOG.info('Ensure we respond with UPDATE+PENDING')
@@ -292,7 +403,7 @@
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'ZonesClient', 'update_zone', expected_allowed, True,
@@ -301,7 +412,7 @@
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'ZonesClient', 'update_zone', expected_allowed, False,
@@ -310,7 +421,7 @@
         self.check_CUD_RBAC_enforcement(
             'ZonesClient', 'update_zone', expected_allowed, False,
             zone['id'], description=description,
-            headers={'x-auth-sudo-project-id': self.client.project_id})
+            headers={'x-auth-sudo-project-id': self.zones_client.project_id})
 
     @decorators.idempotent_id('3acddc86-62cc-4bfa-8589-b99e5d239bf2')
     @decorators.skip_because(bug="1960487")
@@ -318,12 +429,12 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="serial_changes_on_update", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name,
+        zone = self.zones_client.create_zone(name=zone_name,
                                        wait_until=const.ACTIVE)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info("Update Zone's email")
-        update_email = self.client.update_zone(
+        update_email = self.zones_client.update_zone(
             zone['id'], email=dns_data_utils.rand_email())[1]
         self.assertNotEqual(
             zone['serial'], update_email['serial'],
@@ -331,7 +442,7 @@
             "on Email update.")
 
         LOG.info("Update Zone's TTL")
-        update_ttl = self.client.update_zone(
+        update_ttl = self.zones_client.update_zone(
             zone['id'], ttl=dns_data_utils.rand_ttl())[1]
         self.assertNotEqual(
             update_email['serial'], update_ttl['serial'],
@@ -339,7 +450,7 @@
             "on TTL update.")
 
         LOG.info("Update Zone's email and description")
-        update_email_description = self.client.update_zone(
+        update_email_description = self.zones_client.update_zone(
             zone['id'],
             email=dns_data_utils.rand_email(),
             description=data_utils.rand_name())[1]
@@ -349,7 +460,7 @@
             "when the Email and Description are updated")
 
         LOG.info("Update Zone's description")
-        update_description = self.client.update_zone(
+        update_description = self.zones_client.update_zone(
             zone['id'], description=data_utils.rand_name())[1]
         self.assertEqual(
             update_email_description['serial'], update_description['serial'],
@@ -362,12 +473,13 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="get_primary_nameservers", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         zone_pool_id = zone['pool_id']
 
         # Get zone's Name Servers using dedicated API request
-        zone_nameservers = self.client.show_zone_nameservers(zone['id'])[1]
+        zone_nameservers = self.zones_client.show_zone_nameservers(
+            zone['id'])[1]
         zone_nameservers = zone_nameservers['nameservers']
         LOG.info('Zone Name Servers are: {}'.format(zone_nameservers))
         self.assertIsNot(
@@ -384,20 +496,21 @@
             pool_nameservers, zone_nameservers,
             'Failed - Pool and Zone nameservers should be the same')
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_member',
+                                     'os_project_reader'])
 
         self.check_list_show_RBAC_enforcement(
             'ZonesClient', 'show_zone_nameservers', expected_allowed,
             True, zone['id'])
 
         # Test with x-auth-all-projects and x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ZonesClient', 'show_zone_nameservers', expected_allowed,
@@ -405,22 +518,22 @@
         self.check_list_show_RBAC_enforcement(
             'ZonesClient', 'show_zone_nameservers', expected_allowed,
             False, zone['id'],
-            headers={'x-auth-sudo-project-id': self.client.project_id})
+            headers={'x-auth-sudo-project-id': self.zones_client.project_id})
 
     @decorators.idempotent_id('9970b632-f2db-11ec-a757-201e8823901f')
     def test_create_zone_ttl_zero(self):
         LOG.info('Create a PRIMARY zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="test_create_zone_ttl_zero", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name, ttl=0)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name, ttl=0)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Ensure we respond with CREATE+PENDING')
         self.assertEqual(const.CREATE, zone['action'])
         self.assertEqual(const.PENDING, zone['status'])
 
         LOG.info('Fetch the zone, ensure TTL is Zero')
-        body = self.client.show_zone(zone['id'])[1]
+        body = self.zones_client.show_zone(zone['id'])[1]
         self.assertEqual(
             0, body['ttl'],
             "Failed, actual Zone's TTL:{} "
@@ -443,7 +556,6 @@
             cls.admin_client = cls.os_system_admin.dns_v2.ZonesClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.ZonesClient()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_client = cls.os_alt.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('f6fe8cce-8b04-11eb-a861-74e5f9e2a801')
@@ -451,8 +563,8 @@
         LOG.info('Create zone "A" using primary client')
         zone_name = dns_data_utils.rand_zone_name(
             name="show_zone_impersonate", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('As Alt tenant show zone created by Primary tenant. '
                  'Expected: 404 NotFound')
@@ -488,10 +600,10 @@
         LOG.info('Create zone "A" using Primary client')
         zone_name = dns_data_utils.rand_zone_name(
             name="list_zone_all_projects_A", suffix=self.tld_name)
-        primary_zone = self.client.create_zone(name=zone_name,
+        primary_zone = self.zones_client.create_zone(name=zone_name,
                                                wait_until=const.ACTIVE)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.client, primary_zone['id'])
+            self.wait_zone_delete, self.zones_client, primary_zone['id'])
 
         LOG.info('Create zone "B" using Alt client')
         zone_name = dns_data_utils.rand_zone_name(
@@ -542,7 +654,6 @@
     @classmethod
     def setup_clients(cls):
         super(ZoneOwnershipTest, cls).setup_clients()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_client = cls.os_alt.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('5d28580a-a012-4b57-b211-e077b1a01340')
@@ -550,12 +661,12 @@
         LOG.info('Create a zone as a default user')
         zone_name = dns_data_utils.rand_zone_name(
             name="no_create_duplicate", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone as an default with existing domain')
         self.assertRaises(lib_exc.Conflict,
-            self.client.create_zone, name=zone['name'])
+            self.zones_client.create_zone, name=zone['name'])
 
         LOG.info('Create a zone as an alt user with existing domain')
         self.assertRaises(lib_exc.Conflict,
@@ -566,8 +677,8 @@
         LOG.info('Create a zone as a default user')
         zone_name = dns_data_utils.rand_zone_name(
             name="no_create_subdomain_by_alt", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone as an alt user with existing subdomain')
         self.assertRaises(lib_exc.Forbidden,
@@ -581,8 +692,8 @@
             name="no_create_superdomain_by_alt", suffix=self.tld_name)
 
         LOG.info('Create a zone as a default user')
-        zone = self.client.create_zone(name='a.b.' + zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name='a.b.' + zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone as an alt user with existing superdomain')
         self.assertRaises(lib_exc.Forbidden,
@@ -598,11 +709,6 @@
         cls.set_network_resources()
         super(ZonesNegativeTest, cls).setup_credentials()
 
-    @classmethod
-    def setup_clients(cls):
-        super(ZonesNegativeTest, cls).setup_clients()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
-
     @decorators.idempotent_id('551853c0-8593-11eb-8c8a-74e5f9e2a801')
     def test_no_valid_zone_name(self):
         no_valid_names = ['a' * 1000, '___', '!^%&^#%^!@#', 'ggg', '.a', '']
@@ -610,7 +716,7 @@
             LOG.info('Trying to create a zone named: {} '.format(name))
             self.assertRaisesDns(
                 lib_exc.BadRequest, 'invalid_object', 400,
-                self.client.create_zone, name=name)
+                self.zones_client.create_zone, name=name)
 
     @decorators.idempotent_id('551853c0-8593-11eb-8c8a-74e5f9e2a801')
     def test_no_valid_email(self):
@@ -622,7 +728,7 @@
                 ' value: '.format(email))
             self.assertRaisesDns(
                 lib_exc.BadRequest, 'invalid_object', 400,
-                self.client.create_zone, email=email)
+                self.zones_client.create_zone, email=email)
 
     @decorators.idempotent_id('551853c0-8593-11eb-8c8a-74e5f9e2a801')
     def test_no_valid_ttl(self):
@@ -635,44 +741,45 @@
                 ' value: '.format(ttl))
             self.assertRaisesDns(
                 lib_exc.BadRequest, 'invalid_object', 400,
-                self.client.create_zone, ttl=ttl)
+                self.zones_client.create_zone, ttl=ttl)
 
     @decorators.idempotent_id('a3b0a928-a682-11eb-9899-74e5f9e2a801')
     def test_huge_size_description(self):
         LOG.info('Trying to create a zone using huge size description')
         self.assertRaisesDns(
             lib_exc.BadRequest, 'invalid_object', 400,
-            self.client.create_zone,
+            self.zones_client.create_zone,
             description=dns_data_utils.rand_zone_name() * 10000)
 
     @decorators.idempotent_id('49268b24-92de-11eb-9d02-74e5f9e2a801')
     def test_show_not_existing_zone(self):
         LOG.info('Fetch non existing zone')
         self.assertRaises(lib_exc.NotFound,
-            lambda: self.client.show_zone(uuid.uuid1()))
+            lambda: self.zones_client.show_zone(data_utils.rand_uuid()))
 
     @decorators.idempotent_id('736e3b50-92e0-11eb-9d02-74e5f9e2a801')
     def test_use_invalid_id_to_show_zone(self):
         LOG.info('Fetch the zone using invalid zone ID')
         with self.assertRaisesDns(lib_exc.BadRequest, 'invalid_uuid', 400):
-            self.client.show_zone(uuid='zahlabut')
+            self.zones_client.show_zone(uuid='zahlabut')
 
     @decorators.idempotent_id('79921370-92e1-11eb-9d02-74e5f9e2a801')
     def test_delete_non_existing_zone(self):
         LOG.info('Delete non existing zone')
         self.assertRaises(lib_exc.NotFound,
-            lambda: self.client.delete_zone(uuid.uuid1()))
+            lambda: self.zones_client.delete_zone(data_utils.rand_uuid()))
 
     @decorators.idempotent_id('e391e30a-92e0-11eb-9d02-74e5f9e2a801')
     def test_update_non_existing_zone(self):
         LOG.info('Update non existing zone')
         self.assertRaises(lib_exc.NotFound,
-            lambda: self.client.update_zone(
-                uuid.uuid1(), description=data_utils.rand_name()))
+            lambda: self.zones_client.update_zone(
+                data_utils.rand_uuid(),
+                description=data_utils.rand_name()))
 
     @decorators.idempotent_id('925192f2-0ed8-4591-8fe7-a9fa028f90a0')
     def test_list_zones_dot_json_fails(self):
-        uri = self.client.get_uri('zones.json')
+        uri = self.zones_client.get_uri('zones.json')
 
         self.assertRaises(lib_exc.NotFound,
-            lambda: self.client.get(uri))
+            lambda: self.zones_client.get(uri))
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones_exports.py b/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
index 55bd38c..5ca5495 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
@@ -72,7 +72,6 @@
             cls.admin_client = cls.os_system_admin.dns_v2.ZoneExportsClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.ZoneExportsClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
         cls.client = cls.os_primary.dns_v2.ZoneExportsClient()
         cls.alt_client = cls.os_alt.dns_v2.ZoneExportsClient()
@@ -81,8 +80,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name=test_name, suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone export')
         zone_export = self.client.create_zone_export(zone['id'])[1]
@@ -119,20 +118,21 @@
         LOG.info('Ensure the fetched response matches the zone export')
         self.assertExpected(zone_export, body, self.excluded_keys)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_member',
+                                     'os_project_reader'])
 
         self.check_list_show_RBAC_enforcement(
             'ZoneExportsClient', 'show_zone_export', expected_allowed, True,
             zone_export['id'])
 
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ZoneExportsClient', 'show_zone_export', expected_allowed, True,
@@ -143,8 +143,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name='show_zone_export_impersonate', suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone export using primary client')
         resp, zone_export = self.client.create_zone_export(zone['id'])
@@ -164,10 +164,10 @@
                 zone_export['id'], listed_export_ids))
 
         # Test RBAC with x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ZoneExportsClient', 'show_zone_export', expected_allowed, True,
@@ -179,8 +179,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name='delete_zone_export', suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'],
+        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)
 
         LOG.info('Create a zone export')
@@ -189,7 +189,7 @@
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'ZoneExportsClient', 'delete_zone_export', expected_allowed, True,
@@ -198,7 +198,7 @@
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'ZoneExportsClient', 'delete_zone_export', expected_allowed, False,
@@ -226,14 +226,10 @@
 
         self.assertGreater(len(body['exports']), 0)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC - Users that are allowed to call list, but should get
         #             zero zones.
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader',
-                                'os_admin', 'os_project_member',
-                                'os_project_reader']
+            expected_allowed = ['os_system_admin', 'os_admin']
         else:
             expected_allowed = ['os_alt']
 
@@ -263,9 +259,9 @@
         LOG.info('Create a primary zone and its export')
         zone_name = dns_data_utils.rand_zone_name(
             name='list_zone_exports_all_projects', suffix=self.tld_name)
-        primary_zone = self.zone_client.create_zone(name=zone_name)[1]
+        primary_zone = self.zones_client.create_zone(name=zone_name)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.zone_client, primary_zone['id'])
+            self.wait_zone_delete, self.zones_client, primary_zone['id'])
         primary_export = self.client.create_zone_export(primary_zone['id'])[1]
         self.addCleanup(self.client.delete_zone_export, primary_export['id'])
 
@@ -311,9 +307,9 @@
         LOG.info('Create a primary zone and its export')
         zone_name = dns_data_utils.rand_zone_name(
             name='list_zone_exports_filter', suffix=self.tld_name)
-        primary_zone = self.zone_client.create_zone(name=zone_name)[1]
+        primary_zone = self.zones_client.create_zone(name=zone_name)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.zone_client, primary_zone['id'])
+            self.wait_zone_delete, self.zones_client, primary_zone['id'])
         primary_export = self.client.create_zone_export(primary_zone['id'])[1]
         self.addCleanup(self.client.delete_zone_export, primary_export['id'])
 
@@ -377,7 +373,6 @@
     @classmethod
     def setup_clients(cls):
         super(ZonesExportTestNegative, cls).setup_clients()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.client = cls.os_primary.dns_v2.ZoneExportsClient()
         cls.alt_client = cls.os_alt.dns_v2.ZoneExportsClient()
 
@@ -385,8 +380,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(name=test_name,
                                                   suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Create a zone export')
         zone_export = self.client.create_zone_export(zone['id'])[1]
@@ -406,9 +401,9 @@
         LOG.info('Create a primary zone.')
         zone_name = dns_data_utils.rand_zone_name(name='export_not_your_zone',
                                                   suffix=self.tld_name)
-        primary_zone = self.zone_client.create_zone(name=zone_name)[1]
+        primary_zone = self.zones_client.create_zone(name=zone_name)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.zone_client, primary_zone['id'])
+            self.wait_zone_delete, self.zones_client, primary_zone['id'])
 
         LOG.info('Make sure that "404 NotFound" status code is raised.')
         self.assertRaises(
@@ -420,12 +415,12 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(name='export_deleted_zone',
                                                   suffix=self.tld_name)
-        zone = self.zone_client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'],
+        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)
         LOG.info("Delete the zone and wait till it's done.")
-        self.zone_client.delete_zone(zone['id'])[1]
-        self.wait_zone_delete(self.zone_client, zone['id'])
+        self.zones_client.delete_zone(zone['id'])[1]
+        self.wait_zone_delete(self.zones_client, zone['id'])
 
         LOG.info('Ensure we respond with NotFound exception')
         self.assertRaises(
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones_imports.py b/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
index 8e03845..025fa9a 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
@@ -71,7 +71,6 @@
             cls.admin_client = cls.os_system_admin.dns_v2.ZoneImportsClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.ZoneImportsClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.client = cls.os_primary.dns_v2.ZoneImportsClient()
         cls.alt_client = cls.os_alt.dns_v2.ZoneImportsClient()
 
@@ -81,7 +80,7 @@
             waiters.wait_for_zone_import_status(
                 self.client, zone_import_id, const.COMPLETE)
             self.client.delete_zone_import(zone_import['id'])
-            self.wait_zone_delete(self.zone_client, zone_import['zone_id'])
+            self.wait_zone_delete(self.zones_client, zone_import['zone_id'])
         else:  # Import has failed and zone wasn't created.
             self.client.delete_zone_import(zone_import['id'])
 
@@ -149,20 +148,21 @@
         LOG.info('Ensure the fetched response matches the expected one')
         self.assertExpected(zone_import, body, self.excluded_keys)
 
-        # TODO(johnsom) Test reader roles once this bug is fixed.
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test with no extra header overrides (all_projects, sudo-project-id)
         expected_allowed = ['os_primary']
+        if CONF.dns_feature_enabled.enforce_new_defaults:
+            expected_allowed.extend(['os_project_member',
+                                     'os_project_reader'])
 
         self.check_list_show_RBAC_enforcement(
             'ZoneImportsClient', 'show_zone_import', expected_allowed, True,
             zone_import['id'])
 
         # Test with x-auth-all-projects
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ZoneImportsClient', 'show_zone_import', expected_allowed, False,
@@ -180,13 +180,13 @@
                                             const.COMPLETE)
         zone_import = self.client.show_zone_import(zone_import['id'])[1]
         self.addCleanup(self.wait_zone_delete,
-                        self.zone_client,
+                        self.zones_client,
                         zone_import['zone_id'])
 
         # Test RBAC
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'ZoneImportsClient', 'delete_zone_import', expected_allowed, True,
@@ -195,7 +195,7 @@
         # Test RBAC with x-auth-all-projects and x-auth-sudo-project-id header
         expected_allowed = ['os_admin', 'os_primary']
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed.append('os_system_admin')
+            expected_allowed.extend(['os_system_admin', 'os_project_member'])
 
         self.check_CUD_RBAC_enforcement(
             'ZoneImportsClient', 'delete_zone_import', expected_allowed, False,
@@ -230,14 +230,10 @@
 
         self.assertGreater(len(body['imports']), 0)
 
-        # TODO(johnsom) Test reader role once this bug is fixed:
-        #               https://bugs.launchpad.net/tempest/+bug/1964509
         # Test RBAC - Users that are allowed to call list, but should get
         #             zero zones.
         if CONF.dns_feature_enabled.enforce_new_defaults:
-            expected_allowed = ['os_system_admin', 'os_system_reader',
-                                'os_admin', 'os_project_member',
-                                'os_project_reader']
+            expected_allowed = ['os_system_admin', 'os_admin']
         else:
             expected_allowed = ['os_alt']
 
@@ -310,10 +306,10 @@
             zone_import, resp_body['imports'][0], self.excluded_keys)
 
         # Test with x-auth-sudo-project-id header
-        if CONF.dns_feature_enabled.enforce_new_defaults:
+        if CONF.enforce_scope.designate:
             expected_allowed = ['os_system_admin']
         else:
-            expected_allowed = ['os_admin']
+            expected_allowed = ['os_admin', 'os_system_admin']
 
         self.check_list_show_RBAC_enforcement(
             'ZoneImportsClient', 'show_zone_import', expected_allowed, False,
@@ -387,7 +383,6 @@
     @classmethod
     def setup_clients(cls):
         super(ZonesImportTestNegative, cls).setup_clients()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.client = cls.os_primary.dns_v2.ZoneImportsClient()
 
     def _clean_up_resources(self, zone_import_id):
@@ -396,7 +391,7 @@
             waiters.wait_for_zone_import_status(
                 self.client, zone_import_id, const.COMPLETE)
             self.client.delete_zone_import(zone_import['id'])
-            self.wait_zone_delete(self.zone_client, zone_import['zone_id'])
+            self.wait_zone_delete(self.zones_client, zone_import['zone_id'])
         else:  # Import has failed and zone wasn't created.
             self.client.delete_zone_import(zone_import['id'])
 
@@ -434,9 +429,9 @@
         LOG.info('Create a Zone named: "...zone_to_update..."')
         zone_name = dns_data_utils.rand_zone_name(
             name='zone_to_update', suffix=self.tld_name)
-        zone = self.zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name, wait_until=const.ACTIVE)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         LOG.info('Use zone import to update an existing zone, expected: zone'
                  ' import gets into the ERROR status ')
         zone_import_data = dns_data_utils.rand_zonefile_data(name=zone_name)
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/base.py b/designate_tempest_plugin/tests/base.py
index d5cdb6b..26f1f03 100644
--- a/designate_tempest_plugin/tests/base.py
+++ b/designate_tempest_plugin/tests/base.py
@@ -15,8 +15,8 @@
 from tempest import config
 from tempest.lib.common.utils import test_utils as utils
 
-from designate_tempest_plugin.services.dns.query.query_client import \
-    QueryClient
+from designate_tempest_plugin.services.dns.query.query_client import (
+    QueryClient)
 from designate_tempest_plugin.tests import rbac_utils
 
 
@@ -103,6 +103,17 @@
             build_interval=CONF.dns.build_interval,
             build_timeout=CONF.dns.build_timeout,
         )
+        # Most tests need a "primary" zones client and we need it for the
+        # API version check, so create one instance here.
+        cls.zones_client = cls.os_primary.dns_v2.ZonesClient()
+
+    @classmethod
+    def resource_setup(cls):
+        """Setup resources needed by the tests."""
+        super(BaseDnsTest, cls).resource_setup()
+
+        # The credential does not matter here.
+        cls.api_version = cls.zones_client.get_max_api_version()
 
     def assertExpected(self, expected, actual, excluded_keys):
         for key, value in expected.items():
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py b/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py
index a485f5b..85d5d5a 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py
@@ -71,7 +71,6 @@
         else:
             cls.admin_blacklist_client = cls.os_admin.dns_v2.BlacklistsClient()
             cls.admin_zone_client = cls.os_admin.dns_v2.ZonesClient()
-        cls.primary_zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @decorators.idempotent_id('22b1ee72-d8d2-11eb-bcdc-74e5f9e2a801')
     def test_primary_fails_to_create_zone_matches_blacklist_regex(self):
@@ -87,7 +86,7 @@
                  '"blacklistregextest".')
         self.assertRaisesDns(
             lib_exc.BadRequest, 'invalid_zone_name', 400,
-            self.primary_zone_client.create_zone,
+            self.zones_client.create_zone,
             name='blacklistregextest' + dns_data_utils.rand_zone_name())
 
     @decorators.idempotent_id('6956f20c-d8d5-11eb-bcdc-74e5f9e2a801')
@@ -104,7 +103,7 @@
         LOG.info('Try to create a zone named:{}'.format(zone_name))
         self.assertRaisesDns(
             lib_exc.BadRequest, 'invalid_zone_name', 400,
-            self.primary_zone_client.create_zone, name=zone_name)
+            self.zones_client.create_zone, name=zone_name)
 
     @decorators.idempotent_id('de030088-d97e-11eb-8ab8-74e5f9e2a801')
     def test_admin_creates_zone_matches_blacklist_name_or_regex(self):
@@ -126,27 +125,27 @@
                  ' Expected: FAIL')
         with self.assertRaisesDns(
                 lib_exc.BadRequest, 'invalid_zone_name', 400):
-            self.primary_zone_client.create_zone(name=zone_name_to_deny)
+            self.zones_client.create_zone(name=zone_name_to_deny)
 
         LOG.info('Admin tries to create a zone that is blacklisted by name '
                  'for a Primary user. Expected: FAIL')
         zone = self.admin_zone_client.create_zone(
             name=zone_name_to_deny,
-            project_id=self.primary_zone_client.project_id)[1]
+            project_id=self.zones_client.project_id)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.primary_zone_client, zone['id'])
+            self.wait_zone_delete, self.zones_client, zone['id'])
 
         LOG.info('Primary tries to create a zone that is blacklisted by regex.'
                  ' Expected: FAIL')
         with self.assertRaisesDns(
                 lib_exc.BadRequest, 'invalid_zone_name', 400):
-            self.primary_zone_client.create_zone(
+            self.zones_client.create_zone(
                 name='blacklistnameregextest2{}'.format(zone_name_to_deny))
 
         LOG.info('Admin tries to create a zone that is blacklisted by regex'
                  ' for a Primary user. Expected: FAIL')
         zone = self.admin_zone_client.create_zone(
             name='blacklistnameregextest2{}'.format(zone_name_to_deny),
-            project_id=self.primary_zone_client.project_id)[1]
+            project_id=self.zones_client.project_id)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.primary_zone_client, zone['id'])
+            self.wait_zone_delete, self.zones_client, zone['id'])
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_quotas.py b/designate_tempest_plugin/tests/scenario/v2/test_quotas.py
index 9f126a4..7f6bc07 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_quotas.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_quotas.py
@@ -13,6 +13,7 @@
 # under the License.
 import random
 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
@@ -63,7 +64,6 @@
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
         cls.quotas_client = cls.os_primary.dns_v2.QuotasClient()
         cls.alt_client = cls.os_alt.dns_v2.QuotasClient()
-        cls.zone_client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
         cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
 
@@ -92,8 +92,8 @@
     def _reach_quota_limit(
             self, limit_threshold, quota_type, zone=None):
         attempt_number = 0
-        not_raised_msg = "Failed, expected '413 over_quota' response of " \
-                         "type:{} wasn't received.".format(quota_type)
+        not_raised_msg = ("Failed, expected '413 over_quota' response of "
+                          "type:{} wasn't received.".format(quota_type))
         while attempt_number <= limit_threshold + 1:
             try:
                 attempt_number += 1
@@ -101,17 +101,17 @@
                 if quota_type == 'zones_quota':
                     zone_name = dns_data_utils.rand_zone_name(
                         name="_reach_quota_limit", suffix=self.tld_name)
-                    zone = self.zone_client.create_zone(
+                    zone = self.zones_client.create_zone(
                         name=zone_name,
                         description='Test zone for:{}'.format(quota_type))[1]
                     self.addCleanup(
                         self.wait_zone_delete,
-                        self.zone_client, zone['id'])
+                        self.zones_client, zone['id'])
                 else:
                     if quota_type == 'zone_recordsets':
                         max_number_of_records = 10
                         prj_quota = self.admin_client.show_quotas(
-                            project_id=self.zone_client.project_id,
+                            project_id=self.zones_client.project_id,
                             headers=self.all_projects_header)[1][
                             'zone_records']
                         if max_number_of_records > prj_quota:
@@ -136,8 +136,8 @@
                 raised_err = str(e).replace(' ', '')
                 if not_raised_msg in str(e):
                     raise AssertionError(not_raised_msg)
-                elif "'code':413" in raised_err and \
-                        "'type':'over_quota'" in raised_err:
+                elif ("'code':413" in raised_err and
+                      "'type':'over_quota'" in raised_err):
                     LOG.info("OK, type':'over_quota' was raised")
                     break
                 else:
@@ -152,15 +152,15 @@
         quotas = dns_data_utils.rand_quotas()
         quotas['api_export_size'] = self.test_quota_limit
         self._set_quota_for_project(
-            self.zone_client.project_id, quotas)
+            self.zones_client.project_id, quotas)
         LOG.info('Create a Zone, wait until ACTIVE and add:{}'
                  ' Recordsets'.format(self.test_quota_limit + 1))
-        zone = self.zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             description='Zone for test_api_export_size_quota',
             wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete,
-            self.zone_client, zone['id'])
+            self.zones_client, zone['id'])
         for i in range(self.test_quota_limit + 1):
             recordset_data = dns_data_utils.rand_recordset_data(
                 record_type='A', zone_name=zone['name'])
@@ -194,17 +194,17 @@
         quotas = dns_data_utils.rand_quotas()
         quotas['recordset_records'] = self.test_quota_limit
         self._set_quota_for_project(
-            self.zone_client.project_id, quotas)
+            self.zones_client.project_id, quotas)
         LOG.info('Create a Zone and wait until ACTIVE')
         zone_name = dns_data_utils.rand_zone_name(
             name="test_recordset_records_quota", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name,
             description='Zone for test_recordset_records_quota',
             wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete,
-            self.zone_client, zone['id'])
+            self.zones_client, zone['id'])
         LOG.info(
             'Create recordset data with:{} records and try to create'
             ' a recordset. Expected:"413 over_quota"'.format(
@@ -224,19 +224,19 @@
         LOG.info('Create a Zone and wait until ACTIVE')
         zone_name = dns_data_utils.rand_zone_name(
             name="test_zone_records_quota", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name,
             description='Zone for test_zone_records_quota',
             wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete,
-            self.zone_client, zone['id'])
+            self.zones_client, zone['id'])
         LOG.info('Admin sets "zone_records:{}" quota for Primary '
                  'user'.format(self.test_quota_limit))
         quotas = dns_data_utils.rand_quotas()
         quotas['zone_records'] = self.test_quota_limit
         self._set_quota_for_project(
-            self.zone_client.project_id, quotas)
+            self.zones_client.project_id, quotas)
         LOG.info(
             'Try to add:{} recordsets (with a single record) to the Zone in'
             ' loop. Expected:"413 over_quota"'.format(
@@ -250,19 +250,19 @@
         LOG.info('Create a Zone and wait until ACTIVE')
         zone_name = dns_data_utils.rand_zone_name(
             name="test_zone_recordsets_quota", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name,
             description='Zone for test_zone_recordsets_quota',
             wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete,
-            self.zone_client, zone['id'])
+            self.zones_client, zone['id'])
         LOG.info('Admin sets "zone_recordsets:{}" quota for Primary '
                  'user'.format(self.test_quota_limit))
         quotas = dns_data_utils.rand_quotas()
         quotas['zone_recordsets'] = self.test_quota_limit
         self._set_quota_for_project(
-            self.zone_client.project_id, quotas)
+            self.zones_client.project_id, quotas)
         LOG.info(
             'Try to add:{} recordsets (with a random number of records) to a'
             ' Zone in loop. Expected:"413 over_quota"'.format(
@@ -278,14 +278,14 @@
         quotas = dns_data_utils.rand_quotas()
         quotas['zones'] = self.test_quota_limit
         self._set_quota_for_project(
-            self.zone_client.project_id, quotas)
+            self.zones_client.project_id, quotas)
         LOG.info('Try to create Zones. Expected:"413 over_quota"')
         self._reach_quota_limit(self.test_quota_limit, 'zones_quota')
 
 
 class QuotasBoundary(base.BaseDnsV2Test, tempest.test.BaseTestCase):
 
-    credentials = ['admin', 'system_admin']
+    credentials = ['admin', 'system_admin', 'primary']
 
     @classmethod
     def setup_credentials(cls):
@@ -308,15 +308,14 @@
             cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient()
             cls.quota_client = cls.os_system_admin.dns_v2.QuotasClient()
             cls.project_client = cls.os_system_admin.projects_client
-            cls.zone_client = cls.os_system_admin.dns_v2.ZonesClient()
-            cls.recordset_client = \
-                cls.os_system_admin.dns_v2.RecordsetClient()
-            cls.export_zone_client = \
-                cls.os_system_admin.dns_v2.ZoneExportsClient()
+            cls.recordset_client = cls.os_system_admin.dns_v2.RecordsetClient()
+            cls.export_zone_client = (
+                cls.os_system_admin.dns_v2.ZoneExportsClient())
+            cls.admin_zones_client = cls.os_system_admin.dns_v2.ZonesClient()
         else:
             cls.quota_client = cls.os_admin.dns_v2.QuotasClient()
             cls.project_client = cls.os_admin.projects_client
-            cls.zone_client = cls.os_admin.dns_v2.ZonesClient()
+            cls.admin_zones_client = cls.os_admin.dns_v2.ZonesClient()
             cls.recordset_client = cls.os_admin.dns_v2.RecordsetClient()
             cls.export_zone_client = cls.os_admin.dns_v2.ZoneExportsClient()
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
@@ -354,15 +353,16 @@
         # Create a first Zone --> Should PASS
         zone_name = dns_data_utils.rand_zone_name(
             name="test_zone_quota_boundary_attempt_1", suffix=self.tld_name)
-        zone = self.zone_client.create_zone(
+        zone = self.admin_zones_client.create_zone(
             name=zone_name, project_id=tenant_id)[1]
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.admin_zones_client,
+                        zone['id'])
 
         # Create a second zone --> should FAIL on: 413 over_quota
         zone_name = dns_data_utils.rand_zone_name(
             name="test_zone_quota_boundary_attempt_2", suffix=self.tld_name)
         try:
-            response_headers, zone = self.zone_client.create_zone(
+            response_headers, zone = self.admin_zones_client.create_zone(
                 name=zone_name, project_id=tenant_id)
             if response_headers['status'] != 413:
                 raise exceptions.InvalidStatusError(
@@ -373,6 +373,189 @@
         finally:
             self.addCleanup(
                 self.wait_zone_delete,
-                self.zone_client, zone['id'],
+                self.admin_zones_client, zone['id'],
                 headers=sudo_header,
                 ignore_errors=lib_exc.NotFound)
+
+
+class SharedZonesQuotaTest(base.BaseDnsV2Test):
+    credentials = ['primary', 'admin', 'system_admin']
+
+    @classmethod
+    def setup_clients(cls):
+        super(SharedZonesQuotaTest, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient()
+            cls.adm_project_client = cls.os_system_admin.projects_client
+            cls.adm_quota_client = cls.os_system_admin.dns_v2.QuotasClient()
+            cls.adm_zone_client = cls.os_system_admin.dns_v2.ZonesClient()
+            cls.adm_shr_client = cls.os_system_admin.dns_v2.SharedZonesClient()
+        else:
+            cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
+            cls.adm_project_client = cls.os_admin.projects_client
+            cls.adm_quota_client = cls.os_admin.dns_v2.QuotasClient()
+            cls.adm_zone_client = cls.os_admin.dns_v2.ZonesClient()
+            cls.adm_shr_client = cls.os_admin.dns_v2.SharedZonesClient()
+        cls.share_zone_client = cls.os_primary.dns_v2.SharedZonesClient()
+        cls.rec_client = cls.os_primary.dns_v2.RecordsetClient()
+        cls.export_zone_client = cls.os_primary.dns_v2.ZoneExportsClient()
+
+    @classmethod
+    def resource_setup(cls):
+        super(SharedZonesQuotaTest, 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(SharedZonesQuotaTest, cls).resource_cleanup()
+
+    def _create_shared_zone_for_project(
+            self, zone_name, project_id, sudo_header):
+        """Admin creates Zone for project ID and shares it with Primary"""
+        zone_name = dns_data_utils.rand_zone_name(
+            name=zone_name,
+            suffix=self.tld_name)
+        zone = self.adm_zone_client.create_zone(
+            name=zone_name, project_id=project_id, wait_until=const.ACTIVE)[1]
+        self.addCleanup(
+            self.wait_zone_delete, self.adm_zone_client, zone['id'],
+            headers=sudo_header, delete_shares=True)
+        shared_zone = self.adm_shr_client.create_zone_share(
+            zone['id'], self.rec_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(self.adm_shr_client.delete_zone_share,
+                        zone['id'], shared_zone['id'], headers=sudo_header)
+        return zone, shared_zone
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('31968688-c0d3-11ed-b04f-201e8823901f')
+    @decorators.skip_because(bug="1992445")
+    def test_zone_recordsets_enforced_against_owner(self):
+
+        # Create a dedicated Project "A" for shared zone test
+        tenant_id = self.adm_project_client.create_project(
+            name=data_utils.rand_name(
+                name='SharedZonesQuotaTest'))['project']['id']
+        self.addCleanup(self.adm_project_client.delete_project, tenant_id)
+
+        # Set Quotas "zone_recordsets:1" for a project "A"
+        sudo_header = {'x-auth-sudo-project-id': tenant_id}
+        quotas = {
+            'zones': 7, 'zone_recordsets': 1, 'zone_records': 7,
+            'recordset_records': 7, 'api_export_size': 7}
+        self.adm_quota_client.set_quotas(
+            project_id=tenant_id, quotas=quotas,
+            headers=sudo_header)
+
+        # Admin creates a zone for project "A" and shares it Primary
+        zone = self._create_shared_zone_for_project(
+            zone_name='test_zone_recordsets_enforced_against_owner',
+            project_id=tenant_id, sudo_header=sudo_header)[0]
+
+        # Primary creates a first recodset - should PASS
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        recordset = self.rec_client.create_recordset(
+            zone['id'], recordset_data)[1]
+        self.addCleanup(self.wait_recordset_delete, self.rec_client,
+            zone['id'], recordset['id'], ignore_errors=lib_exc.NotFound)
+
+        # Primary creates a second recodset - should FAIL
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        with self.assertRaisesDns(
+                lib_exc.OverLimit, 'over_quota', 413):
+            self.rec_client.create_recordset(zone['id'], recordset_data)
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('cbb8d6b4-c64e-11ed-80d8-201e8823901f')
+    def test_zone_recocrds_enforced_against_owner(self):
+
+        # Create a dedicated Project "A" for shared zone test
+        tenant_id = self.adm_project_client.create_project(
+            name=data_utils.rand_name(
+                name='SharedZonesQuotaTest'))['project']['id']
+        self.addCleanup(self.adm_project_client.delete_project, tenant_id)
+
+        # Set Quotas "zone_records:1" for a project "A"
+        sudo_header = {'x-auth-sudo-project-id': tenant_id}
+        quotas = {
+            'zones': 7, 'zone_recordsets': 7, 'zone_records': 1,
+            'recordset_records': 7, 'api_export_size': 7}
+        self.adm_quota_client.set_quotas(
+            project_id=tenant_id, quotas=quotas,
+            headers=sudo_header)
+
+        # Admin creates a zone for project "A" and share it with Primary
+        zone = self._create_shared_zone_for_project(
+            zone_name='test_zone_recocrds_enforced_against_owner',
+            project_id=tenant_id, sudo_header=sudo_header)[0]
+
+        # Primary creates recordset with (single record) --> Should PASS
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        body = self.rec_client.create_recordset(
+            zone['id'], recordset_data)[1]
+        self.addCleanup(
+            self.wait_recordset_delete, self.rec_client,
+            zone['id'], body['id'])
+
+        # Primary creates one more recordset (single record) --> Should FAIL
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        with self.assertRaisesDns(
+                lib_exc.OverLimit, 'over_quota', 413):
+            self.rec_client.create_recordset(
+                zone['id'], recordset_data)
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('40b436f0-c895-11ed-8900-201e8823901f')
+    def test_recordset_records_enforced_against_owner(self):
+
+        # Create a dedicated Project "A" for shared zone test
+        tenant_id = self.adm_project_client.create_project(
+            name=data_utils.rand_name(
+                name='SharedZonesQuotaTest'))['project']['id']
+        self.addCleanup(self.adm_project_client.delete_project, tenant_id)
+
+        # Set Quotas "zone_records:1" for a project "A"
+        sudo_header = {'x-auth-sudo-project-id': tenant_id}
+        quotas = {
+            'zones': 7, 'zone_recordsets': 7, 'zone_records': 7,
+            'recordset_records': 1, 'api_export_size': 7}
+        self.adm_quota_client.set_quotas(
+            project_id=tenant_id, quotas=quotas,
+            headers=sudo_header)
+
+        # Admin creates a zone for project "A" and share it with Primary
+        zone = self._create_shared_zone_for_project(
+            zone_name='test_recordset_records_enforced_against_owner',
+            project_id=tenant_id, sudo_header=sudo_header)[0]
+
+        # Primary creates recordset with (single record) --> Should PASS
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        body = self.rec_client.create_recordset(
+            zone['id'], recordset_data)[1]
+        self.addCleanup(
+            self.wait_recordset_delete, self.rec_client,
+            zone['id'], body['id'])
+
+        # Primary creates one more recordset (two records) --> Should FAIL
+        recordset_data = dns_data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'], number_of_records=2)
+        with self.assertRaisesDns(
+                lib_exc.OverLimit, 'over_quota', 413):
+            self.rec_client.create_recordset(
+                zone['id'], recordset_data)
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
index 9e5f87a..42cdafa 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
@@ -12,19 +12,20 @@
 
 import time
 
+import ddt
 from oslo_log import log as logging
 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
-import ddt
+import testtools
 
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin.common import constants as const
 from designate_tempest_plugin import data_utils as dns_data_utils
 from designate_tempest_plugin.common import waiters
-from designate_tempest_plugin.services.dns.query.query_client \
-    import SingleQueryClient
+from designate_tempest_plugin.services.dns.query.query_client import (
+    SingleQueryClient)
 
 LOG = logging.getLogger(__name__)
 
@@ -45,7 +46,6 @@
         else:
             cls.admin_client = cls.os_admin.dns_v2.RecordsetClient()
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
 
     @classmethod
@@ -55,7 +55,7 @@
         zone_id = CONF.dns.zone_id
         if zone_id:
             LOG.info('Retrieve info from a zone')
-            zone = cls.client.show_zone(zone_id)[1]
+            zone = cls.zones_client.show_zone(zone_id)[1]
         else:
             # Make sure we have an allowed TLD available
             tld_name = dns_data_utils.rand_zone_name(name="RecordsetsTest")
@@ -65,13 +65,13 @@
             LOG.info('Create a new zone')
             zone_name = dns_data_utils.rand_zone_name(
                 name="recordsets_test_setup", suffix=cls.tld_name)
-            zone = cls.client.create_zone(name=zone_name)[1]
+            zone = cls.zones_client.create_zone(name=zone_name)[1]
             cls.addClassResourceCleanup(
                 test_utils.call_and_ignore_notfound_exc,
-                cls.client.delete_zone, zone['id'])
+                cls.zones_client.delete_zone, zone['id'])
 
         LOG.info('Ensure we respond with ACTIVE')
-        waiters.wait_for_zone_status(cls.client, zone['id'], 'ACTIVE')
+        waiters.wait_for_zone_status(cls.zones_client, zone['id'], 'ACTIVE')
 
         cls.zone = zone
 
@@ -130,10 +130,10 @@
         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):
+    @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:
@@ -177,3 +177,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
new file mode 100644
index 0000000..7ece8a2
--- /dev/null
+++ b/designate_tempest_plugin/tests/scenario/v2/test_shared_zones.py
@@ -0,0 +1,488 @@
+# 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.common import constants as const
+from designate_tempest_plugin.common import waiters
+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()
+            cls.adm_shr_client = cls.os_system_admin.dns_v2.SharedZonesClient()
+        else:
+            cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
+            cls.adm_shr_client = cls.os_admin.dns_v2.SharedZonesClient()
+        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()
+        cls.primary_import_client = cls.os_primary.dns_v2.ZoneImportsClient()
+
+    @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'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('fb170be8-c0bc-11ed-99a3-201e8823901f')
+    def test_admin_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)
+
+        # Generate recordset data to be used latter in the test
+        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)
+
+        # Admin creates shared zone for Alt using "x-auth-sudo-project-id"
+        sudo_header = {
+            'x-auth-sudo-project-id': self.zones_client.project_id}
+        shared_zone = self.adm_shr_client.create_zone_share(
+            zone['id'], self.alt_rec_client.project_id,
+            headers=sudo_header)[1]
+        self.addCleanup(
+            self.adm_shr_client.delete_zone_share, zone['id'],
+            shared_zone['id'], headers=sudo_header)
+
+        # 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 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 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 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('b7dd37b8-c3ea-11ed-a102-201e8823901f')
+    def test_share_imported_zone(self):
+        # Primary user imports zone from a zone file
+        zone_name = dns_data_utils.rand_zone_name(
+            name="test_share_imported_zone", suffix=self.tld_name)
+        zone_data = dns_data_utils.rand_zonefile_data(name=zone_name)
+        zone_import = self.primary_import_client.create_zone_import(
+            zonefile_data=zone_data)[1]
+        self.addCleanup(
+            self.primary_import_client.delete_zone_import, zone_import['id'])
+        waiters.wait_for_zone_import_status(
+            self.primary_import_client, zone_import['id'], const.COMPLETE)
+
+        # Primary shares previously created zone with Alt user
+        zone_id = self.primary_import_client.show_zone_import(
+            zone_import['id'])[1]['zone_id']
+        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'])
+
+
+class SharedZonesTestNegative(base.BaseDnsV2Test):
+    credentials = ['primary', 'admin', 'system_admin', 'alt',
+                   ['demo', 'member']]
+
+    @classmethod
+    def setup_clients(cls):
+        super(SharedZonesTestNegative, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient()
+            cls.adm_shr_client = cls.os_system_admin.dns_v2.SharedZonesClient()
+        else:
+            cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
+            cls.adm_shr_client = cls.os_admin.dns_v2.SharedZonesClient()
+        cls.share_zone_client = cls.os_primary.dns_v2.SharedZonesClient()
+        cls.alt_export_client = cls.os_alt.dns_v2.ZoneExportsClient()
+        cls.primary_export_client = cls.os_primary.dns_v2.ZoneExportsClient()
+        cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
+        cls.primary_import_client = cls.os_primary.dns_v2.ZoneImportsClient()
+        cls.alt_import_client = cls.os_alt.dns_v2.ZoneImportsClient()
+        cls.prm_transfer_client = cls.os_primary.dns_v2.TransferRequestClient()
+        cls.alt_transfer_client = cls.os_alt.dns_v2.TransferRequestClient()
+
+    @classmethod
+    def resource_setup(cls):
+        super(SharedZonesTestNegative, 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(SharedZonesTestNegative, cls).resource_cleanup()
+
+    def _create_shared_zone(self, zone_name):
+        # Primary tenant creates zone and shares it with Alt tenant
+        zone_name = dns_data_utils.rand_zone_name(
+            name=zone_name, 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)
+        shared_zone = self.share_zone_client.create_zone_share(
+            zone['id'], self.alt_export_client.project_id)[1]
+        self.addCleanup(self.share_zone_client.delete_zone_share,
+                        zone['id'], shared_zone['id'])
+        return zone, shared_zone
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('1d2c91c2-c328-11ed-a033-201e8823901f')
+    def test_alt_create_export_for_shared_zone(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_create_export_for_shared_zone')[0]
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.alt_export_client.create_zone_export, zone['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('1e74410c-c32c-11ed-a033-201e8823901f')
+    def test_alt_list_shared_zone_exports(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_list_shared_zone_exports')[0]
+
+        # Primary creates zone export
+        zone_export = self.primary_export_client.create_zone_export(
+            zone['id'])[1]
+        self.addCleanup(
+            self.primary_export_client.delete_zone_export, zone_export['id'])
+        waiters.wait_for_zone_export_status(
+            self.primary_export_client, zone_export['id'], const.COMPLETE)
+
+        # Primary lists zone exports
+        prim_zone_exports = self.primary_export_client.list_zone_exports()[1]
+        self.assertEqual(1, len(prim_zone_exports['exports']),
+                         'Failed, no zone exports listed for a primary tenant')
+
+        # Alt tries to list Primary's zone exports
+        alt_zone_exports = self.alt_export_client.list_zone_exports()[1]
+        self.assertEqual(
+            0, len(alt_zone_exports['exports']),
+            'Failed, Alt tenant is expected to receive an '
+            'empty list of zone exports')
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('cac8ea8e-c33b-11ed-a033-201e8823901f')
+    def test_alt_delete_shared_zone_export(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_delete_shared_zone_export')[0]
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alt_export_client.delete_zone_export, zone['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('962df772-c33d-11ed-a033-201e8823901f')
+    def test_alt_fails_to_show_exported_zonefile_for_shared_zone(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_show_exported_zonefile_for_shared_zone')[0]
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alt_export_client.show_exported_zonefile, zone['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('089136f2-c3e4-11ed-a102-201e8823901f')
+    def test_alt_shows_shared_zones_nameservers(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_shows_shared_zones_nameservers')[0]
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.alt_zone_client.show_zone_nameservers, zone['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('6376d4ca-c3f6-11ed-a102-201e8823901f')
+    def test_alt_transfers_shared_zone(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_transfers_shared_zone')[0]
+        # Alt creates a zone transfer_request
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.alt_transfer_client.create_transfer_request, zone['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('80ffbd8a-c3f7-11ed-a102-201e8823901f')
+    def test_alt_show_delete_transfers_of_shared_zone(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_show_delete_transfers_of_shared_zone')[0]
+
+        # Primary user creates a zone transfer_request
+        transfer_request = self.prm_transfer_client.create_transfer_request(
+            zone['id'])[1]
+        self.addCleanup(
+            self.prm_transfer_client.delete_transfer_request,
+            transfer_request['id'])
+        self.assertEqual('ACTIVE', transfer_request['status'])
+
+        # Alt shows a zone transfer_request
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alt_transfer_client.show_transfer_request, zone['id'])
+
+        # Alt deletes a zone transfer_request
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alt_transfer_client.delete_transfer_request, zone['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('1da0ff64-c3f8-11ed-a102-201e8823901f')
+    def test_alt_lists_transfers_of_shared_zone(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_lists_transfers_of_shared_zone')[0]
+
+        # Primary user creates a zone transfer_request
+        transfer = self.prm_transfer_client.create_transfer_request(
+            zone['id'])[1]
+        self.addCleanup(self.prm_transfer_client.delete_transfer_request,
+                        transfer['id'])
+        self.assertEqual('ACTIVE', transfer['status'])
+        transfer = self.prm_transfer_client.list_transfer_requests()[1]
+        self.assertEqual(
+            1, len(transfer['transfer_requests']),
+            'Failed, there is no transfer request listed for a primary user')
+
+        # Alt user lists shared zone transfer requests
+        transfer = self.alt_transfer_client.list_transfer_requests()[1]
+        self.assertEqual(
+            0, len(transfer['transfer_requests']),
+            'Failed, transfer request list should be empty for for Alt user')
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('1702c1d6-c643-11ed-8d86-201e8823901f')
+    def test_alt_abandon_shared_zone(self):
+        # Primary creates Zone and shares it with Alt
+        zone = self._create_shared_zone(
+            'test_alt_lists_transfers_of_shared_zone')[0]
+        self.assertRaises(
+            lib_exc.Forbidden, self.alt_zone_client.abandon_zone,
+            zone['id'])
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_tld.py b/designate_tempest_plugin/tests/scenario/v2/test_tld.py
index 8dc6d70..bc90c90 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_tld.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_tld.py
@@ -43,7 +43,6 @@
         else:
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
         cls.primary_tld_client = cls.os_primary.dns_v2.TldClient()
-        cls.primary_zone_client = cls.os_primary.dns_v2.ZonesClient()
 
     @classmethod
     def resource_setup(cls):
@@ -63,10 +62,10 @@
         zone_name = dns_data_utils.rand_zone_name(
             name='existing_tld_zone', prefix='rand',
             suffix='.{}.'.format(self.tld_suffix))
-        zone = self.primary_zone_client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name, wait_until=const.ACTIVE)[1]
         self.addCleanup(
-            self.wait_zone_delete, self.primary_zone_client, zone['id'])
+            self.wait_zone_delete, self.zones_client, zone['id'])
 
     @decorators.idempotent_id('06deced8-d4de-11eb-b8ee-74e5f9e2a801')
     def test_create_zone_using_not_existing_tld(self):
@@ -76,5 +75,5 @@
             name='not_existing_tld_zone', prefix='rand',
             suffix='.{}.'.format(self.tld_suffix)[::-1])
         self.assertRaises(
-            lib_exc.BadRequest, self.primary_zone_client.create_zone,
+            lib_exc.BadRequest, self.zones_client.create_zone,
             name=zone_name)
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_zones.py b/designate_tempest_plugin/tests/scenario/v2/test_zones.py
index 74f9563..98b2f9c 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones.py
@@ -25,8 +25,8 @@
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin.common import constants as const
 from designate_tempest_plugin.common import waiters
-from designate_tempest_plugin.services.dns.query.query_client \
-    import SingleQueryClient
+from designate_tempest_plugin.services.dns.query.query_client import (
+    SingleQueryClient)
 
 CONF = config.CONF
 
@@ -46,7 +46,6 @@
         else:
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
             cls.rec_client = cls.os_admin.dns_v2.RecordsetClient()
-        cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.primary_client = cls.os_primary.dns_v2.BlacklistsClient()
 
     @classmethod
@@ -69,8 +68,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="create_and_delete_zone", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'],
+        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)
 
         LOG.info('Ensure we respond with CREATE+PENDING')
@@ -78,23 +77,23 @@
         self.assertEqual(const.PENDING, zone['status'])
 
         waiters.wait_for_zone_status(
-            self.client, zone['id'], const.ACTIVE)
+            self.zones_client, zone['id'], const.ACTIVE)
 
         LOG.info('Re-Fetch the zone')
-        zone = self.client.show_zone(zone['id'])[1]
+        zone = self.zones_client.show_zone(zone['id'])[1]
 
         LOG.info('Ensure we respond with NONE+ACTIVE')
         self.assertEqual(const.NONE, zone['action'])
         self.assertEqual(const.ACTIVE, zone['status'])
 
         LOG.info('Delete the zone')
-        zone = self.client.delete_zone(zone['id'])[1]
+        zone = self.zones_client.delete_zone(zone['id'])[1]
 
         LOG.info('Ensure we respond with DELETE+PENDING')
         self.assertEqual(const.DELETE, zone['action'])
         self.assertEqual(const.PENDING, zone['status'])
 
-        waiters.wait_for_zone_404(self.client, zone['id'])
+        waiters.wait_for_zone_404(self.zones_client, zone['id'])
 
     @decorators.attr(type='slow')
     @decorators.idempotent_id('cabd6334-ba37-11ec-9d8c-201e8823901f')
@@ -105,22 +104,22 @@
         orig_description = 'test_create_and_update_zone: org description'
         zone_name = dns_data_utils.rand_zone_name(
             name="create_and_update_zone", suffix=self.tld_name)
-        zone = self.client.create_zone(
+        zone = self.zones_client.create_zone(
             name=zone_name,
             ttl=orig_ttl, description=orig_description,
             wait_until=const.ACTIVE)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'],
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'],
                         ignore_errors=lib_exc.NotFound)
 
         LOG.info("Update zone's: TTL and Description, wait until ACTIVE")
         updated_ttl = 777
         updated_description = dns_data_utils.rand_string(20)
-        self.client.update_zone(
+        self.zones_client.update_zone(
             zone['id'], ttl=updated_ttl, description=updated_description,
             wait_until=const.ACTIVE)
 
         LOG.info('Re-Fetch/Show the zone')
-        show_zone = self.client.show_zone(zone['id'])[1]
+        show_zone = self.zones_client.show_zone(zone['id'])[1]
 
         LOG.info('Ensure that the Description and TLL has been updated')
         self.assertEqual(
@@ -138,8 +137,8 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="delete_zone_pending_create", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'],
+        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)
 
         # NOTE(kiall): This is certainly a little racey, it's entirely
@@ -148,13 +147,13 @@
         #              Theres not a huge amount we can do, given this is
         #              black-box testing.
         LOG.info('Delete the zone while it is still pending')
-        zone = self.client.delete_zone(zone['id'])[1]
+        zone = self.zones_client.delete_zone(zone['id'])[1]
 
         LOG.info('Ensure we respond with DELETE+PENDING')
         self.assertEqual(const.DELETE, zone['action'])
         self.assertEqual(const.PENDING, zone['status'])
 
-        waiters.wait_for_zone_404(self.client, zone['id'])
+        waiters.wait_for_zone_404(self.zones_client, zone['id'])
 
     @decorators.attr(type='slow')
     @decorators.idempotent_id('ad8d1f5b-da66-46a0-bbee-14dc84a5d791')
@@ -165,10 +164,11 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="zone_create_propagates", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        zone = self.zones_client.create_zone(name=zone_name)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
 
-        waiters.wait_for_zone_status(self.client, zone['id'], const.ACTIVE)
+        waiters.wait_for_zone_status(self.zones_client, zone['id'],
+                                     const.ACTIVE)
         waiters.wait_for_query(self.query_client, zone['name'], const.SOA)
 
     @decorators.attr(type='slow')
@@ -180,17 +180,18 @@
         LOG.info('Create a zone')
         zone_name = dns_data_utils.rand_zone_name(
             name="zone_delete_propagates", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name)[1]
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'],
+        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)
 
-        waiters.wait_for_zone_status(self.client, zone['id'], const.ACTIVE)
+        waiters.wait_for_zone_status(self.zones_client, zone['id'],
+                                     const.ACTIVE)
         waiters.wait_for_query(self.query_client, zone['name'], const.SOA)
 
         LOG.info('Delete the zone')
-        self.client.delete_zone(zone['id'])
+        self.zones_client.delete_zone(zone['id'])
 
-        waiters.wait_for_zone_404(self.client, zone['id'])
+        waiters.wait_for_zone_404(self.zones_client, zone['id'])
         waiters.wait_for_query(self.query_client, zone['name'], const.SOA,
                                found=False)
 
@@ -210,10 +211,11 @@
                  ' and SOA Refresh values')
         zone_name = dns_data_utils.rand_zone_name(
             name="test_notify_msg_sent_to_nameservers", suffix=self.tld_name)
-        zone = self.client.create_zone(name=zone_name, wait_until='ACTIVE')[1]
+        zone = self.zones_client.create_zone(name=zone_name,
+                                             wait_until='ACTIVE')[1]
 
         org_serial = zone['serial']
-        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
         try:
             soa = [
                 rec['records'] for rec in self.rec_client.list_recordset(
@@ -232,9 +234,9 @@
 
         LOG.info("Update Zone's TTL, wait until ACTIVE and"
                  " ensure Zone's Serial has changed")
-        updated_zone = self.client.update_zone(
-            zone['id'], ttl=dns_data_utils.rand_ttl(), wait_until='ACTIVE')[1]
-        new_serial = updated_zone['serial']
+        self.zones_client.update_zone(
+            zone['id'], ttl=dns_data_utils.rand_ttl(), wait_until='ACTIVE')
+        new_serial = self.zones_client.show_zone(zone['id'])[1]['serial']
         self.assertNotEqual(
             new_serial, org_serial,
             "Failed, expected behaviour is that the Designate DNS changes the"
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_zones_export.py b/designate_tempest_plugin/tests/scenario/v2/test_zones_export.py
index 565461d..8c8d674 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_zones_export.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones_export.py
@@ -22,8 +22,8 @@
 from designate_tempest_plugin.common import constants as const
 from designate_tempest_plugin.common import waiters
 from designate_tempest_plugin import data_utils as dns_data_utils
-from designate_tempest_plugin.tests.api.v2.test_zones_exports import \
-    BaseZoneExportsTest
+from designate_tempest_plugin.tests.api.v2.test_zones_exports import (
+    BaseZoneExportsTest)
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -46,7 +46,6 @@
         else:
             cls.admin_client = cls.os_admin.dns_v2.ZoneExportsClient()
         cls.client = cls.os_primary.dns_v2.ZoneExportsClient()
-        cls.zones_client = cls.os_primary.dns_v2.ZonesClient()
         cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
 
     def _create_zone_export(self, test_name):
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py b/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py
index 7286f23..9518d82 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones_import.py
@@ -17,8 +17,8 @@
 from designate_tempest_plugin.common import constants as const
 from designate_tempest_plugin.common import waiters
 from designate_tempest_plugin import data_utils as dns_data_utils
-from designate_tempest_plugin.tests.api.v2.test_zones_imports import \
-    BaseZonesImportTest
+from designate_tempest_plugin.tests.api.v2.test_zones_imports import (
+    BaseZonesImportTest)
 
 LOG = logging.getLogger(__name__)
 
@@ -32,7 +32,6 @@
         super(ZonesImportTest, cls).setup_clients()
 
         cls.client = cls.os_primary.dns_v2.ZoneImportsClient()
-        cls.zones_client = cls.os_primary.dns_v2.ZonesClient()
 
     @decorators.attr(type='slow')
     @decorators.idempotent_id('679f38d0-2f2f-49c5-934e-8fe0c452f56e')
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_zones_transfer.py b/designate_tempest_plugin/tests/scenario/v2/test_zones_transfer.py
index 78aaabc..8527e46 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_zones_transfer.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones_transfer.py
@@ -40,7 +40,6 @@
             cls.admin_accept_client = (
                 cls.os_admin.dns_v2.TransferAcceptClient())
             cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
-        cls.zones_client = cls.os_primary.dns_v2.ZonesClient()
         cls.alt_zones_client = cls.os_alt.dns_v2.ZonesClient()
         cls.request_client = cls.os_primary.dns_v2.TransferRequestClient()
         cls.alt_request_client = cls.os_alt.dns_v2.TransferRequestClient()
diff --git a/requirements.txt b/requirements.txt
index 3a1f2f3..2ba3e1a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,5 +4,7 @@
 
 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
diff --git a/tox.ini b/tox.ini
index 545cdca..7a5148b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -105,3 +105,15 @@
   T112 = tempest.hacking.checks:dont_import_local_tempest_into_lib
   T113 = tempest.hacking.checks:dont_use_config_in_tempest_lib
   T114 = tempest.hacking.checks:use_rand_uuid_instead_of_uuid4
+  D701 = checks:mutable_default_arguments
+  D703 = checks:check_explicit_underscore_import
+  D704 = checks:no_import_graduated_oslo_libraries
+  D705 = checks:use_timeutils_utcnow
+  D706 = checks:no_translate_debug_logs
+  D707 = checks:check_no_basestring
+  D708 = checks:check_python3_xrange
+  D709 = checks:check_no_log_audit
+  D710 = checks:check_no_log_warn
+  D711 = checks:check_line_continuation_no_backslash
+paths = ./designate_tempest_plugin/hacking
+