Merge "New test cases for tsigkey tests suite"
diff --git a/.zuul.yaml b/.zuul.yaml
index 02cd904..cf8323b 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,3 +1,23 @@
+- job:
+    name: designate-bind9-stable-xena
+    parent: designate-bind9
+    override-checkout: stable/xena
+
+- job:
+    name: designate-bind9-stable-wallaby
+    parent: designate-bind9
+    override-checkout: stable/wallaby
+
+- job:
+    name: designate-bind9-stable-victoria
+    parent: designate-bind9
+    override-checkout: stable/victoria
+
+- job:
+    name: designate-bind9-stable-ussuri
+    parent: designate-bind9
+    override-checkout: stable/ussuri
+
 - project:
     templates:
       - designate-devstack-jobs
@@ -7,6 +27,10 @@
       - release-notes-jobs-python3
     check:
       jobs:
+        - designate-bind9-stable-xena
+        - designate-bind9-stable-wallaby
+        - designate-bind9-stable-victoria
+        - designate-bind9-stable-ussuri
         - neutron-tempest-plugin-designate-scenario
     gate:
       queue: designate
diff --git a/designate_tempest_plugin/common/constants.py b/designate_tempest_plugin/common/constants.py
index 84ee5ae..1111c72 100644
--- a/designate_tempest_plugin/common/constants.py
+++ b/designate_tempest_plugin/common/constants.py
@@ -12,13 +12,17 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-# Designate statuses strings
+# Designate statuses/actions strings
 PENDING = 'PENDING'
 COMPLETE = 'COMPLETE'
 ERROR = 'ERROR'
 DELETED = 'DELETED'
+DELETE = 'DELETE'
 ACTIVE = 'ACTIVE'
+INACTIVE = 'INACTIVE'
 UP = 'UP'
+CREATE = 'CREATE'
+UPDATE = 'UPDATE'
 
 # Zone types
 PRIMARY_ZONE_TYPE = 'PRIMARY'
diff --git a/designate_tempest_plugin/common/exceptions.py b/designate_tempest_plugin/common/exceptions.py
new file mode 100644
index 0000000..27bf848
--- /dev/null
+++ b/designate_tempest_plugin/common/exceptions.py
@@ -0,0 +1,29 @@
+# Copyright 2021 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.
+
+
+class InvalidStatusError(Exception):
+    """
+    Exception raised when an entity changes to an unexpected status.
+    """
+
+    def __init__(self, entity, entity_id, status, expected_status=None):
+        if expected_status:
+            message = ("{0} with ID {1} returned status {2} when {3} was "
+                       "expected.".format(entity, entity_id,
+                                          status, expected_status))
+        else:
+            message = ("{0} with ID {1} returned unexpected status {2}".format(
+                entity, entity_id, status))
+        super(InvalidStatusError, self).__init__(message)
diff --git a/designate_tempest_plugin/common/waiters.py b/designate_tempest_plugin/common/waiters.py
index 2b7a3b6..a606fda 100644
--- a/designate_tempest_plugin/common/waiters.py
+++ b/designate_tempest_plugin/common/waiters.py
@@ -18,6 +18,9 @@
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions as lib_exc
 
+from designate_tempest_plugin.common import constants as const
+from designate_tempest_plugin.common import exceptions
+
 LOG = logging.getLogger(__name__)
 
 
@@ -35,6 +38,10 @@
             LOG.info('Zone %s is 404ing', zone_id)
             return
 
+        if zone['status'] == const.ERROR:
+            raise exceptions.InvalidStatusError('Zone', zone_id,
+                                                zone['status'])
+
         if int(time.time()) - start >= client.build_timeout:
             message = ('Zone %(zone_id)s failed to 404 within the required '
                        'time (%(timeout)s s). Current status: '
@@ -66,6 +73,10 @@
             LOG.info('Zone %s reached %s', zone_id, status)
             return
 
+        if zone['status'] == const.ERROR:
+            raise exceptions.InvalidStatusError('Zone', zone_id,
+                                                zone['status'])
+
         if int(time.time()) - start >= client.build_timeout:
             message = ('Zone %(zone_id)s failed to reach status=%(status)s '
                        'within the required time (%(timeout)s s). Current '
@@ -98,6 +109,10 @@
             LOG.info('Zone import %s reached %s', zone_import_id, status)
             return
 
+        if zone_import['status'] == const.ERROR:
+            raise exceptions.InvalidStatusError('Zone Import', zone_import_id,
+                                                zone_import['status'])
+
         if int(time.time()) - start >= client.build_timeout:
             message = ('Zone import %(zone_import_id)s failed to reach '
                        'status=%(status)s within the required time '
@@ -131,6 +146,10 @@
             LOG.info('Zone export %s reached %s', zone_export_id, status)
             return
 
+        if zone_export['status'] == const.ERROR:
+            raise exceptions.InvalidStatusError('Zone Export', zone_export_id,
+                                                zone_export['status'])
+
         if int(time.time()) - start >= client.build_timeout:
             message = ('Zone export %(zone_export_id)s failed to reach '
                        'status=%(status)s within the required time '
@@ -165,6 +184,10 @@
             LOG.info('Recordset %s reached %s', recordset_id, status)
             return
 
+        if recordset['status'] == const.ERROR:
+            raise exceptions.InvalidStatusError('Recordset', recordset_id,
+                                                recordset['status'])
+
         if int(time.time()) - start >= client.build_timeout:
             message = ('Recordset %(recordset_id)s failed to reach '
                        'status=%(status)s within the required time '
@@ -226,3 +249,39 @@
                 message = "(%s) %s" % (caller, message)
 
             raise lib_exc.TimeoutException(message)
+
+
+def wait_for_ptr_status(client, fip_id, status):
+    """Waits for a PTR associated with FIP to reach given status."""
+    LOG.info('Waiting for PTR %s to reach %s', fip_id, status)
+
+    ptr = client.show_ptr_record(fip_id)
+    start = int(time.time())
+
+    while ptr['status'] != status:
+        time.sleep(client.build_interval)
+        ptr = client.show_ptr_record(fip_id)
+        status_curr = ptr['status']
+        if status_curr == status:
+            LOG.info('PTR %s reached %s', fip_id, status)
+            return
+
+        if ptr['status'] == const.ERROR:
+            raise exceptions.InvalidStatusError('PTR', fip_id,
+                                                ptr['status'])
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('PTR for FIP: %(fip_id)s failed to reach '
+                       'status=%(status)s within the required time '
+                       '(%(timeout)s s). Current status: %(status_curr)s' %
+                       {'fip_id': fip_id,
+                        'status': status,
+                        'status_curr': status_curr,
+                        'timeout': client.build_timeout})
+
+            caller = test_utils.find_test_caller()
+
+            if caller:
+                message = '(%s) %s' % (caller, message)
+
+            raise lib_exc.TimeoutException(message)
diff --git a/designate_tempest_plugin/config.py b/designate_tempest_plugin/config.py
index 99c9a04..989acb8 100644
--- a/designate_tempest_plugin/config.py
+++ b/designate_tempest_plugin/config.py
@@ -51,8 +51,10 @@
                help="The timeout on a single dns query to a nameserver"),
     cfg.StrOpt('zone_id',
                help="The target zone to test the dns recordsets "
-                    "If it is not specified, a new zone will be created ")
-
+                    "If it is not specified, a new zone will be created "),
+    cfg.StrOpt('tld_suffix',
+               default='com',
+               help="TLD suffix that used in all tests (if not overridden)")
 ]
 
 dns_feature_group = cfg.OptGroup(name='dns_feature_enabled',
@@ -79,6 +81,10 @@
                 default=True,
                 help="Is https://bugs.launchpad.net/designate/+bug/1573141 "
                 "fixed"),
+    cfg.BoolOpt('bug_1932026_fixed',
+                default=False,
+                help="Is https://bugs.launchpad.net/designate/+bug/1932026 "
+                     "fixed"),
     # Note: Also see the enforce_scope section (from tempest) for Designate API
     #       scope checking setting.
     cfg.BoolOpt('enforce_new_defaults',
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index 2f61d97..61c6da0 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -12,6 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 import random
+from string import ascii_lowercase
 
 import netaddr
 from oslo_log import log as logging
@@ -34,7 +35,7 @@
     return an.format(netaddr.ipv6_compact)
 
 
-def rand_zone_name(name='', prefix=None, suffix='.com.'):
+def rand_zone_name(name='', prefix='rand', suffix=None):
     """Generate a random zone name
     :param str name: The name that you want to include
     :param prefix: the exact text to start the string. Defaults to "rand"
@@ -42,6 +43,8 @@
     :return: a random zone name e.g. example.org.
     :rtype: string
     """
+    if suffix is None:
+        suffix = '.{}.'.format(CONF.dns.tld_suffix)
     name = data_utils.rand_name(name=name, prefix=prefix)
     return name + suffix
 
@@ -66,7 +69,6 @@
 
 def rand_zonefile_data(name=None, ttl=None):
     """Generate random zone data, with optional overrides
-
     :return: A ZoneModel
     """
     zone_base = ('$ORIGIN &\n& # IN SOA ns.& nsadmin.& # # # # #\n'
@@ -96,19 +98,19 @@
         quotas_dict['api_export_size'] = \
             api_export_size or data_utils.rand_int_id(100, 999999)
     else:
-        LOG.warn("Leaving `api_export_size` out of quota data due to: "
-                 "https://bugs.launchpad.net/designate/+bug/1573141")
+        LOG.warning("Leaving `api_export_size` out of quota data due to: "
+                    "https://bugs.launchpad.net/designate/+bug/1573141")
 
     return quotas_dict
 
 
 def rand_zone_data(name=None, email=None, ttl=None, description=None):
     """Generate random zone data, with optional overrides
-
     :return: A ZoneModel
     """
     if name is None:
-        name = rand_zone_name(prefix='testdomain', suffix='.com.')
+        name = rand_zone_name(
+            prefix='testdomain', suffix='.{}.'.format(CONF.dns.tld_suffix))
     if email is None:
         email = ("admin@" + name).strip('.')
     if description is None:
@@ -125,7 +127,6 @@
 def rand_recordset_data(record_type, zone_name, name=None, records=None,
                         ttl=None):
     """Generate random recordset data, with optional overrides
-
     :return: A RecordsetModel
     """
     if name is None:
@@ -141,10 +142,12 @@
         'ttl': ttl}
 
 
-def rand_a_recordset(zone_name, ip=None, **kwargs):
-    if ip is None:
-        ip = rand_ip()
-    return rand_recordset_data('A', zone_name, records=[ip], **kwargs)
+def rand_a_recordset(zone_name, ips=None, **kwargs):
+    if ips is None:
+        return rand_recordset_data(
+            'A', zone_name, records=[rand_ip()], **kwargs)
+    else:
+        return rand_recordset_data('A', zone_name, records=ips, **kwargs)
 
 
 def rand_aaaa_recordset(zone_name, ip=None, **kwargs):
@@ -199,7 +202,7 @@
 
 def wildcard_ns_recordset(zone_name):
     name = "*.{0}".format(zone_name)
-    records = ["ns.example.com."]
+    records = ["ns.example.{}.".format(CONF.dns.tld_suffix)]
     return rand_recordset_data('NS', zone_name, name, records)
 
 
@@ -213,6 +216,19 @@
     return ns_records
 
 
+def rand_soa_records(number_of_records=2):
+    return ['{} {} {} {} {} {}.'.format(
+        '{}.{}.{}'.format(rand_string(3), rand_string(7), rand_string(3)),
+        random.randint(1000000000, 2020080302), random.randint(3000, 7200),
+        random.randint(1000, 3600), random.randint(1000000, 1209600),
+        random.randint(1000, 3600)) for i in range(0, number_of_records)]
+
+
+def rand_soa_recordset(zone_name, **kwargs):
+    return rand_recordset_data(
+        'SOA', zone_name, records=rand_soa_records(), **kwargs)
+
+
 def rand_tld():
     data = {
         "name": rand_zone_name(prefix='tld', suffix='')
@@ -222,7 +238,6 @@
 
 def rand_transfer_request_data(description=None, target_project_id=None):
     """Generate random transfer request data, with optional overrides
-
     :return: A TransferRequest data
     """
 
@@ -252,10 +267,26 @@
     """Create a rand recordset by type
     This essentially just dispatches to the relevant random recordset
     creation functions.
-
     :param str zone_name: The zone name the recordset applies to
     :param str record_type: The type of recordset (ie A, MX, NS, etc...)
     """
 
     func = globals()["rand_{}_recordset".format(record_type.lower())]
     return func(zone_name)
+
+
+def rand_string(size):
+    """Create random string of ASCII chars by size
+    :param int size - length os the string to be create
+    :return - random creates string of ASCII lover characters
+    """
+    return ''.join(random.choice(ascii_lowercase) for _ in range(size))
+
+
+def rand_domain_name(tld=None):
+    """Create random valid domain name
+    :param tld (optional) - TLD that will be used to random domain name
+    :return - valid domain name, for example: paka.zbabun.iuh
+    """
+    domain_tld = tld or rand_string(3)
+    return rand_string(4) + '.' + rand_string(6) + '.' + domain_tld + '.'
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index cbfb34f..e1107ab 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -82,7 +82,7 @@
                        "received not-int read_code %(read_code)r" %
                        {'expected_code': expected_code,
                         'read_code': read_code})
-            LOG.warn(message)
+            LOG.warning(message)
         return super(DnsClientBase, cls).expected_success(
             expected_code=expected_code, read_code=int(read_code),
         )
@@ -191,7 +191,8 @@
 
         return resp, self.deserialize(resp, body)
 
-    def _put_request(self, resource, uuid, data, params=None):
+    def _put_request(self, resource, uuid, data, params=None,
+                     headers=None, extra_headers=False):
         """Updates the specified object using PUT request.
         :param resource: The name of the REST resource, e.g., 'zones'.
         :param uuid: Unique identifier of the object in UUID format.
@@ -200,11 +201,18 @@
                      is sent as-is.
         :param params: A Python dict that represents the query paramaters to
                        include in the request URI.
+        :param headers (dict): The headers to use for the request.
+        :param extra_headers (bool): Boolean value than indicates if the
+                                     headers returned by the get_headers()
+                                     method are to be used but additional
+                                     headers are needed in the request
+                                     pass them in as a dict.
         :returns: Serialized object as a dictionary.
         """
         body = self.serialize(data)
         uri = self.get_uri(resource, uuid=uuid, params=params)
-        resp, body = self.put(uri, body=body)
+        resp, body = self.put(
+            uri, body=body, headers=headers, extra_headers=extra_headers)
 
         self.expected_success(self.PUT_STATUS_CODES, resp.status)
 
diff --git a/designate_tempest_plugin/services/dns/v2/__init__.py b/designate_tempest_plugin/services/dns/v2/__init__.py
index 2f738df..44cc403 100644
--- a/designate_tempest_plugin/services/dns/v2/__init__.py
+++ b/designate_tempest_plugin/services/dns/v2/__init__.py
@@ -39,9 +39,11 @@
     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
 
 __all__ = ['BlacklistsClient', 'DesignateLimitClient', 'PoolClient',
            'PtrClient', 'QuotasClient', 'RecordsetClient', 'ServiceClient',
            'TldClient', 'TransferAcceptClient', 'TransferRequestClient',
            'TsigkeyClient', 'ZonesClient', 'ZoneExportsClient',
-           'ZoneImportsClient']
+           'ZoneImportsClient', 'ApiVersionClient']
diff --git a/designate_tempest_plugin/services/dns/v2/json/api_version_client.py b/designate_tempest_plugin/services/dns/v2/json/api_version_client.py
new file mode 100644
index 0000000..5a78540
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/api_version_client.py
@@ -0,0 +1,27 @@
+# Copyright 2021 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 designate_tempest_plugin.services.dns.v2.json import base
+
+
+class ApiVersionClient(base.DnsClientV2Base):
+
+    @base.handle_errors
+    def list_enabled_api_versions(self):
+        """Show all enabled API versions
+
+        :return: Dictionary containing version details
+        """
+        resp, body = self.get('/')
+        self.expected_success(self.LIST_STATUS_CODES, resp.status)
+        return resp, self.deserialize(resp, body)
diff --git a/designate_tempest_plugin/services/dns/v2/json/blacklists_client.py b/designate_tempest_plugin/services/dns/v2/json/blacklists_client.py
index c3f6de1..bfe65c7 100644
--- a/designate_tempest_plugin/services/dns/v2/json/blacklists_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/blacklists_client.py
@@ -36,6 +36,9 @@
             'description': description or data_utils.rand_name(),
         }
 
+        if pattern == '':
+            blacklist['pattern'] = ''
+
         resp, body = self._create_request('blacklists', blacklist,
                                           params=params)
 
diff --git a/designate_tempest_plugin/services/dns/v2/json/ptr_client.py b/designate_tempest_plugin/services/dns/v2/json/ptr_client.py
index 1bd59b3..eed221b 100644
--- a/designate_tempest_plugin/services/dns/v2/json/ptr_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/ptr_client.py
@@ -24,7 +24,8 @@
 
     @base.handle_errors
     def set_ptr_record(self, floatingip_id, ptr_name=None,
-                       ttl=None, description=None, headers=None):
+                       ttl=None, description=None, headers=None,
+                       tld=None):
         """Set a PTR record for the given FloatingIP
 
         :param floatingip_id: valid UUID of floating IP to be used.
@@ -32,13 +33,13 @@
         :param ttl TTL or random valid value if not provided.
         :param description Description or random if not provided.
         :param headers (dict): The headers to use for the request.
+        :param tld, the TLD to be used in ptrdname generated value.
         :return: created PTR dictionary.
         """
         ptr = {
-            'ptrdname': ptr_name or dns_data_utils.rand_zone_name(),
+            'ptrdname': ptr_name or dns_data_utils.rand_domain_name(tld),
             'ttl': ttl or dns_data_utils.rand_ttl(),
-            'description': description or data_utils.rand_name(
-                'test-ptr')}
+            'description': description or data_utils.rand_name('test-ptr')}
 
         return self._update_request(
             resource='reverse/floatingips/{}'.format(CONF.identity.region),
diff --git a/designate_tempest_plugin/services/dns/v2/json/quotas_client.py b/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
index f9ee10a..1b1d005 100644
--- a/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
@@ -58,17 +58,29 @@
         return resp, body
 
     @base.handle_errors
-    def show_quotas(self, project_id, params=None, headers=None):
+    def show_quotas(self, project_id=None, params=None, headers=None):
         """Gets a specific quota.
 
-        :param project_id: Show the quotas of this project id
+        :param project_id: if provided - show the quotas of this project id.
+                           https://docs.openstack.org/api-ref/dns/?expanded=
+                           get-the-name-servers-for-a-zone-detail#view-quotas
+                           If not - show the quotas for a current
+                           project.
+                           https://docs.openstack.org/api-ref/dns/?expanded=ge
+                           t-the-name-servers-for-a-zone-detail#view-current-p
+                           roject-s-quotas
+
         :param params: A Python dict that represents the query paramaters to
                        include in the request URI.
         :param headers (dict): The headers to use for the request.
         :return: Serialized quota as a dictionary.
         """
-        return self._show_request('quotas', project_id, params=params,
-                                  headers=headers, extra_headers=True)
+        if project_id is None:
+            return self._show_request(
+                'quotas', uuid=None, params=params, headers=headers)
+        else:
+            return self._show_request(
+                'quotas', project_id, params=params, headers=headers)
 
     @base.handle_errors
     def delete_quotas(self, project_id, params=None, headers=None):
@@ -88,3 +100,22 @@
         self.expected_success(204, resp.status)
 
         return resp, body
+
+    @base.handle_errors
+    def set_quotas(self, project_id, quotas, params=None, headers=None):
+        """Set a specific quota to given project ID
+
+        :param project_id: Set the quotas of this project id.
+        :param quotas: Dictionary of quotas to set (POST payload)
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :param headers (dict): The headers to use for the request.
+        :return: Serialized quota as a dictionary.
+        """
+
+        resp, body = self._update_request(
+            "quotas", project_id,
+            data=quotas, params=params, headers=headers,
+            extra_headers=True)
+        self.expected_success(200, resp.status)
+        return resp, body
diff --git a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
index 33e9ee2..20b6fcf 100644
--- a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
@@ -53,13 +53,15 @@
         self.expected_success(202, resp.status)
 
         if wait_until:
-            waiters.wait_for_recordset_status(self, body['id'], wait_until)
+            waiters.wait_for_recordset_status(
+                self, zone_uuid, body['id'], wait_until)
 
         return resp, body
 
     @base.handle_errors
     def update_recordset(self, zone_uuid, recordset_uuid,
-                         recordet_data, params=None):
+                         recordet_data, params=None,
+                         headers=None, extra_headers=None):
         """Update the recordset related to the specified zone.
         :param zone_uuid: Unique identifier of the zone in UUID format.
         :param recordset_uuid: Unique identifier of the recordset in UUID
@@ -68,11 +70,18 @@
                                data.
         :param params: A Python dict that represents the query paramaters to
                        include in the request URI.
+        :param headers (dict): The headers to use for the request.
+        :param extra_headers (bool): Boolean value than indicates if the
+                                     headers returned by the get_headers()
+                                     method are to be used but additional
+                                     headers are needed in the request
+                                     pass them in as a dict.
         :return: A tuple with the server response and the created zone.
         """
         resp, body = self._put_request(
             'zones/{0}/recordsets'.format(zone_uuid), recordset_uuid,
-            data=recordet_data, params=params)
+            data=recordet_data, params=params,
+            headers=headers, extra_headers=extra_headers)
 
         # Update Recordset should Return a HTTP 202, or a 200 if the recordset
         # is already active
