From ab565c71bd174cdda1fc58446afc985ccf4d92a6 Mon Sep 17 00:00:00 2001 From: David Negreira Date: Mon, 25 Mar 2024 09:21:14 +0000 Subject: [PATCH] separate rndc.key file per application This change fixes an issue that when the charm-designate is related to two different designate-bind applications, the same rndc key file is used for the two different applications. We fix this by writing an rndc_key_file per application rather than in a single rndc.key file for all the units. Closes-Bug: #1995975 Change-Id: I5dafeb2b4dcf9549260081d3674038f836d29f0f Signed-off-by: David Negreira --- src/lib/charm/openstack/designate.py | 30 +++++++++- src/reactive/designate_handlers.py | 13 ++++ src/templates/mitaka/pools.yaml | 2 +- src/templates/rocky/pools.yaml | 2 +- unit_tests/test_designate_handlers.py | 3 + .../test_lib_charm_openstack_designate.py | 59 +++++++++++++++++-- 6 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/lib/charm/openstack/designate.py b/src/lib/charm/openstack/designate.py index 285bfdf..351448c 100644 --- a/src/lib/charm/openstack/designate.py +++ b/src/lib/charm/openstack/designate.py @@ -87,11 +87,13 @@ class BindRNDCRelationAdapter(openstack_adapters.OpenStackRelationAdapter): """ pconfig = [] for slave in self.slave_ips: - unit_name = slave['unit'].replace('/', '_').replace('-', '_') + application_name = slave['unit'].split('/')[0].replace('-', '_') pconfig.append({ - 'nameserver': 'nameserver_{}'.format(unit_name), - 'pool_target': 'nameserver_{}'.format(unit_name), + 'nameserver': 'nameserver_{}'.format(application_name), + 'pool_target': 'nameserver_{}'.format(application_name), 'address': slave['address'], + 'rndc_key_file': '/etc/designate/rndc_{}.key'.format( + application_name), }) return pconfig @@ -438,6 +440,28 @@ class DesignateCharm(ch_plugins.PolicydOverridePlugin, hookenv.log("Problem with 'dns-slaves' config: {}" .format(str(e)), level=hookenv.ERROR) + def render_relation_rndc_keys(self): + """Render the rndc keys for each application in the dns-backend + relation + + @returns None + """ + try: + applications = [] + dns_backend = relations.endpoint_from_flag( + 'dns-backend.available').conversations() + for conversation in dns_backend: + application_name = conversation.scope.split( + '/')[0].replace('-', '_') + if application_name not in applications: + applications.append(application_name) + rndckey = conversation.get_remote('rndckey') + self.write_key_file(application_name, rndckey) + + except ValueError as e: + hookenv.log("problem writing relation_rndc_keys: {}" + .format(str(e)), level=hookenv.ERROR) + def configure_sink(self): cmp_os_release = ch_utils.CompareOpenStackReleases( self.release diff --git a/src/reactive/designate_handlers.py b/src/reactive/designate_handlers.py index 35a1b9c..25acea3 100644 --- a/src/reactive/designate_handlers.py +++ b/src/reactive/designate_handlers.py @@ -225,6 +225,19 @@ def configure_designate_full(*args): level=hookenv.ERROR) +@reactive.when_not('is-update-status-hook') +@reactive.when('dns-backend.available') +@reactive.when('db.synched') +@reactive.when(*COMPLETE_INTERFACE_STATES) +def configure_dns_backend_rndc_keys(*args): + """Write the dns-backend relation configuration files and restart + designate-worker to apply the new config. + """ + with charm.provide_charm_instance() as instance: + instance.render_relation_rndc_keys() + host.service_restart('designate-worker') + + def _render_sink_configs(instance, interfaces_list): """Helper: use the singleton from the DesignateCharm to render sink configs diff --git a/src/templates/mitaka/pools.yaml b/src/templates/mitaka/pools.yaml index 12c37db..fec02d5 100644 --- a/src/templates/mitaka/pools.yaml +++ b/src/templates/mitaka/pools.yaml @@ -36,7 +36,7 @@ options: host: {{ slave.address }} rndc_host: {{ slave.address }} - rndc_key_file: /etc/designate/rndc.key + rndc_key_file: {{ slave.rndc_key_file }} {% endfor %} {% endif %} {% if options.pool_config %} diff --git a/src/templates/rocky/pools.yaml b/src/templates/rocky/pools.yaml index 35156cf..ef3b0a7 100644 --- a/src/templates/rocky/pools.yaml +++ b/src/templates/rocky/pools.yaml @@ -36,7 +36,7 @@ options: host: {{ slave.address }} rndc_host: {{ slave.address }} - rndc_key_file: /etc/designate/rndc.key + rndc_key_file: {{ slave.rndc_key_file }} port: 53 {% endfor %} {% endif %} diff --git a/unit_tests/test_designate_handlers.py b/unit_tests/test_designate_handlers.py index b6ab0c7..e7751e9 100644 --- a/unit_tests/test_designate_handlers.py +++ b/unit_tests/test_designate_handlers.py @@ -44,6 +44,8 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks): 'leadership.changed.pool-yaml-hash', ), 'reset_shared_db': ('shared-db.setup', ), 'configure_nrpe': ('base-config.rendered', ), + 'configure_dns_backend_rndc_keys': ( + all_interfaces + ('dns-backend.available', 'db.synched')), }, 'when_not': { 'setup_amqp_req': ('is-update-status-hook', ), @@ -65,6 +67,7 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks): 'configure_designate_full': ('is-update-status-hook', ), 'expose_rndc_address': ('is-update-status-hook', ), 'cluster_connected': ('is-update-status-hook', ), + 'configure_dns_backend_rndc_keys': ('is-update-status-hook', ), }, 'when_any': { 'set_dns_config_available': ( diff --git a/unit_tests/test_lib_charm_openstack_designate.py b/unit_tests/test_lib_charm_openstack_designate.py index 979d32a..d0544db 100644 --- a/unit_tests/test_lib_charm_openstack_designate.py +++ b/unit_tests/test_lib_charm_openstack_designate.py @@ -101,15 +101,17 @@ class TestBindRNDCRelationAdapter(Helper): 'slave_ips', new=_slave_ips): a = designate.BindRNDCRelationAdapter(relation) expect = [{'address': 'addr1', - 'nameserver': 'nameserver_unit_1', - 'pool_target': 'nameserver_unit_1'}, + 'nameserver': 'nameserver_unit', + 'pool_target': 'nameserver_unit', + 'rndc_key_file': '/etc/designate/rndc_unit.key'}, {'address': 'addr2', - 'nameserver': 'nameserver_unit_2', - 'pool_target': 'nameserver_unit_2'}] + 'nameserver': 'nameserver_unit', + 'pool_target': 'nameserver_unit', + 'rndc_key_file': '/etc/designate/rndc_unit.key'}] self.assertEqual(a.pool_config, expect) self.assertEqual( a.pool_targets, - 'nameserver_unit_1, nameserver_unit_2') + 'nameserver_unit, nameserver_unit') self.assertEqual(a.slave_addresses, 'addr1:53, addr2:53') def test_rndc_info(self): @@ -269,6 +271,53 @@ class TestDesignateCharm(Helper): ] self.write_key_file.assert_has_calls(calls) + def test_rndc_keys(self): + + def fake_conversations(): + conversations = [] + Conversation = mock.Mock() + Conversation.key = 'reactive.conversations.dns-backend:65.' + 'designate-bind-t1/1' + Conversation.namespace = 'dns-backend:65' + self.patch(Conversation, 'relation_ids', + return_value='dns-backend:65') + Conversation.relation_name = 'dns-backend' + Conversation.scope = 'designate-bind-t1/1' + self.patch(Conversation, 'get_remote', return_value='rndckey1') + conversations.append(Conversation) + Conversation = mock.Mock() + Conversation.key = 'reactive.conversations.dns-backend:66.' + 'designate-bind-t0/1' + Conversation.namespace = 'dns-backend:66' + self.patch(Conversation, 'relation_ids', + return_value='dns-backend:66') + Conversation.relation_name = 'dns-backend' + Conversation.scope = 'designate-bind-t0/1' + self.patch(Conversation, 'get_remote', return_value='rndckey2') + conversations.append(Conversation) + return conversations + + mock_endpoint_from_flag = mock.MagicMock() + mock_endpoint_from_flag.conversations.side_effect = fake_conversations + + def fake_endpoint_from_flag(*args, **kwargs): + return mock_endpoint_from_flag + + relation = mock.MagicMock() + self.patch(designate.DesignateCharm, 'write_key_file') + self.patch(designate.relations, 'endpoint_from_flag', + side_effect=fake_endpoint_from_flag) + + designate.DesignateConfigurationAdapter(relation) + d = designate.DesignateCharm() + d.render_relation_rndc_keys() + calls = [ + mock.call('designate_bind_t1', 'rndckey1'), + mock.call('designate_bind_t0', 'rndckey2'), + ] + + self.write_key_file.assert_has_calls(calls) + def test_get_domain_id(self): self.patch(designate.DesignateCharm, 'ensure_api_responding') self.patch(designate.subprocess, 'check_output')