Merge "Changes in "test_quotas.py" API script"
diff --git a/designate_tempest_plugin/common/constants.py b/designate_tempest_plugin/common/constants.py
index 4f6c72b..1111c72 100644
--- a/designate_tempest_plugin/common/constants.py
+++ b/designate_tempest_plugin/common/constants.py
@@ -12,14 +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/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 4b5d24d..61c6da0 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -35,7 +35,7 @@
     return an.format(netaddr.ipv6_compact)
 
 
-def rand_zone_name(name='', prefix='rand', 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"
@@ -43,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
 
@@ -67,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'
@@ -105,11 +106,11 @@
 
 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:
@@ -126,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:
@@ -202,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)
 
 
@@ -216,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='')
@@ -225,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
     """
 
@@ -255,7 +267,6 @@
     """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...)
     """
@@ -266,7 +277,6 @@
 
 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
     """
@@ -275,7 +285,6 @@
 
 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
     """
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index d484ac9..e1107ab 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -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/json/recordset_client.py b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
index 33e9ee2..2f12417 100644
--- a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
@@ -59,7 +59,8 @@
 
     @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 +69,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/tests/api/v2/test_ptrs.py b/designate_tempest_plugin/tests/api/v2/test_ptrs.py
index b094a5d..0b0cddf 100644
--- a/designate_tempest_plugin/tests/api/v2/test_ptrs.py
+++ b/designate_tempest_plugin/tests/api/v2/test_ptrs.py
@@ -16,6 +16,7 @@
 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
@@ -67,7 +68,8 @@
         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.primary_ptr_client.unset_ptr_record, fip_id)
+        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(
@@ -77,7 +79,7 @@
     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.DELETED)
+            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):
@@ -137,7 +139,8 @@
             'Failed, expected ID was not found in "received_ptr_ids" list.')
 
     @decorators.idempotent_id('499b5a7e-87e1-11eb-b412-74e5f9e2a801')
-    @decorators.skip_because(bug="1932026")
+    @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._unset_ptr(fip_id)
@@ -172,7 +175,7 @@
         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.primary_ptr_client.unset_ptr_record, fip_id)
+        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(
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
index 38a2dfd..c9e17ed 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -38,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):
@@ -127,16 +127,18 @@
     @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.com.",
-                                 "20 60 5060 server2.example.com.",
-                                 "20 30 5060 server3.example.com."])
+            "_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.com.",
-                                 "10 60 5060 server2.example.com.",
-                                 "20 30 5060 server3.example.com."])
+            "_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):
@@ -201,7 +203,7 @@
             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)
 
@@ -218,7 +220,7 @@
             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)
@@ -229,14 +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,
@@ -248,8 +250,8 @@
             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'])
@@ -258,8 +260,8 @@
             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'])
@@ -270,8 +272,8 @@
             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'])
@@ -281,8 +283,8 @@
         }
 
         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'])
@@ -442,7 +444,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')
@@ -453,7 +455,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')
@@ -463,7 +465,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'])
@@ -477,7 +479,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'])
@@ -490,7 +492,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')
@@ -502,7 +504,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')
@@ -578,7 +580,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)
 
@@ -609,7 +611,7 @@
             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')
@@ -623,7 +625,7 @@
             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']
 
@@ -641,11 +643,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()
@@ -763,7 +765,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(
@@ -777,8 +779,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'])
@@ -860,3 +862,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_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_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py
index dec5028..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__)
 
@@ -62,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]
@@ -76,22 +74,19 @@
         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()[1]
+        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('CREATE', zone['action'])
-        self.assertEqual('PENDING', zone['status'])
-
-        LOG.info('Wait till the zone is Active')
-        waiters.wait_for_zone_status(self.client, zone['id'], 'ACTIVE')
+        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(
@@ -140,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):
@@ -176,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')
@@ -280,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/base.py b/designate_tempest_plugin/tests/base.py
index 2e02a8c..28b43cb 100644
--- a/designate_tempest_plugin/tests/base.py
+++ b/designate_tempest_plugin/tests/base.py
@@ -135,6 +135,10 @@
                               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)
@@ -159,6 +163,7 @@
     """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/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)