diff --git a/designate_tempest_plugin/services/dns/v2/json/service_client.py b/designate_tempest_plugin/services/dns/v2/json/service_client.py
index 267e7a5..87da51a 100644
--- a/designate_tempest_plugin/services/dns/v2/json/service_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/service_client.py
@@ -25,3 +25,14 @@
         """
         return self._list_request(
             'service_statuses', headers=headers)[1]['service_statuses']
+
+    @base.handle_errors
+    def show_statuses(self, uuid, headers=None):
+        """Show Service status
+
+        :param headers: (dict): The headers to use for the request.
+        :param uuid: service ID
+        :return: Service status dictionary
+        """
+        return self._show_request(
+            'service_statuses', uuid, headers=headers)[1]
diff --git a/designate_tempest_plugin/services/dns/v2/json/zone_exports_client.py b/designate_tempest_plugin/services/dns/v2/json/zone_exports_client.py
index 8915ceb..4057b7e 100644
--- a/designate_tempest_plugin/services/dns/v2/json/zone_exports_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/zone_exports_client.py
@@ -23,7 +23,7 @@
         """Create a zone export.
 
         :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 wait_until: Block until the exported zone reaches the
                            desired status
@@ -47,7 +47,7 @@
         """Get the zone export task
 
         :param uuid: Unique identifier of the zone export task 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 exported zone as a dictionary.
@@ -56,25 +56,42 @@
              'zones/tasks/exports', uuid, params=params, headers=headers)
 
     @base.handle_errors
-    def show_exported_zonefile(self, uuid, params=None):
+    def show_exported_zonefile(self, uuid, params=None, headers=None):
+
         """Get the exported zone file
 
-        :param uuid: Unique identifier of the zone exprot task in UUID format.
-        :param params: A Python dict that represents the query paramaters to
+        :param uuid: Unique identifier of the zone export task in UUID format.
+        :param params: A Python dict that represents the query parameters to
                        include in the request URI.
+        :param headers: 3 options to send headers:
+                       1) If headers dict provided is missing "Accept" key -
+                          "{Accept:text/dns}" will be added.
+                       2) If header is None -
+                          "{Accept:text/dns}" will be sent.
+                       3) If function is called with no headers,
+                           means empty dict {} -
+                          no headers will be sent (empty dictionary)
+
         :return: Serialized exported zone as a dictionary.
         """
-        headers = {'Accept': 'text/dns'}
+
+        if headers:
+            if 'accept' not in [key.lower() for key in headers.keys()]:
+                headers['Accept'] = 'text/dns'
+        elif headers is None:
+            headers = {'Accept': 'text/dns'}
+        else:
+            headers = {}
 
         return self._show_request(
-            'zones/tasks/exports/{0}/export'.format(uuid), uuid='',
-            headers=headers, params=params)
+            'zones/tasks/exports/{0}/export'.format(uuid),
+            uuid='', headers=headers, params=params)
 
     @base.handle_errors
     def list_zone_exports(self, params=None, headers=None):
         """List zone export tasks
 
-        :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 exported zone as a list.
@@ -87,7 +104,7 @@
         """Deletes the zone export task with the specified UUID.
 
         :param uuid: The unique identifier of the exported 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.
         :return: A tuple with the server response and the response body.
         """
diff --git a/designate_tempest_plugin/tests/api/v2/test_blacklists.py b/designate_tempest_plugin/tests/api/v2/test_blacklists.py
index d39536f..95688b3 100644
--- a/designate_tempest_plugin/tests/api/v2/test_blacklists.py
+++ b/designate_tempest_plugin/tests/api/v2/test_blacklists.py
@@ -30,8 +30,7 @@
 
 class BlacklistsAdminTest(BaseBlacklistsTest):
 
-    credentials = ["admin", "system_admin"]
-
+    credentials = ["admin", "system_admin", "primary"]
     @classmethod
     def setup_credentials(cls):
         # Do not create network resources for these test.
@@ -41,10 +40,12 @@
     @classmethod
     def setup_clients(cls):
         super(BlacklistsAdminTest, cls).setup_clients()
+
         if CONF.enforce_scope.designate:
             cls.admin_client = cls.os_system_admin.dns_v2.BlacklistsClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.BlacklistsClient()
+        cls.primary_client = cls.os_primary.dns_v2.BlacklistsClient()
 
     @decorators.idempotent_id('3a7f7564-6bdd-446e-addc-a3475b4c3f71')
     def test_create_blacklist(self):
@@ -58,6 +59,30 @@
 
         self.assertExpected(blacklist, body, self.excluded_keys)
 
