summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-04-14 14:53:31 +0000
committerGerrit Code Review <review@openstack.org>2015-04-14 14:53:31 +0000
commitf8ff85371251de40a7311ab2cd0a5a0cdda65d3b (patch)
tree383f38c72e2b0dbe687cceea54a7d1bb4223f466
parent0b1f503459f071c847ed5a0dcfe8e10831754691 (diff)
parentaba646b94f07ad55eef09999f6a14ecb14ab25f1 (diff)
downloaddesignate-f8ff85371251de40a7311ab2cd0a5a0cdda65d3b.tar.gz
Merge "Move Zone Import / Export to /admin API"
-rw-r--r--designate/api/admin/controllers/extensions/export.py61
-rw-r--r--designate/api/admin/controllers/extensions/import_.py77
-rw-r--r--designate/api/admin/controllers/extensions/zones.py39
-rw-r--r--designate/api/v2/controllers/zones/__init__.py94
-rw-r--r--designate/api/v2/patches.py3
-rw-r--r--designate/tests/test_api/test_admin/extensions/test_zones.py81
-rw-r--r--designate/tests/test_api/test_v2/test_zones.py54
-rw-r--r--doc/source/rest/admin/zones.rst114
-rw-r--r--doc/source/rest/v2/zones.rst100
-rw-r--r--etc/designate/designate.conf.sample3
-rw-r--r--etc/designate/policy.json3
-rw-r--r--setup.cfg1
12 files changed, 380 insertions, 250 deletions
diff --git a/designate/api/admin/controllers/extensions/export.py b/designate/api/admin/controllers/extensions/export.py
new file mode 100644
index 00000000..ee4de7c0
--- /dev/null
+++ b/designate/api/admin/controllers/extensions/export.py
@@ -0,0 +1,61 @@
+# COPYRIGHT 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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.
+
+import pecan
+from oslo_log import log as logging
+
+from designate.api.v2.controllers import rest
+from designate import utils
+from designate import policy
+
+LOG = logging.getLogger(__name__)
+
+
+class ExportController(rest.RestController):
+
+ @pecan.expose(template=None, content_type='text/dns')
+ @utils.validate_uuid('zone_id')
+ def get_one(self, zone_id):
+ context = pecan.request.environ['context']
+
+ policy.check('zone_export', context)
+
+ servers = self.central_api.get_domain_servers(context, zone_id)
+ domain = self.central_api.get_domain(context, zone_id)
+
+ criterion = {'domain_id': zone_id}
+ recordsets = self.central_api.find_recordsets(context, criterion)
+
+ records = []
+
+ for recordset in recordsets:
+ criterion = {
+ 'domain_id': domain['id'],
+ 'recordset_id': recordset['id']
+ }
+
+ raw_records = self.central_api.find_records(context, criterion)
+
+ for record in raw_records:
+ records.append({
+ 'name': recordset['name'],
+ 'type': recordset['type'],
+ 'ttl': recordset['ttl'],
+ 'data': record['data'],
+ })
+
+ return utils.render_template('bind9-zone.jinja2',
+ servers=servers,
+ domain=domain,
+ records=records)
diff --git a/designate/api/admin/controllers/extensions/import_.py b/designate/api/admin/controllers/extensions/import_.py
new file mode 100644
index 00000000..d041a89a
--- /dev/null
+++ b/designate/api/admin/controllers/extensions/import_.py
@@ -0,0 +1,77 @@
+# COPYRIGHT 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 dns import zone as dnszone
+from dns import exception as dnsexception
+import pecan
+from oslo_log import log as logging
+from oslo.config import cfg
+
+from designate.api.v2.controllers import rest
+from designate import dnsutils
+from designate import exceptions
+from designate.objects.adapters import DesignateAdapter
+from designate import policy
+
+LOG = logging.getLogger(__name__)
+
+
+class ImportController(rest.RestController):
+
+ BASE_URI = cfg.CONF['service:api'].api_base_uri.rstrip('/')
+
+ @pecan.expose(template='json:', content_type='application/json')
+ def post_all(self):
+ request = pecan.request
+ response = pecan.response
+ context = pecan.request.environ['context']
+
+ policy.check('zone_import', context)
+
+ try:
+ dnspython_zone = dnszone.from_text(
+ request.body,
+ # Don't relativize, otherwise we end up with '@' record names.
+ relativize=False,
+ # Dont check origin, we allow missing NS records (missing SOA
+ # records are taken care of in _create_zone).
+ check_origin=False)
+ domain = dnsutils.from_dnspython_zone(dnspython_zone)
+ domain.type = 'PRIMARY'
+
+ for rrset in list(domain.recordsets):
+ if rrset.type in ('NS', 'SOA'):
+ domain.recordsets.remove(rrset)
+
+ except dnszone.UnknownOrigin:
+ raise exceptions.BadRequest('The $ORIGIN statement is required and'
+ ' must be the first statement in the'
+ ' zonefile.')
+ except dnsexception.SyntaxError:
+ raise exceptions.BadRequest('Malformed zonefile.')
+
+ zone = self.central_api.create_domain(context, domain)
+
+ if zone['status'] == 'PENDING':
+ response.status_int = 202
+ else:
+ response.status_int = 201
+
+ zone = DesignateAdapter.render('API_v2', zone, request=request)
+
+ zone['links']['self'] = '%s%s/%s' % (
+ self.BASE_URI, 'v2/zones', zone['id'])
+
+ response.headers['Location'] = zone['links']['self']
+
+ return zone
diff --git a/designate/api/admin/controllers/extensions/zones.py b/designate/api/admin/controllers/extensions/zones.py
new file mode 100644
index 00000000..f994c729
--- /dev/null
+++ b/designate/api/admin/controllers/extensions/zones.py
@@ -0,0 +1,39 @@
+# COPYRIGHT 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 designate.api.v2.controllers import rest
+from designate.api.admin.controllers.extensions import import_
+from designate.api.admin.controllers.extensions import export
+
+LOG = logging.getLogger(__name__)
+
+
+class ZonesController(rest.RestController):
+
+ @staticmethod
+ def get_path():
+ return '.zones'
+
+ def __init__(self):
+ # Import is a keyword - so we have to do a setattr instead
+ setattr(self, 'import', import_.ImportController())
+ super(ZonesController, self).__init__()
+
+ # We cannot do an assignment as import is a keyword. it is done as part of
+ # the __init__() above
+ #
+ # import = import_.CountsController()
+ export = export.ExportController()
diff --git a/designate/api/v2/controllers/zones/__init__.py b/designate/api/v2/controllers/zones/__init__.py
index 0148f920..09d7da66 100644
--- a/designate/api/v2/controllers/zones/__init__.py
+++ b/designate/api/v2/controllers/zones/__init__.py
@@ -14,13 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import pecan
-from dns import zone as dnszone
-from dns import exception as dnsexception
from oslo.config import cfg
from designate import exceptions
from designate import utils
-from designate import dnsutils
from designate.api.v2.controllers import rest
from designate.api.v2.controllers import recordsets
from designate.api.v2.controllers.zones import tasks
@@ -40,7 +37,6 @@ class ZonesController(rest.RestController):
tasks = tasks.TasksController()
@pecan.expose(template='json:', content_type='application/json')
- @pecan.expose(template=None, content_type='text/dns')
@utils.validate_uuid('zone_id')
def get_one(self, zone_id):
"""Get Zone"""
@@ -48,56 +44,12 @@ class ZonesController(rest.RestController):
request = pecan.request
context = request.environ['context']
- if 'Accept' not in request.headers:
- raise exceptions.BadRequest('Missing Accept header')
- best_match = request.accept.best_match(['application/json',
- 'text/dns'])
- if best_match == 'text/dns':
- return self._get_zonefile(request, context, zone_id)
- elif best_match == 'application/json':
- return self._get_json(request, context, zone_id)
- else:
- raise exceptions.UnsupportedAccept(
- 'Accept must be text/dns or application/json')
- def _get_json(self, request, context, zone_id):
- """'Normal' zone get"""
return DesignateAdapter.render(
'API_v2',
self.central_api.get_domain(context, zone_id),
request=request)
- def _get_zonefile(self, request, context, zone_id):
- """Export zonefile"""
- servers = self.central_api.get_domain_servers(context, zone_id)
- domain = self.central_api.get_domain(context, zone_id)
-
- criterion = {'domain_id': zone_id}
- recordsets = self.central_api.find_recordsets(context, criterion)
-
- records = []
-
- for recordset in recordsets:
- criterion = {
- 'domain_id': domain['id'],
- 'recordset_id': recordset['id']
- }
-
- raw_records = self.central_api.find_records(context, criterion)
-
- for record in raw_records:
- records.append({
- 'name': recordset['name'],
- 'type': recordset['type'],
- 'ttl': recordset['ttl'],
- 'data': record['data'],
- })
-
- return utils.render_template('bind9-zone.jinja2',
- servers=servers,
- domain=domain,
- records=records)
-
@pecan.expose(template='json:', content_type='application/json')
def get_all(self, **params):
"""List Zones"""
@@ -125,16 +77,7 @@ class ZonesController(rest.RestController):
request = pecan.request
response = pecan.response
context = request.environ['context']
- if request.content_type == 'text/dns':
- return self._post_zonefile(request, response, context)
- elif request.content_type == 'application/json':
- return self._post_json(request, response, context)
- else:
- raise exceptions.UnsupportedContentType(
- 'Content-type must be text/dns or application/json')
- def _post_json(self, request, response, context):
- """'Normal' zone creation"""
zone = request.body_dict
# We need to check the zone type before validating the schema since if
@@ -175,43 +118,6 @@ class ZonesController(rest.RestController):
return zone
- def _post_zonefile(self, request, response, context):
- """Import Zone"""
- try:
- dnspython_zone = dnszone.from_text(
- request.body,
- # Don't relativize, otherwise we end up with '@' record names.
- relativize=False,
- # Dont check origin, we allow missing NS records (missing SOA
- # records are taken care of in _create_zone).
- check_origin=False)
- domain = dnsutils.from_dnspython_zone(dnspython_zone)
- domain.type = 'PRIMARY'
-
- for rrset in list(domain.recordsets):
- if rrset.type in ('NS', 'SOA'):
- domain.recordsets.remove(rrset)
-
- except dnszone.UnknownOrigin:
- raise exceptions.BadRequest('The $ORIGIN statement is required and'
- ' must be the first statement in the'
- ' zonefile.')
- except dnsexception.SyntaxError:
- raise exceptions.BadRequest('Malformed zonefile.')
-
- zone = self.central_api.create_domain(context, domain)
-
- if zone['status'] == 'PENDING':
- response.status_int = 202
- else:
- response.status_int = 201
-
- zone = DesignateAdapter.render('API_v2', zone, request=request)
-
- response.headers['Location'] = zone['links']['self']
-
- return zone
-
@pecan.expose(template='json:', content_type='application/json')
@pecan.expose(template='json:', content_type='application/json-patch+json')
@utils.validate_uuid('zone_id')
diff --git a/designate/api/v2/patches.py b/designate/api/v2/patches.py
index a90474d2..79eee1e3 100644
--- a/designate/api/v2/patches.py
+++ b/designate/api/v2/patches.py
@@ -41,7 +41,8 @@ class Request(pecan.core.Request):
except ValueError as valueError:
raise exceptions.InvalidJson(valueError.message)
else:
- raise Exception('TODO: Unsupported Content Type')
+ raise exceptions.UnsupportedContentType(
+ 'Content-type must be application/json')
__init__ = pecan.core.Pecan.__base__.__init__
if not ismethod(__init__) or 'request_cls' not in getargspec(__init__).args:
diff --git a/designate/tests/test_api/test_admin/extensions/test_zones.py b/designate/tests/test_api/test_admin/extensions/test_zones.py
new file mode 100644
index 00000000..8917349e
--- /dev/null
+++ b/designate/tests/test_api/test_admin/extensions/test_zones.py
@@ -0,0 +1,81 @@
+# COPYRIGHT 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 dns import zone as dnszone
+from oslo.config import cfg
+
+from designate.tests.test_api.test_admin import AdminApiTestCase
+
+cfg.CONF.import_opt('enabled_extensions_admin', 'designate.api.admin',
+ group='service:api')
+
+
+class AdminApiZoneImportExportTest(AdminApiTestCase):
+ def setUp(self):
+ self.config(enabled_extensions_admin=['zones'], group='service:api')
+ super(AdminApiZoneImportExportTest, self).setUp()
+
+ # Zone import/export
+ def test_missing_origin(self):
+ self.policy({'zone_import': '@'})
+ fixture = self.get_zonefile_fixture(variant='noorigin')
+
+ self._assert_exception('bad_request', 400, self.client.post,
+ '/zones/import',
+ fixture, headers={'Content-type': 'text/dns'})
+
+ def test_missing_soa(self):
+ self.policy({'zone_import': '@'})
+ fixture = self.get_zonefile_fixture(variant='nosoa')
+
+ self._assert_exception('bad_request', 400, self.client.post,
+ '/zones/import',
+ fixture, headers={'Content-type': 'text/dns'})
+
+ def test_malformed_zonefile(self):
+ self.policy({'zone_import': '@'})
+ fixture = self.get_zonefile_fixture(variant='malformed')
+
+ self._assert_exception('bad_request', 400, self.client.post,
+ '/zones/import',
+ fixture, headers={'Content-type': 'text/dns'})
+
+ def test_import_export(self):
+ self.policy({'default': '@'})
+ # Since v2 doesn't support getting records, import and export the
+ # fixture, making sure they're the same according to dnspython
+ post_response = self.client.post('/zones/import',
+ self.get_zonefile_fixture(),
+ headers={'Content-type': 'text/dns'})
+ get_response = self.client.get('/zones/export/%s' %
+ post_response.json['id'],
+ headers={'Accept': 'text/dns'})
+
+ exported_zonefile = get_response.body
+ imported = dnszone.from_text(self.get_zonefile_fixture())
+ exported = dnszone.from_text(exported_zonefile)
+ # Compare SOA emails, since zone comparison takes care of origin
+ imported_soa = imported.get_rdataset(imported.origin, 'SOA')
+ imported_email = imported_soa[0].rname.to_text()
+ exported_soa = exported.get_rdataset(exported.origin, 'SOA')
+ exported_email = exported_soa[0].rname.to_text()
+ self.assertEqual(imported_email, exported_email)
+ # Delete SOAs since they have, at the very least, different serials,
+ # and dnspython considers that to be not equal.
+ imported.delete_rdataset(imported.origin, 'SOA')
+ exported.delete_rdataset(exported.origin, 'SOA')
+ # Delete NS records, since they won't be the same
+ imported.delete_rdataset(imported.origin, 'NS')
+ exported.delete_rdataset(exported.origin, 'NS')
+ imported.delete_rdataset('delegation', 'NS')
+ self.assertEqual(imported, exported)
diff --git a/designate/tests/test_api/test_v2/test_zones.py b/designate/tests/test_api/test_v2/test_zones.py
index 3fc6573e..a47f9650 100644
--- a/designate/tests/test_api/test_v2/test_zones.py
+++ b/designate/tests/test_api/test_v2/test_zones.py
@@ -13,7 +13,6 @@
# 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 dns import zone as dnszone
from mock import patch
from oslo.config import cfg
from oslo import messaging
@@ -234,11 +233,6 @@ class ApiV2ZonesTest(ApiV2TestCase):
self._assert_exception('domain_not_found', 404, self.client.get, url,
headers={'Accept': 'application/json'})
- def test_get_zone_missing_accept(self):
- url = '/zones/6e2146f3-87bc-4f47-adc5-4df0a5c78218'
-
- self._assert_exception('bad_request', 400, self.client.get, url)
-
def test_get_zone_bad_accept(self):
url = '/zones/6e2146f3-87bc-4f47-adc5-4df0a5c78218'
@@ -480,54 +474,6 @@ class ApiV2ZonesTest(ApiV2TestCase):
self._assert_exception('invalid_object', 400, self.client.patch_json,
'/zones/%s' % zone['id'], body)
- # Zone import/export
- def test_missing_origin(self):
- fixture = self.get_zonefile_fixture(variant='noorigin')
-
- self._assert_exception('bad_request', 400, self.client.post, '/zones',
- fixture, headers={'Content-type': 'text/dns'})
-
- def test_missing_soa(self):
- fixture = self.get_zonefile_fixture(variant='nosoa')
-
- self._assert_exception('bad_request', 400, self.client.post, '/zones',
- fixture, headers={'Content-type': 'text/dns'})
-
- def test_malformed_zonefile(self):
- fixture = self.get_zonefile_fixture(variant='malformed')
-
- self._assert_exception('bad_request', 400, self.client.post, '/zones',
- fixture, headers={'Content-type': 'text/dns'})
-
- def test_import_export(self):
- # Since v2 doesn't support getting records, import and export the
- # fixture, making sure they're the same according to dnspython
- post_response = self.client.post('/zones',
- self.get_zonefile_fixture(),
- headers={'Content-type': 'text/dns'})
- get_response = self.client.get('/zones/%s' %
- post_response.json['id'],
- headers={'Accept': 'text/dns'})
-
- exported_zonefile = get_response.body
- imported = dnszone.from_text(self.get_zonefile_fixture())
- exported = dnszone.from_text(exported_zonefile)
- # Compare SOA emails, since zone comparison takes care of origin
- imported_soa = imported.get_rdataset(imported.origin, 'SOA')
- imported_email = imported_soa[0].rname.to_text()
- exported_soa = exported.get_rdataset(exported.origin, 'SOA')
- exported_email = exported_soa[0].rname.to_text()
- self.assertEqual(imported_email, exported_email)
- # Delete SOAs since they have, at the very least, different serials,
- # and dnspython considers that to be not equal.
- imported.delete_rdataset(imported.origin, 'SOA')
- exported.delete_rdataset(exported.origin, 'SOA')
- # Delete NS records, since they won't be the same
- imported.delete_rdataset(imported.origin, 'NS')
- exported.delete_rdataset(exported.origin, 'NS')
- imported.delete_rdataset('delegation', 'NS')
- self.assertEqual(imported, exported)
-
# Metadata tests
def test_metadata_exists(self):
response = self.client.get('/zones/')
diff --git a/doc/source/rest/admin/zones.rst b/doc/source/rest/admin/zones.rst
new file mode 100644
index 00000000..1eee6d6c
--- /dev/null
+++ b/doc/source/rest/admin/zones.rst
@@ -0,0 +1,114 @@
+Zones
+=====
+
+Overview
+--------
+The zones extension can be used to import and export zonesfiles to designate.
+
+*Note*: Zones is an extension and needs to be enabled before it can be used.
+If Designate returns a 404 error, ensure that the following line has been
+added to the designate.conf file::
+
+ enabled_extensions_admin = zones
+
+Once this line has been added, restart the designate-api service.
+
+Export Zone
+-----------
+
+.. http:get:: /admin/zones/export/(uuid:id)
+
+ **Example request:**
+
+ .. sourcecode:: http
+
+ GET /admin/zones/export/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3 HTTP/1.1
+ Host: 127.0.0.1:9001
+ Accept: text/dns
+
+
+ **Example response:**
+
+ .. sourcecode:: http
+
+ HTTP/1.1 200 OK
+ Content-Type: text/dns
+
+ $ORIGIN example.com.
+ $TTL 42
+
+ example.com. IN SOA ns.designate.com. nsadmin.example.com. (
+ 1394213803 ; serial
+ 3600 ; refresh
+ 600 ; retry
+ 86400 ; expire
+ 3600 ; minimum
+ )
+
+
+ example.com. IN NS ns.designate.com.
+
+
+ example.com. IN MX 10 mail.example.com.
+ ns.example.com. IN A 10.0.0.1
+ mail.example.com. IN A 10.0.0.2
+
+ :statuscode 200: Success
+ :statuscode 406: Not Acceptable
+
+ Notice how the SOA and NS records are replaced with the Designate server(s).
+
+Import Zone
+-----------
+
+.. http:post:: /admin/zones/import
+
+ To import a zonefile, set the Content-type to **text/dns** . The
+ **zoneextractor.py** tool in the **contrib** folder can generate zonefiles
+ that are suitable for Designate (without any **$INCLUDE** statements for
+ example).
+
+ **Example request:**
+
+ .. sourcecode:: http
+
+ POST /admin/zones/import HTTP/1.1
+ Host: 127.0.0.1:9001
+ Content-type: text/dns
+
+ $ORIGIN example.com.
+ example.com. 42 IN SOA ns.example.com. nsadmin.example.com. 42 42 42 42 42
+ example.com. 42 IN NS ns.example.com.
+ example.com. 42 IN MX 10 mail.example.com.
+ ns.example.com. 42 IN A 10.0.0.1
+ mail.example.com. 42 IN A 10.0.0.2
+
+ **Example response:**
+
+ .. sourcecode:: http
+
+ HTTP/1.1 201 Created
+ Content-Type: application/json
+
+ {
+ "email": "nsadmin@example.com",
+ "id": "6b78734a-aef1-45cd-9708-8eb3c2d26ff1",
+ "links": {
+ "self": "http://127.0.0.1:9001/v2/zones/6b78734a-aef1-45cd-9708-8eb3c2d26ff1"
+ },
+ "name": "example.com.",
+ "pool_id": "572ba08c-d929-4c70-8e42-03824bb24ca2",
+ "project_id": "d7accc2f8ce343318386886953f2fc6a",
+ "serial": 1404757531,
+ "ttl": "42",
+ "created_at": "2014-07-07T18:25:31.275934",
+ "updated_at": null,
+ "version": 1,
+ "masters": [],
+ "type": "PRIMARY",
+ "transferred_at": null
+ }
+
+ :statuscode 201: Created
+ :statuscode 415: Unsupported Media Type
+ :statuscode 400: Bad request
diff --git a/doc/source/rest/v2/zones.rst b/doc/source/rest/v2/zones.rst
index 0e88ebed..9b22b83b 100644
--- a/doc/source/rest/v2/zones.rst
+++ b/doc/source/rest/v2/zones.rst
@@ -291,106 +291,6 @@ Delete Zone
:statuscode 202: Accepted
-Import Zone
------------
-
-.. http:post:: /zones
-
- To import a zonefile, set the Content-type to **text/dns** . The
- **zoneextractor.py** tool in the **contrib** folder can generate zonefiles
- that are suitable for Designate (without any **$INCLUDE** statements for
- example).
-
- **Example request:**
-
- .. sourcecode:: http
-
- POST /v2/zones HTTP/1.1
- Host: 127.0.0.1:9001
- Content-type: text/dns
-
- $ORIGIN example.com.
- example.com. 42 IN SOA ns.example.com. nsadmin.example.com. 42 42 42 42 42
- example.com. 42 IN NS ns.example.com.
- example.com. 42 IN MX 10 mail.example.com.
- ns.example.com. 42 IN A 10.0.0.1
- mail.example.com. 42 IN A 10.0.0.2
-
- **Example response:**
-
- .. sourcecode:: http
-
- HTTP/1.1 201 Created
- Content-Type: application/json
-
- {
- "email": "nsadmin@example.com",
- "id": "6b78734a-aef1-45cd-9708-8eb3c2d26ff1",
- "links": {
- "self": "http://127.0.0.1:9001/v2/zones/6b78734a-aef1-45cd-9708-8eb3c2d26ff1"
- },
- "name": "example.com.",
- "pool_id": "572ba08c-d929-4c70-8e42-03824bb24ca2",
- "project_id": "d7accc2f8ce343318386886953f2fc6a",
- "serial": 1404757531,
- "ttl": "42",
- "created_at": "2014-07-07T18:25:31.275934",
- "updated_at": null,
- "version": 1,
- "masters": [],
- "type": "PRIMARY",
- "transferred_at": null
- }
-
- :statuscode 201: Created
- :statuscode 415: Unsupported Media Type
- :statuscode 400: Bad request
-
-Export Zone
------------
-
-.. http:get:: /zones/(uuid:id)
-
- To export a zone in zonefile format, set the **Accept** header to **text/dns**.
-
- **Example request**
-
- .. sourcecode:: http
-
- GET /v2/zones/6b78734a-aef1-45cd-9708-8eb3c2d26ff1 HTTP/1.1
- Host: 127.0.0.1:9001
- Accept: text/dns
-
- **Example response**
-
- .. sourcecode:: http
-
- HTTP/1.1 200 OK
- Content-Type: text/dns
-
- $ORIGIN example.com.
- $TTL 42
-
- example.com. IN SOA ns.designate.com. nsadmin.example.com. (
- 1394213803 ; serial
- 3600 ; refresh
- 600 ; retry
- 86400 ; expire
- 3600 ; minimum
- )
-
-
- example.com. IN NS ns.designate.com.
-
-
- example.com. IN MX 10 mail.example.com.
- ns.example.com. IN A 10.0.0.1
- mail.example.com. IN A 10.0.0.2
-
- :statuscode 200: Success
- :statuscode 406: Not Acceptable
-
- Notice how the SOA and NS records are replaced with the Designate server(s).
Abandon Zone
------------
diff --git a/etc/designate/designate.conf.sample b/etc/designate/designate.conf.sample
index c12be2d2..5cf84510 100644
--- a/etc/designate/designate.conf.sample
+++ b/etc/designate/designate.conf.sample
@@ -101,7 +101,8 @@ debug = False
#enable_api_admin = False
# Enabled Admin API extensions
-# Can be one or more of : reports, quotas, counts, tenants
+# Can be one or more of : reports, quotas, counts, tenants, zones
+# zone import / export is in zones extension
#enabled_extensions_admin =
# Show the pecan HTML based debug interface (v2 only)
diff --git a/etc/designate/policy.json b/etc/designate/policy.json
index a085e2da..c259e884 100644
--- a/etc/designate/policy.json
+++ b/etc/designate/policy.json
@@ -19,6 +19,9 @@
"use_low_ttl": "rule:admin",
+ "zone_import": "rule:admin",
+ "zone_export": "rule:admin",
+
"get_quotas": "rule:admin_or_owner",
"get_quota": "rule:admin_or_owner",
"set_quota": "rule:admin",
diff --git a/setup.cfg b/setup.cfg
index 74f98a8e..e5c5fcb1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -64,6 +64,7 @@ designate.api.v1.extensions =
designate.api.admin.extensions =
reports = designate.api.admin.controllers.extensions.reports:ReportsController
quotas = designate.api.admin.controllers.extensions.quotas:QuotasController
+ zones = designate.api.admin.controllers.extensions.zones:ZonesController
designate.storage =
sqlalchemy = designate.storage.impl_sqlalchemy:SQLAlchemyStorage