Add zones_export_client's methods and tests to Designate tempest plugin
Partially-Implements: blueprint designate-tempest-plugin Change-Id: Iff03a53842ac4e44ed720163695a35a74b970768
This commit is contained in:
parent
89edc117de
commit
c8f7a70b9b
designate_tempest_plugin
@ -22,6 +22,8 @@ from designate_tempest_plugin.services.dns.v2.json.blacklists_client import \
|
|||||||
BlacklistsClient
|
BlacklistsClient
|
||||||
from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
|
from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
|
||||||
QuotasClient
|
QuotasClient
|
||||||
|
from designate_tempest_plugin.services.dns.v2.json.zone_exports_client import \
|
||||||
|
ZoneExportsClient
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
|
||||||
@ -44,3 +46,5 @@ class Manager(clients.Manager):
|
|||||||
**params)
|
**params)
|
||||||
self.blacklists_client = BlacklistsClient(self.auth_provider, **params)
|
self.blacklists_client = BlacklistsClient(self.auth_provider, **params)
|
||||||
self.quotas_client = QuotasClient(self.auth_provider, **params)
|
self.quotas_client = QuotasClient(self.auth_provider, **params)
|
||||||
|
self.zone_exports_client = ZoneExportsClient(self.auth_provider,
|
||||||
|
**params)
|
||||||
|
81
designate_tempest_plugin/common/models.py
Normal file
81
designate_tempest_plugin/common/models.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneFile(object):
|
||||||
|
|
||||||
|
def __init__(self, origin, ttl, records):
|
||||||
|
self.origin = origin
|
||||||
|
self.ttl = ttl
|
||||||
|
self.records = records
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.__dict__)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__dict__ == other.__dict__
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_text(cls, text):
|
||||||
|
"""Return a ZoneFile from a string containing the zone file contents"""
|
||||||
|
# filter out empty lines and strip all leading/trailing whitespace.
|
||||||
|
# this assumes no multiline records
|
||||||
|
lines = [x.strip() for x in text.split('\n') if x.strip()]
|
||||||
|
|
||||||
|
assert lines[0].startswith('$ORIGIN')
|
||||||
|
assert lines[1].startswith('$TTL')
|
||||||
|
|
||||||
|
return ZoneFile(
|
||||||
|
origin=lines[0].split(' ')[1],
|
||||||
|
ttl=int(lines[1].split(' ')[1]),
|
||||||
|
records=[ZoneFileRecord.from_text(x) for x in lines[2:]],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneFileRecord(object):
|
||||||
|
|
||||||
|
def __init__(self, name, type, data):
|
||||||
|
self.name = str(name)
|
||||||
|
self.type = str(type)
|
||||||
|
self.data = str(data)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.__dict__)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__dict__ == other.__dict__
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(tuple(sorted(self.__dict__.items())))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_text(cls, text):
|
||||||
|
"""Create a ZoneFileRecord from a line of text of a zone file, like:
|
||||||
|
|
||||||
|
mydomain.com. IN NS ns1.example.com.
|
||||||
|
"""
|
||||||
|
# assumes records don't have a TTL between the name and the class.
|
||||||
|
# assumes no parentheses in the record, all on a single line.
|
||||||
|
parts = [x for x in text.split(' ', 4) if x.strip()]
|
||||||
|
name, rclass, rtype, data = parts
|
||||||
|
assert rclass == 'IN'
|
||||||
|
return cls(name=name, type=rtype, data=data)
|
@ -16,8 +16,10 @@ import functools
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils as json
|
from oslo_serialization import jsonutils as json
|
||||||
from tempest.lib.common import rest_client
|
from tempest.lib.common import rest_client
|
||||||
|
from tempest.lib import exceptions as lib_exc
|
||||||
from six.moves.urllib import parse as urllib
|
from six.moves.urllib import parse as urllib
|
||||||
|
|
||||||
|
from designate_tempest_plugin.common.models import ZoneFile
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -50,8 +52,13 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
def serialize(self, object_dict):
|
def serialize(self, object_dict):
|
||||||
return json.dumps(object_dict)
|
return json.dumps(object_dict)
|
||||||
|
|
||||||
def deserialize(self, object_str):
|
def deserialize(self, resp, object_str):
|
||||||
return json.loads(object_str)
|
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)
|
||||||
|
else:
|
||||||
|
raise lib_exc.InvalidContentType()
|
||||||
|
|
||||||
def expected_success(self, expected_code, read_code):
|
def expected_success(self, expected_code, read_code):
|
||||||
# the base class method does not check correctly if read_code is not
|
# the base class method does not check correctly if read_code is not
|
||||||
@ -84,7 +91,7 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
params=params)
|
params=params)
|
||||||
|
|
||||||
def _create_request(self, resource, object_dict, params=None,
|
def _create_request(self, resource, object_dict=None, params=None,
|
||||||
headers=None, extra_headers=False):
|
headers=None, extra_headers=False):
|
||||||
"""Create an object of the specified type.
|
"""Create an object of the specified type.
|
||||||
:param resource: The name of the REST resource, e.g., 'zones'.
|
:param resource: The name of the REST resource, e.g., 'zones'.
|
||||||
@ -108,9 +115,9 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
extra_headers=extra_headers)
|
extra_headers=extra_headers)
|
||||||
self.expected_success([201, 202], resp.status)
|
self.expected_success([201, 202], resp.status)
|
||||||
|
|
||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(resp, body)
|
||||||
|
|
||||||
def _show_request(self, resource, uuid, params=None):
|
def _show_request(self, resource, uuid, headers=None, params=None):
|
||||||
"""Gets a specific object of the specified type.
|
"""Gets a specific object of the specified type.
|
||||||
:param resource: The name of the REST resource, e.g., 'zones'.
|
:param resource: The name of the REST resource, e.g., 'zones'.
|
||||||
:param uuid: Unique identifier of the object in UUID format.
|
:param uuid: Unique identifier of the object in UUID format.
|
||||||
@ -120,11 +127,11 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
"""
|
"""
|
||||||
uri = self.get_uri(resource, uuid=uuid, params=params)
|
uri = self.get_uri(resource, uuid=uuid, params=params)
|
||||||
|
|
||||||
resp, body = self.get(uri)
|
resp, body = self.get(uri, headers=headers)
|
||||||
|
|
||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
|
|
||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(resp, body)
|
||||||
|
|
||||||
def _list_request(self, resource, params=None):
|
def _list_request(self, resource, params=None):
|
||||||
"""Gets a list of objects.
|
"""Gets a list of objects.
|
||||||
@ -139,7 +146,7 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
|
|
||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
|
|
||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(resp, body)
|
||||||
|
|
||||||
def _update_request(self, resource, uuid, object_dict, params=None):
|
def _update_request(self, resource, uuid, object_dict, params=None):
|
||||||
"""Updates the specified object.
|
"""Updates the specified object.
|
||||||
@ -158,7 +165,7 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
|
|
||||||
self.expected_success([200, 202], resp.status)
|
self.expected_success([200, 202], resp.status)
|
||||||
|
|
||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(resp, body)
|
||||||
|
|
||||||
def _delete_request(self, resource, uuid, params=None):
|
def _delete_request(self, resource, uuid, params=None):
|
||||||
"""Deletes the specified object.
|
"""Deletes the specified object.
|
||||||
@ -173,6 +180,6 @@ class DnsClientBase(rest_client.RestClient):
|
|||||||
resp, body = self.delete(uri)
|
resp, body = self.delete(uri)
|
||||||
self.expected_success([202, 204], resp.status)
|
self.expected_success([202, 204], resp.status)
|
||||||
if resp.status == 202:
|
if resp.status == 202:
|
||||||
body = self.deserialize(body)
|
body = self.deserialize(resp, body)
|
||||||
|
|
||||||
return resp, body
|
return resp, body
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
# 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 ZoneExportsClient(base.DnsClientV2Base):
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def create_zone_export(self, uuid, params=None, wait_until=False):
|
||||||
|
"""Create a zone export.
|
||||||
|
: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.
|
||||||
|
:param wait_until: Block until the exported zone reaches the
|
||||||
|
desired status
|
||||||
|
:return: Serialized imported zone as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
export_uri = 'zones/{0}/tasks/export'.format(uuid)
|
||||||
|
resp, body = self._create_request(
|
||||||
|
export_uri, params=params)
|
||||||
|
|
||||||
|
# Create Zone Export should Return a HTTP 202
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
|
||||||
|
if wait_until:
|
||||||
|
waiters.wait_for_zone_export_status(self, body['id'], wait_until)
|
||||||
|
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def show_zone_export_records(self, uuid, params=None):
|
||||||
|
"""Gets a specific exported zone.
|
||||||
|
:param uuid: Unique identifier of the exported zone in UUID format.
|
||||||
|
:param params: A Python dict that represents the query paramaters to
|
||||||
|
include in the request URI.
|
||||||
|
:return: Serialized exported zone as a dictionary.
|
||||||
|
"""
|
||||||
|
return self._show_request(
|
||||||
|
'zones/tasks/exports', uuid, params=params)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def show_zone_exported(self, uuid, params=None):
|
||||||
|
"""Gets a specific exported zone.
|
||||||
|
:param uuid: Unique identifier of the exported zone in UUID format.
|
||||||
|
:param params: A Python dict that represents the query paramaters to
|
||||||
|
include in the request URI.
|
||||||
|
:return: Serialized exported zone as a dictionary.
|
||||||
|
"""
|
||||||
|
headers = {'Accept': 'text/dns'}
|
||||||
|
|
||||||
|
return self._show_request(
|
||||||
|
'zones/tasks/exports/{0}/export'.format(uuid),
|
||||||
|
headers=headers, params=params)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def list_zones_exports(self, params=None):
|
||||||
|
"""Gets all the exported zones
|
||||||
|
:param params: A Python dict that represents the query paramaters to
|
||||||
|
include in the request URI.
|
||||||
|
:return: Serialized exported zone as a list.
|
||||||
|
"""
|
||||||
|
return self._list_request(
|
||||||
|
'zones/tasks/exports', params=params)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def delete_zone_export(self, uuid, params=None):
|
||||||
|
"""Deletes an exported zone having the specified UUID.
|
||||||
|
:param uuid: The unique identifier of the exported zone.
|
||||||
|
: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/tasks/exports', uuid, params=params)
|
||||||
|
|
||||||
|
# Delete Zone export should Return a HTTP 204
|
||||||
|
self.expected_success(204, resp.status)
|
||||||
|
|
||||||
|
return resp, body
|
97
designate_tempest_plugin/tests/api/v2/test_zones_exports.py
Normal file
97
designate_tempest_plugin/tests/api/v2/test_zones_exports.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseZoneExportsTest(base.BaseDnsTest):
|
||||||
|
excluded_keys = ['created_at', 'updated_at', 'version', 'links',
|
||||||
|
'status', 'location']
|
||||||
|
|
||||||
|
|
||||||
|
class ZonesExportTest(BaseZoneExportsTest):
|
||||||
|
@classmethod
|
||||||
|
def setup_clients(cls):
|
||||||
|
super(ZonesExportTest, cls).setup_clients()
|
||||||
|
|
||||||
|
cls.zone_client = cls.os.zones_client
|
||||||
|
cls.client = cls.os.zone_exports_client
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('2dd8a9a0-98a2-4bf6-bb51-286583b30f40')
|
||||||
|
def test_create_zone_export(self):
|
||||||
|
LOG.info('Create a zone')
|
||||||
|
_, zone = self.zone_client.create_zone()
|
||||||
|
self.addCleanup(self.zone_client.delete_zone, zone['id'])
|
||||||
|
|
||||||
|
LOG.info('Create a zone export')
|
||||||
|
_, zone_export = self.client.create_zone_export(zone['id'])
|
||||||
|
|
||||||
|
LOG.info('Ensure we respond with PENDING')
|
||||||
|
self.assertEqual('PENDING', zone_export['status'])
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('2d29a2a9-1941-4b7e-9d8a-ad6c2140ea68')
|
||||||
|
def test_show_zone_export(self):
|
||||||
|
LOG.info('Create a zone')
|
||||||
|
_, zone = self.zone_client.create_zone()
|
||||||
|
self.addCleanup(self.zone_client.delete_zone, zone['id'])
|
||||||
|
|
||||||
|
LOG.info('Create a zone export')
|
||||||
|
resp, zone_export = self.client.create_zone_export(zone['id'])
|
||||||
|
|
||||||
|
LOG.info('Re-Fetch the zone export records')
|
||||||
|
_, body = self.client.show_zone_export_records(str(zone_export['id']))
|
||||||
|
|
||||||
|
LOG.info('Ensure the fetched response matches the records of '
|
||||||
|
'exported zone')
|
||||||
|
self.assertExpected(zone_export, body, self.excluded_keys)
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('97234f00-8bcb-43f8-84dd-874f8bc4a80e')
|
||||||
|
def test_delete_zone_export(self):
|
||||||
|
LOG.info('Create a zone')
|
||||||
|
_, zone = self.zone_client.create_zone()
|
||||||
|
self.addCleanup(self.zone_client.delete_zone, zone['id'],
|
||||||
|
ignore_errors=lib_exc.NotFound)
|
||||||
|
|
||||||
|
LOG.info('Create a zone export')
|
||||||
|
_, zone_export = self.client.create_zone_export(zone['id'])
|
||||||
|
|
||||||
|
LOG.info('Delete the exported zone')
|
||||||
|
_, body = self.client.delete_zone_export(zone_export['id'])
|
||||||
|
|
||||||
|
LOG.info('Ensure the exported zone has been successfully deleted')
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
lambda: self.client.show_zone_export_records(zone_export['id']))
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('476bfdfe-58c8-46e2-b376-8403c0fff440')
|
||||||
|
def test_list_zones_exports(self):
|
||||||
|
LOG.info('Create a zone')
|
||||||
|
_, zone = self.zone_client.create_zone()
|
||||||
|
self.addCleanup(self.zone_client.delete_zone, zone['id'])
|
||||||
|
|
||||||
|
_, export = self.client.create_zone_export(zone['id'])
|
||||||
|
|
||||||
|
LOG.info('List zones exports')
|
||||||
|
_, body = self.client.list_zones_exports()
|
||||||
|
|
||||||
|
self.assertTrue(len(body['exports']) > 0)
|
Loading…
x
Reference in New Issue
Block a user