+    @decorators.idempotent_id('ea608152-da3c-11eb-b8b8-74e5f9e2a801')
+    @decorators.skip_because(bug="1934252")
+    def test_create_blacklist_invalid_pattern(self):
+        patterns = ['', '#(*&^%$%$#@$', 'a' * 1000]
+        for pattern in patterns:
+            LOG.info(
+                'Try to create a blacklist using pattern:{}'.format(pattern))
+            self.assertRaises(
+                lib_exc.BadRequest, self.admin_client.create_blacklist,
+                pattern=pattern)
+
+    @decorators.idempotent_id('664bdaa0-da47-11eb-b8b8-74e5f9e2a801')
+    def test_create_blacklist_huge_size_description(self):
+        LOG.info('Try to create a blacklist using huge size description')
+        self.assertRaises(
+            lib_exc.BadRequest, self.admin_client.create_blacklist,
+            description='a' * 1000)
+
+    @decorators.idempotent_id('fe9de464-d8d1-11eb-bcdc-74e5f9e2a801')
+    def test_create_blacklist_as_primary_fails(self):
+        LOG.info('As Primary user, try to create a blacklist')
+        self.assertRaises(
+            lib_exc.Forbidden, self.primary_client.create_blacklist)
+
     @decorators.idempotent_id('5bc02942-6225-4619-8f49-2105581a8dd6')
     def test_show_blacklist(self):
         LOG.info('Create a blacklist')
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
new file mode 100644
index 0000000..c5fcd69
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_enabled_api_version.py
@@ -0,0 +1,71 @@
+# Copyright 2021 Red Hat.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import requests
+
+from oslo_log import log as logging
+from tempest.lib import decorators
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin.services.dns.v2.json import base as service_base
+
+from tempest import config
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class DesignateApiVersion(base.BaseDnsV2Test, service_base.DnsClientV2Base):
+    credentials = ['admin', 'primary']
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(DesignateApiVersion, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(DesignateApiVersion, cls).setup_clients()
+
+        cls.admin_client = cls.os_admin.dns_v2.ApiVersionClient()
+        cls.primary_client = cls.os_primary.dns_v2.ApiVersionClient()
+
+    @decorators.idempotent_id('aa84986e-f2ad-11eb-b58d-74e5f9e2a801')
+    def test_list_enabled_api_versions(self):
+        for user in ['admin', 'primary', 'not_auth_user']:
+            if user == 'admin':
+                versions = self.admin_client.list_enabled_api_versions()[1][
+                    'versions']['values']
+            if user == 'primary':
+                versions = self.primary_client.list_enabled_api_versions()[1][
+                    'versions']['values']
+            if user == 'not_auth_user':
+                response = requests.get(self.primary_client.base_url,
+                                        verify=False)
+                headers = {
+                    k.lower(): v.lower() for k, v in response.headers.items()}
+                versions = self.deserialize(
+                    headers, str(response.text))['versions']['values']
+
+            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']
+            LOG.info('Enabled versions IDs are:{}'.format(enabled_ids))
+            possible_options = [['v1'], ['v2'], ['v1', 'v2']]
+            self.assertIn(
+                enabled_ids, possible_options,
+                'Failed, received version: {} is not in possible options'
+                ' list:{}'.format(enabled_ids, possible_options))
diff --git a/designate_tempest_plugin/tests/api/v2/test_ptrs.py b/designate_tempest_plugin/tests/api/v2/test_ptrs.py
index e2ad98a..0b0cddf 100644
--- a/designate_tempest_plugin/tests/api/v2/test_ptrs.py
+++ b/designate_tempest_plugin/tests/api/v2/test_ptrs.py
@@ -13,15 +13,23 @@
 # under the License.
 from oslo_log import log as logging
 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
+import testtools
 
 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 import data_utils as dns_data_utils
+
 import tempest.test
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
+TLD = dns_data_utils.rand_string(3)
+
 
 class BasePtrTest(base.BaseDnsV2Test):
     excluded_keys = ['created_at', 'updated_at', 'version', 'links',
@@ -29,7 +37,8 @@
 
 
 class DesignatePtrRecord(BasePtrTest, tempest.test.BaseTestCase):
-    credentials = ["primary"]
+
+    credentials = ['primary', 'admin', 'system_admin']
 
     @classmethod
     def setup_credentials(cls):
@@ -40,21 +49,38 @@
     @classmethod
     def setup_clients(cls):
         super(DesignatePtrRecord, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_ptr_client = cls.os_system_admin.dns_v2.PtrClient()
+        else:
+            cls.admin_ptr_client = cls.os_admin.dns_v2.PtrClient()
         cls.primary_ptr_client = cls.os_primary.dns_v2.PtrClient()
         cls.primary_floating_ip_client = cls.os_primary.floating_ips_client
 
-    def _set_ptr(self):
-        fip = self.primary_floating_ip_client.create_floatingip(
-            floating_network_id=CONF.network.public_network_id)['floatingip']
-        fip_id = fip['id']
-        self.addCleanup(self.primary_floating_ip_client.delete_floatingip,
-                        fip_id)
-        ptr = self.primary_ptr_client.set_ptr_record(fip_id)
-        self.addCleanup(self.primary_ptr_client.unset_ptr_record, fip_id)
+    def _set_ptr(self, ptr_name=None, ttl=None, description=None,
+                 headers=None, tld=TLD, fip_id=None):
+        if not fip_id:
+            fip = self.primary_floating_ip_client.create_floatingip(
+                floating_network_id=CONF.network.public_network_id)[
+                'floatingip']
+            fip_id = fip['id']
+            self.addCleanup(
+                self.primary_floating_ip_client.delete_floatingip, fip_id)
+        ptr = self.primary_ptr_client.set_ptr_record(
+            fip_id, ptr_name=ptr_name, ttl=ttl, description=description,
+            headers=headers, tld=tld)
+        self.addCleanup(self.unset_ptr, self.primary_ptr_client, fip_id)
+
         self.assertEqual('CREATE', ptr['action'])
         self.assertEqual('PENDING', ptr['status'])
+        waiters.wait_for_ptr_status(
+            self.primary_ptr_client, fip_id=fip_id, status=const.ACTIVE)
         return fip_id, ptr
 
+    def _unset_ptr(self, fip_id):
+        self.primary_ptr_client.unset_ptr_record(fip_id)
+        waiters.wait_for_ptr_status(
+            self.primary_ptr_client, fip_id=fip_id, status=const.INACTIVE)
+
     @decorators.idempotent_id('2fb9d6ea-871d-11eb-9f9a-74e5f9e2a801')
     def test_set_floatingip_ptr(self):
         self._set_ptr()
@@ -66,6 +92,19 @@
             floatingip_id=fip_id)
         self.assertExpected(ptr, show_ptr, self.excluded_keys)
 
+    @decorators.idempotent_id('d3128a92-e3bd-11eb-a097-74e5f9e2a801')
+    def test_show_floatingip_ptr_impersonate_another_project(self):
+        fip_id, ptr = self._set_ptr()
+
+        LOG.info('As Admin user, show PTR record created by Primary'
+                 ' user by including "x-auth-sudo-project-id" HTTP header'
+                 ' in HTTP request.')
+        show_ptr = self.admin_ptr_client.show_ptr_record(
+            floatingip_id=fip_id,
+            headers={
+                'x-auth-sudo-project-id': self.primary_ptr_client.project_id})
+        self.assertExpected(ptr, show_ptr, self.excluded_keys)
+
     @decorators.idempotent_id('9187a9c6-87d4-11eb-9f9a-74e5f9e2a801')
     def test_list_floatingip_ptr_records(self):
         number_of_ptr_records = 3
@@ -81,14 +120,35 @@
             'Failed - received PTR IDs: {} are not as'
             ' expected: {}'.format(created_ptr_ids, received_ptr_ids))
 
+    @decorators.idempotent_id('a108d6f2-e3c0-11eb-a097-74e5f9e2a801')
+    @decorators.skip_because(bug="1935977")
+    def test_list_floatingip_ptr_all_projects(self):
+        ptr = self._set_ptr()[1]
+        LOG.info('Created PTR is:{}'.format(ptr))
+
+        LOG.info('As Admin user, try to list PTR record for all projects '
+                 'by including "x-auth-all-projects" HTTP header.')
+        received_ptr_ids = [
+            item['id'] for item in self.admin_ptr_client.list_ptr_records(
+                headers={'x-auth-all-projects': True})]
+        self.assertGreater(
+            len(received_ptr_ids), 0,
+            'Failed, "received_ptr_ids" should not be empty')
+        self.assertIn(
+            ptr['id'], received_ptr_ids,
+            'Failed, expected ID was not found in "received_ptr_ids" list.')
+
     @decorators.idempotent_id('499b5a7e-87e1-11eb-b412-74e5f9e2a801')
+    @testtools.skipUnless(config.CONF.dns_feature_enabled.bug_1932026_fixed,
+                          'Skip unless bug 1932026 has been fixed.')
     def test_unset_floatingip_ptr(self):
         fip_id, ptr = self._set_ptr()
-        self.primary_ptr_client.unset_ptr_record(fip_id)
+        self._unset_ptr(fip_id)
 
 
 class DesignatePtrRecordNegative(BasePtrTest, tempest.test.BaseTestCase):
-    credentials = ["primary"]
+
+    credentials = ['primary', 'admin']
 
     @classmethod
     def setup_credentials(cls):
@@ -101,24 +161,79 @@
         super(DesignatePtrRecordNegative, cls).setup_clients()
         cls.primary_ptr_client = cls.os_primary.dns_v2.PtrClient()
         cls.primary_floating_ip_client = cls.os_primary.floating_ips_client
+        cls.admin_ptr_client = cls.os_admin.dns_v2.PtrClient()
 
     def _set_ptr(self, ptr_name=None, ttl=None, description=None,
-                 headers=None):
-        fip = self.primary_floating_ip_client.create_floatingip(
-            floating_network_id=CONF.network.public_network_id)[
-            'floatingip']
-        fip_id = fip['id']
-        self.addCleanup(self.primary_floating_ip_client.delete_floatingip,
-                        fip_id)
+                 headers=None, tld=TLD, fip_id=None):
+        if not fip_id:
+            fip = self.primary_floating_ip_client.create_floatingip(
+                floating_network_id=CONF.network.public_network_id)[
+                'floatingip']
+            fip_id = fip['id']
+            self.addCleanup(
+                self.primary_floating_ip_client.delete_floatingip, fip_id)
         ptr = self.primary_ptr_client.set_ptr_record(
             fip_id, ptr_name=ptr_name, ttl=ttl, description=description,
-            headers=headers)
-        self.addCleanup(self.primary_ptr_client.unset_ptr_record, fip_id)
+            headers=headers, tld=tld)
+        self.addCleanup(self.unset_ptr, self.primary_ptr_client, fip_id)
         self.assertEqual('CREATE', ptr['action'])
         self.assertEqual('PENDING', ptr['status'])
+        waiters.wait_for_ptr_status(
+            self.primary_ptr_client, fip_id=fip_id, status=const.ACTIVE)
         return fip_id, ptr
 
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('8392db50-cdd0-11eb-a00f-74e5f9e2a801')
     def test_set_floatingip_ptr_invalid_ttl(self):
         LOG.info('Try to set PTR record using invalid TTL value')
         with self.assertRaisesDns(lib_exc.BadRequest, 'invalid_object', 400):
             self._set_ptr(ttl=-10)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('0c9349ae-e2e8-11eb-a097-74e5f9e2a801')
+    def test_set_floatingip_ptr_not_existing_fip_id(self):
+        LOG.info('Try to set PTR record using not existing Floating IP')
+        with self.assertRaisesDns(lib_exc.NotFound, 'not_found', 404):
+            self._set_ptr(fip_id=data_utils.rand_uuid())
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('df217902-e3b2-11eb-a097-74e5f9e2a801')
+    def test_set_floatingip_ptr_huge_size_description(self):
+        LOG.info('Try to set PTR record using huge size description string')
+        with self.assertRaisesDns(lib_exc.BadRequest, 'invalid_object', 400):
+            self._set_ptr(description=dns_data_utils.rand_string(5000))
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('cb2264e2-e3b3-11eb-a097-74e5f9e2a801')
+    def test_set_floatingip_ptr_invalid_name(self):
+        invalid_names = ['', '@!(*&', 4564, dns_data_utils.rand_string(5000)]
+        for name in invalid_names:
+            LOG.info('Set PTR record using invalid name:{}'.format(name))
+            with self.assertRaisesDns(
+                    lib_exc.BadRequest, 'invalid_object', 400):
+                self._set_ptr(description=dns_data_utils.rand_string(5000))
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('f616d216-51ac-11ec-8edf-201e8823901f')
+    def test_show_floatingip_ptr_impersonate_another_project_no_header(self):
+        fip_id, ptr = self._set_ptr()
+
+        LOG.info('As Admin user, show PTR record created by Primary'
+                 ' user, without including "x-auth-sudo-project-id" '
+                 'HTTP header in request.')
+        with self.assertRaisesDns(lib_exc.NotFound, 'not_found', 404):
+            self.admin_ptr_client.show_ptr_record(floatingip_id=fip_id)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('0d132ff0-51ad-11ec-8edf-201e8823901f')
+    @decorators.skip_because(bug="1935977")
+    def test_list_floatingip_ptr_all_projects_no_header(self):
+        ptr = self._set_ptr()[1]
+        LOG.info('Created PTR is:{}'.format(ptr))
+
+        LOG.info('As Admin user, try to list PTR record for all projects '
+                 'without including "x-auth-all-projects" HTTP header.')
+        received_ptr_ids = [
+            item['id'] for item in self.admin_ptr_client.list_ptr_records()]
+        self.assertEqual([], received_ptr_ids,
+                         'Failed, "received_ptr_ids" list should be empty')
diff --git a/designate_tempest_plugin/tests/api/v2/test_quotas.py b/designate_tempest_plugin/tests/api/v2/test_quotas.py
index 9adf62e..0366843 100644
--- a/designate_tempest_plugin/tests/api/v2/test_quotas.py
+++ b/designate_tempest_plugin/tests/api/v2/test_quotas.py
@@ -15,6 +15,7 @@
 from tempest import config
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import data_utils as tempest_data_utils
 
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin import data_utils as dns_data_utils
@@ -22,6 +23,9 @@
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
+quotas_types = ["api_export_size", "recordset_records",
+                "zone_records", "zone_recordsets", "zones"]
+
 
 class QuotasV2Test(base.BaseDnsV2Test):
 
@@ -66,29 +70,33 @@
 
     @decorators.idempotent_id('1dac991a-9e2e-452c-a47a-26ac37381ec5')
     def test_show_quotas(self):
-        self._store_quotas(project_id=self.quotas_client.project_id)
-        LOG.info("Updating quotas")
-        quotas = dns_data_utils.rand_quotas()
-        _, body = self.admin_client.update_quotas(
-            project_id=self.quotas_client.project_id,
-            headers=self.all_projects_header,
-            **quotas)
-
-        LOG.info("Fetching quotas")
-        _, body = self.admin_client.show_quotas(
-            project_id=self.quotas_client.project_id,
-            headers=self.all_projects_header)
-
-        LOG.info("Ensuring the response has all quota types")
-        self.assertExpected(quotas, body, [])
+        LOG.info("Show default quotas, validate all quota types exists and "
+                 "their values are integers.")
+        for user in ['primary', 'admin']:
+            if user == 'primary':
+                body = self.quotas_client.show_quotas()[1]
+            if user == 'admin':
+                body = self.admin_client.show_quotas(
+                    project_id=self.quotas_client.project_id,
+                    headers=self.all_projects_header)[1]
+            for quota_type in quotas_types:
+                self.assertIn(
+                    quota_type, body.keys(),
+                    'Failed, expected quota type:{} was not found '
+                    'in received quota body'.format(quota_type))
+            for quota_type, quota_value in body.items():
+                self.assertTrue(
+                    isinstance(quota_value, int),
+                    'Failed, the value of:{} is:{}, expected integer'.format(
+                        quota_type, quota_value))
 
     @decorators.idempotent_id('0448b089-5803-4ce3-8a6c-5c15ff75a2cc')
-    def test_delete_quotas(self):
+    def test_reset_quotas(self):
         self._store_quotas(project_id=self.quotas_client.project_id)
-        LOG.info("Deleting quotas")
-        _, body = self.admin_client.delete_quotas(
+        LOG.info("Deleting (reset) quotas")
+        body = self.admin_client.delete_quotas(
             project_id=self.quotas_client.project_id,
-            headers=self.all_projects_header)
+            headers=self.all_projects_header)[1]
 
         LOG.info("Ensuring an empty response body")
         self.assertEqual(body.strip(), b"")
@@ -102,9 +110,9 @@
         self._store_quotas(project_id=self.admin_client.project_id)
         LOG.info("Updating quotas")
         quotas = dns_data_utils.rand_quotas()
-        _, body = self.admin_client.update_quotas(
+        body = self.admin_client.update_quotas(
             project_id=self.admin_client.project_id,
-            **quotas)
+            **quotas)[1]
 
         LOG.info("Ensuring the response has all quota types")
         self.assertExpected(quotas, body, [])
@@ -119,15 +127,15 @@
 
         quotas = dns_data_utils.rand_quotas()
         request = quotas.copy()
-        _, body = self.admin_client.update_quotas(
+        body = self.admin_client.update_quotas(
             project_id=project_id,
             headers=self.all_projects_header,
-            **request)
+            **request)[1]
 
         LOG.info("Ensuring the response has all quota types")
         self.assertExpected(quotas, body, [])
 
-        _, client_body = self.quotas_client.show_quotas(project_id=project_id)
+        client_body = self.quotas_client.show_quotas(project_id=project_id)[1]
 
         self.assertExpected(quotas, client_body, [])
 
