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>
This commit is contained in:
parent
522ab45d51
commit
3756fc51e7
|
@ -127,6 +127,43 @@ class Bind9Backend(base.Backend):
|
||||||
LOG.warning('RNDC call failure: %s', e)
|
LOG.warning('RNDC call failure: %s', 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().update_zone(context, zone)
|
||||||
|
|
||||||
def _execute_rndc(self, rndc_op):
|
def _execute_rndc(self, rndc_op):
|
||||||
"""Execute rndc
|
"""Execute rndc
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
@ -140,6 +165,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())
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
from designate.tests import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class DesignateManageTestCase(TestCase):
|
||||||
|
pass
|
|
@ -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
|
||||||
|
|
||||||
|
from unittest 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()
|
|
@ -65,6 +65,18 @@ class Bind9BackendTestCase(designate.tests.TestCase):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@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')
|
@mock.patch.object(impl_bind9.Bind9Backend, '_execute_rndc')
|
||||||
def test_create_zone_with_view(self, mock_execute):
|
def test_create_zone_with_view(self, mock_execute):
|
||||||
self.target['options'].append(
|
self.target['options'].append(
|
||||||
|
|
Loading…
Reference in New Issue