From: Casey Bodley Date: Tue, 9 Jun 2026 18:04:53 +0000 (-0400) Subject: qa/dnsmasq: use managed dnsmasq instead of editing resolv.conf X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=617f55391339c1c94259de63a6e7886fcb11ca31;p=ceph.git qa/dnsmasq: use managed dnsmasq instead of editing resolv.conf this task was manually editing /etc/resolv.conf to configure dns names. on ubuntu 24, these changes were getting clobbered by NetworkManager or systemd service restarts instead, manage dnsmasq depending on host distro. rpm-based distros use NetworkManager's dnsmasq plugin, while deb-based distros use systemd-resolved to point at the local dnsmasq Fixes: https://tracker.ceph.com/issues/76961 Signed-off-by: Casey Bodley --- diff --git a/qa/tasks/dnsmasq.py b/qa/tasks/dnsmasq.py index ea6b0bbf5ac..13e44bbfe1f 100644 --- a/qa/tasks/dnsmasq.py +++ b/qa/tasks/dnsmasq.py @@ -66,47 +66,75 @@ def replace_resolv(remote, path, table): remote.run(args=['rm', path]) @contextlib.contextmanager -def setup_dnsmasq(remote, testdir, cnames, table): - """ configure dnsmasq on the given remote, adding each cname given """ - log.info('Configuring dnsmasq on remote %s..', remote.name) - # banner first - dnsmasq = ["# This file is generated by dnsmasq teuthology task", ""] - - # lookup previously used nameservers for the remote in the table - servers = table.get(remote.name, {}).get('nameserver', None) - if servers: - log.info('Reusing nameservers: %s', servers) - dnsmasq.extend(f"server={a}" for a in servers) - else: - log.info('No predefined nameservers found, using 8.8.x.x') - dnsmasq.extend(["server=8.8.8.8", "server=8.8.4.4"]) - +def configure_dnsmasq(remote, path, cnames): + """ + configure dnsmasq on the given remote, adding each cname given + """ + data = """# This file is generated by dnsmasq teuthology task +""" for cname, ip_address in cnames.items(): - dnsmasq.append(f"address=/{cname}/{ip_address}") + data += f"address=/.{cname}/{ip_address}\n" - dnsmasq.append('') - dnsmasq_data = "\n".join(dnsmasq) - dnsmasq_path = '/etc/dnsmasq.d/ceph' - remote.sudo_write_file(dnsmasq_path, dnsmasq_data) - # restore selinux context if necessary - remote.run(args=['sudo', 'restorecon', dnsmasq_path], check_status=False) + remote.sudo_write_file(path, data) + remote.run(args=['sudo', 'restorecon', path], check_status=False) + remote.sh(f'cat {path}') + try: + yield + finally: + remote.run(args=['sudo', 'rm', path]) - # restart dnsmasq - remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq']) - # some pause is required for dnsmasq to become operational, - # though, trying to just print out the config now instead - remote.sh(f'cat {dnsmasq_path}') - # verify dns name is set - remote.run(args=['ping', '-c', '4', next(iter(cnames.keys()))]) +@contextlib.contextmanager +def configure_network_manager(remote): + """ + enable NetworkManager's dnsmasq plugin + """ + path = '/etc/NetworkManager/conf.d/dnsmasq.conf' + data = """# This file is generated by dnsmasq teuthology task +[main] +dns=dnsmasq +""" + remote.sudo_write_file(path, data) + remote.run(args=['sudo', 'restorecon', path], check_status=False) + remote.run(args=['sudo', 'systemctl', 'restart', 'NetworkManager']) + remote.sh(f'cat {path}') + try: + yield + finally: + remote.run(args=['sudo', 'rm', path]) + remote.run(args=['sudo', 'systemctl', 'restart', 'NetworkManager']) +@contextlib.contextmanager +def configure_systemd_resolved(remote, cnames): + """ + configure systemd-resolved to search for listed cnames through dnsmasq at 127.0.0.1 + """ + dir = '/etc/systemd/resolved.conf.d' + remote.sh(f'sudo mkdir {dir}') + + path = dir + '/dnsmasq.conf' + domains = ' '.join([f'~{cname}' for cname in cnames.keys()]) + data = f"""# This file is generated by dnsmasq teuthology task +[Resolve] +DNS=127.0.0.1 +Domains={domains} +""" + remote.sudo_write_file(path, data) + remote.run(args=['sudo', 'restorecon', path], check_status=False) + remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq']) + remote.run(args=['sudo', 'systemctl', 'restart', 'systemd-resolved']) + remote.sh(f'cat {path}') try: yield finally: - log.info('Removing dnsmasq configuration from remote %s..', remote.name) - # remove /etc/dnsmasq.d/ceph - remote.run(args=['sudo', 'rm', dnsmasq_path]) - # restart dnsmasq - remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq']) + remote.run(args=['sudo', 'rm', path]) + remote.run(args=['sudo', 'systemctl', 'restart', 'systemd-resolved']) + +@contextlib.contextmanager +def test_dns_names(remote, cnames): + """ verify that ping can resolve the configured names """ + for cname in cnames.keys(): + remote.run(args=['ping', '-c', '4', cname]) + yield @contextlib.contextmanager def task(ctx, config): @@ -168,18 +196,19 @@ def task(ctx, config): remote_names[remote] = names - testdir = misc.get_testdir(ctx) - resolv_bak = '/'.join((testdir, 'resolv.bak')) - resolv_tmp = '/'.join((testdir, 'resolv.tmp')) - resolv_tbl = dict() # { remote.name -> nameserver list } lookup table - # run subtasks for each unique remote subtasks = [] for remote, cnames in remote_names.items(): subtasks.extend([ lambda r=remote: install_dnsmasq(r) ]) - subtasks.extend([ lambda r=remote: backup_resolv(r, resolv_bak, resolv_tbl) ]) - subtasks.extend([ lambda r=remote: replace_resolv(r, resolv_tmp, resolv_tbl) ]) - subtasks.extend([ lambda r=remote, cn=cnames: setup_dnsmasq(r, testdir, cn, resolv_tbl) ]) + if remote.os.package_type == 'rpm': + path = '/etc/NetworkManager/dnsmasq.d/ceph.conf' + subtasks.extend([ lambda r=remote, p=path, cn=cnames: configure_dnsmasq(r, p, cn) ]) + subtasks.extend([ lambda r=remote: configure_network_manager(r) ]) + else: + path = '/etc/dnsmasq.d/ceph.conf' + subtasks.extend([ lambda r=remote, p=path, cn=cnames: configure_dnsmasq(r, p, cn) ]) + subtasks.extend([ lambda r=remote, cn=cnames: configure_systemd_resolved(r, cn) ]) + subtasks.extend([ lambda r=remote, cn=cnames: test_dns_names(r, cn) ]) with contextutil.nested(*subtasks): yield