@@ -143,15 +151,15 @@
             project_id=project_id,
             headers=self.all_projects_header)
 
-        _, default_quotas = self.admin_client.show_quotas(
+        default_quotas = self.admin_client.show_quotas(
             project_id=project_id,
-            headers=self.all_projects_header)
+            headers=self.all_projects_header)[1]
 
         LOG.info("Updating quotas for %s ", project_id)
 
         quotas = dns_data_utils.rand_quotas()
         request = quotas.copy()
-        _, body = self.admin_client.update_quotas(
+        self.admin_client.update_quotas(
             project_id=project_id,
             headers=self.all_projects_header,
             **request)
@@ -160,9 +168,9 @@
             project_id=project_id,
             headers=self.all_projects_header)
 
-        _, final_quotas = self.admin_client.show_quotas(
+        final_quotas = self.admin_client.show_quotas(
             project_id=project_id,
-            headers=self.all_projects_header)
+            headers=self.all_projects_header)[1]
 
         self.assertExpected(default_quotas, final_quotas, [])
 
@@ -172,11 +180,11 @@
         if not CONF.dns_feature_enabled.api_v2_quotas_verify_project:
             raise self.skipException("Project ID in quotas "
                                      "is not being verified.")
-
+        original_quotas = self.quotas_client.show_quotas(
+            project_id=self.quotas_client.project_id)[1]
         project_id = 'project-that-does-not-exist'
 
         LOG.info("Updating quotas for non-existing %s ", project_id)
-
         quotas = dns_data_utils.rand_quotas()
         request = quotas.copy()
         with self.assertRaisesDns(lib_exc.BadRequest, 'invalid_project', 400):
@@ -184,3 +192,116 @@
                 project_id=project_id,
                 headers=self.all_projects_header,
                 **request)
+
+        LOG.info("Make sure that the quotas weren't changed")
+        client_body = self.quotas_client.show_quotas(
+            project_id=self.quotas_client.project_id)[1]
+        self.assertExpected(original_quotas, client_body, [])
+
+
+class QuotasV2TestNegative(base.BaseDnsV2Test):
+
+    credentials = ["primary", "admin", "system_admin"]
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(QuotasV2TestNegative, cls).setup_credentials()
+
+    @classmethod
+    def skip_checks(cls):
+        super(QuotasV2TestNegative, cls).skip_checks()
+
+        if not CONF.dns_feature_enabled.api_v2_quotas:
+            skip_msg = ("%s skipped as designate V2 Quotas API is not "
+                        "available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(QuotasV2TestNegative, cls).setup_clients()
+
+        if CONF.enforce_scope.designate:
+            cls.admin_client = cls.os_system_admin.dns_v2.QuotasClient()
+        else:
+            cls.admin_client = cls.os_admin.dns_v2.QuotasClient()
+        cls.quotas_client = cls.os_primary.dns_v2.QuotasClient()
+
+    @decorators.idempotent_id('ae82a0ba-da60-11eb-bf12-74e5f9e2a801')
+    def test_admin_sets_quota_for_a_project(self):
+
+        primary_project_id = self.quotas_client.project_id
+        http_headers_to_use = [self.all_projects_header,
+            {'x-auth-sudo-project-id': primary_project_id}]
+
+        for http_header in http_headers_to_use:
+            LOG.info('As Admin user set Zones quota for a Primary user and {} '
+                     'HTTP header'.format(http_header))
+            quotas = dns_data_utils.rand_quotas()
+            self.admin_client.set_quotas(
+                project_id=primary_project_id,
+                quotas=quotas, headers=http_header)
+            self.addCleanup(self.admin_client.delete_quotas,
+                            project_id=primary_project_id)
+
+            LOG.info("As Admin fetch the quotas for a Primary user")
+            body = self.admin_client.show_quotas(
+                project_id=primary_project_id, headers=http_header)[1]
+            LOG.info('Ensuring that the "set" and "shown" quotas are same')
+            self.assertExpected(quotas, body, [])
+
+    @decorators.idempotent_id('40b9d7ac-da5f-11eb-bf12-74e5f9e2a801')
+    def test_primary_fails_to_set_quota(self):
+
+        primary_project_id = self.quotas_client.project_id
+        LOG.info('Try to set quota as Primary user')
+        self.assertRaises(
+            lib_exc.Forbidden, self.quotas_client.set_quotas,
+            project_id=primary_project_id,
+            quotas=dns_data_utils.rand_quotas())
+
+        LOG.info('Try to set quota as Primary user using '
+                 '"x-auth-sudo-project-id" HTTP header')
+        self.assertRaises(
+            lib_exc.Forbidden, self.quotas_client.set_quotas,
+            project_id=self.quotas_client.project_id,
+            quotas=dns_data_utils.rand_quotas(),
+            headers={'x-auth-sudo-project-id': primary_project_id})
+
+        LOG.info('Try to set quota as Primary user using '
+                 '"x-auth-all-projects" HTTP header')
+        self.assertRaises(
+            lib_exc.Forbidden, self.quotas_client.set_quotas,
+            project_id=self.quotas_client.project_id,
+            quotas=dns_data_utils.rand_quotas(),
+            headers=self.all_projects_header)
+
+    @decorators.idempotent_id('a6ce5b46-dcce-11eb-903e-74e5f9e2a801')
+    @decorators.skip_because(bug="1934596")
+    def test_admin_sets_invalid_quota_values(self):
+
+        primary_project_id = self.quotas_client.project_id
+
+        for item in quotas_types:
+            quota = dns_data_utils.rand_quotas()
+            quota[item] = tempest_data_utils.rand_name()
+            self.assertRaises(
+                lib_exc.BadRequest, self.admin_client.set_quotas,
+                project_id=primary_project_id,
+                quotas=quota,
+                headers=self.all_projects_header)
+
+    @decorators.idempotent_id('ac212fd8-c602-11ec-b042-201e8823901f')
+    def test_admin_sets_not_existing_quota_type(self):
+
+        LOG.info('Try to set quota using not existing quota type in its body')
+        primary_project_id = self.quotas_client.project_id
+        quota = dns_data_utils.rand_quotas()
+        quota[tempest_data_utils.rand_name()] = 777
+
+        with self.assertRaisesDns(
+                lib_exc.ServerFault, 'quota_resource_unknown', 500):
+            self.admin_client.set_quotas(
+                project_id=primary_project_id,
+                quotas=quota, headers=self.all_projects_header)
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
index 5184983..f6e9a4a 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -19,6 +19,8 @@
 import ddt
 
 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 import data_utils
 
@@ -36,7 +38,7 @@
 
         # All the recordset tests need a zone, create one to share
         LOG.info('Create a zone')
-        _, cls.zone = cls.zone_client.create_zone()
+        cls.zone = cls.zone_client.create_zone()[1]
 
     @classmethod
     def resource_cleanup(cls):
@@ -80,13 +82,16 @@
         LOG.info('Create a Recordset')
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
 
         LOG.info('Ensure we respond with PENDING')
-        self.assertEqual('PENDING', body['status'])
+        self.assertEqual(const.PENDING, body['status'])
 
-    @decorators.idempotent_id('d03b69a5-5052-43bc-a38a-b511b6b34304')
-    @ddt.file_data("recordset_data.json")
-    def test_create_all_recordset_types(self, name, type, records):
+    # We cannot use DDT here as these tests are part of the refstack
+    # interoperability test suite and need to be unique for traceability.
+    def _test_create_recordset_type(self, name, type, records):
         if name is not None:
             recordset_name = name + "." + self.zone['name']
 
@@ -102,9 +107,63 @@
         LOG.info('Create a Recordset')
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
 
         LOG.info('Ensure we respond with PENDING')
-        self.assertEqual('PENDING', body['status'])
+        self.assertEqual(const.PENDING, body['status'])
+
+    @decorators.idempotent_id('d03b69a5-5052-43bc-a38a-b511b6b34304')
+    def test_create_recordset_type_A(self):
+        self._test_create_recordset_type(
+            "www", "A", ["192.0.2.1", "192.0.2.2", "192.0.2.3"])
+
+    @decorators.idempotent_id('ac110198-d58a-4a18-aceb-414d7e513d49')
+    def test_create_recordset_type_AAAA(self):
+        self._test_create_recordset_type(
+            "www", "AAAA", ["2001:db8::1", "2001:db8::1", "2001:db8::"])
+
+    @decorators.idempotent_id('6c22a3f9-3f4d-4b32-bdf2-5237851ed25e')
+    def test_create_recordset_type_SRV_TCP(self):
+        self._test_create_recordset_type(
+            "_sip._tcp", "SRV", [
+                "10 60 5060 server1.example.{}.".format(self.tld_suffix),
+                "20 60 5060 server2.example.{}.".format(self.tld_suffix),
+                "20 30 5060 server3.example.{}.".format(self.tld_suffix)])
+
+    @decorators.idempotent_id('59c1aa42-278e-4f7b-a6a1-4320d5daf1fd')
+    def test_create_recordset_type_SRV_UDP(self):
+        self._test_create_recordset_type(
+            "_sip._udp", "SRV", [
+                "10 60 5060 server1.example.{}.".format(self.tld_suffix),
+                "10 60 5060 server2.example.{}.".format(self.tld_suffix),
+                "20 30 5060 server3.example.{}.".format(self.tld_suffix)])
+
+    @decorators.idempotent_id('1ac46f94-f03a-4f85-b84f-826a2660b927')
+    def test_create_recordset_type_CNAME(self):
+        self._test_create_recordset_type(
+            "alias-of-target", "CNAME", ["target.example.org."])
+
+    @decorators.idempotent_id('bf872487-7975-4a96-bb03-d24e393a0ce8')
+    def test_create_recordset_type_MX_at_APEX(self):
+        self._test_create_recordset_type(
+            None, "MX", ["10 mail1.example.org.", "20 mail2.example.org."])
+
+    @decorators.idempotent_id('96fe72a4-a81c-4a01-a81f-39ebafad115c')
+    def test_create_recordset_type_MX_under_APEX(self):
+        self._test_create_recordset_type(
+            "under", "MX", ["10 mail.example.org."])
+
+    @decorators.idempotent_id('481496f1-917a-40d5-89fd-4a3794c24215')
+    def test_create_recordset_type_SSHFP(self):
+        self._test_create_recordset_type(
+            "www", "SSHFP", ["2 1 123456789abcdef67890123456789abcdef67890"])
+
+    @decorators.idempotent_id('8e7ecedb-5c35-46f8-ae0e-39e4aaabc97d')
+    def test_create_recordset_type_TXT(self):
+        self._test_create_recordset_type(
+            "www", "TXT", ["\"Any Old Text Goes Here\""])
 
     @decorators.idempotent_id('69f002e5-6511-43d3-abae-7abdd45ae03e')
     @ddt.file_data("recordset_wildcard_data.json")
@@ -124,9 +183,12 @@
         LOG.info('Create a Recordset')
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
 
         LOG.info('Ensure we respond with PENDING')
-        self.assertEqual('PENDING', body['status'])
+        self.assertEqual(const.PENDING, body['status'])
 
     @decorators.idempotent_id('5964f730-5546-46e6-9105-5030e9c492b2')
     def test_list_recordsets(self):
@@ -136,9 +198,12 @@
         LOG.info('Create a Recordset')
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
 
         LOG.info('List zone recordsets')
-        _, body = self.client.list_recordset(self.zone['id'])
+        body = self.client.list_recordset(self.zone['id'])[1]
 
         self.assertGreater(len(body), 0)
 
@@ -150,9 +215,12 @@
         LOG.info('Create a Recordset')
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
 
         LOG.info('Re-Fetch the Recordset')
-        _, record = self.client.show_recordset(self.zone['id'], body['id'])
+        record = self.client.show_recordset(self.zone['id'], body['id'])[1]
 
         LOG.info('Ensure the fetched response matches the expected one')
         self.assertExpected(body, record, self.excluded_keys)
@@ -163,11 +231,14 @@
             record_type='A', zone_name=self.zone['name'])
 
         LOG.info('Create a Recordset')
-        _, record = self.client.create_recordset(
-            self.zone['id'], recordset_data)
+        record = self.client.create_recordset(
+            self.zone['id'], recordset_data)[1]
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], record['id'])
 
         LOG.info('Delete a Recordset')
-        _, body = self.client.delete_recordset(self.zone['id'], record['id'])
+        self.client.delete_recordset(self.zone['id'], record['id'])
 
         LOG.info('Ensure successful deletion of Recordset')
         self.assertRaises(lib_exc.NotFound,
@@ -179,15 +250,18 @@
             record_type='A', zone_name=self.zone['name'])
 
         LOG.info('Create a recordset')
-        _, record = self.client.create_recordset(
-            self.zone['id'], recordset_data)
+        record = self.client.create_recordset(
+            self.zone['id'], recordset_data)[1]
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], record['id'])
 
         recordset_data = data_utils.rand_recordset_data(
             record_type='A', zone_name=self.zone['name'], name=record['name'])
 
         LOG.info('Update the recordset')
-        _, update = self.client.update_recordset(self.zone['id'],
-            record['id'], recordset_data)
+        update = self.client.update_recordset(self.zone['id'],
+            record['id'], recordset_data)[1]
 
         self.assertEqual(record['name'], update['name'])
         self.assertNotEqual(record['records'], update['records'])
@@ -198,16 +272,19 @@
             record_type='A', zone_name=self.zone['name'])
 
         LOG.info('Create a recordset')
-        _, record = self.client.create_recordset(
-            self.zone['id'], recordset_data)
+        record = self.client.create_recordset(
+            self.zone['id'], recordset_data)[1]
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], record['id'])
 
         recordset_data = {
             'ttl': data_utils.rand_ttl(start=record['ttl'] + 1)
         }
 
         LOG.info('Update the recordset')
-        _, update = self.client.update_recordset(self.zone['id'],
-            record['id'], recordset_data)
+        update = self.client.update_recordset(self.zone['id'],
+            record['id'], recordset_data)[1]
 
         self.assertEqual(record['name'], update['name'])
         self.assertEqual(record['records'], update['records'])
@@ -222,12 +299,15 @@
             record_type='A', zone_name=self.zone['name'])
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
-        self.assertEqual('PENDING', body['status'],
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
+        self.assertEqual(const.PENDING, body['status'],
                          'Failed, expected status is PENDING')
         LOG.info('Wait until the recordset is active')
         waiters.wait_for_recordset_status(
             self.client, self.zone['id'],
-            body['id'], 'ACTIVE')
+            body['id'], const.ACTIVE)
 
         LOG.info('Re-Fetch the Recordset as Alt tenant with '
                  '"x-auth-sudo-project-id" HTTP header included in request. '
@@ -259,13 +339,19 @@
             record_type='A', zone_name=self.zone['name'])
         body_pr_1 = self.client.create_recordset(
             self.zone['id'], recordset_data_primary_1)[1]
