summaryrefslogtreecommitdiff
path: root/designateclient
diff options
context:
space:
mode:
authorRahman Syed <rahman.syed@gmail.com>2016-03-22 17:08:18 -0500
committerRahman Syed <rahman.syed@gmail.com>2016-03-23 20:34:14 -0500
commitce50ad944490e6238597a72106f5be705db3aae8 (patch)
treeb004924adfdbe4e511354d665f6f5c16c8a5ef62 /designateclient
parent575917105b51ab5ce37c61d1afee6301e3cdd9de (diff)
downloadpython-designateclient-ce50ad944490e6238597a72106f5be705db3aae8.tar.gz
Implement zone export
Zone export commands (create, list, show, delete, showfile) are missing from the python-designateclient. This change includes implementation, as well as unit tests and functionaltests. Change-Id: I957946d739bceea1074e2fda2ce7f841143b0611 Partial-Bug: #1550532
Diffstat (limited to 'designateclient')
-rw-r--r--designateclient/functionaltests/client.py28
-rw-r--r--designateclient/functionaltests/models.py17
-rw-r--r--designateclient/functionaltests/v2/fixtures.py25
-rw-r--r--designateclient/functionaltests/v2/test_zone_export.py94
-rw-r--r--designateclient/tests/v2/test_zones.py65
-rw-r--r--designateclient/v2/base.py4
-rw-r--r--designateclient/v2/cli/zones.py143
-rw-r--r--designateclient/v2/client.py5
-rw-r--r--designateclient/v2/zones.py24
9 files changed, 369 insertions, 36 deletions
diff --git a/designateclient/functionaltests/client.py b/designateclient/functionaltests/client.py
index 39dc970..370d48f 100644
--- a/designateclient/functionaltests/client.py
+++ b/designateclient/functionaltests/client.py
@@ -148,6 +148,31 @@ class ZoneTransferCommands(object):
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+class ZoneExportCommands(object):
+ """A mixin for DesignateCLI to add zone export commands"""
+
+ def zone_export_list(self, *args, **kwargs):
+ cmd = 'zone export list'
+ return self.parsed_cmd(cmd, ListModel, *args, **kwargs)
+
+ def zone_export_create(self, zone_id, *args, **kwargs):
+ cmd = 'zone export create {0}'.format(
+ zone_id)
+ return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+
+ def zone_export_show(self, zone_export_id, *args, **kwargs):
+ cmd = 'zone export show {0}'.format(zone_export_id)
+ return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+
+ def zone_export_delete(self, zone_export_id, *args, **kwargs):
+ cmd = 'zone export delete {0}'.format(zone_export_id)
+ return self.parsed_cmd(cmd, *args, **kwargs)
+
+ def zone_export_showfile(self, zone_export_id, *args, **kwargs):
+ cmd = 'zone export showfile {0}'.format(zone_export_id)
+ return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
+
+
class RecordsetCommands(object):
def recordset_show(self, zone_id, id, *args, **kwargs):
@@ -258,7 +283,8 @@ class BlacklistCommands(object):
class DesignateCLI(base.CLIClient, ZoneCommands, ZoneTransferCommands,
- RecordsetCommands, TLDCommands, BlacklistCommands):
+ ZoneExportCommands, RecordsetCommands, TLDCommands,
+ BlacklistCommands):
# instantiate this once to minimize requests to keystone
_CLIENTS = None
diff --git a/designateclient/functionaltests/models.py b/designateclient/functionaltests/models.py
index f298084..a6a307c 100644
--- a/designateclient/functionaltests/models.py
+++ b/designateclient/functionaltests/models.py
@@ -45,8 +45,23 @@ class FieldValueModel(Model):
"""
table = output_parser.table(out)
+
+ # Because the output_parser handles Values with multiple lines
+ # in additional Field/Value pairs with Field name '', the following
+ # code is necessary to aggregate Values.
+ #
+ # The list of Field/Value pairs is in-order, so we can append Value
+ # continuation to the previously seen Field, with a newline separator.
+ value_lines = []
+ prev_field = None
for field, value in table['values']:
- setattr(self, field, value)
+ if field == '':
+ value_lines.append(value)
+ setattr(self, prev_field, '\n'.join(value_lines))
+ else:
+ setattr(self, field, value)
+ prev_field = field
+ value_lines = [value]
class ListEntryModel(Model):
diff --git a/designateclient/functionaltests/v2/fixtures.py b/designateclient/functionaltests/v2/fixtures.py
index 54c3daa..2e61148 100644
--- a/designateclient/functionaltests/v2/fixtures.py
+++ b/designateclient/functionaltests/v2/fixtures.py
@@ -98,6 +98,31 @@ class TransferRequestFixture(BaseFixture):
pass
+class ExportFixture(BaseFixture):
+ """See DesignateCLI.zone_export_create for __init__ args"""
+
+ def __init__(self, zone, user='default', *args, **kwargs):
+ super(ExportFixture, self).__init__(user, *args, **kwargs)
+ self.zone = zone
+
+ def _setUp(self):
+ super(ExportFixture, self)._setUp()
+ self.zone_export = self.client.zone_export_create(
+ zone_id=self.zone.id,
+ *self.args, **self.kwargs
+ )
+ self.addCleanup(self.cleanup_zone_export, self.client,
+ self.zone_export.id)
+ self.addCleanup(ZoneFixture.cleanup_zone, self.client, self.zone.id)
+
+ @classmethod
+ def cleanup_zone_export(cls, client, zone_export_id):
+ try:
+ client.zone_export_delete(zone_export_id)
+ except CommandFailed:
+ pass
+
+
class RecordsetFixture(BaseFixture):
"""See DesignateCLI.recordset_create for __init__ args"""
diff --git a/designateclient/functionaltests/v2/test_zone_export.py b/designateclient/functionaltests/v2/test_zone_export.py
new file mode 100644
index 0000000..fc10f85
--- /dev/null
+++ b/designateclient/functionaltests/v2/test_zone_export.py
@@ -0,0 +1,94 @@
+"""
+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_name
+from designateclient.functionaltests.v2.fixtures import ExportFixture
+from designateclient.functionaltests.v2.fixtures import ZoneFixture
+
+
+class TestZoneExport(BaseDesignateTest):
+
+ def setUp(self):
+ super(TestZoneExport, self).setUp()
+ self.ensure_tld_exists('com')
+ fixture = self.useFixture(ZoneFixture(
+ name=random_zone_name(),
+ email='test@example.com',
+ ))
+ self.zone = fixture.zone
+
+ def test_list_zone_exports(self):
+ zone_export = self.useFixture(ExportFixture(
+ zone=self.zone
+ )).zone_export
+
+ zone_exports = self.clients.zone_export_list()
+ self.assertGreater(len(zone_exports), 0)
+ self.assertTrue(self._is_export_in_list(zone_export, zone_exports))
+
+ def test_create_and_show_zone_export(self):
+ zone_export = self.useFixture(ExportFixture(
+ zone=self.zone
+ )).zone_export
+
+ fetched_export = self.clients.zone_export_show(zone_export.id)
+
+ self.assertEqual(zone_export.created_at, fetched_export.created_at)
+ self.assertEqual(zone_export.id, fetched_export.id)
+ self.assertEqual(zone_export.message, fetched_export.message)
+ self.assertEqual(zone_export.project_id, fetched_export.project_id)
+ self.assertEqual(zone_export.zone_id, fetched_export.zone_id)
+
+ def test_delete_zone_export(self):
+ zone_export = self.useFixture(ExportFixture(
+ zone=self.zone
+ )).zone_export
+
+ zone_exports = self.clients.zone_export_list()
+ self.assertTrue(self._is_export_in_list(zone_export, zone_exports))
+
+ self.clients.zone_export_delete(zone_export.id)
+
+ zone_exports = self.clients.zone_export_list()
+ self.assertFalse(self._is_export_in_list(zone_export, zone_exports))
+
+ def test_show_export_file(self):
+ zone_export = self.useFixture(ExportFixture(
+ zone=self.zone
+ )).zone_export
+
+ fetched_export = self.clients.zone_export_showfile(zone_export.id)
+
+ self.assertIn('$ORIGIN', fetched_export.data)
+ self.assertIn('$TTL', fetched_export.data)
+ self.assertIn('SOA', fetched_export.data)
+ self.assertIn('NS', 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])
diff --git a/designateclient/tests/v2/test_zones.py b/designateclient/tests/v2/test_zones.py
index 8f7ac9d..673615e 100644
--- a/designateclient/tests/v2/test_zones.py
+++ b/designateclient/tests/v2/test_zones.py
@@ -13,6 +13,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import time
import uuid
from designateclient.tests import v2
@@ -247,3 +248,67 @@ class TestZoneTransfers(v2.APIV2TestCase, v2.CrudMixin):
response = self.client.zone_transfers.get_accept(accept_id)
self.assertEqual(ref, response)
+
+
+class TestZoneExports(v2.APIV2TestCase, v2.CrudMixin):
+ def new_ref(self, **kwargs):
+ ref = super(TestZoneExports, 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("version", '1')
+ return ref
+
+ def test_create_export(self):
+ zone = uuid.uuid4().hex
+ ref = {}
+
+ parts = ["zones", zone, "tasks", "export"]
+ self.stub_url('POST', parts=parts, json=ref)
+
+ self.client.zone_exports.create(zone)
+ self.assertRequestBodyIs(json=ref)
+
+ def test_get_export(self):
+ ref = self.new_ref()
+
+ parts = ["zones", "tasks", "exports", 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_exports.get_export_record(ref["id"])
+ self.assertEqual(ref, response)
+
+ def test_list_exports(self):
+ items = [
+ self.new_ref(),
+ self.new_ref()
+ ]
+
+ parts = ["zones", "tasks", "exports"]
+ self.stub_url('GET', parts=parts, json={"exports": items})
+
+ listed = self.client.zone_exports.list()
+ self.assertList(items, listed["exports"])
+ self.assertQueryStringIs("")
+
+ def test_delete_export(self):
+ ref = self.new_ref()
+
+ parts = ["zones", "tasks", "exports", ref["id"]]
+ self.stub_url('DELETE', parts=parts, json=ref)
+ self.stub_entity("DELETE", parts=parts, id=ref["id"])
+
+ self.client.zone_exports.delete(ref["id"])
+ self.assertRequestBodyIs(None)
+
+ def test_get_export_file(self):
+ ref = self.new_ref()
+
+ parts = ["zones", "tasks", "exports", ref["id"], "export"]
+ self.stub_url('GET', parts=parts, json=ref)
+ self.stub_entity("GET", parts=parts, entity=ref, id=ref["id"])
+
+ response = self.client.zone_exports.get_export(ref["id"])
+ self.assertEqual(ref, response)
diff --git a/designateclient/v2/base.py b/designateclient/v2/base.py
index e03cfe6..16d47c4 100644
--- a/designateclient/v2/base.py
+++ b/designateclient/v2/base.py
@@ -26,8 +26,8 @@ class DesignateList(list):
class V2Controller(client.Controller):
- def _get(self, url, response_key=None):
- resp, body = self.client.session.get(url)
+ def _get(self, url, response_key=None, **kwargs):
+ resp, body = self.client.session.get(url, **kwargs)
if response_key is not None:
data = DesignateList()
diff --git a/designateclient/v2/cli/zones.py b/designateclient/v2/cli/zones.py
index 465c3ed..938a49a 100644
--- a/designateclient/v2/cli/zones.py
+++ b/designateclient/v2/cli/zones.py
@@ -33,6 +33,10 @@ def _format_zone(zone):
zone['masters'] = ", ".join(zone['masters'])
+def _format_zone_export_record(zone_export_record):
+ zone_export_record.pop('links', None)
+
+
class ListZonesCommand(lister.Lister):
"""List zones"""
@@ -205,38 +209,6 @@ class DeleteZoneCommand(command.Command):
LOG.info('Zone %s was deleted', parsed_args.id)
-class ExportZoneCommand(command.Command):
- """Export a zone."""
- def get_parser(self, prog_name):
- parser = super(ExportZoneCommand, self).get_parser(prog_name)
-
- parser.add_argument('id', help="Zone ID")
-
- return parser
-
- def take_action(self, parsed_args):
- client = self.app.client_manager.dns
- response, _ = client.zones.export(parsed_args.id)
- print(response.content)
-
-
-class ImportZoneCommand(command.Command):
- """Import a zone"""
- def get_parser(self, prog_name):
- parser = super(ImportZoneCommand, self).get_parser(prog_name)
-
- parser.add_argument('--path', help="Path to zone file", required=True)
-
- return parser
-
- def take_action(self, parsed_args):
- client = self.app.client_manager.dns
-
- with open(parsed_args.path) as contents:
- client.zones.import_(contents)
- LOG.info("Imported zone successfully")
-
-
class AbandonZoneCommand(command.Command):
"""Abandon a zone"""
def get_parser(self, prog_name):
@@ -419,3 +391,110 @@ class ShowTransferAcceptCommand(show.ShowOne):
data = client.zone_transfers.get_accept(parsed_args.id)
return six.moves.zip(*sorted(six.iteritems(data)))
+
+
+class ExportZoneCommand(show.ShowOne):
+ """Export a Zone"""
+
+ def get_parser(self, prog_name):
+ parser = super(ExportZoneCommand, self).get_parser(
+ prog_name)
+
+ parser.add_argument('zone_id', help="Zone ID", type=str)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+
+ data = client.zone_exports.create(parsed_args.zone_id)
+ _format_zone_export_record(data)
+
+ LOG.info('Zone Export %s was created', data['id'])
+
+ return six.moves.zip(*sorted(six.iteritems(data)))
+
+
+class ListZoneExportsCommand(lister.Lister):
+ """List Zone Exports"""
+
+ columns = [
+ 'id',
+ 'zone_id',
+ 'created_at',
+ 'status',
+ ]
+
+ def get_parser(self, prog_name):
+ parser = super(ListZoneExportsCommand, self).get_parser(
+ prog_name)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+
+ data = client.zone_exports.list()
+
+ cols = self.columns
+ return cols, (utils.get_item_properties(s, cols)
+ for s in data['exports'])
+
+
+class ShowZoneExportCommand(show.ShowOne):
+ """Show a Zone Export"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowZoneExportCommand, self).get_parser(
+ prog_name)
+
+ parser.add_argument('zone_export_id', help="Zone Export ID", type=str)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+
+ data = client.zone_exports.get_export_record(
+ parsed_args.zone_export_id)
+ _format_zone_export_record(data)
+
+ return six.moves.zip(*sorted(six.iteritems(data)))
+
+
+class DeleteZoneExportCommand(command.Command):
+ """Delete a Zone Export"""
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteZoneExportCommand, self).get_parser(
+ prog_name)
+
+ parser.add_argument('zone_export_id', help="Zone Export ID", type=str)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+
+ client.zone_exports.delete(parsed_args.zone_export_id)
+
+ LOG.info('Zone Export %s was deleted', parsed_args.zone_export_id)
+
+
+class ShowZoneExportFileCommand(show.ShowOne):
+ """Show the zone file for the Zone Export"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowZoneExportFileCommand, self).get_parser(
+ prog_name)
+
+ parser.add_argument('zone_export_id', help="Zone Export ID", type=str)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.dns
+
+ data = client.zone_exports.get_export(parsed_args.zone_export_id)
+
+ return ['data'], [data]
diff --git a/designateclient/v2/client.py b/designateclient/v2/client.py
index daeb48e..2dc3f80 100644
--- a/designateclient/v2/client.py
+++ b/designateclient/v2/client.py
@@ -23,6 +23,8 @@ from designateclient.v2.recordsets import RecordSetController
from designateclient.v2.reverse import FloatingIPController
from designateclient.v2.tlds import TLDController
from designateclient.v2.zones import ZoneController
+from designateclient.v2.zones import ZoneExportsController
+from designateclient.v2.zones import ZoneImportsController
from designateclient.v2.zones import ZoneTransfersController
from designateclient import version
@@ -55,6 +57,7 @@ class DesignateAdapter(adapter.LegacyJsonAdapter):
response_payload = response.json()
except ValueError:
response_payload = {}
+ body = response.text
if response.status_code == 400:
raise exceptions.BadRequest(**response_payload)
@@ -97,3 +100,5 @@ class Client(object):
self.tlds = TLDController(self)
self.zones = ZoneController(self)
self.zone_transfers = ZoneTransfersController(self)
+ self.zone_exports = ZoneExportsController(self)
+ self.zone_imports = ZoneImportsController(self)
diff --git a/designateclient/v2/zones.py b/designateclient/v2/zones.py
index 22631ab..8c200c8 100644
--- a/designateclient/v2/zones.py
+++ b/designateclient/v2/zones.py
@@ -124,3 +124,27 @@ class ZoneTransfersController(V2Controller):
def get_accept(self, accept_id):
url = '/zones/tasks/transfer_accepts/%s' % accept_id
return self._get(url)
+
+
+class ZoneExportsController(V2Controller):
+ def create(self, zone):
+ zone_id = v2_utils.resolve_by_name(self.client.zones.list, zone)
+
+ return self._post('/zones/%s/tasks/export' % zone_id)
+
+ def get_export_record(self, zone_export_id):
+ return self._get('/zones/tasks/exports/%s' % zone_export_id)
+
+ def list(self):
+ return self._get('/zones/tasks/exports')
+
+ def delete(self, zone_export_id):
+ return self._delete('/zones/tasks/exports/%s' % zone_export_id)
+
+ def get_export(self, zone_export_id):
+ return self._get('/zones/tasks/exports/%s/export' % zone_export_id,
+ headers={'Accept': 'text/dns'})
+
+
+class ZoneImportsController(V2Controller):
+ pass