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:
Paul Glass 2015-09-09 21:05:17 +00:00
parent 3c8e0d7891
commit ef96861c2c
5 changed files with 173 additions and 7 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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
]

View 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)