-        self.assertEqual('PENDING', body_pr_1['status'],
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body_pr_1['id'])
+        self.assertEqual(const.PENDING, body_pr_1['status'],
                          'Failed, expected status is PENDING')
         recordset_data_primary_2 = data_utils.rand_recordset_data(
             record_type='A', zone_name=self.zone['name'])
         body_pr_2 = self.client.create_recordset(
             self.zone['id'], recordset_data_primary_2)[1]
-        self.assertEqual('PENDING', body_pr_2['status'],
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body_pr_2['id'])
+        self.assertEqual(const.PENDING, body_pr_2['status'],
                          'Failed, expected status is PENDING')
 
         LOG.info('Re-Fetch Recordsets as Alt tenant for a Primary project. '
@@ -298,6 +384,50 @@
                 'Failed, recordset ID:{} was not found in listed '
                 'recordsets: {}'.format(recordset_id, primary_recordsets_ids))
 
+    @decorators.idempotent_id('48013b7c-f526-11eb-b04f-74e5f9e2a801')
+    def test_create_A_recordset_multiply_ips(self):
+        LOG.info('Create A type Recordset using a list of random IPs')
+        recordset_data = data_utils.rand_a_recordset(
+            zone_name=self.zone['name'],
+            ips=[data_utils.rand_ip() for _ in range(10)])
+        resp, body = self.client.create_recordset(
+            self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
+        LOG.info('Ensure we respond with PENDING')
+        self.assertEqual(const.PENDING, body['status'])
+        LOG.info('Wait until the recordset is active')
+        waiters.wait_for_recordset_status(
+            self.client, self.zone['id'],
+            body['id'], const.ACTIVE)
+
+    @decorators.idempotent_id('f15e583e-e479-11eb-8e5a-74e5f9e2a801')
+    def test_delete_zone_with_existing_recordset(self):
+
+        LOG.info('Create a Zone')
+        zone = self.zone_client.create_zone(wait_until=const.ACTIVE)[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+
+        LOG.info('Create a Recordset')
+        recordset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+        record = self.client.create_recordset(
+            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]
+        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'])
+
+        LOG.info('Ensure successful deletion of Recordset')
+        self.assertRaises(lib_exc.NotFound,
+            lambda: self.client.show_recordset(zone['id'], record['id']))
+
 
 @ddt.ddt
 class RecordsetsNegativeTest(BaseRecordsetsTest):
@@ -340,7 +470,7 @@
     @decorators.idempotent_id('b6dad57e-5ce9-4fa5-8d66-aebbcd23b4ad')
     def test_get_nonexistent_recordset(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         LOG.info('Attempt to get an invalid Recordset')
@@ -351,7 +481,7 @@
     @decorators.idempotent_id('93d744a8-0dfd-4650-bcef-1e6ad632ad72')
     def test_get_nonexistent_recordset_invalid_id(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         LOG.info('Attempt to get an invalid Recordset')
@@ -361,7 +491,7 @@
     @decorators.idempotent_id('da08f19a-7f10-47cc-8b41-994507190812')
     def test_update_nonexistent_recordset(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         recordset_data = data_utils.rand_recordset_data('A', zone['name'])
@@ -375,7 +505,7 @@
     @decorators.idempotent_id('158340a1-3f69-4aaa-9968-956190563768')
     def test_update_nonexistent_recordset_invalid_id(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         recordset_data = data_utils.rand_recordset_data('A', zone['name'])
@@ -388,7 +518,7 @@
     @decorators.idempotent_id('64bd94d4-54bd-4bee-b6fd-92ede063234e')
     def test_delete_nonexistent_recordset(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         LOG.info('Attempt to delete an invalid Recordset')
@@ -400,7 +530,7 @@
     @decorators.idempotent_id('5948b599-a332-4dcb-840b-afc825075ba3')
     def test_delete_nonexistent_recordset_invalid_id(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         LOG.info('Attempt to get an invalid Recordset')
@@ -415,12 +545,15 @@
             record_type='A', zone_name=self.zone['name'])
         resp, body = self.client.create_recordset(
             self.zone['id'], recordset_data)
-        self.assertEqual('PENDING', body['status'],
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], body['id'])
+        self.assertEqual(const.PENDING, body['status'],
                          'Failed, expected status is PENDING')
         LOG.info('Wait until the recordset is active')
         waiters.wait_for_recordset_status(
             self.client, self.zone['id'],
-            body['id'], 'ACTIVE')
+            body['id'], const.ACTIVE)
 
         LOG.info('Ensure 404 NotFound status code is received if '
                  'recordset ID is invalid.')
@@ -473,7 +606,7 @@
     @decorators.idempotent_id('48a081b9-4474-4da0-9b1a-6359a80456ce')
     def test_list_zones_recordsets(self):
         LOG.info('List recordsets')
-        _, body = self.client.list_zones_recordsets()
+        body = self.client.list_zones_recordsets()[1]
 
         self.assertGreater(len(body['recordsets']), 0)
 
@@ -485,6 +618,9 @@
         LOG.info('Create a Recordset')
         resp, zone_recordset = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], zone_recordset['id'])
 
         self.client.show_zones_recordset(zone_recordset['id'])
 
@@ -496,9 +632,12 @@
         LOG.info('Create a Recordset')
         resp, zone_recordset = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], zone_recordset['id'])
 
         LOG.info('Create another zone')
-        _, zone2 = self.zone_client.create_zone()
+        zone2 = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone2['id'])
 
         LOG.info('Create another Recordset')
@@ -507,9 +646,12 @@
             records=['10.0.1.3'])
         resp, zone_recordset2 = self.client.create_recordset(
             zone2['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], zone_recordset2['id'])
 
         LOG.info('List recordsets')
-        _, body = self.client.list_zones_recordsets(params={"data": "10.0.*"})
+        body = self.client.list_zones_recordsets(params={"data": "10.0.*"})[1]
 
         recordsets = body['recordsets']
 
@@ -527,11 +669,11 @@
     @decorators.idempotent_id('7f4970bf-9aeb-4a3c-9afd-02f5a7178d35')
     def test_list_zones_recordsets_zone_names(self):
         LOG.info('Create another zone')
-        _, zone2 = self.zone_client.create_zone()
+        zone2 = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone2['id'])
 
         LOG.info('List recordsets')
-        _, body = self.client.list_zones_recordsets()
+        body = self.client.list_zones_recordsets()[1]
 
         recordsets = body['recordsets']
         zone_names = set()
@@ -577,19 +719,24 @@
                                 self.zone_client,
                                 zone['id'])
                 waiters.wait_for_zone_status(
-                    self.zone_client, zone['id'], 'ACTIVE')
+                    self.zone_client, zone['id'], const.ACTIVE)
 
                 # Create a recordset and wait till it's ACTIVE
                 recordset_data = data_utils.rand_recordset_data(
                     record_type='A', zone_name=zone['name'])
                 resp, body = self.client.create_recordset(
                     zone['id'], recordset_data)
-                self.assertEqual('PENDING', body['status'],
+
+                self.addCleanup(
+                    self.wait_recordset_delete, self.client,
+                    self.zone['id'], body['id'])
+                self.assertEqual(const.PENDING, body['status'],
                                  'Failed, expected status is PENDING')
+
                 LOG.info('Wait until the recordset is active')
                 waiters.wait_for_recordset_status(
                     self.client, zone['id'],
-                    body['id'], 'ACTIVE')
+                    body['id'], const.ACTIVE)
 
                 # Add "project_id" into the recordset_data
                 recordset_data['project_id'] = zone['project_id']
@@ -602,24 +749,30 @@
                                 self.alt_zone_client,
                                 alt_zone['id'])
                 waiters.wait_for_zone_status(
-                    self.alt_zone_client, alt_zone['id'], 'ACTIVE')
+                    self.alt_zone_client, alt_zone['id'], const.ACTIVE)
 
                 # Create a recordset and wait till it's ACTIVE
                 recordset_data = data_utils.rand_recordset_data(
                     record_type='A', zone_name=alt_zone['name'])
                 resp, body = self.alt_client.create_recordset(
                     alt_zone['id'], recordset_data)
-                self.assertEqual('PENDING', body['status'],
+
+                self.addCleanup(
+                    self.wait_recordset_delete, self.client,
+                    self.zone['id'], body['id'])
+                self.assertEqual(const.PENDING, body['status'],
                                  'Failed, expected status is PENDING')
+
                 LOG.info('Wait until the recordset is active')
                 waiters.wait_for_recordset_status(
                     self.alt_client, alt_zone['id'],
-                    body['id'], 'ACTIVE')
+                    body['id'], const.ACTIVE)
 
                 # Add "project_id" into the recordset_data
                 recordset_data['project_id'] = alt_zone['project_id']
                 recordsets_created['alt'] = recordset_data
 
+        LOG.info('Created resordsets are {}:'.format(recordsets_created))
         return recordsets_created
 
     @decorators.idempotent_id('9c0f58ad-1b31-4899-b184-5380720604e5')
@@ -629,6 +782,9 @@
             record_type='A', zone_name=self.zone['name'])
         resp, rrset = self.client.create_recordset(
             self.zone['id'], recordset_data)
