Check the contents of a zone export in the functional tests
* Fixes a policy check on doing the zone export that caused a 403 unless the tenant was an admin * Adds ZoneFile and ZoneFileRecord models to parse a zone file * Adds some "meta" tests for the new models * Updates the test_export_domain test to check the zone file text Change-Id: I65c93f5d5283fc5962f4a2bf0ac8abae1966e6d6
This commit is contained in:
parent
3c8e0d7891
commit
ef96861c2c
@ -33,7 +33,8 @@ class ZoneExportController(rest.RestController):
|
||||
@utils.validate_uuid('export_id')
|
||||
def get_all(self, export_id):
|
||||
context = pecan.request.environ['context']
|
||||
policy.check('zone_export', context)
|
||||
target = {'tenant_id': context.tenant}
|
||||
policy.check('zone_export', context, target)
|
||||
|
||||
export = self.central_api.get_zone_export(context, export_id)
|
||||
|
||||
|
@ -15,8 +15,9 @@ limitations under the License.
|
||||
"""
|
||||
from functionaltests.api.v2.models.zone_export_model import ZoneExportModel
|
||||
from functionaltests.api.v2.models.zone_export_model import ZoneExportListModel
|
||||
from functionaltests.common.client import ClientMixin
|
||||
from functionaltests.common import utils
|
||||
from functionaltests.common.client import ClientMixin
|
||||
from functionaltests.common.models import ZoneFile
|
||||
|
||||
|
||||
class ZoneExportClient(ClientMixin):
|
||||
@ -42,13 +43,15 @@ class ZoneExportClient(ClientMixin):
|
||||
return self.deserialize(resp, body, ZoneExportModel)
|
||||
|
||||
def get_exported_zone(self, id, **kwargs):
|
||||
uri = "/v2/zones/tasks/exports/{0}".format(id)
|
||||
resp, body = self.client.get(uri)
|
||||
uri = "/v2/zones/tasks/exports/{0}/export".format(id)
|
||||
headers = {'Accept': 'text/dns'}
|
||||
resp, body = self.client.get(uri, headers=headers)
|
||||
if resp.status < 400:
|
||||
return resp, ZoneFile.from_text(body)
|
||||
return resp, body
|
||||
|
||||
def post_zone_export(self, zone_id, **kwargs):
|
||||
uri = "/v2/zones/{0}/tasks/export".format(zone_id)
|
||||
|
||||
resp, body = self.client.post(uri, body='', **kwargs)
|
||||
return self.deserialize(resp, body, ZoneExportModel)
|
||||
|
||||
|
@ -20,12 +20,14 @@ from tempest_lib.exceptions import NotFound
|
||||
|
||||
from functionaltests.common import datagen
|
||||
from functionaltests.api.v2.base import DesignateV2Test
|
||||
from functionaltests.api.v2.clients.recordset_client import RecordsetClient
|
||||
from functionaltests.api.v2.clients.zone_client import ZoneClient
|
||||
from functionaltests.api.v2.clients.zone_import_client import ZoneImportClient
|
||||
from functionaltests.api.v2.clients.zone_export_client import ZoneExportClient
|
||||
from functionaltests.api.v2.fixtures import ZoneFixture
|
||||
from functionaltests.api.v2.fixtures import ZoneImportFixture
|
||||
from functionaltests.api.v2.fixtures import ZoneExportFixture
|
||||
from functionaltests.common.models import ZoneFileRecord
|
||||
|
||||
|
||||
class ZoneTest(DesignateV2Test):
|
||||
@ -147,9 +149,21 @@ class ZoneExportTest(DesignateV2Test):
|
||||
self.assertEqual(resp.status, 200)
|
||||
self.assertEqual(model.status, 'COMPLETE')
|
||||
|
||||
resp, body = export_client.get_exported_zone(export_id)
|
||||
# fetch the zone file
|
||||
resp, zone_file = export_client.get_exported_zone(export_id)
|
||||
self.assertEqual(resp.status, 200)
|
||||
self.assertEqual(zone_file.origin, zone.name)
|
||||
self.assertEqual(zone_file.ttl, zone.ttl)
|
||||
|
||||
# the list of records in the zone file must match the zone's recordsets
|
||||
# (in this case there should be only two records - a SOA and an NS?)
|
||||
recordset_client = RecordsetClient.as_user(user)
|
||||
resp, model = recordset_client.list_recordsets(zone.id)
|
||||
records = []
|
||||
for recordset in model.recordsets:
|
||||
records.extend(ZoneFileRecord.records_from_recordset(recordset))
|
||||
self.assertEqual(set(records), set(zone_file.records))
|
||||
|
||||
export_client.delete_zone_export(export_id)
|
||||
self.assertRaises(NotFound,
|
||||
lambda: export_client.get_zone_export(model.id))
|
||||
lambda: export_client.get_zone_export(export_id))
|
||||
|
@ -99,3 +99,79 @@ class EntityModel(BaseModel):
|
||||
val = getattr(model, cls.ENTITY_NAME)
|
||||
setattr(model, cls.ENTITY_NAME, cls.MODEL_TYPE.from_dict(val))
|
||||
return model
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def records_from_recordset(cls, recordset):
|
||||
"""Returns a list of ZoneFileRecords, one for each entry in the
|
||||
recordset's list of records
|
||||
"""
|
||||
return [
|
||||
cls(name=recordset.name, type=recordset.type, data=data)
|
||||
for data in recordset.records
|
||||
]
|
||||
|
72
functionaltests/common/test_meta.py
Normal file
72
functionaltests/common/test_meta.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
from functionaltests.api.v2.models.recordset_model import RecordsetModel
|
||||
from functionaltests.common.models import ZoneFile
|
||||
from functionaltests.common.models import ZoneFileRecord
|
||||
|
||||
import tempest_lib.base
|
||||
|
||||
|
||||
class MetaTest(tempest_lib.base.BaseTestCase):
|
||||
|
||||
def test_zone_file_model_meta_test(self):
|
||||
zone_file = ZoneFile.from_text(
|
||||
"""
|
||||
$ORIGIN mydomain.com.
|
||||
$TTL 1234
|
||||
|
||||
mydomain.com. IN NS ns1.example.com.
|
||||
mydomain.com. IN SOA ns1.example.com. mail.mydomain.com. 1 2 3 4 5
|
||||
""")
|
||||
self.assertEqual(zone_file.origin, 'mydomain.com.')
|
||||
self.assertEqual(zone_file.ttl, 1234)
|
||||
|
||||
ns_record = ZoneFileRecord(
|
||||
name='mydomain.com.', type='NS', data='ns1.example.com.')
|
||||
soa_record = ZoneFileRecord(
|
||||
name='mydomain.com.', type='SOA',
|
||||
data='ns1.example.com. mail.mydomain.com. 1 2 3 4 5')
|
||||
|
||||
self.assertEqual(zone_file.records[0], ns_record)
|
||||
self.assertEqual(zone_file.records[1], soa_record)
|
||||
|
||||
def test_zone_file_record_model_meta_test(self):
|
||||
record = ZoneFileRecord(name='one.com.', type='A', data='1.2.3.4')
|
||||
wrong_name = ZoneFileRecord(name='two.com.', type='A', data='1.2.3.4')
|
||||
wrong_type = ZoneFileRecord(name='one.com.', type='MX', data='1.2.3.4')
|
||||
wrong_data = ZoneFileRecord(name='one.com.', type='A', data='1.2.3.5')
|
||||
|
||||
self.assertEqual(record, record)
|
||||
self.assertNotEqual(record, wrong_name)
|
||||
self.assertNotEqual(record, wrong_type)
|
||||
self.assertNotEqual(record, wrong_data)
|
||||
|
||||
def test_zone_file_records_from_recordset(self):
|
||||
# we don't need all of the recordset's fields here
|
||||
recordset = RecordsetModel.from_dict({
|
||||
"type": "NS",
|
||||
"name": "mydomain.com.",
|
||||
"records": ["ns1.a.com.", "ns2.a.com.", "ns3.a.com."],
|
||||
})
|
||||
|
||||
records = ZoneFileRecord.records_from_recordset(recordset)
|
||||
expected = [
|
||||
ZoneFileRecord(name="mydomain.com.", type="NS", data="ns1.a.com."),
|
||||
ZoneFileRecord(name="mydomain.com.", type="NS", data="ns2.a.com."),
|
||||
ZoneFileRecord(name="mydomain.com.", type="NS", data="ns3.a.com."),
|
||||
]
|
||||
self.assertEqual(records, expected)
|
Loading…
x
Reference in New Issue
Block a user