Skip to content

Commit

Permalink
Update zones masters using pool target masters.
Browse files Browse the repository at this point in the history
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>
(cherry picked from commit 3756fc5)
  • Loading branch information
bilboer authored and nicolasbock committed Feb 26, 2021
1 parent dc2426f commit b967e9f
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 0 deletions.
37 changes: 37 additions & 0 deletions designate/backend/impl_bind9.py
Expand Up @@ -127,6 +127,43 @@ def delete_zone(self, context, zone):
LOG.warning('RNDC call failure: %s', e)
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().update_zone(context, zone)

def _execute_rndc(self, rndc_op):
"""Execute rndc
Expand Down
28 changes: 28 additions & 0 deletions designate/manage/pool.py
Expand Up @@ -23,6 +23,7 @@
from designate import exceptions
from designate import rpc
from designate import objects
from designate import policy
from designate.central import rpcapi as central_rpcapi
from designate.manage import base
from designate.objects.adapters import DesignateAdapter
Expand All @@ -43,6 +44,30 @@ def _startup(self):
rpc.init(cfg.CONF)
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 '
'written to',
default='/etc/designate/pools.yaml')
Expand Down Expand Up @@ -140,6 +165,9 @@ def update(self, file, delete, dry_run):
output_msg.append("Update Pool: %s" % pool)
else:
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:
pool = DesignateAdapter.parse('YAML', xpool, objects.Pool())
Expand Down
5 changes: 5 additions & 0 deletions designate/tests/test_manage/__init__.py
@@ -0,0 +1,5 @@
from designate.tests import TestCase


class DesignateManageTestCase(TestCase):
pass
65 changes: 65 additions & 0 deletions designate/tests/test_manage/test_update_pool.py
@@ -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()
12 changes: 12 additions & 0 deletions designate/tests/unit/backend/test_bind9.py
Expand Up @@ -65,6 +65,18 @@ def test_create_zone(self, mock_execute):
]
)

@mock.patch.object(impl_bind9.Bind9Backend, '_execute_rndc')
def test_update_zone(self, mock_execute):
with fixtures.random_seed(0):
self.backend.update_zone(self.admin_context, self.zone)

mock_execute.assert_called_with(
[
'modzone',
'example.com { type slave; masters { 192.168.1.1 port 53; 192.168.1.2 port 35;}; file "slave.example.com.cca7908b-dad4-4c50-adba-fb67d4c556e8"; };' # noqa
]
)

@mock.patch.object(impl_bind9.Bind9Backend, '_execute_rndc')
def test_create_zone_with_view(self, mock_execute):
self.target['options'].append(
Expand Down

0 comments on commit b967e9f

Please sign in to comment.