+        self.addCleanup(
+            self.wait_recordset_delete, self.client,
+            self.zone['id'], rrset['id'])
         self.assertRaises(
             lib_exc.RestClientException,
             lambda: self.alt_client.create_recordset(
@@ -640,7 +796,7 @@
         zone_name = data_utils.rand_zone_name()
 
         LOG.info('Create a zone as a default user')
-        _, zone = self.zone_client.create_zone(name='a.b.' + zone_name)
+        zone = self.zone_client.create_zone(name='a.b.' + zone_name)[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         rrset_data = data_utils.rand_recordset_data(
@@ -654,8 +810,8 @@
 
     @decorators.idempotent_id('3dbe244d-fa85-4afc-869b-0306388d8746')
     def test_no_create_recordset_via_alt_domain(self):
-        _, zone = self.zone_client.create_zone()
-        _, alt_zone = self.alt_zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
+        alt_zone = self.alt_zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete,
                         self.zone_client,
                         zone['id'])
@@ -737,3 +893,59 @@
             {primary_project_id}, project_ids_api,
             'Failed, unique project_ids {} are not as expected {}'.format(
                 project_ids_api, primary_project_id))
+
+
+class AdminManagedRecordsetTest(BaseRecordsetsTest):
+
+    credentials = ["primary", "admin", "system_admin"]
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(AdminManagedRecordsetTest, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(AdminManagedRecordsetTest, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_client = cls.os_system_admin.dns_v2.RecordsetClient()
+        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):
+        # HTTP headers to be used in the test
+        sudo_header = {'X-Auth-All-Projects': True}
+        managed_records_header = {'X-Designate-Edit-Managed-Records': True}
+        sudo_managed_headers = sudo_header.copy()
+        sudo_managed_headers.update(managed_records_header)
+
+        LOG.info('Primary user creates a Zone')
+        zone = self.zone_client.create_zone(
+            description='Zone for "managed recordsets update" test',
+            wait_until=const.ACTIVE)[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+        recordsets = self.admin_client.list_recordset(
+            zone['id'], headers=sudo_header)[1]['recordsets']
+
+        LOG.info('As Admin try to update SOA and NS recordsets,'
+                 ' Expected not allowed')
+        for recordset in recordsets:
+            if recordset['type'] == 'NS':
+                self.assertRaisesDns(
+                    lib_exc.BadRequest, 'bad_request', 400,
+                    self.admin_client.update_recordset,
+                    zone['id'], recordset['id'],
+                    recordet_data=data_utils.rand_ns_records(),
+                    headers=sudo_managed_headers, extra_headers=True)
+
+            if recordset['type'] == 'SOA':
+                self.assertRaisesDns(
+                    lib_exc.BadRequest, 'bad_request', 400,
+                    self.admin_client.update_recordset,
+                    zone['id'], recordset['id'],
+                    recordet_data=data_utils.rand_soa_recordset(zone['name']),
+                    headers=sudo_managed_headers, extra_headers=True)
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 32db61a..bee364e 100644
--- a/designate_tempest_plugin/tests/api/v2/test_service_statuses.py
+++ b/designate_tempest_plugin/tests/api/v2/test_service_statuses.py
@@ -15,6 +15,8 @@
 from designate_tempest_plugin.common import constants as const
 from tempest import config
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
 
 from designate_tempest_plugin.tests import base
 
@@ -22,27 +24,31 @@
 LOG = logging.getLogger(__name__)
 
 
-class ServiceStatus(base.BaseDnsV2Test):
+class ServiceStatusAdmin(base.BaseDnsV2Test):
 
-    credentials = ["primary", "admin", "system_admin"]
+    credentials = ["admin", "system_admin"]
+
+    mandatory_services = ['central', 'mdns', 'worker', 'producer']
+    service_status_fields = [
+        'id', 'hostname', 'service_name', 'status', 'stats', 'capabilities',
+        'heartbeated_at', 'created_at', 'updated_at', 'links']
 
     @classmethod
     def setup_credentials(cls):
         # Do not create network resources for these test.
         cls.set_network_resources()
-        super(ServiceStatus, cls).setup_credentials()
+        super(ServiceStatusAdmin, cls).setup_credentials()
 
     @classmethod
     def setup_clients(cls):
-        super(ServiceStatus, cls).setup_clients()
+        super(ServiceStatusAdmin, cls).setup_clients()
         if CONF.enforce_scope.designate:
             cls.admin_client = cls.os_system_admin.dns_v2.ServiceClient()
         else:
             cls.admin_client = cls.os_admin.dns_v2.ServiceClient()
-        cls.client = cls.os_primary.dns_v2.ServiceClient()
 
     @decorators.idempotent_id('bf277a76-8583-11eb-a557-74e5f9e2a801')
-    def test_list_service_statuses(self):
+    def test_admin_list_service_statuses(self):
 
         services_statuses_tup = [
             (item['service_name'],
@@ -52,8 +58,8 @@
 
         LOG.info('Make sure that all expected/mandatory services are '
                  'listed in API response.')
-        expected_services = ['central', 'mdns', 'worker', 'producer']
-        for service in expected_services:
+
+        for service in self.mandatory_services:
             self.assertIn(
                 service, [item[0] for item in services_statuses_tup],
                 "Failed, expected service: {} wasn't detected in API "
@@ -64,3 +70,50 @@
             {const.UP}, set([item[1] for item in services_statuses_tup]),
             "Failed, not all listed services are in UP status, "
             "services: {}".format(services_statuses_tup))
+
+    @decorators.idempotent_id('fce0f704-c0ae-11ec-8213-201e8823901f')
+    def test_admin_show_service_status(self):
+
+        LOG.info('List services and get the IDs of mandatory services only')
+        services_ids = [
+            service['id'] for service in self.admin_client.list_statuses()
+            if service['service_name'] in self.mandatory_services]
+
+        LOG.info('Ensure all service status fields presents in response')
+        for id in services_ids:
+            service_show = self.admin_client.show_statuses(id)
+            self.assertEqual(
+                sorted(self.service_status_fields), sorted(service_show))
+
+
+class ServiceStatusNegative(base.BaseDnsV2Test):
+
+    credentials = ["primary", "alt"]
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(ServiceStatusNegative, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServiceStatusNegative, cls).setup_clients()
+        cls.primary_client = cls.os_primary.dns_v2.ServiceClient()
+        cls.alt_client = cls.os_alt.dns_v2.ServiceClient()
+
+    @decorators.idempotent_id('d4753f76-de43-11eb-91d1-74e5f9e2a801')
+    def test_primary_is_forbidden_to_list_service_statuses(self):
+
+        LOG.info('Try to "list service statuses" as Primary user')
+        self.assertRaises(
+            lib_exc.Forbidden, self.primary_client.list_statuses)
+
+        headers = [{'x-auth-all-projects': True},
+                   {'x-auth-sudo-project-id': self.alt_client.project_id}]
+        for header in headers:
+            LOG.info('Try to "list service statuses" using {} '
+                     'HTTP header'.format(header))
+            self.assertRaises(
+                lib_exc.Forbidden, self.primary_client.list_statuses,
+                headers=header)
diff --git a/designate_tempest_plugin/tests/api/v2/test_tld.py b/designate_tempest_plugin/tests/api/v2/test_tld.py
index 831c13c..11882a3 100644
--- a/designate_tempest_plugin/tests/api/v2/test_tld.py
+++ b/designate_tempest_plugin/tests/api/v2/test_tld.py
@@ -51,7 +51,7 @@
     def resource_setup(cls):
         super(TldAdminTest, cls).resource_setup()
         cls.tld = cls.admin_client.create_tld(
-            tld_name='com', ignore_errors=lib_exc.Conflict
+            tld_name=cls.tld_suffix, ignore_errors=lib_exc.Conflict
         )
 
     @classmethod
@@ -66,8 +66,8 @@
                      "description": "sample tld"}
 
         LOG.info('Create a tld')
-        _, tld = self.admin_client.create_tld(tld_data['name'],
-                                        tld_data['description'])
+        tld = self.admin_client.create_tld(tld_data['name'],
+                                        tld_data['description'])[1]
         self.addCleanup(self.admin_client.delete_tld, tld['id'])
 
         self.assertEqual(tld_data["name"], tld['name'])
@@ -116,21 +116,6 @@
             lib_exc.BadRequest, self.admin_client.create_tld,
             tld_name='org', description='test_create_invalid_tld' * 1000)
 
-    @decorators.idempotent_id('06deced8-d4de-11eb-b8ee-74e5f9e2a801')
-    def test_create_zone_for_not_existing_tld(self):
-        LOG.info('Create an "org" TLD')
-        tld_data = {"name": "org",
-                    "description": "test_create_zone_for_not_existing_tld"}
-        tld = self.admin_client.create_tld(
-            tld_data['name'], tld_data['description'])[1]
-        self.addCleanup(self.admin_client.delete_tld, tld['id'])
-        self.assertEqual(tld_data["name"], tld['name'])
-
-        LOG.info('Try to create a Primary zone with "zzz" (not existing) TLD.')
-        self.assertRaises(
-            lib_exc.BadRequest, self.primary_zone_client.create_zone,
-            name='example.zzz.')
-
     @decorators.idempotent_id('757019c0-d4e2-11eb-b8ee-74e5f9e2a801')
     def test_create_tld_as_primary_user(self):
         tld_data = {
@@ -147,12 +132,12 @@
                      "description": "sample tld"}
 
         LOG.info('Create a tld')
-        _, tld = self.admin_client.create_tld(tld_data['name'],
-                                        tld_data['description'])
+        tld = self.admin_client.create_tld(tld_data['name'],
+                                        tld_data['description'])[1]
         self.addCleanup(self.admin_client.delete_tld, tld['id'])
 
         LOG.info('Fetch the tld')
-        _, body = self.admin_client.show_tld(tld['id'])
+        body = self.admin_client.show_tld(tld['id'])[1]
 
         LOG.info('Ensure the fetched response matches the created tld')
         self.assertExpected(tld, body, self.excluded_keys)
@@ -160,12 +145,12 @@
     @decorators.idempotent_id('26708cb8-7126-48a7-9424-1c225e56e609')
     def test_delete_tld(self):
         LOG.info('Create a tld')
-        _, tld = self.admin_client.create_tld()
+        tld = self.admin_client.create_tld()[1]
         self.addCleanup(self.admin_client.delete_tld, tld['id'],
                         ignore_errors=lib_exc.NotFound)
 
         LOG.info('Delete the tld')
-        _, body = self.admin_client.delete_tld(tld['id'])
+        self.admin_client.delete_tld(tld['id'])
 
         self.assertRaises(lib_exc.NotFound,
            lambda: self.admin_client.show_tld(tld['id']))
@@ -173,13 +158,13 @@
     @decorators.idempotent_id('95b13759-c85c-4791-829b-9591ca15779d')
     def test_list_tlds(self):
         LOG.info('List tlds')
-        _, body = self.admin_client.list_tlds()
+        body = self.admin_client.list_tlds()[1]
 
         self.assertGreater(len(body['tlds']), 0)
 
     @decorators.idempotent_id('1a233812-48d9-4d15-af5e-9961744286ff')
     def test_update_tld(self):
-        _, tld = self.admin_client.create_tld()
+        tld = self.admin_client.create_tld()[1]
         self.addCleanup(self.admin_client.delete_tld, tld['id'])
 
         tld_data = {
@@ -188,8 +173,8 @@
         }
 
         LOG.info('Update the tld')
-        _, patch_tld = self.admin_client.update_tld(tld['id'],
-                       tld_data['name'], tld_data['description'])
+        patch_tld = self.admin_client.update_tld(tld['id'],
+                       tld_data['name'], tld_data['description'])[1]
 
         self.assertEqual(tld_data["name"], patch_tld["name"])
         self.assertEqual(tld_data["description"], patch_tld["description"])
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 794fafa..6c8ed07 100644
--- a/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
+++ b/designate_tempest_plugin/tests/api/v2/test_transfer_request.py
@@ -248,6 +248,37 @@
                           "Failed, transfer request ID:{} wasn't found in "
                           "listed IDs{}".format(request_id, request_ids))
 
+    @decorators.idempotent_id('bee42f38-e666-4b85-a710-01f40ea1e56a')
+    def test_list_transfer_requests_impersonate_another_project(self):
+        LOG.info('Create a Primary zone')
+        primary_zone = self.zone_client.create_zone()[1]
+        self.addCleanup(self.wait_zone_delete,
+                        self.zone_client, primary_zone['id'])
+
+        LOG.info('Create an Alt zone')
+        alt_zone = self.alt_zone_client.create_zone()[1]
+        self.addCleanup(self.wait_zone_delete,
+                        self.alt_zone_client, alt_zone['id'])
+
+        LOG.info('Create a zone transfer_request using Primary client')
+        primary_transfer_request = self.client.create_transfer_request(
+            primary_zone['id'])[1]
+        self.addCleanup(self.client.delete_transfer_request,
+                        primary_transfer_request['id'])
+
+        LOG.info('Create a zone transfer_request using Alt client')
+        alt_transfer_request = self.alt_client.create_transfer_request(
+            alt_zone['id'])[1]
+        self.addCleanup(self.alt_client.delete_transfer_request,
+                        alt_transfer_request['id'])
+
+        request_ids = [
+            item['id'] for item in self.admin_client.list_transfer_requests(
+                headers={'x-auth-sudo-project-id': self.alt_client.project_id},
+                params={'limit': 1000})[1]['transfer_requests']]
+
+        self.assertEqual([alt_transfer_request['id']], request_ids)
+
     @decorators.idempotent_id('de5e9d32-c723-4518-84e5-58da9722cc13')
     def test_update_transfer_request(self):
         LOG.info('Create a zone')
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py
index 0462d8b..f2faa88 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones.py
@@ -24,8 +24,6 @@
 from designate_tempest_plugin import data_utils as dns_data_utils
 from designate_tempest_plugin.tests import base
 
-from designate_tempest_plugin.common import waiters
-
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
@@ -52,6 +50,7 @@
         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()
 
     @decorators.idempotent_id('9d2e20fc-e56f-4a62-9c61-9752a9ec615c')
     def test_create_zones(self):
@@ -61,8 +60,8 @@
         self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
 
         LOG.info('Ensure we respond with CREATE+PENDING')
-        self.assertEqual('CREATE', zone['action'])
-        self.assertEqual('PENDING', zone['status'])
+        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]
@@ -75,8 +74,30 @@
         self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
 
         LOG.info('Ensure we respond with CREATE+PENDING')
-        self.assertEqual('CREATE', zone['action'])
-        self.assertEqual('PENDING', zone['status'])
+        self.assertEqual(const.CREATE, zone['action'])
+        self.assertEqual(const.PENDING, zone['status'])
+
+    @decorators.idempotent_id('ec150c22-f52e-11eb-b09b-74e5f9e2a801')
+    def test_create_zone_validate_recordsets_created(self):
+        # Create a PRIMARY zone and wait till it's Active
+        LOG.info('Create a PRIMARY zone')
+        zone = self.client.create_zone(wait_until=const.ACTIVE)[1]
+        self.addCleanup(self.wait_zone_delete, self.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('Ensure that SOA and NS recordsets types has been created.')
+        recordsets = self.recordset_client.list_recordset(
+            zone['id'])[1]['recordsets']
+        types = [rec['type'] for rec in recordsets]
+        expected_types = ['SOA', 'NS']
+        for exp_type in expected_types:
+            self.assertIn(
+                exp_type, types,
+                'Failed, expected recordset type:{} was'
+                ' not created'.format(exp_type))
 
     @decorators.idempotent_id('02ca5d6a-86ce-4f02-9d94-9e5db55c3055')
     def test_show_zone(self):
@@ -114,8 +135,8 @@
         _, body = self.client.delete_zone(zone['id'])
 
         LOG.info('Ensure we respond with DELETE+PENDING')
-        self.assertEqual('DELETE', body['action'])
-        self.assertEqual('PENDING', body['status'])
+        self.assertEqual(const.DELETE, body['action'])
+        self.assertEqual(const.PENDING, body['status'])
 
     @decorators.idempotent_id('79921370-92e1-11eb-9d02-74e5f9e2a801')
     def test_delete_non_existing_zone(self):
@@ -150,12 +171,53 @@
             zone['id'], description=description)
 
         LOG.info('Ensure we respond with UPDATE+PENDING')
-        self.assertEqual('UPDATE', zone['action'])
-        self.assertEqual('PENDING', zone['status'])
+        self.assertEqual(const.UPDATE, zone['action'])
+        self.assertEqual(const.PENDING, zone['status'])
 
         LOG.info('Ensure we respond with updated values')
         self.assertEqual(description, zone['description'])
 
+    @decorators.idempotent_id('3acddc86-62cc-4bfa-8589-b99e5d239bf2')
+    @decorators.skip_because(bug="1960487")
+    def test_serial_changes_on_update(self):
+        LOG.info('Create a zone')
+        zone = self.client.create_zone(wait_until=const.ACTIVE)[1]
+        self.addCleanup(self.wait_zone_delete, self.client, zone['id'])
+
+        LOG.info("Update Zone's email")
+        update_email = self.client.update_zone(
+            zone['id'], email=dns_data_utils.rand_email())[1]
+        self.assertNotEqual(
+            zone['serial'], update_email['serial'],
+            "Failed, expected: 'Serial' is supposed to be changed "
+            "on Email update.")
+
+        LOG.info("Update Zone's TTL")
+        update_ttl = self.client.update_zone(
+            zone['id'], ttl=dns_data_utils.rand_ttl())[1]
+        self.assertNotEqual(
+            update_email['serial'], update_ttl['serial'],
+            "Failed, expected: 'Serial' is supposed to be changed "
+            "on TTL update.")
+
+        LOG.info("Update Zone's email and description")
+        update_email_description = self.client.update_zone(
+            zone['id'],
+            email=dns_data_utils.rand_email(),
+            description=data_utils.rand_name())[1]
+        self.assertNotEqual(
+            update_ttl['serial'], update_email_description['serial'],
+            "Failed, expect the Serial to change "
+            "when the Email and Description are updated")
+
+        LOG.info("Update Zone's description")
+        update_description = self.client.update_zone(
+            zone['id'], description=data_utils.rand_name())[1]
+        self.assertEqual(
+            update_email_description['serial'], update_description['serial'],
+            "Failed, expect the Serial to not change "
+            "when the Description is updated")
+
     @decorators.idempotent_id('e391e30a-92e0-11eb-9d02-74e5f9e2a801')
     def test_update_non_existing_zone(self):
         LOG.info('Update non existing zone')
@@ -254,31 +316,21 @@
     def test_list_all_projects_zones(self):
 
         LOG.info('Create zone "A" using Primary client')
-        primary_zone = self.client.create_zone()[1]
+        primary_zone = self.client.create_zone(wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete, self.client, primary_zone['id'])
-        LOG.info('Wait till the zone is ACTIVE')
-        waiters.wait_for_zone_status(
-            self.client, primary_zone['id'], 'ACTIVE')
 
         LOG.info('Create zone "B" using Alt client')
-        alt_zone = self.alt_client.create_zone()[1]
+        alt_zone = self.alt_client.create_zone(wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete, self.alt_client, alt_zone['id'])
-        LOG.info('Wait till the zone is ACTIVE')
-        waiters.wait_for_zone_status(
-            self.alt_client, alt_zone['id'], 'ACTIVE')
 
         LOG.info('Create zone "C" using Admin client')
         admin_zone = self.admin_client.create_zone(
-            project_id="FakeProjectID")[1]
+            project_id="FakeProjectID", wait_until=const.ACTIVE)[1]
         self.addCleanup(
             self.wait_zone_delete, self.admin_client, admin_zone['id'],
             headers=self.all_projects_header)
-        LOG.info('Wait till the zone is ACTIVE')
-        waiters.wait_for_zone_status(
-            self.admin_client, admin_zone['id'], 'ACTIVE',
-            headers=self.all_projects_header)
 
         LOG.info('As admin user list all projects zones')
         # Note: This is an all-projects list call, so other tests running
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 4841d18..31da506 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
@@ -16,8 +16,11 @@
 from tempest import config
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import data_utils
 
 from designate_tempest_plugin.tests import base
+from designate_tempest_plugin.common import waiters
+from designate_tempest_plugin.common import constants as const
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -49,32 +52,32 @@
         cls.client = cls.os_primary.dns_v2.ZoneExportsClient()
         cls.alt_client = cls.os_alt.dns_v2.ZoneExportsClient()
 
-    @decorators.idempotent_id('2dd8a9a0-98a2-4bf6-bb51-286583b30f40')
-    def test_create_zone_export(self):
+    def _create_zone_export(self):
         LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
+        zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
 
         LOG.info('Create a zone export')
-        _, zone_export = self.client.create_zone_export(zone['id'])
+        zone_export = self.client.create_zone_export(zone['id'])[1]
         self.addCleanup(self.client.delete_zone_export, zone_export['id'])
+        waiters.wait_for_zone_export_status(
+            self.client, zone_export['id'], const.COMPLETE)
+        return zone, zone_export
+
+    @decorators.idempotent_id('2dd8a9a0-98a2-4bf6-bb51-286583b30f40')
+    def test_create_zone_export(self):
+        zone_export = self._create_zone_export()[1]
 
         LOG.info('Ensure we respond with PENDING')
-        self.assertEqual('PENDING', zone_export['status'])
+        self.assertEqual(const.PENDING, zone_export['status'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('2d29a2a9-1941-4b7e-9d8a-ad6c2140ea68')
     def test_show_zone_export(self):
-        LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
-
-        LOG.info('Create a zone export')
-        resp, zone_export = self.client.create_zone_export(zone['id'])
-        self.addCleanup(self.client.delete_zone_export, zone_export['id'])
+        zone_export = self._create_zone_export()[1]
 
         LOG.info('Re-Fetch the zone export')
-        _, body = self.client.show_zone_export(zone_export['id'])
+        body = self.client.show_zone_export(zone_export['id'])[1]
 
         LOG.info('Ensure the fetched response matches the zone export')
         self.assertExpected(zone_export, body, self.excluded_keys)
@@ -116,20 +119,16 @@
         _, body = self.client.delete_zone_export(zone_export['id'])
 
         LOG.info('Ensure the zone export has been successfully deleted')
-        self.assertRaises(lib_exc.NotFound,
-            lambda: self.client.show_zone_export(zone_export['id']))
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.client.show_zone_export, zone_export['id'])
 
     @decorators.idempotent_id('476bfdfe-58c8-46e2-b376-8403c0fff440')
     def test_list_zone_exports(self):
-        LOG.info('Create a zone')
-        _, zone = self.zone_client.create_zone()
-        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
-
-        _, export = self.client.create_zone_export(zone['id'])
-        self.addCleanup(self.client.delete_zone_export, export['id'])
+        self._create_zone_export()[1]
 
         LOG.info('List zone exports')
-        _, body = self.client.list_zone_exports()
+        body = self.client.list_zone_exports()[1]
 
         self.assertGreater(len(body['exports']), 0)
 
@@ -183,8 +182,10 @@
         alt_export = self.alt_client.create_zone_export(alt_zone['id'])[1]
         self.alt_client.delete_zone_export(alt_export['id'])
         LOG.info('Ensure the zone export has been successfully deleted')
-        self.assertRaises(lib_exc.NotFound,
-            lambda: self.alt_client.show_zone_export(alt_export['id']))
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alt_client.show_zone_export,
+            alt_export['id'])
 
         LOG.info('Filter out "export zones" in status:ZAHLABUT,'
                  ' expected: empty list')
@@ -236,6 +237,18 @@
         cls.client = cls.os_primary.dns_v2.ZoneExportsClient()
         cls.alt_client = cls.os_alt.dns_v2.ZoneExportsClient()
 
+    def _create_zone_export(self):
+        LOG.info('Create a zone')
+        zone = self.zone_client.create_zone()[1]
+        self.addCleanup(self.wait_zone_delete, self.zone_client, zone['id'])
+
+        LOG.info('Create a zone export')
+        zone_export = self.client.create_zone_export(zone['id'])[1]
+        self.addCleanup(self.client.delete_zone_export, zone_export['id'])
+        waiters.wait_for_zone_export_status(
+            self.client, zone_export['id'], const.COMPLETE)
+        return zone, zone_export
+
     @decorators.idempotent_id('76ab8ec4-95fd-11eb-b1cd-74e5f9e2a801')
     def test_create_zone_export_using_invalid_zone_id(self):
         self.assertRaises(
@@ -260,7 +273,6 @@
         zone = self.zone_client.create_zone()[1]
         self.addCleanup(self.wait_zone_delete, self.zone_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'])
@@ -268,3 +280,21 @@
         LOG.info('Ensure we respond with NotFound exception')
         self.assertRaises(
             lib_exc.NotFound, self.client.create_zone_export, zone['id'])
+
+    @decorators.idempotent_id('9a878646-f66b-4fa4-ae95-f3ac3f8e3d31')
+    def test_show_zonefile_using_not_existing_zone_export_id(self):
+        LOG.info('Expected: 404 Not Found zone export')
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.show_exported_zonefile,
+                          data_utils.rand_uuid())
+
+    @decorators.idempotent_id('52a1fee0-c338-4ed9-b9f9-41ee7fd73375')
+    def test_show_zonefile_not_supported_accept_value(self):
+        zone, zone_export = self._create_zone_export()
+        # Tempest-lib _error_checker will raise UnexpectedResponseCode
+        e = self.assertRaises(
+            lib_exc.UnexpectedResponseCode, self.client.show_exported_zonefile,
+            zone_export['id'], headers={'Accept': 'image/jpeg'})
+        self.assertEqual(406, e.resp.status,
+                         "Failed, actual response code is:{0}"
+                         "but expected is: 406".format(e.resp.status))
diff --git a/designate_tempest_plugin/tests/base.py b/designate_tempest_plugin/tests/base.py
index 82d18fd..28b43cb 100644
--- a/designate_tempest_plugin/tests/base.py
+++ b/designate_tempest_plugin/tests/base.py
@@ -124,6 +124,21 @@
                               zone_client,
                               zone_id)
 
+    def wait_recordset_delete(self, recordset_client, zone_id,
+                              recordset_id, **kwargs):
+        self._delete_recordset(
+            recordset_client, zone_id, recordset_id, **kwargs)
+        utils.call_until_true(self._check_recordset_deleted,
+                              CONF.dns.build_timeout,
+                              CONF.dns.build_interval,
+                              recordset_client,
+                              zone_id,
+                              recordset_id)
+
+    def unset_ptr(self, ptr_client, fip_id, **kwargs):
+        return utils.call_and_ignore_notfound_exc(
+            ptr_client.unset_ptr_record, fip_id, **kwargs)
+
     def _delete_zone(self, zone_client, zone_id, **kwargs):
         return utils.call_and_ignore_notfound_exc(zone_client.delete_zone,
                                                   zone_id, **kwargs)
@@ -132,11 +147,23 @@
         return utils.call_and_ignore_notfound_exc(zone_client.show_zone,
                                                   zone_id) is None
 
+    def _delete_recordset(self, recordset_client, zone_id,
+                          recordset_id, **kwargs):
+        return utils.call_and_ignore_notfound_exc(
+            recordset_client.delete_recordset,
+            zone_id, recordset_id, **kwargs)
+
+    def _check_recordset_deleted(
+            self, recordset_client, zone_id, recordset_id):
+        return utils.call_and_ignore_notfound_exc(
+            recordset_client.show_recordset, zone_id, recordset_id) is None
+
 
 class BaseDnsV2Test(BaseDnsTest):
     """Base class for DNS V2 API tests."""
 
     all_projects_header = {'X-Auth-All-Projects': True}
+    tld_suffix = CONF.dns.tld_suffix
 
     @classmethod
     def skip_checks(cls):
diff --git a/designate_tempest_plugin/tests/api/v2/recordset_data.json b/designate_tempest_plugin/tests/scenario/v2/recordset_data.json
similarity index 62%
rename from designate_tempest_plugin/tests/api/v2/recordset_data.json
rename to designate_tempest_plugin/tests/scenario/v2/recordset_data.json
index 9f365a7..3168722 100644
--- a/designate_tempest_plugin/tests/api/v2/recordset_data.json
+++ b/designate_tempest_plugin/tests/scenario/v2/recordset_data.json
@@ -48,5 +48,37 @@
         "name": "www",
         "type": "TXT",
         "records": ["\"Any Old Text Goes Here\""]
+    },
+    "SPF": {
+        "name": "*.sub",
+        "type": "SPF",
+        "records": ["\"v=spf1; a -all\""]
+    },
+    "NS": {
+        "name": "NS_Record",
+        "type": "NS",
+        "records": ["ns1.example.org."]
+    },
+    "PTR_IPV4": {
+        "name": "PTR_Record_IPV4",
+        "type": "PTR",
+        "records": ["34.216.184.93.in-addr.arpa."]
+    },
+   "PTR_IPV6":{
+       "name":"PTR_Record_IPV6",
+       "type":"PTR",
+       "records":[
+           "6.4.9.1.8.c.5.2.3.9.8.1.8.4.2.0.1.0.0.0.0.2.2.0.0.0.8.2.6.0.6.2.ip6.arpa."
+      ]
+   },
+   "CAA_Record": {
+       "name": "CAA_Record",
+       "type": "CAA",
+       "records": ["0 issue letsencrypt.org"]
+    },
+   "NAPTR_Record": {
+       "name": "NAPTR_Record",
+       "type": "NAPTR",
+       "records": ["0 0 S SIP+D2U !^.*$!sip:customer-service@example.com! _sip._udp.example.com."]
     }
