Merge "Skip nameserver propagation tests when no NS's in conf"
diff --git a/designate_tempest_plugin/config.py b/designate_tempest_plugin/config.py
index c1dc79f..5b99e81 100644
--- a/designate_tempest_plugin/config.py
+++ b/designate_tempest_plugin/config.py
@@ -38,7 +38,7 @@
                default=1,
                help="Time in seconds between build status checks."),
     cfg.IntOpt('build_timeout',
-               default=60,
+               default=120,
                help="Timeout in seconds to wait for an resource to build."),
     cfg.IntOpt('min_ttl',
                default=1,
@@ -68,4 +68,7 @@
     cfg.BoolOpt('api_v1_servers',
                 default=False,
                 help="Is the v1 dns servers API enabled."),
+    cfg.BoolOpt('api_v2_root_recordsets',
+                default=False,
+                help="Is the v2 root recordsets API enabled."),
 ]
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 3bc9418..00b49de 100644
--- a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
+++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
@@ -87,6 +87,16 @@
             'zones/{0}/recordsets'.format(uuid), params=params)
 
     @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
+                       include in the request URI.
+        :return: Serialized recordset as a list.
+        """
+        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.
diff --git a/designate_tempest_plugin/tests/api/v2/recordset_data.json b/designate_tempest_plugin/tests/api/v2/recordset_data.json
new file mode 100644
index 0000000..e9483b8
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/recordset_data.json
@@ -0,0 +1,57 @@
+{
+    "A": {
+        "name": "www",
+        "type": "A",
+        "records": ["192.0.2.1", "192.0.2.2", "192.0.2.3"]
+    },
+    "AAAA": {
+        "name": "www",
+        "type": "AAAA",
+        "records": ["2001:db8::1", "2001:db8::1", "2001:db8::"]
+    },
+    "SRV TCP": {
+        "name": "_sip._tcp",
+        "type": "SRV",
+        "records": ["10 60 5060 server1.example.com.",
+                    "20 60 5060 server2.example.com.",
+                    "20 30 5060 server3.example.com."]
+    },
+    "SRV UDP": {
+        "name": "_sip._udp",
+        "type": "SRV",
+        "records": ["10 60 5060 server1.example.com.",
+                    "10 60 5060 server2.example.com.",
+                    "20 30 5060 server3.example.com."]
+    },
+    "CNAME": {
+        "name": "www",
+        "type": "CNAME",
+        "records": ["target.example.org."]
+    },
+    "MX at APEX": {
+        "name": null,
+        "type": "MX",
+        "records": ["10 mail.example.org."]
+    },
+    "MX at APEX multiple": {
+        "name": null,
+        "type": "MX",
+        "records": ["10 mail1.example.org.",
+                    "20 mail2.example.org."]
+    },
+    "MX under APEX": {
+        "name": "under",
+        "type": "MX",
+        "records": ["10 mail.example.org."]
+    },
+    "SSHFP": {
+        "name": "www",
+        "type": "SSHFP",
+        "records": ["2 1 123456789abcdef67890123456789abcdef67890"]
+    },
+    "TXT": {
+        "name": "www",
+        "type": "TXT",
+        "records": ["Any Old Text Goes Here"]
+    }
+}
diff --git a/designate_tempest_plugin/tests/api/v2/recordset_data_invalid.json b/designate_tempest_plugin/tests/api/v2/recordset_data_invalid.json
new file mode 100644
index 0000000..413698f
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/recordset_data_invalid.json
@@ -0,0 +1,12 @@
+{
+    "CNAME multiple": {
+        "name": "www",
+        "type": "CNAME",
+        "records": ["target1.example.org.", "target2.example.org."]
+    },
+    "CNAME at Apex": {
+        "name": null,
+        "type": "CNAME",
+        "records": ["target1.example.org."]
+    }
+}
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
index 4e0f0fb..5d45f01 100644
--- a/designate_tempest_plugin/tests/api/v2/test_recordset.py
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -12,20 +12,25 @@
 #    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 import test
 from tempest.lib import exceptions as lib_exc
+import ddt
 
 from designate_tempest_plugin.tests import base
 from designate_tempest_plugin import data_utils
 
 LOG = logging.getLogger(__name__)
 
+CONF = config.CONF
+
 
 class BaseRecordsetsTest(base.BaseDnsV2Test):
     excluded_keys = ['created_at', 'updated_at', 'version', 'links',
                      'type']
 
 
+@ddt.ddt
 class RecordsetsTest(BaseRecordsetsTest):
     @classmethod
     def setup_clients(cls):
@@ -63,7 +68,10 @@
         LOG.info('Create a Recordset')
         resp, body = self.client.create_recordset(zone['id'], recordset_data)
 
-        self.assertTrue(len(body) > 0)
+        LOG.info('List zone recordsets')
+        _, body = self.client.list_recordset(zone['id'])
+
+        self.assertGreater(len(body), 0)
 
     @test.attr(type='smoke')
     @test.idempotent_id('84c13cb2-9020-4c1e-aeb0-c348d9a70caa')
@@ -126,3 +134,104 @@
 
         self.assertEqual(record['name'], update['name'])
         self.assertNotEqual(record['records'], update['records'])
+
+
+@ddt.ddt
+class RecordsetsNegativeTest(BaseRecordsetsTest):
+    @classmethod
+    def setup_clients(cls):
+        super(RecordsetsNegativeTest, cls).setup_clients()
+
+        cls.client = cls.os.recordset_client
+        cls.zone_client = cls.os.zones_client
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('631d74fd-6909-4684-a61b-5c4d2f92c3e7')
+    @ddt.file_data("recordset_data_invalid.json")
+    def test_create_recordset_invalid(self, name, type, records):
+        LOG.info('Create a zone')
+        _, zone = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+        if name is not None:
+            recordset_name = name + "." + zone['name']
+
+        else:
+            recordset_name = zone['name']
+
+        recordset_data = {
+            'name': recordset_name,
+            'type': type,
+            'records': records,
+        }
+
+        LOG.info('Attempt to create a invalid Recordset')
+        self.assertRaises(lib_exc.BadRequest,
+            lambda: self.client.create_recordset(zone['id'], recordset_data))
+
+
+class RootRecordsetsTests(BaseRecordsetsTest):
+
+    @classmethod
+    def setup_clients(cls):
+        super(RootRecordsetsTests, cls).setup_clients()
+
+        cls.client = cls.os.recordset_client
+        cls.zone_client = cls.os.zones_client
+
+    @classmethod
+    def skip_checks(cls):
+        super(RootRecordsetsTests, cls).skip_checks()
+
+        if not CONF.dns_feature_enabled.api_v2_root_recordsets:
+            skip_msg = ("%s skipped as designate V2 recordsets API is not "
+                        "available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('48a081b9-4474-4da0-9b1a-6359a80456ce')
+    def test_list_zones_recordsets(self):
+        LOG.info('Create a zone')
+        _, zone1 = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone1['id'])
+
+        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()
+
+        self.assertGreater(len(body['recordsets']), 0)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('a8e41020-65be-453b-a8c1-2497d539c345')
+    def test_list_filter_zones_recordsets(self):
+        LOG.info('Create a zone')
+        _, zone1 = self.zone_client.create_zone()
+        self.addCleanup(self.zone_client.delete_zone, zone1['id'])
+
+        recordset_data = {
+            "name": zone1['name'],
+            "description": "This is an example record set.",
+            "type": "A",
+            "ttl": 3600,
+            "records": [
+                "10.1.0.2"
+            ]
+        }
+
+        LOG.info('Create a Recordset')
+        resp, zone1_recordset = self.client.create_recordset(zone1['id'],
+                                                             recordset_data)
+
+        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(params={"data": "10.1.*"})
+
+        recordsets = body['recordsets']
+
+        self.assertEqual(zone1_recordset['id'], recordsets[0]['id'])
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones.py b/designate_tempest_plugin/tests/api/v2/test_zones.py
index 62f188e..015010c 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones.py
@@ -85,7 +85,7 @@
 
         # TODO(kiall): We really want to assert that out newly created zone is
         #              present in the response.
-        self.assertTrue(len(body['zones']) > 0)
+        self.assertGreater(len(body['zones']), 0)
 
     @test.attr(type='smoke')
     @test.idempotent_id('123f51cb-19d5-48a9-aacc-476742c02141')
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 c9350e6..05cb9e8 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones_exports.py
@@ -94,4 +94,4 @@
         LOG.info('List zones exports')
         _, body = self.client.list_zones_exports()
 
-        self.assertTrue(len(body['exports']) > 0)
+        self.assertGreater(len(body['exports']), 0)
diff --git a/designate_tempest_plugin/tests/api/v2/test_zones_imports.py b/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
index ff2d5ef..574d8b5 100644
--- a/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
+++ b/designate_tempest_plugin/tests/api/v2/test_zones_imports.py
@@ -77,4 +77,4 @@
         LOG.info('List zones imports')
         _, body = self.client.list_zone_imports()
 
-        self.assertTrue(len(body['imports']) > 0)
+        self.assertGreater(len(body['imports']), 0)
diff --git a/requirements.txt b/requirements.txt
index 8ba92bf..13e29e6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,5 @@
 
 dnspython!=1.13.0,>=1.12.0;python_version<'3.0' # http://www.dnspython.org/LICENSE
 dnspython3>=1.12.0;python_version>='3.0' # http://www.dnspython.org/LICENSE
+ddt>=1.0.1  # MIT
 tempest>=11.0.0 # Apache-2.0