Merge "Add tests for wildcard RRSets"
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index d85d343..5a8136c 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -13,9 +13,7 @@
 # under the License.
 from tempest import clients
 from tempest import config
-from tempest.lib.auth import KeystoneAuthProvider
-from tempest.lib.auth import KeystoneV2AuthProvider
-from tempest.lib.auth import KeystoneV3AuthProvider
+from tempest.lib import auth
 
 from designate_tempest_plugin.services.dns.v1.json.domains_client import \
     DomainsClient
@@ -158,7 +156,7 @@
             return KeystoneV2UnauthedProvider
 
 
-class BaseUnauthedProvider(KeystoneAuthProvider):
+class BaseUnauthedProvider(auth.KeystoneAuthProvider):
 
     def _decorate_request(self, filters, method, url, headers=None, body=None,
                           auth_data=None):
@@ -173,13 +171,15 @@
         return url, headers, body
 
 
-class KeystoneV2UnauthedProvider(KeystoneV2AuthProvider, BaseUnauthedProvider):
+class KeystoneV2UnauthedProvider(auth.KeystoneV2AuthProvider,
+                                 BaseUnauthedProvider):
 
     def _decorate_request(self, *args, **kwargs):
         return BaseUnauthedProvider._decorate_request(self, *args, **kwargs)
 
 
-class KeystoneV3UnauthedProvider(KeystoneV3AuthProvider, BaseUnauthedProvider):
+class KeystoneV3UnauthedProvider(auth.KeystoneV3AuthProvider,
+                                 BaseUnauthedProvider):
 
     def _decorate_request(self, *args, **kwargs):
         return BaseUnauthedProvider._decorate_request(self, *args, **kwargs)
diff --git a/designate_tempest_plugin/services/dns/admin/json/quotas_client.py b/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
index 08b1790..fb233d2 100644
--- a/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
+++ b/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
@@ -11,13 +11,10 @@
 # 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 designate_tempest_plugin import data_utils as dns_data_utils
 from designate_tempest_plugin.services.dns.admin.json import base
 
-LOG = logging.getLogger(__name__)
-
 
 class QuotasClient(base.DnsClientAdminBase):
 
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index 19dbe45..6c5f1d2 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -20,7 +20,7 @@
 from six.moves.urllib import parse as urllib
 import six
 
-from designate_tempest_plugin.common.models import ZoneFile
+from designate_tempest_plugin.common import models
 
 LOG = logging.getLogger(__name__)
 
@@ -66,7 +66,7 @@
         if 'application/json' in resp['content-type']:
             return json.loads(object_str)
         elif 'text/dns' in resp['content-type']:
-            return ZoneFile.from_text(object_str)
+            return models.ZoneFile.from_text(object_str)
         else:
             raise lib_exc.InvalidContentType()
 
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 3522a1d..df2cdee 100644
--- a/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/quotas_client.py
@@ -11,13 +11,10 @@
 # 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 designate_tempest_plugin import data_utils as dns_data_utils
 from designate_tempest_plugin.services.dns.v2.json import base
 
-LOG = logging.getLogger(__name__)
-
 
 class QuotasClient(base.DnsClientV2Base):
 
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 3363e0c..28ec56a 100644
--- a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
@@ -26,7 +26,7 @@
 
         :param zone_uuid: Unique identifier of the zone in UUID format..
         :param recordset_data: A dictionary that represents the recordset
-                                data.
+                               data.
         :param params: A Python dict that represents the query paramaters to
                        include in the request URI.
         :return: A tuple with the server response and the created zone.
@@ -44,6 +44,28 @@
         return resp, body
 
     @base.handle_errors
+    def update_recordset(self, zone_uuid, recordset_uuid,
+                         recordet_data, params=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
+                               format.
+        :param recordset_data: A dictionary that represents the recordset
+                               data.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :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)
+
+        # Update Recordset should Return a HTTP 202
+        self.expected_success(202, resp.status)
+
+        return resp, body
+
+    @base.handle_errors
     def show_recordset(self, zone_uuid, recordset_uuid, params=None):
         """Gets a specific recordset related to a specific zone.
         :param zone_uuid: Unique identifier of the zone in UUID format.
@@ -87,6 +109,24 @@
             'zones/{0}/recordsets'.format(uuid), params=params)
 
     @base.handle_errors
+    def show_zones_recordset(self, recordset_uuid, params=None):
+        """Gets a single recordset, using the cross_zone endpoint
+        :param recordset_uuid: Unique identifier of the recordset in UUID
+                               format.
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: A tuple with the server response and the response body.
+        """
+        resp, body = self._show_request(
+            'recordsets', recordset_uuid,
+            params=params)
+
+        # Show recordsets/id should return a HTTP 301
+        self.expected_success(301, resp.status)
+
+        return resp, body
+
+    @base.handle_errors
     def list_zones_recordsets(self, params=None):
         """List recordsets across all zones.
         :param params: A Python dict that represents the query paramaters to
