Asynchronous Zone Import
* Creates /v2/zones/tasks/imports, which allows users to view imports as resources * Creates new database table zone_tasks for asynchronous tasks related to zones, along with the associated objects/adapters * Imports are done by passing over the request body, creating an async record in the db, and spawning a thread to do the import * Adds a config option to enable zone import Implements: async-import-export APIImpact: Adds /zones/tasks/imports and removes import from admin api Change-Id: Ib23810bf8b25d962b9d2d75e042bb097f3c12f7a
This commit is contained in:
parent
53a7300103
commit
021946e386
@ -1,82 +0,0 @@
|
|||||||
# 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)
|
|
||||||
|
|
||||||
if request.content_type != 'text/dns':
|
|
||||||
raise exceptions.UnsupportedContentType(
|
|
||||||
'Content-type must be text/dns')
|
|
||||||
|
|
||||||
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
|
|
@ -15,7 +15,6 @@
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from designate.api.v2.controllers import rest
|
from designate.api.v2.controllers import rest
|
||||||
from designate.api.admin.controllers.extensions import import_
|
|
||||||
from designate.api.admin.controllers.extensions import export
|
from designate.api.admin.controllers.extensions import export
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -28,12 +27,6 @@ class ZonesController(rest.RestController):
|
|||||||
return '.zones'
|
return '.zones'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Import is a keyword - so we have to do a setattr instead
|
|
||||||
setattr(self, 'import', import_.ImportController())
|
|
||||||
super(ZonesController, self).__init__()
|
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()
|
export = export.ExportController()
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from designate.api.v2.controllers.zones.tasks.transfer_requests \
|
from designate.api.v2.controllers.zones.tasks.transfer_requests \
|
||||||
import TransferRequestsController as TRC
|
import TransferRequestsController as TRC
|
||||||
@ -21,7 +22,10 @@ from designate.api.v2.controllers.zones.tasks.transfer_accepts \
|
|||||||
import TransferAcceptsController as TRA
|
import TransferAcceptsController as TRA
|
||||||
from designate.api.v2.controllers.zones.tasks import abandon
|
from designate.api.v2.controllers.zones.tasks import abandon
|
||||||
from designate.api.v2.controllers.zones.tasks.xfr import XfrController
|
from designate.api.v2.controllers.zones.tasks.xfr import XfrController
|
||||||
|
from designate.api.v2.controllers.zones.tasks.imports \
|
||||||
|
import ZoneImportController
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -31,3 +35,4 @@ class TasksController(object):
|
|||||||
transfer_requests = TRC()
|
transfer_requests = TRC()
|
||||||
abandon = abandon.AbandonController()
|
abandon = abandon.AbandonController()
|
||||||
xfr = XfrController()
|
xfr = XfrController()
|
||||||
|
imports = ZoneImportController()
|
||||||
|
102
designate/api/v2/controllers/zones/tasks/imports.py
Normal file
102
designate/api/v2/controllers/zones/tasks/imports.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Copyright 2015 Rackspace Inc.
|
||||||
|
#
|
||||||
|
# Author: Tim Simmons <tim.simmons@rackspae.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.
|
||||||
|
import pecan
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from designate import exceptions
|
||||||
|
from designate import utils
|
||||||
|
from designate.api.v2.controllers import rest
|
||||||
|
from designate.objects.adapters.api_v2.zone_import \
|
||||||
|
import ZoneImportAPIv2Adapter
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportController(rest.RestController):
|
||||||
|
|
||||||
|
SORT_KEYS = ['created_at', 'id', 'updated_at']
|
||||||
|
|
||||||
|
@pecan.expose(template='json:', content_type='application/json')
|
||||||
|
@utils.validate_uuid('import_id')
|
||||||
|
def get_one(self, import_id):
|
||||||
|
"""Get imports"""
|
||||||
|
|
||||||
|
request = pecan.request
|
||||||
|
context = request.environ['context']
|
||||||
|
|
||||||
|
return ZoneImportAPIv2Adapter.render(
|
||||||
|
'API_v2',
|
||||||
|
self.central_api.get_zone_import(
|
||||||
|
context, import_id),
|
||||||
|
request=request)
|
||||||
|
|
||||||
|
@pecan.expose(template='json:', content_type='application/json')
|
||||||
|
def get_all(self, **params):
|
||||||
|
"""List ZoneImports"""
|
||||||
|
request = pecan.request
|
||||||
|
context = request.environ['context']
|
||||||
|
marker, limit, sort_key, sort_dir = utils.get_paging_params(
|
||||||
|
params, self.SORT_KEYS)
|
||||||
|
|
||||||
|
# Extract any filter params.
|
||||||
|
accepted_filters = ('status', 'message', 'zone_id', )
|
||||||
|
|
||||||
|
criterion = self._apply_filter_params(
|
||||||
|
params, accepted_filters, {})
|
||||||
|
|
||||||
|
return ZoneImportAPIv2Adapter.render(
|
||||||
|
'API_v2',
|
||||||
|
self.central_api.find_zone_imports(
|
||||||
|
context, criterion, marker, limit, sort_key, sort_dir),
|
||||||
|
request=request)
|
||||||
|
|
||||||
|
@pecan.expose(template='json:', content_type='application/json')
|
||||||
|
def post_all(self):
|
||||||
|
"""Create ZoneImport"""
|
||||||
|
request = pecan.request
|
||||||
|
response = pecan.response
|
||||||
|
context = request.environ['context']
|
||||||
|
body = request.body
|
||||||
|
|
||||||
|
if request.content_type != 'text/dns':
|
||||||
|
raise exceptions.UnsupportedContentType(
|
||||||
|
'Content-type must be text/dns')
|
||||||
|
|
||||||
|
# Create the zone_import
|
||||||
|
zone_import = self.central_api.create_zone_import(
|
||||||
|
context, body)
|
||||||
|
response.status_int = 202
|
||||||
|
|
||||||
|
zone_import = ZoneImportAPIv2Adapter.render(
|
||||||
|
'API_v2', zone_import, request=request)
|
||||||
|
|
||||||
|
response.headers['Location'] = zone_import['links']['self']
|
||||||
|
# Prepare and return the response body
|
||||||
|
return zone_import
|
||||||
|
|
||||||
|
@pecan.expose(template='json:', content_type='application/json')
|
||||||
|
@utils.validate_uuid('zone_import_id')
|
||||||
|
def delete_one(self, zone_import_id):
|
||||||
|
"""Delete Zone"""
|
||||||
|
request = pecan.request
|
||||||
|
response = pecan.response
|
||||||
|
context = request.environ['context']
|
||||||
|
|
||||||
|
self.central_api.delete_zone_import(context, zone_import_id)
|
||||||
|
response.status_int = 204
|
||||||
|
|
||||||
|
return ''
|
@ -48,14 +48,15 @@ class CentralAPI(object):
|
|||||||
4.3 - Added Zone Transfer Methods
|
4.3 - Added Zone Transfer Methods
|
||||||
5.0 - Remove dead server code
|
5.0 - Remove dead server code
|
||||||
5.1 - Add xfr_domain
|
5.1 - Add xfr_domain
|
||||||
|
5.2 - Add Zone Import methods
|
||||||
"""
|
"""
|
||||||
RPC_API_VERSION = '5.1'
|
RPC_API_VERSION = '5.2'
|
||||||
|
|
||||||
def __init__(self, topic=None):
|
def __init__(self, topic=None):
|
||||||
topic = topic if topic else cfg.CONF.central_topic
|
topic = topic if topic else cfg.CONF.central_topic
|
||||||
|
|
||||||
target = messaging.Target(topic=topic, version=self.RPC_API_VERSION)
|
target = messaging.Target(topic=topic, version=self.RPC_API_VERSION)
|
||||||
self.client = rpc.get_client(target, version_cap='5.1')
|
self.client = rpc.get_client(target, version_cap='5.2')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
@ -494,5 +495,38 @@ class CentralAPI(object):
|
|||||||
|
|
||||||
def xfr_domain(self, context, domain_id):
|
def xfr_domain(self, context, domain_id):
|
||||||
LOG.info(_LI("xfr_domain: Calling central's xfr_domain"))
|
LOG.info(_LI("xfr_domain: Calling central's xfr_domain"))
|
||||||
cctxt = self.client.prepare(version='5.1')
|
cctxt = self.client.prepare(version='5.2')
|
||||||
return cctxt.call(context, 'xfr_domain', domain_id=domain_id)
|
return cctxt.call(context, 'xfr_domain', domain_id=domain_id)
|
||||||
|
|
||||||
|
# Zone Import Methods
|
||||||
|
def create_zone_import(self, context, request_body):
|
||||||
|
LOG.info(_LI("create_zone_import: Calling central's "
|
||||||
|
"create_zone_import."))
|
||||||
|
return self.client.call(context, 'create_zone_import',
|
||||||
|
request_body=request_body)
|
||||||
|
|
||||||
|
def find_zone_imports(self, context, criterion=None, marker=None,
|
||||||
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
LOG.info(_LI("find_zone_imports: Calling central's "
|
||||||
|
"find_zone_imports."))
|
||||||
|
return self.client.call(context, 'find_zone_imports',
|
||||||
|
criterion=criterion, marker=marker,
|
||||||
|
limit=limit, sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
|
def get_zone_import(self, context, zone_import_id):
|
||||||
|
LOG.info(_LI("get_zone_import: Calling central's get_zone_import."))
|
||||||
|
return self.client.call(context, 'get_zone_import',
|
||||||
|
zone_import_id=zone_import_id)
|
||||||
|
|
||||||
|
def update_zone_import(self, context, zone_import):
|
||||||
|
LOG.info(_LI("update_zone_import: Calling central's "
|
||||||
|
"update_zone_import."))
|
||||||
|
return self.client.call(context, 'update_zone_import',
|
||||||
|
zone_import=zone_import)
|
||||||
|
|
||||||
|
def delete_zone_import(self, context, zone_import_id):
|
||||||
|
LOG.info(_LI("delete_zone_import: Calling central's "
|
||||||
|
"delete_zone_import."))
|
||||||
|
return self.client.call(context, 'delete_zone_import',
|
||||||
|
zone_import_id=zone_import_id)
|
||||||
|
@ -24,6 +24,9 @@ import string
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from eventlet import tpool
|
||||||
|
from dns import zone as dnszone
|
||||||
|
from dns import exception as dnsexception
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -36,6 +39,7 @@ from designate.i18n import _LC
|
|||||||
from designate.i18n import _LW
|
from designate.i18n import _LW
|
||||||
from designate import context as dcontext
|
from designate import context as dcontext
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
|
from designate import dnsutils
|
||||||
from designate import network_api
|
from designate import network_api
|
||||||
from designate import objects
|
from designate import objects
|
||||||
from designate import policy
|
from designate import policy
|
||||||
@ -247,7 +251,7 @@ def notification(notification_type):
|
|||||||
|
|
||||||
|
|
||||||
class Service(service.RPCService, service.Service):
|
class Service(service.RPCService, service.Service):
|
||||||
RPC_API_VERSION = '5.1'
|
RPC_API_VERSION = '5.2'
|
||||||
|
|
||||||
target = messaging.Target(version=RPC_API_VERSION)
|
target = messaging.Target(version=RPC_API_VERSION)
|
||||||
|
|
||||||
@ -865,6 +869,9 @@ class Service(service.RPCService, service.Service):
|
|||||||
|
|
||||||
if domain.obj_attr_is_set('recordsets'):
|
if domain.obj_attr_is_set('recordsets'):
|
||||||
for rrset in domain.recordsets:
|
for rrset in domain.recordsets:
|
||||||
|
# This allows eventlet to yield, as this looping operation
|
||||||
|
# can be very long-lived.
|
||||||
|
time.sleep(0)
|
||||||
self._create_recordset_in_storage(
|
self._create_recordset_in_storage(
|
||||||
context, domain, rrset, increment_serial=False)
|
context, domain, rrset, increment_serial=False)
|
||||||
|
|
||||||
@ -2452,3 +2459,126 @@ class Service(service.RPCService, service.Service):
|
|||||||
return self.storage.delete_zone_transfer_accept(
|
return self.storage.delete_zone_transfer_accept(
|
||||||
context,
|
context,
|
||||||
zone_transfer_accept_id)
|
zone_transfer_accept_id)
|
||||||
|
|
||||||
|
# Zone Import Methods
|
||||||
|
@notification('dns.zone_import.create')
|
||||||
|
def create_zone_import(self, context, request_body):
|
||||||
|
target = {'tenant_id': context.tenant}
|
||||||
|
policy.check('create_zone_import', context, target)
|
||||||
|
|
||||||
|
values = {
|
||||||
|
'status': 'PENDING',
|
||||||
|
'message': None,
|
||||||
|
'domain_id': None,
|
||||||
|
'tenant_id': context.tenant,
|
||||||
|
'task_type': 'IMPORT'
|
||||||
|
}
|
||||||
|
zone_import = objects.ZoneTask(**values)
|
||||||
|
|
||||||
|
created_zone_import = self.storage.create_zone_task(context,
|
||||||
|
zone_import)
|
||||||
|
|
||||||
|
self.tg.add_thread(self._import_zone, context, created_zone_import,
|
||||||
|
request_body)
|
||||||
|
|
||||||
|
return created_zone_import
|
||||||
|
|
||||||
|
def _import_zone(self, context, zone_import, request_body):
|
||||||
|
|
||||||
|
def _import(self, context, zone_import, request_body):
|
||||||
|
# Dnspython needs a str instead of a unicode object
|
||||||
|
request_body = str(request_body)
|
||||||
|
domain = None
|
||||||
|
try:
|
||||||
|
dnspython_zone = dnszone.from_text(
|
||||||
|
request_body,
|
||||||
|
# Don't relativize, or 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:
|
||||||
|
zone_import.message = ('The $ORIGIN statement is required and'
|
||||||
|
' must be the first statement in the'
|
||||||
|
' zonefile.')
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
except dnsexception.SyntaxError:
|
||||||
|
zone_import.message = 'Malformed zonefile.'
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
except exceptions.BadRequest:
|
||||||
|
zone_import.message = 'An SOA record is required.'
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
except Exception:
|
||||||
|
zone_import.message = 'An undefined error occured.'
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
|
||||||
|
return domain, zone_import
|
||||||
|
|
||||||
|
# Execute the import in a real Python thread
|
||||||
|
domain, zone_import = tpool.execute(_import, self, context,
|
||||||
|
zone_import, request_body)
|
||||||
|
|
||||||
|
# If the zone import was valid, create the domain
|
||||||
|
if zone_import.status != 'ERROR':
|
||||||
|
try:
|
||||||
|
zone = self.create_domain(context, domain)
|
||||||
|
zone_import.status = 'COMPLETE'
|
||||||
|
zone_import.domain_id = zone.id
|
||||||
|
zone_import.message = '%(name)s imported' % {'name':
|
||||||
|
zone.name}
|
||||||
|
except exceptions.DuplicateDomain:
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
zone_import.message = 'Duplicate zone.'
|
||||||
|
except exceptions.InvalidTTL as e:
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
zone_import.message = e.message
|
||||||
|
except Exception:
|
||||||
|
zone_import.message = 'An undefined error occured.'
|
||||||
|
zone_import.status = 'ERROR'
|
||||||
|
|
||||||
|
self.update_zone_import(context, zone_import)
|
||||||
|
|
||||||
|
def find_zone_imports(self, context, criterion=None, marker=None,
|
||||||
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
target = {'tenant_id': context.tenant}
|
||||||
|
policy.check('find_zone_imports', context, target)
|
||||||
|
|
||||||
|
criterion = {
|
||||||
|
'task_type': 'IMPORT'
|
||||||
|
}
|
||||||
|
return self.storage.find_zone_tasks(context, criterion, marker,
|
||||||
|
limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
def get_zone_import(self, context, zone_import_id):
|
||||||
|
target = {'tenant_id': context.tenant}
|
||||||
|
policy.check('get_zone_import', context, target)
|
||||||
|
return self.storage.get_zone_task(context, zone_import_id)
|
||||||
|
|
||||||
|
@notification('dns.zone_import.update')
|
||||||
|
def update_zone_import(self, context, zone_import):
|
||||||
|
target = {
|
||||||
|
'tenant_id': zone_import.tenant_id,
|
||||||
|
}
|
||||||
|
policy.check('update_zone_import', context, target)
|
||||||
|
|
||||||
|
return self.storage.update_zone_task(context, zone_import)
|
||||||
|
|
||||||
|
@notification('dns.zone_import.delete')
|
||||||
|
@transaction
|
||||||
|
def delete_zone_import(self, context, zone_import_id):
|
||||||
|
target = {
|
||||||
|
'zone_import_id': zone_import_id,
|
||||||
|
'tenant_id': context.tenant
|
||||||
|
}
|
||||||
|
policy.check('delete_zone_import', context, target)
|
||||||
|
|
||||||
|
zone_import = self.storage.delete_zone_task(context, zone_import_id)
|
||||||
|
|
||||||
|
return zone_import
|
||||||
|
@ -259,6 +259,10 @@ class DuplicatePoolNsRecord(Duplicate):
|
|||||||
error_type = 'duplicate_pool_ns_record'
|
error_type = 'duplicate_pool_ns_record'
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateZoneTask(Duplicate):
|
||||||
|
error_type = 'duplicate_zone_task'
|
||||||
|
|
||||||
|
|
||||||
class MethodNotAllowed(Base):
|
class MethodNotAllowed(Base):
|
||||||
expected = True
|
expected = True
|
||||||
error_code = 405
|
error_code = 405
|
||||||
@ -343,6 +347,10 @@ class ZoneTransferAcceptNotFound(NotFound):
|
|||||||
error_type = 'zone_transfer_accept_not_found'
|
error_type = 'zone_transfer_accept_not_found'
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneTaskNotFound(NotFound):
|
||||||
|
error_type = 'zone_task_not_found'
|
||||||
|
|
||||||
|
|
||||||
class LastServerDeleteNotAllowed(BadRequest):
|
class LastServerDeleteNotAllowed(BadRequest):
|
||||||
error_type = 'last_server_delete_not_allowed'
|
error_type = 'last_server_delete_not_allowed'
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ from designate.objects.validation_error import ValidationError # noqa
|
|||||||
from designate.objects.validation_error import ValidationErrorList # noqa
|
from designate.objects.validation_error import ValidationErrorList # noqa
|
||||||
from designate.objects.zone_transfer_request import ZoneTransferRequest, ZoneTransferRequestList # noqa
|
from designate.objects.zone_transfer_request import ZoneTransferRequest, ZoneTransferRequestList # noqa
|
||||||
from designate.objects.zone_transfer_accept import ZoneTransferAccept, ZoneTransferAcceptList # noqa
|
from designate.objects.zone_transfer_accept import ZoneTransferAccept, ZoneTransferAcceptList # noqa
|
||||||
|
from designate.objects.zone_task import ZoneTask, ZoneTaskList # noqa
|
||||||
|
|
||||||
# Record Types
|
# Record Types
|
||||||
|
|
||||||
|
@ -28,3 +28,4 @@ from designate.objects.adapters.api_v2.quota import QuotaAPIv2Adapter, QuotaList
|
|||||||
from designate.objects.adapters.api_v2.zone_transfer_accept import ZoneTransferAcceptAPIv2Adapter, ZoneTransferAcceptListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.zone_transfer_accept import ZoneTransferAcceptAPIv2Adapter, ZoneTransferAcceptListAPIv2Adapter # noqa
|
||||||
from designate.objects.adapters.api_v2.zone_transfer_request import ZoneTransferRequestAPIv2Adapter, ZoneTransferRequestListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.zone_transfer_request import ZoneTransferRequestAPIv2Adapter, ZoneTransferRequestListAPIv2Adapter # noqa
|
||||||
from designate.objects.adapters.api_v2.validation_error import ValidationErrorAPIv2Adapter, ValidationErrorListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.validation_error import ValidationErrorAPIv2Adapter, ValidationErrorListAPIv2Adapter # noqa
|
||||||
|
from designate.objects.adapters.api_v2.zone_import import ZoneImportAPIv2Adapter, ZoneImportListAPIv2Adapter # noqa
|
||||||
|
@ -47,7 +47,8 @@ class APIv2Adapter(base.DesignateAdapter):
|
|||||||
# Check if we should include metadata
|
# Check if we should include metadata
|
||||||
if isinstance(list_object, obj_base.PagedListObjectMixin):
|
if isinstance(list_object, obj_base.PagedListObjectMixin):
|
||||||
metadata = {}
|
metadata = {}
|
||||||
metadata['total_count'] = list_object.total_count
|
if list_object.total_count is not None:
|
||||||
|
metadata['total_count'] = list_object.total_count
|
||||||
r_list['metadata'] = metadata
|
r_list['metadata'] = metadata
|
||||||
|
|
||||||
return r_list
|
return r_list
|
||||||
|
71
designate/objects/adapters/api_v2/zone_import.py
Normal file
71
designate/objects/adapters/api_v2/zone_import.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Copyright 2015 Rackspace Inc.
|
||||||
|
#
|
||||||
|
# Author: Tim Simmons <tim.simmons@rackspace.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 oslo_log import log as logging
|
||||||
|
|
||||||
|
from designate.objects.adapters.api_v2 import base
|
||||||
|
from designate import objects
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportAPIv2Adapter(base.APIv2Adapter):
|
||||||
|
|
||||||
|
ADAPTER_OBJECT = objects.ZoneTask
|
||||||
|
|
||||||
|
MODIFICATIONS = {
|
||||||
|
'fields': {
|
||||||
|
"id": {},
|
||||||
|
"status": {},
|
||||||
|
"message": {},
|
||||||
|
"zone_id": {
|
||||||
|
'rename': 'domain_id',
|
||||||
|
},
|
||||||
|
"project_id": {
|
||||||
|
'rename': 'tenant_id'
|
||||||
|
},
|
||||||
|
"created_at": {},
|
||||||
|
"updated_at": {},
|
||||||
|
"version": {},
|
||||||
|
},
|
||||||
|
'options': {
|
||||||
|
'links': True,
|
||||||
|
'resource_name': 'import',
|
||||||
|
'collection_name': 'imports',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _render_object(cls, object, *args, **kwargs):
|
||||||
|
obj = super(ZoneImportAPIv2Adapter, cls)._render_object(
|
||||||
|
object, *args, **kwargs)
|
||||||
|
|
||||||
|
if obj['zone_id'] is not None:
|
||||||
|
obj['links']['zone'] = \
|
||||||
|
'%s/v2/%s/%s' % (cls.BASE_URI, 'zones', obj['zone_id'])
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportListAPIv2Adapter(base.APIv2Adapter):
|
||||||
|
|
||||||
|
ADAPTER_OBJECT = objects.ZoneTaskList
|
||||||
|
|
||||||
|
MODIFICATIONS = {
|
||||||
|
'options': {
|
||||||
|
'links': True,
|
||||||
|
'resource_name': 'import',
|
||||||
|
'collection_name': 'imports',
|
||||||
|
}
|
||||||
|
}
|
61
designate/objects/zone_task.py
Normal file
61
designate/objects/zone_task.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Copyright 2015 Rackspace Inc.
|
||||||
|
#
|
||||||
|
# Author: Tim Simmons <tim.simmons@rackspace.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 designate.objects import base
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneTask(base.DictObjectMixin, base.PersistentObjectMixin,
|
||||||
|
base.DesignateObject):
|
||||||
|
FIELDS = {
|
||||||
|
'status': {
|
||||||
|
'schema': {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["ACTIVE", "PENDING", "DELETED", "ERROR", "COMPLETE"],
|
||||||
|
},
|
||||||
|
'read_only': True
|
||||||
|
},
|
||||||
|
'task_type': {
|
||||||
|
'schema': {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["IMPORT"],
|
||||||
|
},
|
||||||
|
'read_only': True
|
||||||
|
},
|
||||||
|
'tenant_id': {
|
||||||
|
'schema': {
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'read_only': True
|
||||||
|
},
|
||||||
|
'message': {
|
||||||
|
'schema': {
|
||||||
|
'type': ['string', 'null'],
|
||||||
|
'maxLength': 160
|
||||||
|
},
|
||||||
|
'read_only': True
|
||||||
|
},
|
||||||
|
'domain_id': {
|
||||||
|
'schema': {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
'read_only': True
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneTaskList(base.ListObjectMixin, base.DesignateObject,
|
||||||
|
base.PagedListObjectMixin):
|
||||||
|
LIST_ITEM_TYPE = ZoneTask
|
@ -630,6 +630,67 @@ class Storage(DriverPlugin):
|
|||||||
:param pool_attribute_id: The ID of the PoolAttribute to be deleted
|
:param pool_attribute_id: The ID of the PoolAttribute to be deleted
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_zone_task(self, context, zone_task):
|
||||||
|
"""
|
||||||
|
Create a Zone Task.
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param zone_task: Tld object with the values to be created.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_zone_task(self, context, zone_task_id):
|
||||||
|
"""
|
||||||
|
Get a Zone Task via ID.
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param zone_task_id: Zone Task ID to get.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def find_zone_tasks(self, context, criterion=None, marker=None,
|
||||||
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
"""
|
||||||
|
Find Zone Tasks
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param criterion: Criteria to filter by.
|
||||||
|
:param marker: Resource ID from which after the requested page will
|
||||||
|
start after
|
||||||
|
:param limit: Integer limit of objects of the page size after the
|
||||||
|
marker
|
||||||
|
:param sort_key: Key from which to sort after.
|
||||||
|
:param sort_dir: Direction to sort after using sort_key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def find_zone_task(self, context, criterion):
|
||||||
|
"""
|
||||||
|
Find a single Zone Task.
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param criterion: Criteria to filter by.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update_zone_task(self, context, zone_task):
|
||||||
|
"""
|
||||||
|
Update a Zone Task
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param zone_task: Zone Task to update.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_zone_task(self, context, zone_task_id):
|
||||||
|
"""
|
||||||
|
Delete a Zone Task via ID.
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param zone_task_id: Delete a Zone Task via ID
|
||||||
|
"""
|
||||||
|
|
||||||
def ping(self, context):
|
def ping(self, context):
|
||||||
"""Ping the Storage connection"""
|
"""Ping the Storage connection"""
|
||||||
return {
|
return {
|
||||||
|
@ -1122,6 +1122,43 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
zone_transfer_accept,
|
zone_transfer_accept,
|
||||||
exceptions.ZoneTransferAcceptNotFound)
|
exceptions.ZoneTransferAcceptNotFound)
|
||||||
|
|
||||||
|
# Zone Task Methods
|
||||||
|
def _find_zone_tasks(self, context, criterion, one=False, marker=None,
|
||||||
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
return self._find(
|
||||||
|
context, tables.zone_tasks, objects.ZoneTask,
|
||||||
|
objects.ZoneTaskList, exceptions.ZoneTaskNotFound, criterion,
|
||||||
|
one, marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
def create_zone_task(self, context, zone_task):
|
||||||
|
return self._create(
|
||||||
|
tables.zone_tasks, zone_task, exceptions.DuplicateZoneTask)
|
||||||
|
|
||||||
|
def get_zone_task(self, context, zone_task_id):
|
||||||
|
return self._find_zone_tasks(context, {'id': zone_task_id},
|
||||||
|
one=True)
|
||||||
|
|
||||||
|
def find_zone_tasks(self, context, criterion=None, marker=None,
|
||||||
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
return self._find_zone_tasks(context, criterion, marker=marker,
|
||||||
|
limit=limit, sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
|
def find_zone_task(self, context, criterion):
|
||||||
|
return self._find_zone_tasks(context, criterion, one=True)
|
||||||
|
|
||||||
|
def update_zone_task(self, context, zone_task):
|
||||||
|
return self._update(
|
||||||
|
context, tables.zone_tasks, zone_task,
|
||||||
|
exceptions.DuplicateZoneTask, exceptions.ZoneTaskNotFound)
|
||||||
|
|
||||||
|
def delete_zone_task(self, context, zone_task_id):
|
||||||
|
# Fetch the existing zone_task, we'll need to return it.
|
||||||
|
zone_task = self._find_zone_tasks(context, {'id': zone_task_id},
|
||||||
|
one=True)
|
||||||
|
return self._delete(context, tables.zone_tasks, zone_task,
|
||||||
|
exceptions.ZoneTaskNotFound)
|
||||||
|
|
||||||
# diagnostics
|
# diagnostics
|
||||||
def ping(self, context):
|
def ping(self, context):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
# Copyright 2015 Rackspace Inc.
|
||||||
|
#
|
||||||
|
# Author: Tim Simmons <tim.simmons@rackspace.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 sqlalchemy import Integer, String, DateTime, Enum
|
||||||
|
from sqlalchemy.schema import Table, Column, MetaData
|
||||||
|
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
from designate import utils
|
||||||
|
from designate.sqlalchemy.types import UUID
|
||||||
|
|
||||||
|
meta = MetaData()
|
||||||
|
TASK_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR', 'COMPLETE']
|
||||||
|
TASK_TYPES = ['IMPORT']
|
||||||
|
|
||||||
|
zone_tasks_table = Table('zone_tasks', meta,
|
||||||
|
Column('id', UUID(), default=utils.generate_uuid, primary_key=True),
|
||||||
|
Column('created_at', DateTime, default=lambda: timeutils.utcnow()),
|
||||||
|
Column('updated_at', DateTime, onupdate=lambda: timeutils.utcnow()),
|
||||||
|
Column('version', Integer(), default=1, nullable=False),
|
||||||
|
Column('tenant_id', String(36), default=None, nullable=True),
|
||||||
|
|
||||||
|
Column('domain_id', UUID(), nullable=True),
|
||||||
|
Column('task_type', Enum(name='task_types', *TASK_TYPES), nullable=True),
|
||||||
|
Column('message', String(160), nullable=True),
|
||||||
|
Column('status', Enum(name='resource_statuses', *TASK_STATUSES),
|
||||||
|
nullable=False, server_default='ACTIVE',
|
||||||
|
default='ACTIVE'),
|
||||||
|
|
||||||
|
mysql_engine='INNODB',
|
||||||
|
mysql_charset='utf8')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(migrate_engine):
|
||||||
|
meta.bind = migrate_engine
|
||||||
|
|
||||||
|
# Create the table
|
||||||
|
zone_tasks_table.create()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(migrate_engine):
|
||||||
|
meta.bind = migrate_engine
|
||||||
|
|
||||||
|
# Find the table and drop it
|
||||||
|
zone_tasks_table = Table('zone_tasks', meta, autoload=True)
|
||||||
|
zone_tasks_table.drop()
|
@ -39,6 +39,7 @@ ACTIONS = ['CREATE', 'DELETE', 'UPDATE', 'NONE']
|
|||||||
ZONE_ATTRIBUTE_KEYS = ('master',)
|
ZONE_ATTRIBUTE_KEYS = ('master',)
|
||||||
|
|
||||||
ZONE_TYPES = ('PRIMARY', 'SECONDARY',)
|
ZONE_TYPES = ('PRIMARY', 'SECONDARY',)
|
||||||
|
ZONE_TASK_TYPES = ['IMPORT']
|
||||||
|
|
||||||
|
|
||||||
metadata = MetaData()
|
metadata = MetaData()
|
||||||
@ -307,3 +308,21 @@ zone_transfer_accepts = Table('zone_transfer_accepts', metadata,
|
|||||||
mysql_engine='InnoDB',
|
mysql_engine='InnoDB',
|
||||||
mysql_charset='utf8',
|
mysql_charset='utf8',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
zone_tasks = Table('zone_tasks', metadata,
|
||||||
|
Column('id', UUID(), default=utils.generate_uuid, primary_key=True),
|
||||||
|
Column('created_at', DateTime, default=lambda: timeutils.utcnow()),
|
||||||
|
Column('updated_at', DateTime, onupdate=lambda: timeutils.utcnow()),
|
||||||
|
Column('version', Integer(), default=1, nullable=False),
|
||||||
|
Column('tenant_id', String(36), default=None, nullable=True),
|
||||||
|
|
||||||
|
Column('domain_id', UUID(), nullable=True),
|
||||||
|
Column('task_type', Enum(name='task_types', *ZONE_TASK_TYPES),
|
||||||
|
nullable=True),
|
||||||
|
Column('message', String(160), nullable=True),
|
||||||
|
Column('status', Enum(name='resource_statuses', *TASK_STATUSES),
|
||||||
|
nullable=False, server_default='ACTIVE',
|
||||||
|
default='ACTIVE'),
|
||||||
|
|
||||||
|
mysql_engine='INNODB',
|
||||||
|
mysql_charset='utf8')
|
||||||
|
@ -17,6 +17,7 @@ import copy
|
|||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import inspect
|
import inspect
|
||||||
|
import time
|
||||||
|
|
||||||
from testtools import testcase
|
from testtools import testcase
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
@ -237,6 +238,23 @@ class TestCase(base.BaseTestCase):
|
|||||||
"target_tenant_id": "target_tenant_id"
|
"target_tenant_id": "target_tenant_id"
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
zone_task_fixtures = [{
|
||||||
|
'status': 'PENDING',
|
||||||
|
'domain_id': None,
|
||||||
|
'message': None,
|
||||||
|
'task_type': 'IMPORT'
|
||||||
|
}, {
|
||||||
|
'status': 'ERROR',
|
||||||
|
'domain_id': None,
|
||||||
|
'message': None,
|
||||||
|
'task_type': 'IMPORT'
|
||||||
|
}, {
|
||||||
|
'status': 'COMPLETE',
|
||||||
|
'domain_id': '6ca6baef-3305-4ad0-a52b-a82df5752b62',
|
||||||
|
'message': None,
|
||||||
|
'task_type': 'IMPORT'
|
||||||
|
}]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCase, self).setUp()
|
super(TestCase, self).setUp()
|
||||||
|
|
||||||
@ -503,6 +521,13 @@ class TestCase(base.BaseTestCase):
|
|||||||
_values.update(values)
|
_values.update(values)
|
||||||
return _values
|
return _values
|
||||||
|
|
||||||
|
def get_zone_task_fixture(self, fixture=0, values=None):
|
||||||
|
values = values or {}
|
||||||
|
|
||||||
|
_values = copy.copy(self.zone_task_fixtures[fixture])
|
||||||
|
_values.update(values)
|
||||||
|
return _values
|
||||||
|
|
||||||
def create_tld(self, **kwargs):
|
def create_tld(self, **kwargs):
|
||||||
context = kwargs.pop('context', self.admin_context)
|
context = kwargs.pop('context', self.admin_context)
|
||||||
fixture = kwargs.pop('fixture', 0)
|
fixture = kwargs.pop('fixture', 0)
|
||||||
@ -646,6 +671,44 @@ class TestCase(base.BaseTestCase):
|
|||||||
return self.central_service.create_zone_transfer_accept(
|
return self.central_service.create_zone_transfer_accept(
|
||||||
context, objects.ZoneTransferAccept.from_dict(values))
|
context, objects.ZoneTransferAccept.from_dict(values))
|
||||||
|
|
||||||
|
def create_zone_task(self, **kwargs):
|
||||||
|
context = kwargs.pop('context', self.admin_context)
|
||||||
|
fixture = kwargs.pop('fixture', 0)
|
||||||
|
|
||||||
|
zone_task = self.get_zone_task_fixture(fixture=fixture,
|
||||||
|
values=kwargs)
|
||||||
|
|
||||||
|
return self.storage.create_zone_task(
|
||||||
|
context, objects.ZoneTask.from_dict(zone_task))
|
||||||
|
|
||||||
|
def wait_for_import(self, zone_import_id, errorok=False):
|
||||||
|
"""
|
||||||
|
Zone imports spawn a thread to parse the zone file and
|
||||||
|
insert the data. This waits for this process before continuing
|
||||||
|
"""
|
||||||
|
attempts = 0
|
||||||
|
while attempts < 20:
|
||||||
|
# Give the import a half second to complete
|
||||||
|
time.sleep(.5)
|
||||||
|
|
||||||
|
# Retrieve it, and ensure it's the same
|
||||||
|
zone_import = self.central_service.get_zone_import(
|
||||||
|
self.admin_context, zone_import_id)
|
||||||
|
|
||||||
|
# If the import is done, we're done
|
||||||
|
if zone_import.status == 'COMPLETE':
|
||||||
|
break
|
||||||
|
|
||||||
|
# If errors are allowed, just make sure that something completed
|
||||||
|
if errorok:
|
||||||
|
if zone_import.status != 'PENDING':
|
||||||
|
break
|
||||||
|
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
if not errorok:
|
||||||
|
self.assertEqual(zone_import.status, 'COMPLETE')
|
||||||
|
|
||||||
def _ensure_interface(self, interface, implementation):
|
def _ensure_interface(self, interface, implementation):
|
||||||
for name in interface.__abstractmethods__:
|
for name in interface.__abstractmethods__:
|
||||||
in_arginfo = inspect.getargspec(getattr(interface, name))
|
in_arginfo = inspect.getargspec(getattr(interface, name))
|
||||||
|
21
designate/tests/resources/zonefiles/two_example.com.zone
Normal file
21
designate/tests/resources/zonefiles/two_example.com.zone
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
$ORIGIN example2.com.
|
||||||
|
example2.com. 600 IN SOA ns1.example2.com. nsadmin.example2.com. (
|
||||||
|
2013091101 ; serial
|
||||||
|
7200 ; refresh
|
||||||
|
3600 ; retry
|
||||||
|
2419200 ; expire
|
||||||
|
10800 ; minimum
|
||||||
|
)
|
||||||
|
ipv4.example2.com. 300 IN A 192.0.0.1
|
||||||
|
ipv6.example2.com. IN AAAA fd00::1
|
||||||
|
cname.example2.com. IN CNAME example2.com.
|
||||||
|
example2.com. IN MX 5 192.0.0.2
|
||||||
|
example2.com. IN MX 10 192.0.0.3
|
||||||
|
_http._tcp.example2.com. IN SRV 10 0 80 192.0.0.4
|
||||||
|
_http._tcp.example2.com. IN SRV 10 5 80 192.0.0.5
|
||||||
|
example2.com. IN TXT "abc" "def"
|
||||||
|
example2.com. IN SPF "v=spf1 mx a"
|
||||||
|
example2.com. IN NS ns1.example2.com.
|
||||||
|
example2.com. IN NS ns2.example2.com.
|
||||||
|
delegation.example2.com. IN NS ns1.example2.com.
|
||||||
|
1.0.0.192.in-addr.arpa. IN PTR ipv4.example2.com.
|
@ -1,81 +0,0 @@
|
|||||||
# 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)
|
|
131
designate/tests/test_api/test_v2/test_import_export.py
Normal file
131
designate/tests/test_api/test_v2/test_import_export.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# 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 webtest import TestApp
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from designate.api import admin as admin_api
|
||||||
|
from designate.api import middleware
|
||||||
|
from designate.tests.test_api.test_v2 import ApiV2TestCase
|
||||||
|
|
||||||
|
|
||||||
|
cfg.CONF.import_opt('enabled_extensions_admin', 'designate.api.admin',
|
||||||
|
group='service:api')
|
||||||
|
|
||||||
|
|
||||||
|
class APIV2ZoneImportExportTest(ApiV2TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(APIV2ZoneImportExportTest, self).setUp()
|
||||||
|
|
||||||
|
self.config(enable_api_admin=True, group='service:api')
|
||||||
|
self.config(enabled_extensions_admin=['zones'], group='service:api')
|
||||||
|
# Create the application
|
||||||
|
adminapp = admin_api.factory({})
|
||||||
|
# Inject the NormalizeURIMiddleware middleware
|
||||||
|
adminapp = middleware.NormalizeURIMiddleware(adminapp)
|
||||||
|
# Inject the FaultWrapper middleware
|
||||||
|
adminapp = middleware.FaultWrapperMiddleware(adminapp)
|
||||||
|
# Inject the TestContext middleware
|
||||||
|
adminapp = middleware.TestContextMiddleware(
|
||||||
|
adminapp, self.admin_context.tenant,
|
||||||
|
self.admin_context.tenant)
|
||||||
|
# Obtain a test client
|
||||||
|
self.adminclient = TestApp(adminapp)
|
||||||
|
|
||||||
|
# # Zone import/export
|
||||||
|
def test_missing_origin(self):
|
||||||
|
fixture = self.get_zonefile_fixture(variant='noorigin')
|
||||||
|
|
||||||
|
response = self.client.post_json('/zones/tasks/imports', fixture,
|
||||||
|
headers={'Content-type': 'text/dns'})
|
||||||
|
|
||||||
|
import_id = response.json_body['id']
|
||||||
|
self.wait_for_import(import_id, errorok=True)
|
||||||
|
|
||||||
|
url = '/zones/tasks/imports/%s' % import_id
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.json['status'], 'ERROR')
|
||||||
|
origin_msg = ("The $ORIGIN statement is required and must be the"
|
||||||
|
" first statement in the zonefile.")
|
||||||
|
self.assertEqual(response.json['message'], origin_msg)
|
||||||
|
|
||||||
|
def test_missing_soa(self):
|
||||||
|
fixture = self.get_zonefile_fixture(variant='nosoa')
|
||||||
|
|
||||||
|
response = self.client.post_json('/zones/tasks/imports', fixture,
|
||||||
|
headers={'Content-type': 'text/dns'})
|
||||||
|
|
||||||
|
import_id = response.json_body['id']
|
||||||
|
self.wait_for_import(import_id, errorok=True)
|
||||||
|
|
||||||
|
url = '/zones/tasks/imports/%s' % import_id
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.json['status'], 'ERROR')
|
||||||
|
origin_msg = ("Malformed zonefile.")
|
||||||
|
self.assertEqual(response.json['message'], origin_msg)
|
||||||
|
|
||||||
|
def test_malformed_zonefile(self):
|
||||||
|
fixture = self.get_zonefile_fixture(variant='malformed')
|
||||||
|
|
||||||
|
response = self.client.post_json('/zones/tasks/imports', fixture,
|
||||||
|
headers={'Content-type': 'text/dns'})
|
||||||
|
|
||||||
|
import_id = response.json_body['id']
|
||||||
|
self.wait_for_import(import_id, errorok=True)
|
||||||
|
|
||||||
|
url = '/zones/tasks/imports/%s' % import_id
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.json['status'], 'ERROR')
|
||||||
|
origin_msg = ("Malformed zonefile.")
|
||||||
|
self.assertEqual(response.json['message'], origin_msg)
|
||||||
|
|
||||||
|
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/tasks/imports',
|
||||||
|
self.get_zonefile_fixture(),
|
||||||
|
headers={'Content-type': 'text/dns'})
|
||||||
|
|
||||||
|
import_id = post_response.json_body['id']
|
||||||
|
self.wait_for_import(import_id)
|
||||||
|
|
||||||
|
url = '/zones/tasks/imports/%s' % import_id
|
||||||
|
response = self.client.get(url)
|
||||||
|
|
||||||
|
self.policy({'zone_export': '@'})
|
||||||
|
get_response = self.adminclient.get('/zones/export/%s' %
|
||||||
|
response.json['zone_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)
|
@ -2913,3 +2913,112 @@ class CentralServiceTest(CentralTestCase):
|
|||||||
zone_transfer_accept = \
|
zone_transfer_accept = \
|
||||||
self.central_service.create_zone_transfer_accept(
|
self.central_service.create_zone_transfer_accept(
|
||||||
tenant_3_context, zone_transfer_accept)
|
tenant_3_context, zone_transfer_accept)
|
||||||
|
|
||||||
|
# Zone Import Tests
|
||||||
|
def test_create_zone_import(self):
|
||||||
|
# Create a Zone Import
|
||||||
|
context = self.get_context()
|
||||||
|
request_body = self.get_zonefile_fixture()
|
||||||
|
zone_import = self.central_service.create_zone_import(context,
|
||||||
|
request_body)
|
||||||
|
|
||||||
|
# Ensure all values have been set correctly
|
||||||
|
self.assertIsNotNone(zone_import['id'])
|
||||||
|
self.assertEqual(zone_import.status, 'PENDING')
|
||||||
|
self.assertEqual(zone_import.message, None)
|
||||||
|
self.assertEqual(zone_import.domain_id, None)
|
||||||
|
|
||||||
|
self.wait_for_import(zone_import.id)
|
||||||
|
|
||||||
|
def test_find_zone_imports(self):
|
||||||
|
context = self.get_context()
|
||||||
|
|
||||||
|
# Ensure we have no zone_imports to start with.
|
||||||
|
zone_imports = self.central_service.find_zone_imports(
|
||||||
|
self.admin_context)
|
||||||
|
self.assertEqual(len(zone_imports), 0)
|
||||||
|
|
||||||
|
# Create a single zone_import
|
||||||
|
request_body = self.get_zonefile_fixture()
|
||||||
|
self.central_service.create_zone_import(context, request_body)
|
||||||
|
|
||||||
|
# Ensure we can retrieve the newly created zone_import
|
||||||
|
zone_imports = self.central_service.find_zone_imports(
|
||||||
|
self.admin_context)
|
||||||
|
self.assertEqual(len(zone_imports), 1)
|
||||||
|
|
||||||
|
# Create a second zone_import
|
||||||
|
request_body = self.get_zonefile_fixture(variant="two")
|
||||||
|
zone_import = self.central_service.create_zone_import(context,
|
||||||
|
request_body)
|
||||||
|
|
||||||
|
# Wait for the imports to complete
|
||||||
|
self.wait_for_import(zone_import.id)
|
||||||
|
|
||||||
|
# Ensure we can retrieve both zone_imports
|
||||||
|
zone_imports = self.central_service.find_zone_imports(
|
||||||
|
self.admin_context)
|
||||||
|
self.assertEqual(len(zone_imports), 2)
|
||||||
|
self.assertEqual(zone_imports[0].status, 'COMPLETE')
|
||||||
|
self.assertEqual(zone_imports[1].status, 'COMPLETE')
|
||||||
|
|
||||||
|
def test_get_zone_import(self):
|
||||||
|
# Create a Zone Import
|
||||||
|
context = self.get_context()
|
||||||
|
request_body = self.get_zonefile_fixture()
|
||||||
|
zone_import = self.central_service.create_zone_import(
|
||||||
|
context, request_body)
|
||||||
|
|
||||||
|
# Wait for the import to complete
|
||||||
|
# time.sleep(1)
|
||||||
|
self.wait_for_import(zone_import.id)
|
||||||
|
|
||||||
|
# Retrieve it, and ensure it's the same
|
||||||
|
zone_import = self.central_service.get_zone_import(
|
||||||
|
self.admin_context, zone_import.id)
|
||||||
|
|
||||||
|
self.assertEqual(zone_import['id'], zone_import.id)
|
||||||
|
self.assertEqual(zone_import['status'], zone_import.status)
|
||||||
|
self.assertEqual('COMPLETE', zone_import.status)
|
||||||
|
|
||||||
|
def test_update_zone_import(self):
|
||||||
|
# Create a Zone Import
|
||||||
|
context = self.get_context()
|
||||||
|
request_body = self.get_zonefile_fixture()
|
||||||
|
zone_import = self.central_service.create_zone_import(
|
||||||
|
context, request_body)
|
||||||
|
|
||||||
|
self.wait_for_import(zone_import.id)
|
||||||
|
|
||||||
|
# Update the Object
|
||||||
|
zone_import.message = 'test message'
|
||||||
|
|
||||||
|
# Perform the update
|
||||||
|
zone_import = self.central_service.update_zone_import(
|
||||||
|
self.admin_context, zone_import)
|
||||||
|
|
||||||
|
# Fetch the zone_import again
|
||||||
|
zone_import = self.central_service.get_zone_import(context,
|
||||||
|
zone_import.id)
|
||||||
|
|
||||||
|
# Ensure the zone_import was updated correctly
|
||||||
|
self.assertEqual('test message', zone_import.message)
|
||||||
|
|
||||||
|
def test_delete_zone_import(self):
|
||||||
|
# Create a Zone Import
|
||||||
|
context = self.get_context()
|
||||||
|
request_body = self.get_zonefile_fixture()
|
||||||
|
zone_import = self.central_service.create_zone_import(
|
||||||
|
context, request_body)
|
||||||
|
|
||||||
|
self.wait_for_import(zone_import.id)
|
||||||
|
|
||||||
|
# Delete the zone_import
|
||||||
|
self.central_service.delete_zone_import(context,
|
||||||
|
zone_import['id'])
|
||||||
|
|
||||||
|
# Fetch the zone_import again, ensuring an exception is raised
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ZoneTaskNotFound,
|
||||||
|
self.central_service.get_zone_import,
|
||||||
|
context, zone_import['id'])
|
||||||
|
@ -2346,3 +2346,123 @@ class StorageTestCase(object):
|
|||||||
|
|
||||||
with testtools.ExpectedException(exceptions.DuplicatePoolAttribute):
|
with testtools.ExpectedException(exceptions.DuplicatePoolAttribute):
|
||||||
self.create_pool_attribute(fixture=0)
|
self.create_pool_attribute(fixture=0)
|
||||||
|
|
||||||
|
# Zone Import Tests
|
||||||
|
def test_create_zone_task(self):
|
||||||
|
values = {
|
||||||
|
'status': 'PENDING',
|
||||||
|
'task_type': 'IMPORT'
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.storage.create_zone_task(
|
||||||
|
self.admin_context, objects.ZoneTask.from_dict(values))
|
||||||
|
|
||||||
|
self.assertIsNotNone(result['id'])
|
||||||
|
self.assertIsNotNone(result['created_at'])
|
||||||
|
self.assertIsNone(result['updated_at'])
|
||||||
|
self.assertIsNotNone(result['version'])
|
||||||
|
self.assertEqual(result['status'], values['status'])
|
||||||
|
self.assertEqual(result['domain_id'], None)
|
||||||
|
self.assertEqual(result['message'], None)
|
||||||
|
|
||||||
|
def test_find_zone_tasks(self):
|
||||||
|
|
||||||
|
actual = self.storage.find_zone_tasks(self.admin_context)
|
||||||
|
self.assertEqual(0, len(actual))
|
||||||
|
|
||||||
|
# Create a single ZoneTask
|
||||||
|
zone_task = self.create_zone_task(fixture=0)
|
||||||
|
|
||||||
|
actual = self.storage.find_zone_tasks(self.admin_context)
|
||||||
|
self.assertEqual(1, len(actual))
|
||||||
|
|
||||||
|
self.assertEqual(zone_task['status'], actual[0]['status'])
|
||||||
|
self.assertEqual(zone_task['message'], actual[0]['message'])
|
||||||
|
self.assertEqual(zone_task['domain_id'], actual[0]['domain_id'])
|
||||||
|
|
||||||
|
def test_find_zone_tasks_paging(self):
|
||||||
|
# Create 10 ZoneTasks
|
||||||
|
created = [self.create_zone_task() for i in xrange(10)]
|
||||||
|
|
||||||
|
# Ensure we can page through the results.
|
||||||
|
self._ensure_paging(created, self.storage.find_zone_tasks)
|
||||||
|
|
||||||
|
def test_find_zone_tasks_with_criterion(self):
|
||||||
|
zone_task_one = self.create_zone_task(fixture=0)
|
||||||
|
zone_task_two = self.create_zone_task(fixture=1)
|
||||||
|
|
||||||
|
criterion_one = dict(status=zone_task_one['status'])
|
||||||
|
|
||||||
|
results = self.storage.find_zone_tasks(self.admin_context,
|
||||||
|
criterion_one)
|
||||||
|
self.assertEqual(len(results), 1)
|
||||||
|
|
||||||
|
self.assertEqual(results[0]['status'], zone_task_one['status'])
|
||||||
|
|
||||||
|
criterion_two = dict(status=zone_task_two['status'])
|
||||||
|
|
||||||
|
results = self.storage.find_zone_tasks(self.admin_context,
|
||||||
|
criterion_two)
|
||||||
|
self.assertEqual(len(results), 1)
|
||||||
|
|
||||||
|
self.assertEqual(results[0]['status'], zone_task_two['status'])
|
||||||
|
|
||||||
|
def test_get_zone_task(self):
|
||||||
|
# Create a zone_task
|
||||||
|
expected = self.create_zone_task()
|
||||||
|
actual = self.storage.get_zone_task(self.admin_context,
|
||||||
|
expected['id'])
|
||||||
|
|
||||||
|
self.assertEqual(actual['status'], expected['status'])
|
||||||
|
|
||||||
|
def test_get_zone_task_missing(self):
|
||||||
|
with testtools.ExpectedException(exceptions.ZoneTaskNotFound):
|
||||||
|
uuid = '4c8e7f82-3519-4bf7-8940-a66a4480f223'
|
||||||
|
self.storage.get_zone_task(self.admin_context, uuid)
|
||||||
|
|
||||||
|
def test_find_zone_task_criterion_missing(self):
|
||||||
|
expected = self.create_zone_task()
|
||||||
|
|
||||||
|
criterion = dict(status=expected['status'] + "NOT FOUND")
|
||||||
|
|
||||||
|
with testtools.ExpectedException(exceptions.ZoneTaskNotFound):
|
||||||
|
self.storage.find_zone_task(self.admin_context, criterion)
|
||||||
|
|
||||||
|
def test_update_zone_task(self):
|
||||||
|
# Create a zone_task
|
||||||
|
zone_task = self.create_zone_task(status='PENDING', task_type='IMPORT')
|
||||||
|
|
||||||
|
# Update the zone_task
|
||||||
|
zone_task.status = 'COMPLETE'
|
||||||
|
|
||||||
|
# Update storage
|
||||||
|
zone_task = self.storage.update_zone_task(self.admin_context,
|
||||||
|
zone_task)
|
||||||
|
|
||||||
|
# Verify the new value
|
||||||
|
self.assertEqual('COMPLETE', zone_task.status)
|
||||||
|
|
||||||
|
# Ensure the version column was incremented
|
||||||
|
self.assertEqual(2, zone_task.version)
|
||||||
|
|
||||||
|
def test_update_zone_task_missing(self):
|
||||||
|
zone_task = objects.ZoneTask(
|
||||||
|
id='486f9cbe-b8b6-4d8c-8275-1a6e47b13e00')
|
||||||
|
with testtools.ExpectedException(exceptions.ZoneTaskNotFound):
|
||||||
|
self.storage.update_zone_task(self.admin_context, zone_task)
|
||||||
|
|
||||||
|
def test_delete_zone_task(self):
|
||||||
|
# Create a zone_task
|
||||||
|
zone_task = self.create_zone_task()
|
||||||
|
|
||||||
|
# Delete the zone_task
|
||||||
|
self.storage.delete_zone_task(self.admin_context, zone_task['id'])
|
||||||
|
|
||||||
|
# Verify that it's deleted
|
||||||
|
with testtools.ExpectedException(exceptions.ZoneTaskNotFound):
|
||||||
|
self.storage.get_zone_task(self.admin_context, zone_task['id'])
|
||||||
|
|
||||||
|
def test_delete_zone_task_missing(self):
|
||||||
|
with testtools.ExpectedException(exceptions.ZoneTaskNotFound):
|
||||||
|
uuid = 'cac1fc02-79b2-4e62-a1a4-427b6790bbe6'
|
||||||
|
self.storage.delete_zone_task(self.admin_context, uuid)
|
||||||
|
@ -88,7 +88,6 @@ V2 API
|
|||||||
rest/v2/recordsets
|
rest/v2/recordsets
|
||||||
rest/v2/tlds
|
rest/v2/tlds
|
||||||
rest/v2/blacklists
|
rest/v2/blacklists
|
||||||
rest/v2/quotas
|
|
||||||
rest/v2/pools
|
rest/v2/pools
|
||||||
|
|
||||||
Admin API
|
Admin API
|
||||||
@ -98,4 +97,5 @@ Admin API
|
|||||||
:glob:
|
:glob:
|
||||||
|
|
||||||
rest/admin/quotas
|
rest/admin/quotas
|
||||||
|
rest/admin/zones
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ Zones
|
|||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
The zones extension can be used to import and export zonesfiles to designate.
|
The zones extension can be used to export zonesfiles from designate.
|
||||||
|
|
||||||
*Note*: Zones is an extension and needs to be enabled before it can be used.
|
*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
|
If Designate returns a 404 error, ensure that the following line has been
|
||||||
@ -57,58 +57,3 @@ Export Zone
|
|||||||
:statuscode 406: Not Acceptable
|
:statuscode 406: Not Acceptable
|
||||||
|
|
||||||
Notice how the SOA and NS records are replaced with the Designate server(s).
|
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
|
|
||||||
|
@ -559,3 +559,192 @@ Accept a Transfer Request
|
|||||||
"status": "COMPLETE"
|
"status": "COMPLETE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Import Zone
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Create a Zone Import
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. http:post:: /zones/tasks/imports
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
An object will be returned that can be queried using the 'self' link the
|
||||||
|
'links' field.
|
||||||
|
|
||||||
|
**Example request:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
POST /v2/zones/tasks/imports 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
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "PENDING",
|
||||||
|
"zone_id": null,
|
||||||
|
"links": {
|
||||||
|
"self": "http://127.0.0.1:9001/v2/zones/tasks/imports/074e805e-fe87-4cbb-b10b-21a06e215d41"
|
||||||
|
},
|
||||||
|
"created_at": "2015-05-08T15:43:42.000000",
|
||||||
|
"updated_at": null,
|
||||||
|
"version": 1,
|
||||||
|
"message": null,
|
||||||
|
"project_id": "1",
|
||||||
|
"id": "074e805e-fe87-4cbb-b10b-21a06e215d41"
|
||||||
|
}
|
||||||
|
|
||||||
|
:statuscode 202: Accepted
|
||||||
|
:statuscode 415: Unsupported Media Type
|
||||||
|
|
||||||
|
|
||||||
|
View a Zone Import
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. http:get:: /zones/tasks/imports/(uuid:id)
|
||||||
|
|
||||||
|
The status of a zone import can be viewed by querying the id
|
||||||
|
given when the request was created.
|
||||||
|
|
||||||
|
**Example request:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /v2/zones/tasks/imports/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3 HTTP/1.1
|
||||||
|
Host: 127.0.0.1:9001
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
**Example response:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "COMPLETE",
|
||||||
|
"zone_id": "6625198b-d67d-47dc-8d29-f90bd60f3ac4",
|
||||||
|
"links": {
|
||||||
|
"self": "http://127.0.0.1:9001/v2/zones/tasks/imports/074e805e-fe87-4cbb-b10b-21a06e215d41",
|
||||||
|
"href": "http://127.0.0.1:9001/v2/zones/6625198b-d67d-47dc-8d29-f90bd60f3ac4"
|
||||||
|
},
|
||||||
|
"created_at": "2015-05-08T15:43:42.000000",
|
||||||
|
"updated_at": "2015-05-08T15:43:42.000000",
|
||||||
|
"version": 2,
|
||||||
|
"message": "example.com. imported",
|
||||||
|
"project_id": "noauth-project",
|
||||||
|
"id": "074e805e-fe87-4cbb-b10b-21a06e215d41"
|
||||||
|
}
|
||||||
|
|
||||||
|
:statuscode 200: Success
|
||||||
|
:statuscode 401: Access Denied
|
||||||
|
:statuscode 404: Not Found
|
||||||
|
|
||||||
|
Notice the status has been updated, the message field shows that the zone was
|
||||||
|
successfully imported, and there is now a 'href' in the 'links' field that points
|
||||||
|
to the new zone.
|
||||||
|
|
||||||
|
List Zone Imports
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. http:get:: /zones/tasks/imports/
|
||||||
|
|
||||||
|
List all of the zone imports created by this project.
|
||||||
|
|
||||||
|
**Example request:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /v2/zones/tasks/imports/ HTTP/1.1
|
||||||
|
Host: 127.0.0.1:9001
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
**Example response:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"imports": [
|
||||||
|
{
|
||||||
|
"status": "COMPLETE",
|
||||||
|
"zone_id": "ea2fd415-dc6d-401c-a8af-90a89d7efcf9",
|
||||||
|
"links": {
|
||||||
|
"self": "http://127.0.0.1:9001/v2/zones/tasks/imports/fb47a23e-eb97-4c86-a3d4-f3e1a4ca9f5e",
|
||||||
|
"href": "http://127.0.0.1:9001/v2/zones/ea2fd415-dc6d-401c-a8af-90a89d7efcf9"
|
||||||
|
},
|
||||||
|
"created_at": "2015-05-08T15:22:50.000000",
|
||||||
|
"updated_at": "2015-05-08T15:22:50.000000",
|
||||||
|
"version": 2,
|
||||||
|
"message": "example.com. imported",
|
||||||
|
"project_id": "noauth-project",
|
||||||
|
"id": "fb47a23e-eb97-4c86-a3d4-f3e1a4ca9f5e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "COMPLETE",
|
||||||
|
"zone_id": "6625198b-d67d-47dc-8d29-f90bd60f3ac4",
|
||||||
|
"links": {
|
||||||
|
"self": "http://127.0.0.1:9001/v2/zones/tasks/imports/074e805e-fe87-4cbb-b10b-21a06e215d41",
|
||||||
|
"href": "http://127.0.0.1:9001/v2/zones/6625198b-d67d-47dc-8d29-f90bd60f3ac4"
|
||||||
|
},
|
||||||
|
"created_at": "2015-05-08T15:43:42.000000",
|
||||||
|
"updated_at": "2015-05-08T15:43:42.000000",
|
||||||
|
"version": 2,
|
||||||
|
"message": "example.com. imported",
|
||||||
|
"project_id": "noauth-project",
|
||||||
|
"id": "074e805e-fe87-4cbb-b10b-21a06e215d41"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": {
|
||||||
|
"self": "http://127.0.0.1:9001/v2/zones/tasks/imports"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:statuscode 200: Success
|
||||||
|
:statuscode 401: Access Denied
|
||||||
|
:statuscode 404: Not Found
|
||||||
|
|
||||||
|
Delete Zone Import
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. http:delete:: /zones/tasks/imports/(uuid:id)
|
||||||
|
|
||||||
|
Deletes a zone import with the specified ID. This does not affect the zone
|
||||||
|
that was imported, it simply removes the record of the import.
|
||||||
|
|
||||||
|
**Example Request:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
DELETE /v2/zones/tasks/imports/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3 HTTP/1.1
|
||||||
|
Host: 127.0.0.1:9001
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
**Example Response:**
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 204 No Content
|
||||||
|
|
||||||
|
:statuscode 204: No Content
|
||||||
|
@ -126,7 +126,7 @@ debug = False
|
|||||||
|
|
||||||
# Enabled Admin API extensions
|
# Enabled Admin API extensions
|
||||||
# Can be one or more of : reports, quotas, counts, tenants, zones
|
# Can be one or more of : reports, quotas, counts, tenants, zones
|
||||||
# zone import / export is in zones extension
|
# zone export is in zones extension
|
||||||
#enabled_extensions_admin =
|
#enabled_extensions_admin =
|
||||||
|
|
||||||
# Show the pecan HTML based debug interface (v2 only)
|
# Show the pecan HTML based debug interface (v2 only)
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
|
|
||||||
"use_low_ttl": "rule:admin",
|
"use_low_ttl": "rule:admin",
|
||||||
|
|
||||||
"zone_import": "rule:admin",
|
|
||||||
"zone_export": "rule:admin",
|
|
||||||
|
|
||||||
"get_quotas": "rule:admin_or_owner",
|
"get_quotas": "rule:admin_or_owner",
|
||||||
"get_quota": "rule:admin_or_owner",
|
"get_quota": "rule:admin_or_owner",
|
||||||
"set_quota": "rule:admin",
|
"set_quota": "rule:admin",
|
||||||
@ -109,5 +106,13 @@
|
|||||||
"find_zone_transfer_accepts": "rule:admin",
|
"find_zone_transfer_accepts": "rule:admin",
|
||||||
"find_zone_transfer_accept": "rule:admin",
|
"find_zone_transfer_accept": "rule:admin",
|
||||||
"update_zone_transfer_accept": "rule:admin",
|
"update_zone_transfer_accept": "rule:admin",
|
||||||
"delete_zone_transfer_accept": "rule:admin"
|
"delete_zone_transfer_accept": "rule:admin",
|
||||||
|
|
||||||
|
"zone_export": "rule:admin_or_owner",
|
||||||
|
|
||||||
|
"create_zone_import": "rule:admin_or_owner",
|
||||||
|
"find_zone_imports": "rule:admin_or_owner",
|
||||||
|
"get_zone_import": "rule:admin_or_owner",
|
||||||
|
"update_zone_import": "rule:admin_or_owner",
|
||||||
|
"delete_zone_import": "rule:admin_or_owner"
|
||||||
}
|
}
|
||||||
|
62
functionaltests/api/v2/clients/zone_import_client.py
Normal file
62
functionaltests/api/v2/clients/zone_import_client.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
"""
|
||||||
|
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.zone_import_model import ZoneImportModel
|
||||||
|
from functionaltests.api.v2.models.zone_import_model import ZoneImportListModel
|
||||||
|
from functionaltests.common.client import ClientMixin
|
||||||
|
from functionaltests.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportClient(ClientMixin):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zone_imports_uri(cls):
|
||||||
|
return "/v2/zones/tasks/imports"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zone_import_uri(cls, id):
|
||||||
|
return "{0}/{1}".format(cls.zone_imports_uri(), id)
|
||||||
|
|
||||||
|
def list_zone_imports(self, **kwargs):
|
||||||
|
resp, body = self.client.get(self.zone_imports_uri(), **kwargs)
|
||||||
|
return self.deserialize(resp, body, ZoneImportListModel)
|
||||||
|
|
||||||
|
def get_zone_import(self, id, **kwargs):
|
||||||
|
resp, body = self.client.get(self.zone_import_uri(id))
|
||||||
|
return self.deserialize(resp, body, ZoneImportModel)
|
||||||
|
|
||||||
|
def post_zone_import(self, zonefile_data, **kwargs):
|
||||||
|
headers = {'Content-Type': 'text/dns'}
|
||||||
|
resp, body = self.client.post(self.zone_imports_uri(),
|
||||||
|
body=zonefile_data, headers=headers, **kwargs)
|
||||||
|
return self.deserialize(resp, body, ZoneImportModel)
|
||||||
|
|
||||||
|
def delete_zone_import(self, id, **kwargs):
|
||||||
|
resp, body = self.client.delete(self.zone_import_uri(id), **kwargs)
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def wait_for_zone_import(self, zone_import_id):
|
||||||
|
utils.wait_for_condition(
|
||||||
|
lambda: self.is_zone_import_active(zone_import_id))
|
||||||
|
|
||||||
|
def is_zone_import_active(self, zone_import_id):
|
||||||
|
resp, model = self.get_zone_import(zone_import_id)
|
||||||
|
# don't have assertEqual but still want to fail fast
|
||||||
|
assert resp.status == 200
|
||||||
|
if model.status == 'COMPLETE':
|
||||||
|
return True
|
||||||
|
elif model.status == 'ERROR':
|
||||||
|
raise Exception("Saw ERROR status")
|
||||||
|
return False
|
27
functionaltests/api/v2/models/zone_import_model.py
Normal file
27
functionaltests/api/v2/models/zone_import_model.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
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.common.models import BaseModel
|
||||||
|
from functionaltests.common.models import CollectionModel
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportListModel(CollectionModel):
|
||||||
|
COLLECTION_NAME = 'imports'
|
||||||
|
MODEL_TYPE = ZoneImportModel
|
@ -16,10 +16,12 @@ limitations under the License.
|
|||||||
|
|
||||||
from tempest_lib.exceptions import Conflict
|
from tempest_lib.exceptions import Conflict
|
||||||
from tempest_lib.exceptions import Forbidden
|
from tempest_lib.exceptions import Forbidden
|
||||||
|
from tempest_lib.exceptions import NotFound
|
||||||
|
|
||||||
from functionaltests.common import datagen
|
from functionaltests.common import datagen
|
||||||
from functionaltests.api.v2.base import DesignateV2Test
|
from functionaltests.api.v2.base import DesignateV2Test
|
||||||
from functionaltests.api.v2.clients.zone_client import ZoneClient
|
from functionaltests.api.v2.clients.zone_client import ZoneClient
|
||||||
|
from functionaltests.api.v2.clients.zone_import_client import ZoneImportClient
|
||||||
|
|
||||||
|
|
||||||
class ZoneTest(DesignateV2Test):
|
class ZoneTest(DesignateV2Test):
|
||||||
@ -105,3 +107,36 @@ class ZoneOwnershipTest(DesignateV2Test):
|
|||||||
self._create_zone(zone, user='default')
|
self._create_zone(zone, user='default')
|
||||||
self.assertRaises(Forbidden,
|
self.assertRaises(Forbidden,
|
||||||
lambda: self._create_zone(superzone, user='alt'))
|
lambda: self._create_zone(superzone, user='alt'))
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneImportTest(DesignateV2Test):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ZoneImportTest, self).setUp()
|
||||||
|
|
||||||
|
def test_import_domain(self):
|
||||||
|
user = 'default'
|
||||||
|
import_client = ZoneImportClient.as_user(user)
|
||||||
|
zone_client = ZoneClient.as_user(user)
|
||||||
|
|
||||||
|
zonefile = datagen.random_zonefile_data()
|
||||||
|
resp, model = import_client.post_zone_import(
|
||||||
|
zonefile)
|
||||||
|
import_id = model.id
|
||||||
|
self.assertEqual(resp.status, 202)
|
||||||
|
self.assertEqual(model.status, 'PENDING')
|
||||||
|
import_client.wait_for_zone_import(import_id)
|
||||||
|
|
||||||
|
resp, model = import_client.get_zone_import(
|
||||||
|
model.id)
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertEqual(model.status, 'COMPLETE')
|
||||||
|
|
||||||
|
# Wait for the zone to become 'ACTIVE'
|
||||||
|
zone_client.wait_for_zone(model.zone_id)
|
||||||
|
resp, zone_model = zone_client.get_zone(model.zone_id)
|
||||||
|
|
||||||
|
# Now make sure we can delete the zone_import
|
||||||
|
import_client.delete_zone_import(import_id)
|
||||||
|
self.assertRaises(NotFound,
|
||||||
|
lambda: import_client.get_zone_import(model.id))
|
||||||
|
@ -120,3 +120,18 @@ def random_pool_data():
|
|||||||
data["ns_records"] = []
|
data["ns_records"] = []
|
||||||
|
|
||||||
return PoolModel.from_dict(data)
|
return PoolModel.from_dict(data)
|
||||||
|
|
||||||
|
|
||||||
|
def random_zonefile_data(name=None, ttl=None):
|
||||||
|
"""Generate random zone data, with optional overrides
|
||||||
|
|
||||||
|
:return: A ZoneModel
|
||||||
|
"""
|
||||||
|
zone_base = ('$ORIGIN &\n& # IN SOA ns.& nsadmin.& # # # # #\n'
|
||||||
|
'& # IN NS ns.&\n& # IN MX 10 mail.&\nns.& 360 IN A 1.0.0.1')
|
||||||
|
if name is None:
|
||||||
|
name = random_string(prefix='testdomain', suffix='.com.')
|
||||||
|
if ttl is None:
|
||||||
|
ttl = str(random.randint(1200, 8400))
|
||||||
|
|
||||||
|
return zone_base.replace('&', name).replace('#', ttl)
|
||||||
|
Loading…
Reference in New Issue
Block a user