Migrate the recordset validation functional test

This migrates the recordset validation test from designate to the
tempest plugin.

Change-Id: I4ae2dd980c1ad0547f360a9dc07f97a46146fd18
diff --git a/designate_tempest_plugin/common/waiters.py b/designate_tempest_plugin/common/waiters.py
index d13465d..cd22541 100644
--- a/designate_tempest_plugin/common/waiters.py
+++ b/designate_tempest_plugin/common/waiters.py
@@ -149,17 +149,17 @@
             raise lib_exc.TimeoutException(message)
 
 
-def wait_for_recordset_status(client, recordset_id, status):
+def wait_for_recordset_status(client, zone_id, recordset_id, status):
     """Waits for a recordset to reach the given status."""
     LOG.info('Waiting for recordset %s to reach %s',
              recordset_id, status)
 
-    _, recordset = client.show_recordset(recordset_id)
+    _, recordset = client.show_recordset(zone_id, recordset_id)
     start = int(time.time())
 
     while recordset['status'] != status:
         time.sleep(client.build_interval)
-        _, recordset = client.show_recordset(recordset_id)
+        _, recordset = client.show_recordset(zone_id, recordset_id)
         status_curr = recordset['status']
         if status_curr == status:
             LOG.info('Recordset %s reached %s', recordset_id, status)
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index 7600542..46c8165 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -246,3 +246,16 @@
 def rand_tsig_scope():
     scope = ["ZONE", "POOL"]
     return random.choice(scope)