@@ -95,23 +135,3 @@
         """
         return self._list_request(
             'recordsets', params=params)
-
-    @base.handle_errors
-    def update_recordset(self, zone_uuid, recordset_uuid,
-                         recordset_model, params=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 records in UUID format.
-        :param recordset_model: .
-        :param params: A Python dict that represents the query paramaters to
-                       include in the request URI.
-        :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=recordset_model, params=params)
-
-        # Update Recordset should Return a HTTP 202
-        self.expected_success(202, resp.status)
-
-        return resp, body
diff --git a/designate_tempest_plugin/tests/api/v2/test_quotas.py b/designate_tempest_plugin/tests/api/v2/test_quotas.py
index 9411270..bd865bb 100644
--- a/designate_tempest_plugin/tests/api/v2/test_quotas.py
+++ b/designate_tempest_plugin/tests/api/v2/test_quotas.py
@@ -75,7 +75,7 @@
         LOG.info("Ensuring the response has all quota types")
         self.assertExpected(quotas, body, [])
 
-    @decorators.idempotent_id('76d24c87-1b39-4e19-947c-c08e1380dc61')
+    @decorators.idempotent_id('9b09b3e2-7e88-4569-bce3-9be2f7ac70c3')
     def test_update_quotas_other_project(self):
 
         project_id = self.quotas_client.tenant_id
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
index 44ac45b..d686d94 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -209,7 +209,7 @@
         cls.client = cls.os.recordset_client
         cls.zone_client = cls.os.zones_client
 
-    @decorators.idempotent_id('631d74fd-6909-4684-a61b-5c4d2f92c3e7')
+    @decorators.idempotent_id('98c94f8c-217a-4056-b996-b1f856d0753e')
     @ddt.file_data("recordset_data_invalid.json")
     def test_create_recordset_invalid(self, name, type, records):
         if name is not None:
@@ -325,17 +325,22 @@
 
         self.assertGreater(len(body['recordsets']), 0)
 
+    @decorators.skip_because(bug="1616892")
+    @decorators.idempotent_id('65ec0495-81d9-4cfb-8007-9d93b32ae883')
+    def test_get_single_zones_recordsets(self):
+        recordset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=self.zone['name'], records=['10.1.0.2'])
+
+        LOG.info('Create a Recordset')
+        resp, zone_recordset = self.client.create_recordset(
+            self.zone['id'], recordset_data)
+
+        self.client.show_zones_recordset(zone_recordset['id'])
+
     @decorators.idempotent_id('a8e41020-65be-453b-a8c1-2497d539c345')
     def test_list_filter_zones_recordsets(self):
-        recordset_data = {
-            "name": self.zone['name'],
-            "description": "This is an example record set.",
-            "type": "A",
-            "ttl": 3600,
-            "records": [
-                "10.1.0.2"
-            ]
-        }
+        recordset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=self.zone['name'], records=['10.0.1.2'])
 
         LOG.info('Create a Recordset')
         resp, zone_recordset = self.client.create_recordset(
@@ -345,9 +350,109 @@
         _, zone2 = self.zone_client.create_zone()
         self.addCleanup(self.zone_client.delete_zone, zone2['id'])
 
+        LOG.info('Create another Recordset')
+        recordset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone2['name'],
+            records=['10.0.1.3'])
+        resp, zone_recordset2 = self.client.create_recordset(
+            zone2['id'], recordset_data)
+
         LOG.info('List recordsets')
-        _, body = self.client.list_zones_recordsets(params={"data": "10.1.*"})
+        _, body = self.client.list_zones_recordsets(params={"data": "10.0.*"})
 
         recordsets = body['recordsets']
 
-        self.assertEqual(zone_recordset['id'], recordsets[0]['id'])
+        ids = [r['id'] for r in recordsets]
+        self.assertIn(zone_recordset['id'], ids)
+        self.assertIn(zone_recordset2['id'], ids)
+        # Ensure that every rrset has a record with the filtered data
+        for r in recordsets:
+            one_record_has_data = False
+            for record in r['records']:
+                if record.startswith('10.0.'):
+                    one_record_has_data = True
+            self.assertTrue(one_record_has_data)
+
+    @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()
+        self.addCleanup(self.zone_client.delete_zone, zone2['id'])
+
+        LOG.info('List recordsets')
+        _, body = self.client.list_zones_recordsets()
+
+        recordsets = body['recordsets']
+        zone_names = set()
+        for r in recordsets:
+            zone_names.add(r['zone_name'])
+
+        self.assertGreaterEqual(len(zone_names), 2)
+
+
+class RecordsetOwnershipTest(BaseRecordsetsTest):
+
+    credentials = ['primary', 'alt']
+
+    @classmethod
+    def setup_clients(cls):
+        super(RecordsetOwnershipTest, cls).setup_clients()
+
+        cls.client = cls.os.recordset_client
+        cls.zone_client = cls.os.zones_client
+        cls.alt_zone_client = cls.os_alt.zones_client
+        cls.alt_client = cls.os_alt.recordset_client
+
+    @decorators.idempotent_id('9c0f58ad-1b31-4899-b184-5380720604e5')
+    def test_no_create_recordset_by_alt_tenant(self):
+        # try with name=A123456.zone.com.
+        recordset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=self.zone['name'])
+        resp, rrset = self.client.create_recordset(
+            self.zone['id'], recordset_data)
+        self.assertRaises(
+            lib_exc.RestClientException,
+            lambda: self.alt_client.create_recordset(
+                self.zone['id'], recordset_data)
+        )
+
+    @decorators.idempotent_id('d4a9aad9-c778-429b-9a0c-4cd2b61a0a01')
+    def test_no_create_super_recordsets(self):
+        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)
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        rrset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone_name)
+
+        LOG.info('Create a zone as an alt user with existing superdomain')
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alt_client.create_recordset,
+            self.zone['id'], rrset_data)
+
+    @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()
+
+        # alt attempts to create record with name A12345.{zone}
+        recordset_data = data_utils.rand_recordset_data(
+            record_type='A', zone_name=zone['name'])
+
+        self.assertRaises(
+            lib_exc.RestClientException,
+            lambda: self.alt_client.create_recordset(
+                zone['id'],
+                recordset_data
+            )
+        )
+        self.assertRaises(
+            lib_exc.RestClientException,
+            lambda: self.alt_client.create_recordset(
+                alt_zone['id'],
+                recordset_data
+            )
+        )
diff --git a/designate_tempest_plugin/tests/api/v2/test_unauthed.py b/designate_tempest_plugin/tests/api/v2/test_unauthed.py
index e788a3b..b48cfd7 100644
--- a/designate_tempest_plugin/tests/api/v2/test_unauthed.py
+++ b/designate_tempest_plugin/tests/api/v2/test_unauthed.py
@@ -17,7 +17,7 @@
 import ddt
 
 from designate_tempest_plugin.tests import base
-from designate_tempest_plugin.clients import ManagerV2Unauthed
+from designate_tempest_plugin import clients
 
 LOG = logging.getLogger(__name__)
 
@@ -25,7 +25,7 @@
 @ddt.ddt
 class TestDnsUnauthed(base.BaseDnsV2Test):
 
-    client_manager = ManagerV2Unauthed
+    client_manager = clients.ManagerV2Unauthed
     credentials = ["primary"]
 
     @classmethod
diff --git a/designate_tempest_plugin/tests/scenario/v2/test_zones.py b/designate_tempest_plugin/tests/scenario/v2/test_zones.py
index c98a551..ccbfc6c 100644
--- a/designate_tempest_plugin/tests/scenario/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/scenario/v2/test_zones.py
@@ -87,6 +87,7 @@
         waiters.wait_for_zone_404(self.client, zone['id'])
 
     @test.attr(type='slow')
+    @decorators.skip_because(bug='1623576')
     @decorators.idempotent_id('ad8d1f5b-da66-46a0-bbee-14dc84a5d791')
     @config.skip_unless_config('dns', 'nameservers')
     def test_zone_create_propagates_to_nameservers(self):
@@ -98,6 +99,7 @@
         waiters.wait_for_query(self.query_client, zone['name'], "SOA")
 
     @test.attr(type='slow')
+    @decorators.skip_because(bug='1623576')
     @decorators.idempotent_id('d13d3095-c78f-4aae-8fe3-a74ccc335c84')
     @config.skip_unless_config('dns', 'nameservers')
     def test_zone_delete_propagates_to_nameservers(self):