Merge "Implement zone import"
This commit is contained in:
commit
a9385ac8f5
@ -44,7 +44,10 @@ class Controller(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _serialize(self, kwargs):
|
def _serialize(self, kwargs):
|
||||||
if 'data' in kwargs:
|
headers = kwargs.get('headers')
|
||||||
|
content_type = headers.get('Content-Type') if headers else None
|
||||||
|
|
||||||
|
if 'data' in kwargs and content_type in {None, 'application/json'}:
|
||||||
kwargs['data'] = json.dumps(kwargs['data'])
|
kwargs['data'] = json.dumps(kwargs['data'])
|
||||||
|
|
||||||
def _post(self, url, response_key=None, **kwargs):
|
def _post(self, url, response_key=None, **kwargs):
|
||||||
|
@ -31,3 +31,16 @@ class BaseDesignateTest(base.ClientTestBase):
|
|||||||
self.clients.as_user('admin').tld_create(tld)
|
self.clients.as_user('admin').tld_create(tld)
|
||||||
except CommandFailed:
|
except CommandFailed:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _is_entity_in_list(self, entity, entity_list):
|
||||||
|
"""Determines if the given entity exists in the given list.
|
||||||
|
|
||||||
|
Uses the id for comparison.
|
||||||
|
|
||||||
|
Certain entities (e.g. zone import, export) cannot be made
|
||||||
|
comparable in a list of CLI output results, because the fields
|
||||||
|
in a list command can be different from those in a show command.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return any([entity_record.id == entity.id
|
||||||
|
for entity_record in entity_list])
|
||||||
|
@ -175,6 +175,26 @@ class ZoneExportCommands(object):
|
|||||||
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
|
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportCommands(object):
|
||||||
|
"""A mixin for DesignateCLI to add zone import commands"""
|
||||||
|
|
||||||
|
def zone_import_list(self, *args, **kwargs):
|
||||||
|
cmd = 'zone import list'
|
||||||
|
return self.parsed_cmd(cmd, ListModel, *args, **kwargs)
|
||||||
|
|
||||||
|
def zone_import_create(self, zone_file_path, *args, **kwargs):
|
||||||
|
cmd = 'zone import create {0}'.format(zone_file_path)
|
||||||
|
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
|
||||||
|
|
||||||
|
def zone_import_show(self, zone_import_id, *args, **kwargs):
|
||||||
|
cmd = 'zone import show {0}'.format(zone_import_id)
|
||||||
|
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
|
||||||
|
|
||||||
|
def zone_import_delete(self, zone_import_id, *args, **kwargs):
|
||||||
|
cmd = 'zone import delete {0}'.format(zone_import_id)
|
||||||
|
return self.parsed_cmd(cmd, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RecordsetCommands(object):
|
class RecordsetCommands(object):
|
||||||
|
|
||||||
def recordset_show(self, zone_id, id, *args, **kwargs):
|
def recordset_show(self, zone_id, id, *args, **kwargs):
|
||||||
@ -285,8 +305,8 @@ class BlacklistCommands(object):
|
|||||||
|
|
||||||
|
|
||||||
class DesignateCLI(base.CLIClient, ZoneCommands, ZoneTransferCommands,
|
class DesignateCLI(base.CLIClient, ZoneCommands, ZoneTransferCommands,
|
||||||
ZoneExportCommands, RecordsetCommands, TLDCommands,
|
ZoneExportCommands, ZoneImportCommands, RecordsetCommands,
|
||||||
BlacklistCommands):
|
TLDCommands, BlacklistCommands):
|
||||||
|
|
||||||
# instantiate this once to minimize requests to keystone
|
# instantiate this once to minimize requests to keystone
|
||||||
_CLIENTS = None
|
_CLIENTS = None
|
||||||
|
@ -35,3 +35,13 @@ def random_a_recordset_name(zone_name, recordset_name='testrecord'):
|
|||||||
|
|
||||||
def random_blacklist(name='testblacklist'):
|
def random_blacklist(name='testblacklist'):
|
||||||
return '{0}{1}'.format(name, random_digits())
|
return '{0}{1}'.format(name, random_digits())
|
||||||
|
|
||||||
|
|
||||||
|
def random_zone_file(name='testzoneimport'):
|
||||||
|
return "$ORIGIN {0}{1}.com.\n" \
|
||||||
|
"$TTL 300\n" \
|
||||||
|
"{0}{1}.com. 300 IN SOA ns.{0}{1}.com. nsadmin.{0}{1}.com. 42 42 42 42 42\n" \
|
||||||
|
"{0}{1}.com. 300 IN NS ns.{0}{1}.com.\n" \
|
||||||
|
"{0}{1}.com. 300 IN MX 10 mail.{0}{1}.com.\n" \
|
||||||
|
"ns.{0}{1}.com. 300 IN A 10.0.0.1\n" \
|
||||||
|
"mail.{0}{1}.com. 300 IN A 10.0.0.2\n".format(name, random_digits())
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
@ -123,6 +124,38 @@ class ExportFixture(BaseFixture):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFixture(BaseFixture):
|
||||||
|
"""See DesignateCLI.zone_import_create for __init__ args"""
|
||||||
|
|
||||||
|
def __init__(self, zone_file_contents, user='default', *args, **kwargs):
|
||||||
|
super(ImportFixture, self).__init__(user, *args, **kwargs)
|
||||||
|
self.zone_file_contents = zone_file_contents
|
||||||
|
|
||||||
|
def _setUp(self):
|
||||||
|
super(ImportFixture, self)._setUp()
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
f.write(self.zone_file_contents)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
self.zone_import = self.client.zone_import_create(
|
||||||
|
zone_file_path=f.name,
|
||||||
|
*self.args, **self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
self.addCleanup(self.cleanup_zone_import, self.client,
|
||||||
|
self.zone_import.id)
|
||||||
|
self.addCleanup(ZoneFixture.cleanup_zone, self.client,
|
||||||
|
self.zone_import.zone_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cleanup_zone_import(cls, client, zone_import_id):
|
||||||
|
try:
|
||||||
|
client.zone_import_delete(zone_import_id)
|
||||||
|
except CommandFailed:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RecordsetFixture(BaseFixture):
|
class RecordsetFixture(BaseFixture):
|
||||||
"""See DesignateCLI.recordset_create for __init__ args"""
|
"""See DesignateCLI.recordset_create for __init__ args"""
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class TestZoneExport(BaseDesignateTest):
|
|||||||
|
|
||||||
zone_exports = self.clients.zone_export_list()
|
zone_exports = self.clients.zone_export_list()
|
||||||
self.assertGreater(len(zone_exports), 0)
|
self.assertGreater(len(zone_exports), 0)
|
||||||
self.assertTrue(self._is_export_in_list(zone_export, zone_exports))
|
self.assertTrue(self._is_entity_in_list(zone_export, zone_exports))
|
||||||
|
|
||||||
def test_create_and_show_zone_export(self):
|
def test_create_and_show_zone_export(self):
|
||||||
zone_export = self.useFixture(ExportFixture(
|
zone_export = self.useFixture(ExportFixture(
|
||||||
@ -60,12 +60,12 @@ class TestZoneExport(BaseDesignateTest):
|
|||||||
)).zone_export
|
)).zone_export
|
||||||
|
|
||||||
zone_exports = self.clients.zone_export_list()
|
zone_exports = self.clients.zone_export_list()
|
||||||
self.assertTrue(self._is_export_in_list(zone_export, zone_exports))
|
self.assertTrue(self._is_entity_in_list(zone_export, zone_exports))
|
||||||
|
|
||||||
self.clients.zone_export_delete(zone_export.id)
|
self.clients.zone_export_delete(zone_export.id)
|
||||||
|
|
||||||
zone_exports = self.clients.zone_export_list()
|
zone_exports = self.clients.zone_export_list()
|
||||||
self.assertFalse(self._is_export_in_list(zone_export, zone_exports))
|
self.assertFalse(self._is_entity_in_list(zone_export, zone_exports))
|
||||||
|
|
||||||
def test_show_export_file(self):
|
def test_show_export_file(self):
|
||||||
zone_export = self.useFixture(ExportFixture(
|
zone_export = self.useFixture(ExportFixture(
|
||||||
@ -79,16 +79,3 @@ class TestZoneExport(BaseDesignateTest):
|
|||||||
self.assertIn('SOA', fetched_export.data)
|
self.assertIn('SOA', fetched_export.data)
|
||||||
self.assertIn('NS', fetched_export.data)
|
self.assertIn('NS', fetched_export.data)
|
||||||
self.assertIn(self.zone.name, fetched_export.data)
|
self.assertIn(self.zone.name, fetched_export.data)
|
||||||
|
|
||||||
def _is_export_in_list(self, zone_export, zone_export_list):
|
|
||||||
"""Determines if the given export exists in the given export list.
|
|
||||||
|
|
||||||
Uses the zone export id for comparison.
|
|
||||||
|
|
||||||
Because the zone export list command displays fewer fields than
|
|
||||||
the show command, an __eq__ method on the FieldValueModel class
|
|
||||||
is insufficient.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return any([export_record.id == zone_export.id
|
|
||||||
for export_record in zone_export_list])
|
|
||||||
|
63
designateclient/functionaltests/v2/test_zone_import.py
Normal file
63
designateclient/functionaltests/v2/test_zone_import.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2016 Rackspace
|
||||||
|
|
||||||
|
Author: Rahman Syed <rahman.syed@gmail.com>
|
||||||
|
|
||||||
|
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 designateclient.functionaltests.base import BaseDesignateTest
|
||||||
|
from designateclient.functionaltests.datagen import random_zone_file
|
||||||
|
from designateclient.functionaltests.v2.fixtures import ImportFixture
|
||||||
|
|
||||||
|
|
||||||
|
class TestZoneImport(BaseDesignateTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestZoneImport, self).setUp()
|
||||||
|
self.ensure_tld_exists('com')
|
||||||
|
self.zone_file_contents = random_zone_file()
|
||||||
|
|
||||||
|
def test_list_zone_imports(self):
|
||||||
|
zone_import = self.useFixture(ImportFixture(
|
||||||
|
zone_file_contents=self.zone_file_contents
|
||||||
|
)).zone_import
|
||||||
|
|
||||||
|
zone_imports = self.clients.zone_import_list()
|
||||||
|
self.assertGreater(len(zone_imports), 0)
|
||||||
|
self.assertTrue(self._is_entity_in_list(zone_import, zone_imports))
|
||||||
|
|
||||||
|
def test_create_and_show_zone_import(self):
|
||||||
|
zone_import = self.useFixture(ImportFixture(
|
||||||
|
zone_file_contents=self.zone_file_contents
|
||||||
|
)).zone_import
|
||||||
|
|
||||||
|
fetched_import = self.clients.zone_import_show(zone_import.id)
|
||||||
|
|
||||||
|
self.assertEqual(zone_import.created_at, fetched_import.created_at)
|
||||||
|
self.assertEqual(zone_import.id, fetched_import.id)
|
||||||
|
self.assertEqual(zone_import.project_id, fetched_import.project_id)
|
||||||
|
|
||||||
|
self.assertEqual('COMPLETE', fetched_import.status)
|
||||||
|
|
||||||
|
def test_delete_zone_import(self):
|
||||||
|
zone_import = self.useFixture(ImportFixture(
|
||||||
|
zone_file_contents=self.zone_file_contents
|
||||||
|
)).zone_import
|
||||||
|
|
||||||
|
zone_imports = self.clients.zone_import_list()
|
||||||
|
self.assertTrue(self._is_entity_in_list(zone_import, zone_imports))
|
||||||
|
|
||||||
|
self.clients.zone_import_delete(zone_import.id)
|
||||||
|
|
||||||
|
zone_imports = self.clients.zone_import_list()
|
||||||
|
self.assertFalse(self._is_entity_in_list(zone_import, zone_imports))
|
@ -312,3 +312,57 @@ class TestZoneExports(v2.APIV2TestCase, v2.CrudMixin):
|
|||||||
|
|
||||||
response = self.client.zone_exports.get_export(ref["id"])
|
response = self.client.zone_exports.get_export(ref["id"])
|
||||||
self.assertEqual(ref, response)
|
self.assertEqual(ref, response)
|
||||||
|
|
||||||
|
|
||||||
|
class TestZoneImports(v2.APIV2TestCase, v2.CrudMixin):
|
||||||
|
def new_ref(self, **kwargs):
|
||||||
|
ref = super(TestZoneImports, self).new_ref(**kwargs)
|
||||||
|
ref.setdefault("zone_id", uuid.uuid4().hex)
|
||||||
|
ref.setdefault("created_at", time.strftime("%c"))
|
||||||
|
ref.setdefault("updated_at", time.strftime("%c"))
|
||||||
|
ref.setdefault("status", 'PENDING')
|
||||||
|
ref.setdefault("message", 'Importing...')
|
||||||
|
ref.setdefault("version", '1')
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def test_create_import(self):
|
||||||
|
zonefile = '$ORIGIN example.com'
|
||||||
|
|
||||||
|
parts = ["zones", "tasks", "imports"]
|
||||||
|
self.stub_url('POST', parts=parts, json=zonefile)
|
||||||
|
|
||||||
|
self.client.zone_imports.create(zonefile)
|
||||||
|
self.assertRequestBodyIs(body=zonefile)
|
||||||
|
|
||||||
|
def test_get_import(self):
|
||||||
|
ref = self.new_ref()
|
||||||
|
|
||||||
|
parts = ["zones", "tasks", "imports", ref["id"]]
|
||||||
|
self.stub_url('GET', parts=parts, json=ref)
|
||||||
|
self.stub_entity("GET", parts=parts, entity=ref, id=ref["id"])
|
||||||
|
|
||||||
|
response = self.client.zone_imports.get_import_record(ref["id"])
|
||||||
|
self.assertEqual(ref, response)
|
||||||
|
|
||||||
|
def test_list_imports(self):
|
||||||
|
items = [
|
||||||
|
self.new_ref(),
|
||||||
|
self.new_ref()
|
||||||
|
]
|
||||||
|
|
||||||
|
parts = ["zones", "tasks", "imports"]
|
||||||
|
self.stub_url('GET', parts=parts, json={"imports": items})
|
||||||
|
|
||||||
|
listed = self.client.zone_imports.list()
|
||||||
|
self.assertList(items, listed["imports"])
|
||||||
|
self.assertQueryStringIs("")
|
||||||
|
|
||||||
|
def test_delete_import(self):
|
||||||
|
ref = self.new_ref()
|
||||||
|
|
||||||
|
parts = ["zones", "tasks", "imports", ref["id"]]
|
||||||
|
self.stub_url('DELETE', parts=parts, json=ref)
|
||||||
|
self.stub_entity("DELETE", parts=parts, id=ref["id"])
|
||||||
|
|
||||||
|
self.client.zone_imports.delete(ref["id"])
|
||||||
|
self.assertRequestBodyIs(None)
|
||||||
|
@ -37,6 +37,10 @@ def _format_zone_export_record(zone_export_record):
|
|||||||
zone_export_record.pop('links', None)
|
zone_export_record.pop('links', None)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_zone_import_record(zone_import_record):
|
||||||
|
zone_import_record.pop('links', None)
|
||||||
|
|
||||||
|
|
||||||
class ListZonesCommand(lister.Lister):
|
class ListZonesCommand(lister.Lister):
|
||||||
"""List zones"""
|
"""List zones"""
|
||||||
|
|
||||||
@ -501,3 +505,96 @@ class ShowZoneExportFileCommand(show.ShowOne):
|
|||||||
data = client.zone_exports.get_export(parsed_args.zone_export_id)
|
data = client.zone_exports.get_export(parsed_args.zone_export_id)
|
||||||
|
|
||||||
return ['data'], [data]
|
return ['data'], [data]
|
||||||
|
|
||||||
|
|
||||||
|
class ImportZoneCommand(show.ShowOne):
|
||||||
|
"""Import a Zone from a file on the filesystem"""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ImportZoneCommand, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
|
||||||
|
parser.add_argument('zone_file_path',
|
||||||
|
help="Path to a zone file", type=str)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
client = self.app.client_manager.dns
|
||||||
|
|
||||||
|
with open(parsed_args.zone_file_path, 'r') as f:
|
||||||
|
zone_file_contents = f.read()
|
||||||
|
|
||||||
|
data = client.zone_imports.create(zone_file_contents)
|
||||||
|
_format_zone_import_record(data)
|
||||||
|
|
||||||
|
LOG.info('Zone Import %s was created', data['id'])
|
||||||
|
|
||||||
|
return six.moves.zip(*sorted(six.iteritems(data)))
|
||||||
|
|
||||||
|
|
||||||
|
class ListZoneImportsCommand(lister.Lister):
|
||||||
|
"""List Zone Imports"""
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
'id',
|
||||||
|
'zone_id',
|
||||||
|
'created_at',
|
||||||
|
'status',
|
||||||
|
'message',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ListZoneImportsCommand, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
client = self.app.client_manager.dns
|
||||||
|
|
||||||
|
data = client.zone_imports.list()
|
||||||
|
|
||||||
|
cols = self.columns
|
||||||
|
return cols, (utils.get_item_properties(s, cols)
|
||||||
|
for s in data['imports'])
|
||||||
|
|
||||||
|
|
||||||
|
class ShowZoneImportCommand(show.ShowOne):
|
||||||
|
"""Show a Zone Import"""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ShowZoneImportCommand, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
|
||||||
|
parser.add_argument('zone_import_id', help="Zone Import ID", type=str)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
client = self.app.client_manager.dns
|
||||||
|
|
||||||
|
data = client.zone_imports.get_import_record(
|
||||||
|
parsed_args.zone_import_id)
|
||||||
|
_format_zone_import_record(data)
|
||||||
|
|
||||||
|
return six.moves.zip(*sorted(six.iteritems(data)))
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteZoneImportCommand(command.Command):
|
||||||
|
"""Delete a Zone Import"""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(DeleteZoneImportCommand, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
|
||||||
|
parser.add_argument('zone_import_id', help="Zone Import ID", type=str)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
client = self.app.client_manager.dns
|
||||||
|
|
||||||
|
client.zone_imports.delete(parsed_args.zone_import_id)
|
||||||
|
|
||||||
|
LOG.info('Zone Import %s was deleted', parsed_args.zone_import_id)
|
||||||
|
@ -147,4 +147,15 @@ class ZoneExportsController(V2Controller):
|
|||||||
|
|
||||||
|
|
||||||
class ZoneImportsController(V2Controller):
|
class ZoneImportsController(V2Controller):
|
||||||
pass
|
def create(self, zone_file_contents):
|
||||||
|
return self._post('/zones/tasks/imports', data=zone_file_contents,
|
||||||
|
headers={'Content-Type': 'text/dns'})
|
||||||
|
|
||||||
|
def get_import_record(self, zone_import_id):
|
||||||
|
return self._get('/zones/tasks/imports/%s' % zone_import_id)
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
return self._get('/zones/tasks/imports')
|
||||||
|
|
||||||
|
def delete(self, zone_import_id):
|
||||||
|
return self._delete('/zones/tasks/imports/%s' % zone_import_id)
|
||||||
|
@ -115,6 +115,11 @@ openstack.dns.v2 =
|
|||||||
zone_export_delete = designateclient.v2.cli.zones:DeleteZoneExportCommand
|
zone_export_delete = designateclient.v2.cli.zones:DeleteZoneExportCommand
|
||||||
zone_export_showfile = designateclient.v2.cli.zones:ShowZoneExportFileCommand
|
zone_export_showfile = designateclient.v2.cli.zones:ShowZoneExportFileCommand
|
||||||
|
|
||||||
|
zone_import_create = designateclient.v2.cli.zones:ImportZoneCommand
|
||||||
|
zone_import_list = designateclient.v2.cli.zones:ListZoneImportsCommand
|
||||||
|
zone_import_show = designateclient.v2.cli.zones:ShowZoneImportCommand
|
||||||
|
zone_import_delete = designateclient.v2.cli.zones:DeleteZoneImportCommand
|
||||||
|
|
||||||
zone_transfer_request_create = designateclient.v2.cli.zones:CreateTransferRequestCommand
|
zone_transfer_request_create = designateclient.v2.cli.zones:CreateTransferRequestCommand
|
||||||
zone_transfer_request_list = designateclient.v2.cli.zones:ListTransferRequestsCommand
|
zone_transfer_request_list = designateclient.v2.cli.zones:ListTransferRequestsCommand
|
||||||
zone_transfer_request_show = designateclient.v2.cli.zones:ShowTransferRequestCommand
|
zone_transfer_request_show = designateclient.v2.cli.zones:ShowTransferRequestCommand
|
||||||
|
Loading…
x
Reference in New Issue
Block a user