Move Zone Import / Export to /admin API
* Moves zonefile import/export to admin API due to issues with non async action of import / export * Added policy checks to allow restriction of usage * Fixed 500 Error on invalid Content-Type when using body_dict() Closes-Bug: #1443366 Change-Id: Iea0f2077f24da9bb1a93d5ac9aadced402343405 APIImpact: Move zone import / export to /admin
This commit is contained in:
parent
9ca327945d
commit
aba646b94f
61
designate/api/admin/controllers/extensions/export.py
Normal file
61
designate/api/admin/controllers/extensions/export.py
Normal file
@ -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)
|
77
designate/api/admin/controllers/extensions/import_.py
Normal file
77
designate/api/admin/controllers/extensions/import_.py
Normal file
@ -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
|
39
designate/api/admin/controllers/extensions/zones.py
Normal file
39
designate/api/admin/controllers/extensions/zones.py
Normal file
@ -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()
|
@ -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')
|
||||
|
@ -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:
|
||||
|
81
designate/tests/test_api/test_admin/extensions/test_zones.py
Normal file
81
designate/tests/test_api/test_admin/extensions/test_zones.py
Normal file
@ -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)
|
@ -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/')
|
||||
|
114
doc/source/rest/admin/zones.rst
Normal file
114
doc/source/rest/admin/zones.rst
Normal file
@ -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
|
@ -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
|
||||
------------
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user