ceph/qa/tasks/dnsmasq.py

171 lines
5.9 KiB
Python

"""
Task for dnsmasq configuration
"""
import contextlib
import logging
from teuthology import misc
from teuthology.exceptions import ConfigError
from teuthology import contextutil
from teuthology import packaging
from tasks.util import get_remote_for_role
log = logging.getLogger(__name__)
@contextlib.contextmanager
def install_dnsmasq(remote):
"""
If dnsmasq is not installed, install it for the duration of the task.
"""
try:
existing = packaging.get_package_version(remote, 'dnsmasq')
except:
existing = None
if existing is None:
packaging.install_package('dnsmasq', remote)
try:
yield
finally:
if existing is None:
packaging.remove_package('dnsmasq', remote)
@contextlib.contextmanager
def backup_resolv(remote, path):
"""
Store a backup of resolv.conf in the testdir and restore it after the task.
"""
remote.run(args=['cp', '/etc/resolv.conf', path])
try:
yield
finally:
# restore with 'cp' to avoid overwriting its security context
remote.run(args=['sudo', 'cp', path, '/etc/resolv.conf'])
remote.run(args=['rm', path])
@contextlib.contextmanager
def replace_resolv(remote, path):
"""
Update resolv.conf to point the nameserver at localhost.
"""
misc.write_file(remote, path, "nameserver 127.0.0.1\n")
try:
# install it
if remote.os.package_type == "rpm":
# for centos ovh resolv.conf has immutable attribute set
remote.run(args=['sudo', 'chattr', '-i', '/etc/resolv.conf'], check_status=False)
remote.run(args=['sudo', 'cp', path, '/etc/resolv.conf'])
yield
finally:
remote.run(args=['rm', path])
@contextlib.contextmanager
def setup_dnsmasq(remote, testdir, cnames):
""" configure dnsmasq on the given remote, adding each cname given """
log.info('Configuring dnsmasq on remote %s..', remote.name)
# add address entries for each cname
dnsmasq = "server=8.8.8.8\nserver=8.8.4.4\n"
address_template = "address=/{cname}/{ip_address}\n"
for cname, ip_address in cnames.items():
dnsmasq += address_template.format(cname=cname, ip_address=ip_address)
# write to temporary dnsmasq file
dnsmasq_tmp = '/'.join((testdir, 'ceph.tmp'))
misc.write_file(remote, dnsmasq_tmp, dnsmasq)
# move into /etc/dnsmasq.d/
dnsmasq_path = '/etc/dnsmasq.d/ceph'
remote.run(args=['sudo', 'mv', dnsmasq_tmp, dnsmasq_path])
# restore selinux context if necessary
remote.run(args=['sudo', 'restorecon', dnsmasq_path], check_status=False)
# restart dnsmasq
remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq'])
# verify dns name is set
remote.run(args=['ping', '-c', '4', next(iter(cnames.keys()))])
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'])
@contextlib.contextmanager
def task(ctx, config):
"""
Configures dnsmasq to add cnames for teuthology remotes. The task expects a
dictionary, where each key is a role. If all cnames for that role use the
same address as that role, the cnames can be given as a list. For example,
this entry configures dnsmasq on the remote associated with client.0, adding
two cnames for the ip address associated with client.0:
- dnsmasq:
client.0:
- client0.example.com
- c0.example.com
If the addresses do not all match the given role, a dictionary can be given
to specify the ip address by its target role. For example:
- dnsmasq:
client.0:
client.0.example.com: client.0
client.1.example.com: client.1
Cnames that end with a . are treated as prefix for the existing hostname.
For example, if the remote for client.0 has a hostname of 'example.com',
this task will add cnames for dev.example.com and test.example.com:
- dnsmasq:
client.0: [dev., test.]
"""
# apply overrides
overrides = config.get('overrides', {})
misc.deep_merge(config, overrides.get('dnsmasq', {}))
# multiple roles may map to the same remote, so collect names by remote
remote_names = {}
for role, cnames in config.items():
remote = get_remote_for_role(ctx, role)
if remote is None:
raise ConfigError('no remote for role %s' % role)
names = remote_names.get(remote, {})
if isinstance(cnames, list):
# when given a list of cnames, point to local ip
for cname in cnames:
if cname.endswith('.'):
cname += remote.hostname
names[cname] = remote.ip_address
elif isinstance(cnames, dict):
# when given a dict, look up the remote ip for each
for cname, client in cnames.items():
r = get_remote_for_role(ctx, client)
if r is None:
raise ConfigError('no remote for role %s' % client)
if cname.endswith('.'):
cname += r.hostname
names[cname] = r.ip_address
remote_names[remote] = names
testdir = misc.get_testdir(ctx)
resolv_bak = '/'.join((testdir, 'resolv.bak'))
resolv_tmp = '/'.join((testdir, 'resolv.tmp'))
# 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) ])
subtasks.extend([ lambda r=remote: replace_resolv(r, resolv_tmp) ])
subtasks.extend([ lambda r=remote, cn=cnames: setup_dnsmasq(r, testdir, cn) ])
with contextutil.nested(*subtasks):
yield