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
|
||||
from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
|
||||
QuotasClient
|
||||
from designate_tempest_plugin.services.dns.v2.json.zone_exports_client import \
|
||||
ZoneExportsClient
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
@ -44,3 +46,5 @@ class Manager(clients.Manager):
|
||||
**params)
|
||||
self.blacklists_client = BlacklistsClient(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_serialization import jsonutils as json
|
||||
from tempest.lib.common import rest_client
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from six.moves.urllib import parse as urllib
|
||||
|
||||
from designate_tempest_plugin.common.models import ZoneFile
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -50,8 +52,13 @@ class DnsClientBase(rest_client.RestClient):
|
||||
def serialize(self, object_dict):
|
||||
return json.dumps(object_dict)
|
||||
|
||||
def deserialize(self, object_str):
|
||||
return json.loads(object_str)
|
||||
def deserialize(self, resp, 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):
|
||||
# the base class method does not check correctly if read_code is not
|
||||
@ -84,7 +91,7 @@ class DnsClientBase(rest_client.RestClient):
|
||||
uuid=uuid,
|
||||
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):
|
||||
"""Create an object of the specified type.
|
||||
:param resource: The name of the REST resource, e.g., 'zones'.
|
||||
@ -108,9 +115,9 @@ class DnsClientBase(rest_client.RestClient):
|
||||
extra_headers=extra_headers)
|
||||
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.
|
||||
:param resource: The name of the REST resource, e.g., 'zones'.
|
||||
: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)
|
||||
|
||||
resp, body = self.get(uri)
|
||||
resp, body = self.get(uri, headers=headers)
|
||||
|
||||
self.expected_success(200, resp.status)
|
||||
|
||||
return resp, self.deserialize(body)
|
||||
return resp, self.deserialize(resp, body)
|
||||
|
||||
def _list_request(self, resource, params=None):
|
||||
"""Gets a list of objects.
|
||||
@ -139,7 +146,7 @@ class DnsClientBase(rest_client.RestClient):
|
||||
|
||||
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):
|
||||
"""Updates the specified object.
|
||||
@ -158,7 +165,7 @@ class DnsClientBase(rest_client.RestClient):
|
||||
|
||||
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):
|
||||
"""Deletes the specified object.
|
||||
@ -173,6 +180,6 @@ class DnsClientBase(rest_client.RestClient):
|
||||
resp, body = self.delete(uri)
|
||||
self.expected_success([202, 204], resp.status)
|
||||
if resp.status == 202:
|
||||
body = self.deserialize(body)
|
||||
body = self.deserialize(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