Add recordset_client's methods and tests to Designate tempest plugin
Partially-Implements: blueprint designate-tempest-plugin
Change-Id: I55ebc5210f7b1e50b59411658a1ae4d1f39a3ff4
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index d461a53..db1c61f 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -24,6 +24,8 @@
QuotasClient
from designate_tempest_plugin.services.dns.v2.json.zone_exports_client import \
ZoneExportsClient
+from designate_tempest_plugin.services.dns.v2.json.recordset_client import \
+ RecordsetClient
CONF = config.CONF
@@ -48,3 +50,5 @@
self.quotas_client = QuotasClient(self.auth_provider, **params)
self.zone_exports_client = ZoneExportsClient(self.auth_provider,
**params)
+ self.recordset_client = RecordsetClient(self.auth_provider,
+ **params)
diff --git a/designate_tempest_plugin/common/waiters.py b/designate_tempest_plugin/common/waiters.py
index 9563656..dbdbd5a 100644
--- a/designate_tempest_plugin/common/waiters.py
+++ b/designate_tempest_plugin/common/waiters.py
@@ -114,3 +114,37 @@
message = '(%s) %s' % (caller, message)
raise lib_exc.TimeoutException(message)
+
+
+def wait_for_recordset_status(client, 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)
+ start = int(time.time())
+
+ while recordset['status'] != status:
+ time.sleep(client.build_interval)
+ _, recordset = client.show_recordset(recordset_id)
+ status_curr = recordset['status']
+ if status_curr == status:
+ LOG.info('Recordset %s reached %s', recordset_id, status)
+ return
+
+ if int(time.time()) - start >= client.build_timeout:
+ message = ('Recordset %(recordset_id)s failed to reach '
+ 'status=%(status) within the required time '
+ '(%(timeout)s s). Current '
+ 'status: %(status_curr)s' %
+ {'recordset_id': recordset_id,
+ 'status': status,
+ 'status_curr': status_curr,
+ 'timeout': client.build_timeout})
+
+ caller = misc_utils.find_test_caller()
+
+ if caller:
+ message = '(%s) %s' % (caller, message)
+
+ raise lib_exc.TimeoutException(message)
\ No newline at end of file
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index c77c4b1..322e506 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -11,12 +11,27 @@
# 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 random
+
+import netaddr
from oslo_log import log as logging
from tempest.lib.common.utils import data_utils
LOG = logging.getLogger(__name__)
+def rand_ip():
+ return ".".join(str(random.randrange(0, 256)) for _ in range(4))
+
+
+def rand_ipv6():
+ def hexes(n):
+ return "".join(random.choice("1234567890abcdef") for _ in range(n))
+ result = ":".join(hexes(4) for _ in range(8))
+ an = netaddr.IPAddress(result, version=6)
+ return an.format(netaddr.ipv6_compact)
+
+
def rand_zone_name(name='', prefix=None, suffix='.com.'):
"""Generate a random zone name
:param str name: The name that you want to include
@@ -80,3 +95,104 @@
# api_export_size or data_utils.rand_int_id(100, 999999),
}
}
+
+
+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.')
+ if email is None:
+ email = ("admin@" + name).strip('.')
+ if description is None:
+ description = rand_zone_name(prefix='Description ', suffix='')
+ if ttl is None:
+ ttl = random.randint(1200, 8400),
+ return {
+ 'name': name,
+ 'email': email,
+ 'ttl': random.randint(1200, 8400),
+ 'description': description}
+
+
+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:
+ name = rand_zone_name(prefix=record_type, suffix='.' + zone_name)
+ if records is None:
+ records = [rand_ip()]
+ if ttl is None:
+ ttl = random.randint(1200, 8400)
+ return {
+ 'type': record_type,
+ 'name': name,
+ 'records': records,
+ '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_aaaa_recordset(zone_name, ip=None, **kwargs):
+ if ip is None:
+ ip = rand_ipv6()
+ return rand_recordset_data('AAAA', zone_name, records=[ip], **kwargs)
+
+
+def rand_cname_recordset(zone_name, cname=None, **kwargs):
+ if cname is None:
+ cname = zone_name
+ return rand_recordset_data('CNAME', zone_name, records=[cname], **kwargs)
+
+
+def rand_mx_recordset(zone_name, pref=None, host=None, **kwargs):
+ if pref is None:
+ pref = str(random.randint(0, 65535))
+ if host is None:
+ host = rand_zone_name(prefix='mail', suffix='.' + zone_name)
+ data = "{0} {1}".format(pref, host)
+ return rand_recordset_data('MX', zone_name, records=[data], **kwargs)
+
+
+def rand_spf_recordset(zone_name, data=None, **kwargs):
+ data = data or "v=spf1 +all"
+ return rand_recordset_data('SPF', zone_name, records=[data], **kwargs)
+
+
+def rand_srv_recordset(zone_name, data=None):
+ data = data or "10 0 8080 %s.%s" % (rand_zone_name(suffix=''), zone_name)
+ return rand_recordset_data('SRV', zone_name,
+ name="_sip._tcp.%s" % zone_name,
+ records=[data])
+
+
+def rand_sshfp_recordset(zone_name, algorithm_number=None,
+ fingerprint_type=None, fingerprint=None,
+ **kwargs):
+ algorithm_number = algorithm_number or 2
+ fingerprint_type = fingerprint_type or 1
+ fingerprint = fingerprint or \
+ "123456789abcdef67890123456789abcdef67890"
+
+ data = "%s %s %s" % (algorithm_number, fingerprint_type, fingerprint)
+ return rand_recordset_data('SSHFP', zone_name, records=[data], **kwargs)
+
+
+def rand_txt_recordset(zone_name, data=None, **kwargs):
+ data = data or "v=spf1 +all"
+ return rand_recordset_data('TXT', zone_name, records=[data], **kwargs)
+
+
+def wildcard_ns_recordset(zone_name):
+ name = "*.{0}".format(zone_name)
+ records = ["ns.example.com."]
+ return rand_recordset_data('NS', zone_name, name, records)
diff --git a/designate_tempest_plugin/services/dns/json/base.py b/designate_tempest_plugin/services/dns/json/base.py
index b53712f..ef07ec1 100644
--- a/designate_tempest_plugin/services/dns/json/base.py
+++ b/designate_tempest_plugin/services/dns/json/base.py
@@ -148,8 +148,26 @@
return resp, self.deserialize(resp, body)
+ def _put_request(self, resource, uuid, object_dict, params=None):
+ """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.
+ :param object_dict: A Python dict that represents an object of the
+ specified type
+ :param params: A Python dict that represents the query paramaters to
+ include in the request URI.
+ :returns: Serialized object as a dictionary.
+ """
+ body = self.serialize(object_dict)
+ uri = self.get_uri(resource, uuid=uuid, params=params)
+ resp, body = self.put(uri, body=body)
+
+ self.expected_success([200, 202], resp.status)
+
+ return resp, self.deserialize(resp, body)
+
def _update_request(self, resource, uuid, object_dict, params=None):
- """Updates the specified object.
+ """Updates the specified object using PATCH request.
:param resource: The name of the REST resource, e.g., 'zones'
:param uuid: Unique identifier of the object in UUID format.
:param object_dict: A Python dict that represents an object of the
diff --git a/designate_tempest_plugin/services/dns/v2/json/recordset_client.py b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
new file mode 100644
index 0000000..3bc9418
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/v2/json/recordset_client.py
@@ -0,0 +1,107 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# 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.common import waiters
+from designate_tempest_plugin.services.dns.v2.json import base
+
+
+class RecordsetClient(base.DnsClientV2Base):
+ """API V2 Tempest REST client for Recordset API"""
+
+ @base.handle_errors
+ def create_recordset(self, zone_uuid, recordset_data,
+ params=None, wait_until=False):
+ """Create a recordset for the specified zone.
+
+ :param zone_uuid: Unique identifier of the zone 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._create_request(
+ "/zones/{0}/recordsets".format(zone_uuid), params=params,
+ object_dict=recordset_data)
+
+ # Create Recordset should Return a HTTP 202
+ self.expected_success(202, resp.status)
+
+ if wait_until:
+ waiters.wait_for_recordset_status(self, body['id'], wait_until)
+
+ 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.
+ :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: Serialized recordset as a list.
+ """
+ return self._show_request(
+ 'zones/{0}/recordsets'.format(zone_uuid), recordset_uuid,
+ params=params)
+
+ @base.handle_errors
+ def delete_recordset(self, zone_uuid, recordset_uuid, params=None):
+ """Deletes a recordset related to the specified zone UUID.
+ :param zone_uuid: The unique identifier of the zone.
+ :param recordset_uuid: The unique identifier of the record 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._delete_request(
+ 'zones/{0}/recordsets'.format(zone_uuid), recordset_uuid)
+
+ # Delete Recordset should Return a HTTP 202
+ self.expected_success(202, resp.status)
+
+ return resp, body
+
+ @base.handle_errors
+ def list_recordset(self, uuid, params=None):
+ """List recordsets related to the specified zone.
+ :param uuid: Unique identifier of the zone in UUID format.
+ :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(
+ 'zones/{0}/recordsets'.format(uuid), 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,
+ object_dict=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_recordset.py b/designate_tempest_plugin/tests/api/v2/test_recordset.py
new file mode 100644
index 0000000..1c9d87d
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/v2/test_recordset.py
@@ -0,0 +1,128 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# 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 test
+from tempest.lib import exceptions as lib_exc
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin import data_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseRecordsetsTest(base.BaseDnsTest):
+ excluded_keys = ['created_at', 'updated_at', 'version', 'links',
+ 'type']
+
+
+class RecordsetsTest(BaseRecordsetsTest):
+ @classmethod
+ def setup_clients(cls):
+ super(RecordsetsTest, 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')
+ def test_create_recordset(self):
+ LOG.info('Create a zone')
+ _, zone = self.zone_client.create_zone()
+ self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+ recordset_data = data_utils.rand_recordset_data(
+ record_type='A', zone_name=zone['name'])
+
+ LOG.info('Create a Recordset')
+ resp, body = self.client.create_recordset(zone['id'], recordset_data)
+
+ LOG.info('Ensure we respond with PENDING')
+ self.assertEqual('PENDING', body['status'])
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('5964f730-5546-46e6-9105-5030e9c492b2')
+ def test_list_recordsets(self):
+ LOG.info('Create a zone')
+ _, zone = self.zone_client.create_zone()
+ self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+ recordset_data = data_utils.rand_recordset_data(
+ record_type='A', zone_name=zone['name'])
+
+ LOG.info('Create a Recordset')
+ resp, body = self.client.create_recordset(zone['id'], recordset_data)
+
+ self.assertTrue(len(body) > 0)
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('84c13cb2-9020-4c1e-aeb0-c348d9a70caa')
+ def test_show_recordsets(self):
+ LOG.info('Create a zone')
+ _, zone = self.zone_client.create_zone()
+ self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+ recordset_data = data_utils.rand_recordset_data(
+ record_type='A', zone_name=zone['name'])
+
+ LOG.info('Create a Recordset')
+ resp, body = self.client.create_recordset(zone['id'], recordset_data)
+
+ LOG.info('Re-Fetch the Recordset')
+ _, record = self.client.show_recordset(zone['id'], body['id'])
+
+ LOG.info('Ensure the fetched response matches the expected one')
+ self.assertExpected(body, record, self.excluded_keys)
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('855399c1-8806-4ae5-aa31-cb8a6f35e218')
+ def test_delete_recordset(self):
+ LOG.info('Create a zone')
+ _, zone = self.zone_client.create_zone()
+ self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+ recordset_data = data_utils.rand_recordset_data(
+ record_type='A', zone_name=zone['name'])
+
+ LOG.info('Create a Recordset')
+ _, record = self.client.create_recordset(zone['id'], recordset_data)
+
+ LOG.info('Delete a Recordset')
+ _, body = self.client.delete_recordset(zone['id'], record['id'])
+
+ LOG.info('Ensure successful deletion of Recordset')
+ self.assertRaises(lib_exc.NotFound,
+ lambda: self.client.show_recordset(zone['id'], record['id']))
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('8d41c85f-09f9-48be-a202-92d1bdf5c796')
+ def test_update_recordset(self):
+ LOG.info('Create a zone')
+ _, zone = self.zone_client.create_zone()
+ self.addCleanup(self.zone_client.delete_zone, zone['id'])
+
+ recordset_data = data_utils.rand_recordset_data(
+ record_type='A', zone_name=zone['name'])
+
+ LOG.info('Create a recordset')
+ _, record = self.client.create_recordset(zone['id'], recordset_data)
+
+ recordset_data = data_utils.rand_recordset_data(
+ record_type='A', zone_name=zone['name'], name=record['name'])
+
+ LOG.info('Update the recordset')
+ _, update = self.client.update_recordset(zone['id'],
+ record['id'], recordset_data)
+
+ self.assertEqual(record['name'], update['name'])
+ self.assertNotEqual(record['records'], update['records'])