2017-05-12 16:34:18 +00:00
|
|
|
"""
|
|
|
|
Task for dnsmasq configuration
|
|
|
|
"""
|
|
|
|
import contextlib
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from teuthology import misc
|
|
|
|
from teuthology.exceptions import ConfigError
|
|
|
|
from teuthology import contextutil
|
2018-02-19 18:36:05 +00:00
|
|
|
from teuthology import packaging
|
2020-03-24 08:33:22 +00:00
|
|
|
from tasks.util import get_remote_for_role
|
2017-05-12 16:34:18 +00:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
2018-02-19 18:36:05 +00:00
|
|
|
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.
|
|
|
|
"""
|
2020-07-20 10:40:09 +00:00
|
|
|
remote.write_file(path, "nameserver 127.0.0.1\n")
|
2018-02-19 18:36:05 +00:00
|
|
|
try:
|
|
|
|
# install it
|
2018-03-22 21:21:22 +00:00
|
|
|
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)
|
2018-02-19 18:36:05 +00:00
|
|
|
remote.run(args=['sudo', 'cp', path, '/etc/resolv.conf'])
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
remote.run(args=['rm', path])
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def setup_dnsmasq(remote, testdir, cnames):
|
2017-05-12 16:34:18 +00:00
|
|
|
""" configure dnsmasq on the given remote, adding each cname given """
|
|
|
|
log.info('Configuring dnsmasq on remote %s..', remote.name)
|
|
|
|
|
2018-02-19 18:36:05 +00:00
|
|
|
# add address entries for each cname
|
2017-05-12 16:34:18 +00:00
|
|
|
dnsmasq = "server=8.8.8.8\nserver=8.8.4.4\n"
|
|
|
|
address_template = "address=/{cname}/{ip_address}\n"
|
2019-10-09 12:36:58 +00:00
|
|
|
for cname, ip_address in cnames.items():
|
2017-05-12 16:34:18 +00:00
|
|
|
dnsmasq += address_template.format(cname=cname, ip_address=ip_address)
|
|
|
|
|
2018-02-19 18:36:05 +00:00
|
|
|
# write to temporary dnsmasq file
|
|
|
|
dnsmasq_tmp = '/'.join((testdir, 'ceph.tmp'))
|
2020-07-20 10:40:09 +00:00
|
|
|
remote.write_file(dnsmasq_tmp, dnsmasq)
|
2018-02-19 18:36:05 +00:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2017-05-12 16:34:18 +00:00
|
|
|
# restart dnsmasq
|
|
|
|
remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq'])
|
|
|
|
# verify dns name is set
|
qa/tasks: use next(iter(..)) for accessing first element in a view
in python2, dict.values() and dict.keys() return lists. but in python3,
they return views, which cannot be indexed directly using an integer index.
there are three use cases when we access these views in python3:
1. get the first element
2. get all the elements and then *might* want to access them by index
3. get the first element assuming there is only a single element in
the view
4. iterate thru the view
in the 1st case, we cannot assume the number of elements, so to be
python3 compatible, we should use `next(iter(a_dict))` instead.
in the 2nd case, in this change, the view is materialized using
`list(a_dict)`.
in the 3rd case, we can just continue using the short hand of
```py
(first_element,) = a_dict.keys()
```
to unpack the view. this works in both python2 and python3.
in the 4th case, the existing code works in both python2 and python3, as
both list and view can be iterated using `iter`, and `len` works as
well.
Signed-off-by: Kefu Chai <kchai@redhat.com>
2020-03-31 02:16:40 +00:00
|
|
|
remote.run(args=['ping', '-c', '4', next(iter(cnames.keys()))])
|
2017-05-12 16:34:18 +00:00
|
|
|
|
2018-02-19 18:36:05 +00:00
|
|
|
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'])
|
2017-05-12 16:34:18 +00:00
|
|
|
|
|
|
|
@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
|
2018-02-19 18:36:05 +00:00
|
|
|
|
|
|
|
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.]
|
2017-05-12 16:34:18 +00:00
|
|
|
"""
|
|
|
|
# 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 = {}
|
2019-10-09 12:36:58 +00:00
|
|
|
for role, cnames in config.items():
|
2017-05-12 16:34:18 +00:00
|
|
|
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:
|
2018-02-19 18:36:05 +00:00
|
|
|
if cname.endswith('.'):
|
|
|
|
cname += remote.hostname
|
2017-05-12 16:34:18 +00:00
|
|
|
names[cname] = remote.ip_address
|
|
|
|
elif isinstance(cnames, dict):
|
|
|
|
# when given a dict, look up the remote ip for each
|
2019-10-09 12:36:58 +00:00
|
|
|
for cname, client in cnames.items():
|
2017-05-12 16:34:18 +00:00
|
|
|
r = get_remote_for_role(ctx, client)
|
|
|
|
if r is None:
|
|
|
|
raise ConfigError('no remote for role %s' % client)
|
2018-02-19 18:36:05 +00:00
|
|
|
if cname.endswith('.'):
|
|
|
|
cname += r.hostname
|
2017-05-12 16:34:18 +00:00
|
|
|
names[cname] = r.ip_address
|
|
|
|
|
|
|
|
remote_names[remote] = names
|
|
|
|
|
2018-02-19 18:36:05 +00:00
|
|
|
testdir = misc.get_testdir(ctx)
|
|
|
|
resolv_bak = '/'.join((testdir, 'resolv.bak'))
|
|
|
|
resolv_tmp = '/'.join((testdir, 'resolv.tmp'))
|
|
|
|
|
|
|
|
# run subtasks for each unique remote
|
2017-05-12 16:34:18 +00:00
|
|
|
subtasks = []
|
2019-10-09 12:36:58 +00:00
|
|
|
for remote, cnames in remote_names.items():
|
2018-02-19 18:36:05 +00:00
|
|
|
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) ])
|
2017-05-12 16:34:18 +00:00
|
|
|
|
|
|
|
with contextutil.nested(*subtasks):
|
|
|
|
yield
|