Add new API for zone move
The new API would be v2/zones/<zone_id>/tasks/move Only POST would be allowed on this API. This move zone from existing pool and add it in new pool. After zone pool_id field will be updated in DB, clone-zone will be created on target pool backend servers. The zone transfer(AXFR/IXFR) will happen and the zone on target pool gets synced with the Designate DB. This command serve as replacement to "zone export + zone delete + zone import" procedure. Added following things in pool move operation: - Add/Update NS servers of new pool in the zone Implements: blueprint zone-move Change-Id: I5307de429114b20efd9785c3c0cdb33977418423
This commit is contained in:
parent
f5a034272d
commit
8733f8f85b
@ -46,7 +46,6 @@ Request
|
|||||||
- zone_id: path_zone_id
|
- zone_id: path_zone_id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@ -102,3 +101,57 @@ Response Parameters
|
|||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- x-openstack-request-id: x-openstack-request-id
|
- x-openstack-request-id: x-openstack-request-id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Pool Move Zone
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. rest_method:: POST /v2/zones/{zone_id}/tasks/pool_move
|
||||||
|
|
||||||
|
Move a zone to another pool.
|
||||||
|
|
||||||
|
This moves a zone from the existing designate pool to specified target pool. If
|
||||||
|
pool is not specified by admin, designate will determine suitable pool by
|
||||||
|
itself and move zone to that pool.
|
||||||
|
|
||||||
|
.. rest_status_code:: success status.yaml
|
||||||
|
|
||||||
|
- 202
|
||||||
|
|
||||||
|
|
||||||
|
.. rest_status_code:: error status.yaml
|
||||||
|
|
||||||
|
- 400
|
||||||
|
- 401
|
||||||
|
- 403
|
||||||
|
- 404
|
||||||
|
- 405
|
||||||
|
- 500
|
||||||
|
- 503
|
||||||
|
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- x-auth-token: x-auth-token
|
||||||
|
- x-auth-all-projects: x-auth-all-projects
|
||||||
|
- x-auth-sudo-project-id: x-auth-sudo-project-id
|
||||||
|
- zone_id: path_zone_id
|
||||||
|
- pool_id: zone_pool_target_id
|
||||||
|
|
||||||
|
Request Example
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. literalinclude:: samples/zones/poolmove-zone-request.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
|
Response Parameters
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- x-openstack-request-id: x-openstack-request-id
|
||||||
|
@ -953,6 +953,13 @@ zone_pool_id:
|
|||||||
required: true
|
required: true
|
||||||
type: uuid
|
type: uuid
|
||||||
|
|
||||||
|
zone_pool_target_id:
|
||||||
|
description: |
|
||||||
|
The target pool ID to move the zone into
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: uuid
|
||||||
|
|
||||||
zone_serial:
|
zone_serial:
|
||||||
description: |
|
description: |
|
||||||
current serial number for the zone
|
current serial number for the zone
|
||||||
|
3
api-ref/source/samples/zones/poolmove-zone-request.json
Normal file
3
api-ref/source/samples/zones/poolmove-zone-request.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"pool_id": "8e6f1c59-15e7-4a14-8640-8d5e07f95b10"
|
||||||
|
}
|
@ -22,6 +22,8 @@ from designate.api.v2.controllers.zones.tasks.exports import (
|
|||||||
ZoneExportsController)
|
ZoneExportsController)
|
||||||
from designate.api.v2.controllers.zones.tasks.imports import (
|
from designate.api.v2.controllers.zones.tasks.imports import (
|
||||||
ZoneImportController)
|
ZoneImportController)
|
||||||
|
from designate.api.v2.controllers.zones.tasks.pool_move import (
|
||||||
|
PoolMoveController)
|
||||||
from designate.api.v2.controllers.zones.tasks.transfer_accepts import (
|
from designate.api.v2.controllers.zones.tasks.transfer_accepts import (
|
||||||
TransferAcceptsController as TRA)
|
TransferAcceptsController as TRA)
|
||||||
from designate.api.v2.controllers.zones.tasks.transfer_requests import (
|
from designate.api.v2.controllers.zones.tasks.transfer_requests import (
|
||||||
@ -37,6 +39,7 @@ class TasksController:
|
|||||||
transfer_requests = TRC()
|
transfer_requests = TRC()
|
||||||
abandon = abandon.AbandonController()
|
abandon = abandon.AbandonController()
|
||||||
xfr = XfrController()
|
xfr = XfrController()
|
||||||
|
pool_move = PoolMoveController()
|
||||||
imports = ZoneImportController()
|
imports = ZoneImportController()
|
||||||
exports = ZoneExportsController()
|
exports = ZoneExportsController()
|
||||||
export = ZoneExportCreateController()
|
export = ZoneExportCreateController()
|
||||||
|
64
designate/api/v2/controllers/zones/tasks/pool_move.py
Normal file
64
designate/api/v2/controllers/zones/tasks/pool_move.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Copyright 2022 Cloudification GmbH
|
||||||
|
#
|
||||||
|
# Author: Kiran P <contact@cloudification.io>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
import pecan
|
||||||
|
|
||||||
|
from designate.api.v2.controllers import rest
|
||||||
|
from designate import exceptions
|
||||||
|
from designate.objects.adapters import DesignateAdapter
|
||||||
|
from designate import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PoolMoveController(rest.RestController):
|
||||||
|
|
||||||
|
@pecan.expose(template='json:', content_type='application/json')
|
||||||
|
@pecan.expose(template='json:', content_type='application/json-patch+json')
|
||||||
|
@utils.validate_uuid('zone_id')
|
||||||
|
def post_all(self, zone_id):
|
||||||
|
"""Move a zone"""
|
||||||
|
request = pecan.request
|
||||||
|
response = pecan.response
|
||||||
|
body = request.body_dict
|
||||||
|
context = request.environ['context']
|
||||||
|
|
||||||
|
zone = self.central_api.get_zone(context, zone_id)
|
||||||
|
|
||||||
|
if zone.action == "DELETE":
|
||||||
|
raise exceptions.BadRequest('Can not move a deleting zone')
|
||||||
|
|
||||||
|
target_pool_id = None
|
||||||
|
if 'pool_id' in body:
|
||||||
|
if zone.pool_id == body['pool_id']:
|
||||||
|
raise exceptions.BadRequest(
|
||||||
|
'Target pool must be different for zone pool move')
|
||||||
|
target_pool_id = body['pool_id']
|
||||||
|
|
||||||
|
# Update the zone object with the new values
|
||||||
|
zone = DesignateAdapter.parse('API_v2', body, zone)
|
||||||
|
zone.validate()
|
||||||
|
|
||||||
|
LOG.info("Triggered pool move for %(zone)s", {'zone': zone})
|
||||||
|
zone = self.central_api.pool_move_zone(
|
||||||
|
context, zone_id, target_pool_id)
|
||||||
|
if zone.status == 'PENDING':
|
||||||
|
response.status_int = 202
|
||||||
|
else:
|
||||||
|
response.status_int = 500
|
||||||
|
|
||||||
|
return ''
|
@ -72,8 +72,9 @@ class CentralAPI:
|
|||||||
6.7 - Add increment_zone_serial
|
6.7 - Add increment_zone_serial
|
||||||
6.8 - Add managed recordset methods
|
6.8 - Add managed recordset methods
|
||||||
6.9 - Removed unused methods
|
6.9 - Removed unused methods
|
||||||
|
6.10 - Add Zone Pool Move method
|
||||||
"""
|
"""
|
||||||
RPC_API_VERSION = '6.9'
|
RPC_API_VERSION = '6.10'
|
||||||
|
|
||||||
# This allows us to mark some methods as not logged.
|
# This allows us to mark some methods as not logged.
|
||||||
# This can be for a few reasons - some methods my not actually call over
|
# This can be for a few reasons - some methods my not actually call over
|
||||||
@ -86,7 +87,7 @@ class CentralAPI:
|
|||||||
|
|
||||||
target = messaging.Target(topic=self.topic,
|
target = messaging.Target(topic=self.topic,
|
||||||
version=self.RPC_API_VERSION)
|
version=self.RPC_API_VERSION)
|
||||||
self.client = rpc.get_client(target, version_cap='6.9')
|
self.client = rpc.get_client(target, version_cap='6.10')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
@ -175,6 +176,11 @@ class CentralAPI:
|
|||||||
return self.client.call(context, 'purge_zones',
|
return self.client.call(context, 'purge_zones',
|
||||||
criterion=criterion, limit=limit)
|
criterion=criterion, limit=limit)
|
||||||
|
|
||||||
|
def pool_move_zone(self, context, zone_id, target_pool_id):
|
||||||
|
return self.client.call(context, 'pool_move_zone',
|
||||||
|
zone_id=zone_id,
|
||||||
|
target_pool_id=target_pool_id)
|
||||||
|
|
||||||
# Shared Zone methods
|
# Shared Zone methods
|
||||||
def share_zone(self, context, zone_id, shared_zone):
|
def share_zone(self, context, zone_id, shared_zone):
|
||||||
return self.client.call(context, 'share_zone', zone_id=zone_id,
|
return self.client.call(context, 'share_zone', zone_id=zone_id,
|
||||||
|
@ -54,7 +54,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Service(service.RPCService):
|
class Service(service.RPCService):
|
||||||
RPC_API_VERSION = '6.9'
|
RPC_API_VERSION = '6.10'
|
||||||
|
|
||||||
target = messaging.Target(version=RPC_API_VERSION)
|
target = messaging.Target(version=RPC_API_VERSION)
|
||||||
|
|
||||||
@ -1329,6 +1329,81 @@ class Service(service.RPCService):
|
|||||||
"Could not find %s" % zone.obj_name())
|
"Could not find %s" % zone.obj_name())
|
||||||
return zone_shared
|
return zone_shared
|
||||||
|
|
||||||
|
@rpc.expected_exceptions()
|
||||||
|
@notification.notify_type('dns.domain.update')
|
||||||
|
@notification.notify_type('dns.zone.update')
|
||||||
|
def pool_move_zone(self, context, zone_id, target_pool_id=None):
|
||||||
|
"""Move zone. Perform checks and then create zone in destination pool
|
||||||
|
|
||||||
|
:returns: moved zone
|
||||||
|
"""
|
||||||
|
if policy.enforce_new_defaults():
|
||||||
|
target = {
|
||||||
|
'zone_id': zone_id,
|
||||||
|
constants.RBAC_PROJECT_ID: context.project_id,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
target = {
|
||||||
|
'zone_id': zone_id,
|
||||||
|
'tenant_id': context.project_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
policy.check('pool_move_zone', context, target)
|
||||||
|
|
||||||
|
# Get the destination pool
|
||||||
|
zone = self.storage.get_zone(context, zone_id)
|
||||||
|
orig_pool_id = zone.pool_id
|
||||||
|
|
||||||
|
if target_pool_id is None:
|
||||||
|
target_pool_id = self.scheduler.schedule_zone(context, zone)
|
||||||
|
if target_pool_id == orig_pool_id:
|
||||||
|
raise exceptions.BadRequest('No valid pool selected')
|
||||||
|
# Update the orignal zone with new pool_id
|
||||||
|
zone.pool_id = target_pool_id
|
||||||
|
|
||||||
|
# Need elevated context to get the pool
|
||||||
|
elevated_context = context.elevated(all_tenants=True)
|
||||||
|
try:
|
||||||
|
self.storage.get_pool(elevated_context, target_pool_id)
|
||||||
|
except exceptions.PoolNotFound:
|
||||||
|
raise exceptions.BadRequest('Target pool does not exist')
|
||||||
|
|
||||||
|
target_pool_ns_records = self._get_pool_ns_records(context,
|
||||||
|
target_pool_id)
|
||||||
|
if len(target_pool_ns_records) == 0:
|
||||||
|
LOG.critical('No nameservers configured. Please create at least '
|
||||||
|
'one nameserver on target pool')
|
||||||
|
raise exceptions.NoServersConfigured()
|
||||||
|
|
||||||
|
orig_pool_ns_records = self._get_pool_ns_records(context,
|
||||||
|
orig_pool_id)
|
||||||
|
|
||||||
|
target_ns = {n.hostname for n in target_pool_ns_records}
|
||||||
|
orig_ns = {n.hostname for n in orig_pool_ns_records}
|
||||||
|
create_ns = target_ns.difference(orig_ns)
|
||||||
|
delete_ns = orig_ns.difference(target_ns)
|
||||||
|
|
||||||
|
# Update target NS servers for the zone
|
||||||
|
for ns_record in create_ns:
|
||||||
|
self._add_ns(elevated_context, zone, ns_record)
|
||||||
|
|
||||||
|
# Then handle the ns_records to delete
|
||||||
|
for ns_record in delete_ns:
|
||||||
|
self._delete_ns(elevated_context, zone, ns_record)
|
||||||
|
|
||||||
|
zone = self._update_zone_in_storage(
|
||||||
|
context, zone, increment_serial=False)
|
||||||
|
|
||||||
|
LOG.info("Moving zone '%(zone)s' to pool '%(pool)s'",
|
||||||
|
{'zone': zone.name, 'pool': target_pool_id})
|
||||||
|
zone.pool_id = target_pool_id
|
||||||
|
zone.refresh = self._generate_soa_refresh_interval()
|
||||||
|
zone.action = 'CREATE'
|
||||||
|
zone.status = 'PENDING'
|
||||||
|
self.worker_api.create_zone(context, zone)
|
||||||
|
|
||||||
|
return zone
|
||||||
|
|
||||||
# RecordSet Methods
|
# RecordSet Methods
|
||||||
@rpc.expected_exceptions()
|
@rpc.expected_exceptions()
|
||||||
@notification.notify_type('dns.recordset.create')
|
@notification.notify_type('dns.recordset.create')
|
||||||
|
@ -101,6 +101,12 @@ deprecated_purge_zones = policy.DeprecatedRule(
|
|||||||
deprecated_reason=DEPRECATED_REASON,
|
deprecated_reason=DEPRECATED_REASON,
|
||||||
deprecated_since=versionutils.deprecated.WALLABY
|
deprecated_since=versionutils.deprecated.WALLABY
|
||||||
)
|
)
|
||||||
|
deprecated_pool_move_zone = policy.DeprecatedRule(
|
||||||
|
name="pool_move_zone",
|
||||||
|
check_str=base.RULE_ADMIN,
|
||||||
|
deprecated_reason=DEPRECATED_REASON,
|
||||||
|
deprecated_since=versionutils.deprecated.WALLABY
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
@ -238,6 +244,19 @@ rules = [
|
|||||||
scope_types=[constants.PROJECT],
|
scope_types=[constants.PROJECT],
|
||||||
deprecated_rule=deprecated_purge_zones
|
deprecated_rule=deprecated_purge_zones
|
||||||
),
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name="pool_move_zone",
|
||||||
|
check_str=base.SYSTEM_ADMIN,
|
||||||
|
scope_types=[constants.PROJECT],
|
||||||
|
description="Pool Move Zone",
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v2/zones/{zone_id}/tasks/pool_move',
|
||||||
|
'method': 'POST'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
deprecated_rule=deprecated_pool_move_zone,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,9 @@ class ZoneAPIv2Adapter(base.APIv2Adapter):
|
|||||||
MODIFICATIONS = {
|
MODIFICATIONS = {
|
||||||
'fields': {
|
'fields': {
|
||||||
"id": {},
|
"id": {},
|
||||||
"pool_id": {},
|
"pool_id": {
|
||||||
|
'read_only': False
|
||||||
|
},
|
||||||
"project_id": {
|
"project_id": {
|
||||||
'rename': 'tenant_id'
|
'rename': 'tenant_id'
|
||||||
},
|
},
|
||||||
|
@ -517,6 +517,56 @@ class ApiV2ZonesTest(ApiV2TestCase):
|
|||||||
self._assert_exception('not_found', 404, self.client.get, url,
|
self._assert_exception('not_found', 404, self.client.get, url,
|
||||||
headers={'X-Test-Role': 'member'})
|
headers={'X-Test-Role': 'member'})
|
||||||
|
|
||||||
|
def test_post_pool_zone_move_invalid_pool_id(self):
|
||||||
|
zone = self.create_zone()
|
||||||
|
body = {'pool_id': zone.pool_id}
|
||||||
|
self._assert_exception('bad_request', 400, self.client.post_json,
|
||||||
|
'/zones/%s/tasks/pool_move' % zone['id'],
|
||||||
|
body, headers={'X-Test-Role': 'admin'})
|
||||||
|
|
||||||
|
def test_post_pool_zone_move_invalid_action(self):
|
||||||
|
# Create a zone
|
||||||
|
zone = self.create_zone()
|
||||||
|
body = {'pool_id': '12345'}
|
||||||
|
zone.action = 'DELETE'
|
||||||
|
with mock.patch.object(central_service.Service, 'get_zone',
|
||||||
|
return_value=zone):
|
||||||
|
self._assert_exception('bad_request', 400,
|
||||||
|
self.client.post_json,
|
||||||
|
'/zones/%s/tasks/pool_move' % zone['id'],
|
||||||
|
body, headers={'X-Test-Role': 'admin'})
|
||||||
|
|
||||||
|
def test_post_pool_zone_move_non_admin_user(self):
|
||||||
|
# Create a zone
|
||||||
|
zone = self.create_zone()
|
||||||
|
body = {'pool_id': '12345'}
|
||||||
|
self._assert_exception('forbidden', 403, self.client.post_json,
|
||||||
|
'/zones/%s/tasks/pool_move' % zone['id'], body)
|
||||||
|
|
||||||
|
def test_post_pool_zone_move_admin_user_status_500(self):
|
||||||
|
# Create a zone
|
||||||
|
zone = self.create_zone()
|
||||||
|
body = {'pool_id': '12345'}
|
||||||
|
response = self.client.post_json(
|
||||||
|
'/zones/%s/tasks/pool_move' % zone['id'],
|
||||||
|
body, status=500, headers={'X-Test-Role': 'admin'})
|
||||||
|
|
||||||
|
# Check the headers are what we expect
|
||||||
|
self.assertEqual(500, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
|
||||||
|
def test_post_pool_zone_move_admin_user_status_202(self):
|
||||||
|
# Create a zone
|
||||||
|
zone = self.create_zone()
|
||||||
|
body = {'pool_id': '12345'}
|
||||||
|
zone.status = 'PENDING'
|
||||||
|
with mock.patch.object(central_service.Service, 'pool_move_zone',
|
||||||
|
return_value=zone):
|
||||||
|
response = self.client.post_json(
|
||||||
|
'/zones/%s/tasks/pool_move' % zone['id'], body,
|
||||||
|
headers={'X-Test-Role': 'admin'})
|
||||||
|
self.assertEqual(202, response.status_int)
|
||||||
|
|
||||||
def test_get_zone_tasks(self):
|
def test_get_zone_tasks(self):
|
||||||
# This is an invalid endpoint - should return 404
|
# This is an invalid endpoint - should return 404
|
||||||
zone = self.create_zone()
|
zone = self.create_zone()
|
||||||
|
@ -4240,6 +4240,66 @@ class CentralServiceTest(designate.tests.TestCase):
|
|||||||
self.assertEqual(shared_zone.project_id,
|
self.assertEqual(shared_zone.project_id,
|
||||||
retrived_shared_zone.project_id)
|
retrived_shared_zone.project_id)
|
||||||
|
|
||||||
|
def test_pool_move_zone(self):
|
||||||
|
pool = self.create_pool(fixture=0)
|
||||||
|
zone = self.create_zone(context=self.admin_context, pool_id=pool.id)
|
||||||
|
|
||||||
|
# create second pool
|
||||||
|
second_pool = self.create_pool(fixture=1)
|
||||||
|
new_ns_record = objects.PoolNsRecord(hostname='ns-new.example.org.')
|
||||||
|
second_pool.ns_records.append(new_ns_record)
|
||||||
|
|
||||||
|
moved_zone = self.central_service.pool_move_zone(
|
||||||
|
self.admin_context,
|
||||||
|
zone.id, second_pool['id'])
|
||||||
|
self.assertEqual(zone.id, moved_zone.id)
|
||||||
|
self.assertEqual(moved_zone.pool_id, second_pool['id'])
|
||||||
|
|
||||||
|
def test_pool_move_zone_without_target_pool(self):
|
||||||
|
pool = self.create_pool(fixture=0)
|
||||||
|
zone = self.create_zone(context=self.admin_context, pool_id=pool.id)
|
||||||
|
|
||||||
|
# create second pool
|
||||||
|
second_pool = self.create_pool(fixture=1)
|
||||||
|
new_ns_record = objects.PoolNsRecord(hostname='ns-new.example.org.')
|
||||||
|
second_pool.ns_records.append(new_ns_record)
|
||||||
|
|
||||||
|
zone.pool_id = None
|
||||||
|
with mock.patch.object(self.central_service.scheduler, 'schedule_zone',
|
||||||
|
return_value=second_pool['id']):
|
||||||
|
moved_zone = self.central_service.pool_move_zone(
|
||||||
|
self.admin_context,
|
||||||
|
zone.id)
|
||||||
|
self.assertEqual(zone.id, moved_zone.id)
|
||||||
|
self.assertEqual(moved_zone.pool_id, second_pool['id'])
|
||||||
|
|
||||||
|
def test_pool_move_zone_exception_no_ns_records(self):
|
||||||
|
pool = self.create_pool(fixture=0)
|
||||||
|
zone = self.create_zone(context=self.admin_context, pool_id=pool.id)
|
||||||
|
|
||||||
|
# create second pool
|
||||||
|
second_pool = self.create_pool(fixture=1)
|
||||||
|
|
||||||
|
zone.pool_id = second_pool['id']
|
||||||
|
with mock.patch.object(self.central_service, '_get_pool_ns_records',
|
||||||
|
return_value=[]):
|
||||||
|
self.assertRaises(exceptions.NoServersConfigured,
|
||||||
|
self.central_service.pool_move_zone,
|
||||||
|
self.admin_context,
|
||||||
|
zone.id, second_pool['id'])
|
||||||
|
|
||||||
|
def test_pool_move_zone_exception_invalid_pool_id(self):
|
||||||
|
pool = self.create_pool(fixture=0)
|
||||||
|
zone = self.create_zone(context=self.admin_context, pool_id=pool.id)
|
||||||
|
|
||||||
|
# Use fake pool ID
|
||||||
|
pool_id = '521935cf-d5be-44a2-9f64-fb5a316a61d2'
|
||||||
|
exc = self.assertRaises(rpc_dispatcher.ExpectedException,
|
||||||
|
self.central_service.pool_move_zone,
|
||||||
|
self.admin_context,
|
||||||
|
zone.id, target_pool_id=pool_id)
|
||||||
|
self.assertEqual(exceptions.BadRequest, exc.exc_info[0])
|
||||||
|
|
||||||
def test_create_managed_records(self):
|
def test_create_managed_records(self):
|
||||||
zone = self.create_zone()
|
zone = self.create_zone()
|
||||||
|
|
||||||
|
10
releasenotes/notes/zone-pool-move-7bb8e1f0839c3c0d.yaml
Normal file
10
releasenotes/notes/zone-pool-move-7bb8e1f0839c3c0d.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added zone pool move command which allows admin user to move zone from
|
||||||
|
pool A to specified pool B. This command overcome the issues observed in
|
||||||
|
zone export-import thereby reducing hours of time of large zone imports
|
||||||
|
(e.g. 20-30k records). Please note, if you have moved a zone to a
|
||||||
|
different pool, the pool must be configured with a proper tsig key for
|
||||||
|
mini-DNS query operations. Without this, you cannot have overlapping zones
|
||||||
|
in different pools.
|
Loading…
Reference in New Issue
Block a user