Update zones masters using pool target masters.

This change enforces the update of the zone masters
for all zones that belongs to a particular pool, using
the pool's defined target(s) masters and forcing a update_zone
call.

This change also, moves the backend base class update_zone
method as an abstract method, allowing to each backend
implementation to create its own update logic. For the
case of bind9 its extended to allow running a rndc modzone
with the new given masters for the zone fixing the behavior
exposed on LP: #1879798.

Fixes-Bug: #1879798

Change-Id: I9dddd4130a0cbb29311eeb52e077e216c8c03f3a
Signed-off-by: Jorge Niedbalski <jorge.niedbalski@canonical.com>
Signed-off-by: Nicolas Bock <nicolas.bock@canonical.com>
(cherry picked from commit 3756fc51e7)
This commit is contained in:
Jorge Niedbalski 2020-05-28 14:57:49 -04:00 committed by Nicolas Bock
parent acc82755f1
commit 0b5634643b
No known key found for this signature in database
GPG Key ID: 23EDF7B8E50200B5
4 changed files with 135 additions and 0 deletions

View File

@ -125,6 +125,43 @@ class Bind9Backend(base.Backend):
if "not found" not in six.text_type(e): if "not found" not in six.text_type(e):
raise raise
def update_zone(self, context, zone):
"""
Update a DNS zone.
This will execute a rndc modzone as the zone
already exists but masters might need to be refreshed.
:param context: Security context information.
:param zone: the DNS zone.
"""
LOG.debug('Update Zone')
masters = []
for master in self.masters:
host = master['host']
port = master['port']
masters.append('%s port %s' % (host, port))
# Ensure different MiniDNS instances are targeted for AXFRs
random.shuffle(masters)
view = 'in %s' % self._view if self._view else ''
rndc_op = [
'modzone',
'%s %s { type slave; masters { %s;}; file "slave.%s%s"; };' %
(zone['name'].rstrip('.'), view, '; '.join(masters), zone['name'],
zone['id']),
]
try:
self._execute_rndc(rndc_op)
except exceptions.Backend as e:
LOG.warning("Error updating zone: %s", e)
pass
super(Bind9Backend, self).update_zone(context, zone)
def _execute_rndc(self, rndc_op): def _execute_rndc(self, rndc_op):
"""Execute rndc """Execute rndc

View File

@ -23,6 +23,7 @@ import oslo_messaging as messaging
from designate import exceptions from designate import exceptions
from designate import rpc from designate import rpc
from designate import objects from designate import objects
from designate import policy
from designate.central import rpcapi as central_rpcapi from designate.central import rpcapi as central_rpcapi
from designate.manage import base from designate.manage import base
from designate.objects.adapters import DesignateAdapter from designate.objects.adapters import DesignateAdapter
@ -43,6 +44,30 @@ class PoolCommands(base.Commands):
rpc.init(cfg.CONF) rpc.init(cfg.CONF)
self.central_api = central_rpcapi.CentralAPI() self.central_api = central_rpcapi.CentralAPI()
def _update_zones(self, pool):
LOG.info("Updating zone masters for pool: {}".format(pool.id))
def __get_masters_from_pool(pool):
masters = []
for target in pool.targets:
for master in target.get("masters", []):
masters.append({'host': master['host'],
'port': master['port']})
return masters
policy.init()
self.context.all_tenants = True
zones = self.central_api.find_zones(
self.context,
criterion={'pool_id': pool.id})
for zone in zones:
zone.masters = objects.ZoneMasterList().from_list(
__get_masters_from_pool(pool))
self.central_api.update_zone(self.context,
zone)
@base.args('--file', help='The path to the file the yaml output should be ' @base.args('--file', help='The path to the file the yaml output should be '
'written to', 'written to',
default='/etc/designate/pools.yaml') default='/etc/designate/pools.yaml')
@ -163,6 +188,9 @@ class PoolCommands(base.Commands):
output_msg.append("Update Pool: %s" % pool) output_msg.append("Update Pool: %s" % pool)
else: else:
pool = self.central_api.update_pool(self.context, pool) pool = self.central_api.update_pool(self.context, pool)
# Bug: Changes in the pool targets should trigger a
# zone masters update LP: #1879798.
self._update_zones(pool)
except exceptions.PoolNotFound: except exceptions.PoolNotFound:
pool = DesignateAdapter.parse('YAML', xpool, objects.Pool()) pool = DesignateAdapter.parse('YAML', xpool, objects.Pool())

View File

@ -0,0 +1,5 @@
from designate.tests import TestCase
class DesignateManageTestCase(TestCase):
pass

View File

@ -0,0 +1,65 @@
# 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 mock
from designate.tests.test_manage import DesignateManageTestCase
from designate.manage.pool import PoolCommands
from designate.tests import fixtures
from designate import objects
LOG = logging.getLogger(__name__)
class UpdatePoolTestCase(DesignateManageTestCase):
def setUp(self):
super(DesignateManageTestCase, self).setUp()
self.stdlog = fixtures.StandardLogging()
self.useFixture(self.stdlog)
def hydrate_pool_targets(self, targets):
pool_targets = objects.PoolTargetList()
masters = objects.PoolTargetMasterList()
for target in targets:
masters.append(target)
target = objects.PoolTarget(masters=masters)
target.masters = masters
pool_targets.append(target)
return pool_targets
def test_update_pools_zones(self):
values = dict(
name='example.com.',
email='info@example.com',
type='PRIMARY'
)
zone = self.central_service.create_zone(
self.admin_context, zone=objects.Zone.from_dict(values))
# Ensure the correct NS Records are in place
pool = self.central_service.get_pool(
self.admin_context, zone.pool_id)
pool.targets = self.hydrate_pool_targets([objects.PoolTargetMaster(
pool_target_id=pool.id,
host="127.0.0.1",
port="53")])
command = PoolCommands()
command.context = self.admin_context
command.central_api = self.central_service
with mock.patch.object(self.central_service,
"update_zone") as mock_update_zone:
command._update_zones(pool)
mock_update_zone.assert_called_once()