+
+
+def make_rand_recordset(zone_name, record_type):
+    """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)
diff --git a/designate_tempest_plugin/tests/api/v2/invalid_mx_dataset.json b/designate_tempest_plugin/tests/api/v2/invalid_mx_dataset.json
new file mode 100644
index 0000000..100995a
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/invalid_mx_dataset.json
@@ -0,0 +1,5 @@
+{
+  "empty_preference": {"pref": ""},
+  "minus_zero_preference": {"pref": "-0"},
+  "minus_one_preference": {"pref": "-1"}
+}
diff --git a/designate_tempest_plugin/tests/api/v2/invalid_sshfp_dataset.json b/designate_tempest_plugin/tests/api/v2/invalid_sshfp_dataset.json
new file mode 100644
index 0000000..133b460
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/invalid_sshfp_dataset.json
@@ -0,0 +1,6 @@
+{
+  "minus_zero_algorithm": {"algo": "-0", "finger": null},
+  "minus_zero_fingerprint": {"algo": null, "finger": "-0"},
+  "minus_one_algorithm": {"algo": "-1", "finger": null},
+  "minus_one_fingerprint": {"algo": null, "finger": "-1"}
+}
diff --git a/designate_tempest_plugin/tests/api/v2/invalid_txt_dataset.json b/designate_tempest_plugin/tests/api/v2/invalid_txt_dataset.json
new file mode 100644
index 0000000..26633f3
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/invalid_txt_dataset.json
@@ -0,0 +1,5 @@
+{
+  "trailing_slash": {"data": "\\"},
+  "trailing_double_slash": {"data": "\\\\"},
+  "trailing_slash_after_text": {"data": "v=spf1 +all\\"}
+}
diff --git a/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py b/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
new file mode 100644
index 0000000..48c05df
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset_validation.py
@@ -0,0 +1,175 @@
+"""
+Copyright 2016 Rackspace
+
+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 ddt
+from tempest.lib import exceptions
+from tempest.lib import decorators
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin.common import waiters
+from designate_tempest_plugin import data_utils
+
+
+RECORDSETS_DATASET = [
+    'A',
+    'AAAA',
+    'CNAME',
+    'MX',
+    'SPF',
+    'SRV',
+    'SSHFP',
+    'TXT',
+]
+
+
+@ddt.ddt
+class RecordsetValidationTest(base.BaseDnsV2Test):
+
+    def setUp(self):
+        super(RecordsetValidationTest, self).setUp()
+        self._zone = None
+
+    @classmethod
+    def setup_clients(cls):
+        super(RecordsetValidationTest, cls).setup_clients()
+
+        cls.recordset_client = cls.os.recordset_client
+        cls.zones_client = cls.os.zones_client
+
+    @property
+    def zone(self):
+        if self._zone is None:
+            zone_data = data_utils.rand_zone_data()
+            resp, body = self.zones_client.create_zone(**zone_data)
+            self._zone = body
+        return self._zone
+
+    def create_recordset(self, data):
+        resp, body = self.recordset_client.create_recordset(
+            self.zone['id'], data)
+
+        return body
+
+    @decorators.idempotent_id('c5ef87e2-cb79-4758-b968-18eef2c251df')
+    @ddt.data(*RECORDSETS_DATASET)
+    def test_create_invalid(self, rtype):
+        data = ["b0rk"]
+
+        for i in data:
+            model = data_utils.make_rand_recordset(self.zone['name'], rtype)
+            model['data'] = i
+
+            self.assertRaisesDns(
+                exceptions.BadRequest, 'invalid_object', 400,
+                self.recordset_client.create_recordset,
+                self.zone['id'], model
+            )
+
+    @decorators.idempotent_id('1164c826-dceb-4557-9a22-7d65c4a4f5f4')
+    @ddt.data(*RECORDSETS_DATASET)
+    def test_update_invalid(self, rtype):
+        data = ["b0rk"]
+
+        post_model = data_utils.make_rand_recordset(self.zone['name'], rtype)
+        recordset = self.create_recordset(post_model)
+
+        for i in data:
+            model = data_utils.make_rand_recordset(self.zone['name'], rtype)
+            model['data'] = i
+            self.assertRaisesDns(
+                exceptions.BadRequest, 'invalid_object', 400,
+                self.recordset_client.update_recordset,
+                self.zone['id'], recordset['id'], model
+            )
+
+    @decorators.idempotent_id('61da1015-291f-43d1-a1a8-345cff12d201')
+    def test_cannot_create_wildcard_NS_recordset(self):
+        model = data_utils.wildcard_ns_recordset(self.zone['name'])
+        self.assertRaisesDns(
+            exceptions.BadRequest, 'invalid_object', 400,
+            self.recordset_client.create_recordset, self.zone['id'], model
+        )
+
+    @decorators.idempotent_id('92f681aa-d953-4d18-b12e-81a9149ccfd9')
+    def test_cname_recordsets_cannot_have_more_than_one_record(self):
+        post_model = data_utils.rand_cname_recordset(
+            zone_name=self.zone['name'])
+
+        post_model['records'] = [
+            "a.{0}".format(self.zone['name']),
+            "b.{0}".format(self.zone['name']),
+        ]
+
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.recordset_client.create_recordset,
+            self.zone['id'], post_model
+        )
+
+    @decorators.idempotent_id('22a9544b-2382-4ed2-ba12-4dbaedb8e880')
+    @ddt.file_data("invalid_txt_dataset.json")
+    def test_cannot_create_TXT_with(self, data):
+        post_model = data_utils.rand_txt_recordset(self.zone['name'], data)
+        self.assertRaisesDns(
+            exceptions.BadRequest, 'invalid_object', 400,
+            self.recordset_client.create_recordset,
+            self.zone['id'], post_model,
+        )
+
+    @decorators.idempotent_id('03e4f811-0c37-4ce2-8b16-662c824f8f18')
+    @ddt.file_data("valid_txt_dataset.json")
+    def test_create_TXT_with(self, data):
+        post_model = data_utils.rand_txt_recordset(self.zone['name'], data)
+        recordset = self.create_recordset(post_model)
+
+        waiters.wait_for_recordset_status(
+            self.recordset_client, self.zone['id'], recordset['id'], 'ACTIVE')
+
+    @decorators.idempotent_id('775b3db5-ec60-4dd7-85d2-f05a9c544978')
+    @ddt.file_data("valid_txt_dataset.json")
+    def test_create_SPF_with(self, data):
+        post_model = data_utils.rand_spf_recordset(self.zone['name'], data)
+        recordset = self.create_recordset(post_model)
+
+        waiters.wait_for_recordset_status(
+            self.recordset_client, self.zone['id'], recordset['id'], 'ACTIVE')
+
+    @decorators.idempotent_id('7fa7783f-1624-4122-bfb2-6cfbf7a5b49b')
+    @ddt.file_data("invalid_mx_dataset.json")
+    def test_cannot_create_MX_with(self, pref):
+        post_model = data_utils.rand_mx_recordset(
+            self.zone['name'], pref=pref
+        )
+
+        self.assertRaisesDns(
+            exceptions.BadRequest, 'invalid_object', 400,
+            self.recordset_client.create_recordset,
+            self.zone['id'], post_model,
+        )
+
+    @decorators.idempotent_id('3016f998-4e4a-4712-b15a-4e8dfbc5a60b')
+    @ddt.data("invalid_sshfp_dataset.json")
+    def test_cannot_create_SSHFP_with(self, algo=None, finger=None):
+        post_model = data_utils.rand_sshfp_recordset(
+            zone_name=self.zone['name'],
+            algorithm_number=algo,
+            fingerprint_type=finger,
+        )
+
+        self.assertRaisesDns(
+            exceptions.BadRequest, 'invalid_object', 400,
+            self.recordset_client.create_recordset,
+            self.zone['id'], post_model,
+        )
diff --git a/designate_tempest_plugin/tests/api/v2/valid_txt_dataset.json b/designate_tempest_plugin/tests/api/v2/valid_txt_dataset.json
new file mode 100644
index 0000000..b54b1b3
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/valid_txt_dataset.json
@@ -0,0 +1,5 @@
+{
+  "slash_with_one_trailing_space": {"data": "\\ "},
+  "slash_with_many_trailing_space": {"data": "\\    "},
+  "text_with_slash_and_trailing_space": {"data": "the txts    "}
+}