-}
+}
\ No newline at end of file
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py b/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py
new file mode 100644
index 0000000..27b3f7f
--- /dev/null
+++ b/designate_tempest_plugin/tests/scenario/v2/test_blacklists.py
@@ -0,0 +1,111 @@
+# Copyright 2021 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 tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from designate_tempest_plugin import data_utils as dns_data_utils
+from designate_tempest_plugin.tests import base
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class BaseBlacklistsTest(base.BaseDnsV2Test):
+    excluded_keys = ['created_at', 'updated_at', 'links']
+
+
+class BlacklistE2E(BaseBlacklistsTest):
+
+    credentials = ["admin", 'primary', 'system_admin']
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(BlacklistE2E, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(BlacklistE2E, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_blacklist_client = (
+                cls.os_system_admin.dns_v2.BlacklistsClient())
+            cls.admin_zone_client = cls.os_system_admin.dns_v2.ZonesClient()
+        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):
+        LOG.info('Create a blacklist using regex')
+        blacklist = {
+            'pattern': '^blacklistregextest.*',
+            'description': 'Zone starts with "blacklistregextest" char'}
+        body = self.admin_blacklist_client.create_blacklist(**blacklist)[1]
+        self.addCleanup(
+            self.admin_blacklist_client.delete_blacklist, body['id'])
+
+        LOG.info('Try to create a zone that is starts with '
+                 '"blacklistregextest".')
+        self.assertRaisesDns(
+            lib_exc.BadRequest, 'invalid_zone_name', 400,
+            self.primary_zone_client.create_zone,
+            name='blacklistregextest' + dns_data_utils.rand_zone_name())
+
+    @decorators.idempotent_id('6956f20c-d8d5-11eb-bcdc-74e5f9e2a801')
+    def test_primary_fails_to_create_zone_matches_blacklist_name(self):
+        LOG.info('Create a blacklist using the exact name(string)')
+        zone_name = 'blacklistnametest' + dns_data_utils.rand_zone_name()
+        blacklist = {
+            'pattern': zone_name,
+            'description': 'Zone named:{} '.format(zone_name)}
+        body = self.admin_blacklist_client.create_blacklist(**blacklist)[1]
+        self.addCleanup(
+            self.admin_blacklist_client.delete_blacklist, body['id'])
+
+        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)
+
+    @decorators.idempotent_id('de030088-d97e-11eb-8ab8-74e5f9e2a801')
+    def test_admin_creates_zone_matches_blacklist_name_or_regex(self):
+        LOG.info('Create a blacklists using: regex and exact string(name)')
+        zone_name = 'blacklistnameregextest1' + dns_data_utils.rand_zone_name()
+        blacklists = [
+            {'pattern': '^blacklistnameregextest2.*',
+             'description': 'Zone starts with "a" char'},
+            {'pattern': zone_name,
+             'description': 'Deny if Zone named:{} '.format(zone_name)}]
+        for blacklist in blacklists:
+            body = self.admin_blacklist_client.create_blacklist(**blacklist)[1]
+            self.addCleanup(
+                self.admin_blacklist_client.delete_blacklist, body['id'])
+
+        LOG.info('As Admin user try to create zones that are '
+                 'supposed to be blocked')
+        zone = self.admin_zone_client.create_zone(
+            name='blacklistnameregextest2' +
+                 dns_data_utils.rand_zone_name(),
+            project_id=self.primary_zone_client.project_id)[1]
+        self.addCleanup(
+            self.wait_zone_delete, self.admin_zone_client, zone['id'])
+        zone = self.admin_zone_client.create_zone(
+            name=zone_name, project_id=self.primary_zone_client.project_id)[1]
+        self.addCleanup(
+            self.wait_zone_delete, self.admin_zone_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
new file mode 100644
index 0000000..0c20b3d
--- /dev/null
+++ b/designate_tempest_plugin/tests/scenario/v2/test_quotas.py
@@ -0,0 +1,92 @@
+# Copyright 2021 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 tempest import config
+from tempest.lib import decorators
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin import data_utils as dns_data_utils
+
+LOG = logging.getLogger(__name__)
+
+
+CONF = config.CONF
+
+
+class QuotasV2Test(base.BaseDnsV2Test):
+
+    credentials = ['primary', 'admin', 'system_admin', 'alt']
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(QuotasV2Test, cls).setup_credentials()
+
+    @classmethod
+    def skip_checks(cls):
+        super(QuotasV2Test, cls).skip_checks()
+
+        if not CONF.dns_feature_enabled.api_v2_quotas:
+            skip_msg = ("%s skipped as designate V2 Quotas API is not "
+                        "available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(QuotasV2Test, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_client = cls.os_system_admin.dns_v2.QuotasClient()
+        else:
+            cls.admin_client = cls.os_admin.dns_v2.QuotasClient()
+        cls.quotas_client = cls.os_primary.dns_v2.QuotasClient()
+        cls.alt_client = cls.os_alt.dns_v2.QuotasClient()
+        cls.alt_zone_client = cls.os_alt.dns_v2.ZonesClient()
+
+    @decorators.idempotent_id('6987953a-dccf-11eb-903e-74e5f9e2a801')
+    def test_alt_reaches_zones_quota(self):
+
+        alt_project_id = self.alt_client.project_id
+        http_header = {'x-auth-sudo-project-id': alt_project_id}
+        limit_zones_quota = 3
+
+        LOG.info('As Admin user set Zones quota for Alt user '
+                 'to:{} '.format(limit_zones_quota))
+        quotas = dns_data_utils.rand_quotas()
+        quotas['zones'] = limit_zones_quota
+        self.admin_client.set_quotas(
+            project_id=alt_project_id, quotas=quotas, headers=http_header)
+        self.addCleanup(
+            self.admin_client.delete_quotas, project_id=alt_project_id)
+
+        LOG.info('As Alt user try to create zones, up untill'
+                 ' "zones" quota (status code 413) is reached')
+        attempt_number = 0
+        while attempt_number <= limit_zones_quota + 1:
+            attempt_number += 1
+            LOG.info('Attempt No:{} '.format(attempt_number))
+            try:
+                zone = self.alt_zone_client.create_zone()[1]
+                self.addCleanup(
+                    self.wait_zone_delete, self.alt_zone_client, zone['id'])
+            except Exception as err:
+                raised_error = str(err).replace(' ', '')
+                if not "'code':413" and "'type':'over_quota'" in raised_error \
+                        and attempt_number == limit_zones_quota + 1:
+                    raise (
+                        "Failed, expected status code 413 (type:over_quota) "
+                        "was not raised or maybe it has been raised mistakenly"
+                        "(bug) before the quota was actually reached."
+                        " Test failed with: {} ".format(err))
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
index def1150..4c40c28 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_recordsets.py
@@ -27,10 +27,16 @@
 
 @ddt.ddt
 class RecordsetsTest(base.BaseDnsV2Test):
+
+    credentials = ["admin", "system_admin", "primary"]
+
     @classmethod
     def setup_clients(cls):
         super(RecordsetsTest, cls).setup_clients()
-
+        if CONF.enforce_scope.designate:
+            cls.admin_client = cls.os_system_admin.dns_v2.RecordsetClient()
+        else:
+            cls.admin_client = cls.os_admin.dns_v2.RecordsetClient()
         cls.client = cls.os_primary.dns_v2.ZonesClient()
         cls.recordset_client = cls.os_primary.dns_v2.RecordsetClient()
 
@@ -56,7 +62,7 @@
 
     @decorators.attr(type='slow')
     @decorators.idempotent_id('4664ed66-9ff1-45f2-9e60-d4913195c505')
-    @ddt.file_data("../../api/v2/recordset_data.json")
+    @ddt.file_data("recordset_data.json")
     def test_create_and_delete_records_on_existing_zone(self, name,
                                                         type, records):
         if name is not None:
@@ -98,3 +104,21 @@
         self.assertRaises(lib_exc.NotFound,
                           lambda: self.recordset_client.show_recordset(
                               self.zone['id'], recordset['id']))
+
+    @decorators.idempotent_id('1e78a742-66ee-11ec-8dc3-201e8823901f')
+    def test_create_soa_record_not_permitted(self):
+        # SOA record is automatically created for a zone, no user
+        # should be able to create a SOA record.
+        soa_record = ("s1.devstack.org. admin.example.net. 1510721487 3510"
+                      " 600 86400 3600")
+        LOG.info('Primary tries to create a Recordset on '
+                 'the existing zone')
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.recordset_client.create_recordset,
+            self.zone['id'], soa_record)
+        LOG.info('Admin tries to create a Recordset on the existing zone')
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.admin_client.create_recordset,
+            self.zone['id'], soa_record)
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_tld.py b/designate_tempest_plugin/tests/scenario/v2/test_tld.py
new file mode 100644
index 0000000..9c048a1
--- /dev/null
+++ b/designate_tempest_plugin/tests/scenario/v2/test_tld.py
@@ -0,0 +1,80 @@
+# Copyright 2021 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 tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from designate_tempest_plugin.common import constants as const
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin import data_utils as dns_data_utils
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class TldZoneTest(base.BaseDnsV2Test):
+    credentials = ["admin", "system_admin", "primary"]
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(TldZoneTest, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(TldZoneTest, cls).setup_clients()
+        if CONF.enforce_scope.designate:
+            cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient()
+        else:
+            cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
+        cls.primary_tld_client = cls.os_primary.dns_v2.TldClient()
+        cls.primary_zone_client = cls.os_primary.dns_v2.ZonesClient()
+
+    @classmethod
+    def resource_setup(cls):
+        super(TldZoneTest, cls).resource_setup()
+        cls.tld = cls.admin_tld_client.create_tld(
+            tld_name=cls.tld_suffix, ignore_errors=lib_exc.Conflict
+        )
+
+    @classmethod
+    def resource_cleanup(cls):
+        cls.admin_tld_client.delete_tld(cls.tld[1]['id'])
+        super(TldZoneTest, cls).resource_cleanup()
+
+    @decorators.idempotent_id('68b3e7cc-bf0e-11ec-b803-201e8823901f')
+    def test_create_zone_using_existing_tld(self):
+        LOG.info('Creates a zone using existing TLD:"{}"'.format(
+            self.tld_suffix))
+        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(
+            name=zone_name, wait_until=const.ACTIVE)[1]
+        self.addCleanup(
+            self.wait_zone_delete, self.primary_zone_client, zone['id'])
+
+    @decorators.idempotent_id('06deced8-d4de-11eb-b8ee-74e5f9e2a801')
+    def test_create_zone_using_not_existing_tld(self):
+        LOG.info('Try to create a Zone using not existing TLD:"{}"'.format(
+            self.tld_suffix[::-1]))
+        zone_name = dns_data_utils.rand_zone_name(
+            name='not_existing_tld_zone', prefix='rand',
+            suffix='.{}.'.format(self.tld_suffix)[::-1])
+        self.assertRaises(
+            lib_exc.BadRequest, self.primary_zone_client.create_zone,
+            name=zone_name)
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 916e634..f094498 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_zones_export.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones_export.py
@@ -12,49 +12,65 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import json
+import os
 from oslo_log import log as logging
+from tempest import config
 from tempest.lib import decorators
-
 from designate_tempest_plugin.common import waiters
 from designate_tempest_plugin.tests.api.v2.test_zones_exports import \
     BaseZoneExportsTest
+from designate_tempest_plugin.common import constants as const
 
+CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
 
 class ZonesExportTest(BaseZoneExportsTest):
+    credentials = ["primary", "admin", "system_admin"]
+
+    @classmethod
+    def setup_credentials(cls):
+        # Do not create network resources for these test.
+        cls.set_network_resources()
+        super(ZonesExportTest, cls).setup_credentials()
 
     @classmethod
     def setup_clients(cls):
         super(ZonesExportTest, cls).setup_clients()
-
+        if CONF.enforce_scope.designate:
+            cls.admin_client = cls.os_system_admin.dns_v2.ZoneExportsClient()
+        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()
 
-    @decorators.attr(type='slow')
-    @decorators.idempotent_id('0484c3c4-df57-458e-a6e5-6eb63e0475e0')
-    def test_create_zone_export_and_show_exported_zonefile(self):
-        LOG.info('Create a zone to be exported')
-        _, zone = self.zones_client.create_zone()
+    def _create_zone_export(self):
+        LOG.info('Create a zone')
+        zone = self.zones_client.create_zone()[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'])
+        zone_export = self.client.create_zone_export(
+            zone['id'], wait_until=const.COMPLETE)[1]
         self.addCleanup(self.client.delete_zone_export, zone_export['id'])
 
-        self.assertEqual('PENDING', zone_export['status'])
+        return zone, zone_export
+
+    @decorators.idempotent_id('0484c3c4-df57-458e-a6e5-6eb63e0475e0')
+    def test_create_zone_export_and_show_exported_zonefile(self):
+        zone, zone_export = self._create_zone_export()
+
+        self.assertEqual(const.PENDING, zone_export['status'])
         self.assertEqual(zone['id'], zone_export['zone_id'])
         self.assertIsNone(zone_export['links'].get('export'))
         self.assertIsNone(zone_export['location'])
 
-        LOG.info('Wait for the zone export to COMPLETE')
-        waiters.wait_for_zone_export_status(
-            self.client, zone_export['id'], 'COMPLETE')
-
         LOG.info('Check the zone export looks good')
         _, zone_export = self.client.show_zone_export(zone_export['id'])
 
-        self.assertEqual('COMPLETE', zone_export['status'])
+        self.assertEqual(const.COMPLETE, zone_export['status'])
         self.assertEqual(zone['id'], zone_export['zone_id'])
         self.assertIsNotNone(zone_export['links'].get('export'))
         self.assertIsNotNone(zone_export['location'])
@@ -63,3 +79,109 @@
         _, zonefile = self.client.show_exported_zonefile(zone_export['id'])
         self.assertEqual(zone['name'], zonefile.origin)
         self.assertEqual(zone['ttl'], zonefile.ttl)
+
+    @decorators.idempotent_id('56b8f30e-cd45-4c7a-bc0c-bbf92d7dc697')
+    def test_show_exported_zonefile_impersonate_another_project(self):
+        zone, zone_export = self._create_zone_export()
+
+        LOG.info('As Admin impersonate "primary" client,'
+                 ' to show exported zone file')
+        response = self.admin_client.show_exported_zonefile(
+            zone_export['id'], headers={
+                'x-auth-sudo-project-id': zone['project_id']})[1]
+        self.assertEqual(zone['name'], response.origin)
+        self.assertEqual(zone['ttl'], response.ttl)
+
+    @decorators.idempotent_id('c2e55514-ff2e-41d9-a3cc-9e78873254c9')
+    def test_show_exported_zonefile_all_projects(self):
+        zone, zone_export = self._create_zone_export()
+        resp_headers, resp_data = self.admin_client.show_exported_zonefile(
+            zone_export['id'], headers={
+                'x-auth-all-projects': True
+            })
+        self.assertEqual(zone['name'], resp_data.origin)
+        self.assertEqual(zone['ttl'], resp_data.ttl)
+
+    @decorators.idempotent_id('9746b7f2-2df4-448c-8a85-5ab6bf74f1fe')
+    def test_show_exported_zonefile_any_mime_type(self):
+        zone, zone_export = self._create_zone_export()
+        resp_headers, resp_data = self.client.show_exported_zonefile(
+            zone_export['id'], headers={'Accept': '*/*'})
+
+        LOG.info('Ensure Content-Type: text/dns')
+        self.assertIn(
+            'text/dns', resp_headers['content-type'],
+            "Failed, the expected 'Content-type:text/dns wasn't received.")
+
+        LOG.info('Ensure exported data ia as expected')
+        self.assertEqual(zone['name'], resp_data.origin)
+        self.assertEqual(zone['ttl'], resp_data.ttl)
+
+    @decorators.idempotent_id('dc7a9dde-d287-4e22-9788-26578f0d3bf0')
+    def test_missing_accept_headers(self):
+        zone, zone_export = self._create_zone_export()
+        resp_headers, resp_data = self.client.show_exported_zonefile(
+            zone_export['id'], headers={})
+        LOG.info('Ensure Content-Type: text/dns')
+        self.assertIn(
+            'text/dns', resp_headers['content-type'],
+            "Failed, the expected 'Content-type:text/dns wasn't received.")
+
+        LOG.info('Ensure exported data ia as expected')
+        self.assertEqual(zone['name'], resp_data.origin)
+        self.assertEqual(zone['ttl'], resp_data.ttl)
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('d8f444aa-a645-4a03-b366-46836f57dc69')
+    def test_all_recordset_types_exist_in_show_zonefile(self):
+        recorsets_data_file = os.path.join(
+            os.path.dirname(__file__), 'recordset_data.json')
+
+        if not os.path.exists(recorsets_data_file):
+            raise self.skipException(
+                f"Could not find {recorsets_data_file}")
+
+        file = open(recorsets_data_file, "r")
+        load_file = json.loads(file.read())
+        file.close()
+
+        LOG.info('Create a zone')
+        zone = self.zones_client.create_zone(wait_until=const.ACTIVE)[1]
+        self.addCleanup(self.wait_zone_delete, self.zones_client, zone['id'])
+
+        created_records = []
+        for record_data in load_file.values():
+            recordset_data = {
+                'name': f"{record_data['name']}.{zone['name']}",
+                'type': record_data['type'],
+                'records': record_data['records'],
+            }
+            try:
+                LOG.info('Create a Recordset')
+                recordset = self.recordset_client.create_recordset(
+                    zone['id'], recordset_data)[1]
+                self.addCleanup(self.wait_recordset_delete,
+                                self.recordset_client, zone['id'],
+                                recordset['id'])
+                created_records.append(recordset['records'])
+                waiters.wait_for_recordset_status(self.recordset_client,
+                                                  zone['id'], recordset['id'],
+                                                  const.ACTIVE)
+            except Exception as err:
+                LOG.warning(f"Record of type {recordset['type']} could not be"
+                            f" created and failed with error: {err}")
+
+        LOG.info('Create a zone export')
+        zone_export = self.client.create_zone_export(
+            zone['id'], wait_until=const.COMPLETE)[1]
+        self.addCleanup(self.client.delete_zone_export, zone_export['id'])
+
+        LOG.info('Show exported zonefile')
+        created_zonefile = self.client.show_exported_zonefile(
+            zone_export['id'])[1]
+
+        file_records = [item.data for item in created_zonefile.records]
+        for record in created_records:
+            for r in record:
+                self.assertIn(r, file_records,
+                            f"Failed, missing record: {r} in zone file")
diff --git a/setup.cfg b/setup.cfg
index 3cf22d8..728e365 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,12 @@
 [metadata]
 name = designate-tempest-plugin
 summary = OpenStack DNS As A Service (Designate) Functional Tests
-description-file =
+description_file =
     README.rst
 author = OpenStack
-author-email = openstack-discuss@lists.openstack.org
-home-page = https://docs.openstack.org/designate-tempest-plugin/latest/
-python-requires = >=3.6
+author_email = openstack-discuss@lists.openstack.org
+home_page = https://docs.openstack.org/designate-tempest-plugin/latest/
+python_requires = >=3.6
 classifier =
     Environment :: OpenStack
     Environment :: No Input/Output (Daemon)
diff --git a/tox.ini b/tox.ini
index f1581bc..a413aeb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
-minversion = 3.1.1
-envlist = py37,pep8
+minversion = 3.18.0
+envlist = pep8
 skipsdist = True
 ignore_basepython_conflict = True
 
@@ -9,7 +9,7 @@
 usedevelop = True
 install_command = pip install {opts} {packages}
 deps =
-       -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
+       -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
        -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
 setenv =