]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/dnsmasq.py
import 15.2.4
[ceph.git] / ceph / qa / tasks / dnsmasq.py
1 """
2 Task for dnsmasq configuration
3 """
4 import contextlib
5 import logging
6
7 from teuthology import misc
8 from teuthology.exceptions import ConfigError
9 from teuthology import contextutil
10 from teuthology import packaging
11 from tasks.util import get_remote_for_role
12
13 log = logging.getLogger(__name__)
14
15 @contextlib.contextmanager
16 def install_dnsmasq(remote):
17 """
18 If dnsmasq is not installed, install it for the duration of the task.
19 """
20 try:
21 existing = packaging.get_package_version(remote, 'dnsmasq')
22 except:
23 existing = None
24
25 if existing is None:
26 packaging.install_package('dnsmasq', remote)
27 try:
28 yield
29 finally:
30 if existing is None:
31 packaging.remove_package('dnsmasq', remote)
32
33 @contextlib.contextmanager
34 def backup_resolv(remote, path):
35 """
36 Store a backup of resolv.conf in the testdir and restore it after the task.
37 """
38 remote.run(args=['cp', '/etc/resolv.conf', path])
39 try:
40 yield
41 finally:
42 # restore with 'cp' to avoid overwriting its security context
43 remote.run(args=['sudo', 'cp', path, '/etc/resolv.conf'])
44 remote.run(args=['rm', path])
45
46 @contextlib.contextmanager
47 def replace_resolv(remote, path):
48 """
49 Update resolv.conf to point the nameserver at localhost.
50 """
51 misc.write_file(remote, path, "nameserver 127.0.0.1\n")
52 try:
53 # install it
54 if remote.os.package_type == "rpm":
55 # for centos ovh resolv.conf has immutable attribute set
56 remote.run(args=['sudo', 'chattr', '-i', '/etc/resolv.conf'], check_status=False)
57 remote.run(args=['sudo', 'cp', path, '/etc/resolv.conf'])
58 yield
59 finally:
60 remote.run(args=['rm', path])
61
62 @contextlib.contextmanager
63 def setup_dnsmasq(remote, testdir, cnames):
64 """ configure dnsmasq on the given remote, adding each cname given """
65 log.info('Configuring dnsmasq on remote %s..', remote.name)
66
67 # add address entries for each cname
68 dnsmasq = "server=8.8.8.8\nserver=8.8.4.4\n"
69 address_template = "address=/{cname}/{ip_address}\n"
70 for cname, ip_address in cnames.items():
71 dnsmasq += address_template.format(cname=cname, ip_address=ip_address)
72
73 # write to temporary dnsmasq file
74 dnsmasq_tmp = '/'.join((testdir, 'ceph.tmp'))
75 misc.write_file(remote, dnsmasq_tmp, dnsmasq)
76
77 # move into /etc/dnsmasq.d/
78 dnsmasq_path = '/etc/dnsmasq.d/ceph'
79 remote.run(args=['sudo', 'mv', dnsmasq_tmp, dnsmasq_path])
80 # restore selinux context if necessary
81 remote.run(args=['sudo', 'restorecon', dnsmasq_path], check_status=False)
82
83 # restart dnsmasq
84 remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq'])
85 # verify dns name is set
86 remote.run(args=['ping', '-c', '4', next(iter(cnames.keys()))])
87
88 try:
89 yield
90 finally:
91 log.info('Removing dnsmasq configuration from remote %s..', remote.name)
92 # remove /etc/dnsmasq.d/ceph
93 remote.run(args=['sudo', 'rm', dnsmasq_path])
94 # restart dnsmasq
95 remote.run(args=['sudo', 'systemctl', 'restart', 'dnsmasq'])
96
97 @contextlib.contextmanager
98 def task(ctx, config):
99 """
100 Configures dnsmasq to add cnames for teuthology remotes. The task expects a
101 dictionary, where each key is a role. If all cnames for that role use the
102 same address as that role, the cnames can be given as a list. For example,
103 this entry configures dnsmasq on the remote associated with client.0, adding
104 two cnames for the ip address associated with client.0:
105
106 - dnsmasq:
107 client.0:
108 - client0.example.com
109 - c0.example.com
110
111 If the addresses do not all match the given role, a dictionary can be given
112 to specify the ip address by its target role. For example:
113
114 - dnsmasq:
115 client.0:
116 client.0.example.com: client.0
117 client.1.example.com: client.1
118
119 Cnames that end with a . are treated as prefix for the existing hostname.
120 For example, if the remote for client.0 has a hostname of 'example.com',
121 this task will add cnames for dev.example.com and test.example.com:
122
123 - dnsmasq:
124 client.0: [dev., test.]
125 """
126 # apply overrides
127 overrides = config.get('overrides', {})
128 misc.deep_merge(config, overrides.get('dnsmasq', {}))
129
130 # multiple roles may map to the same remote, so collect names by remote
131 remote_names = {}
132 for role, cnames in config.items():
133 remote = get_remote_for_role(ctx, role)
134 if remote is None:
135 raise ConfigError('no remote for role %s' % role)
136
137 names = remote_names.get(remote, {})
138
139 if isinstance(cnames, list):
140 # when given a list of cnames, point to local ip
141 for cname in cnames:
142 if cname.endswith('.'):
143 cname += remote.hostname
144 names[cname] = remote.ip_address
145 elif isinstance(cnames, dict):
146 # when given a dict, look up the remote ip for each
147 for cname, client in cnames.items():
148 r = get_remote_for_role(ctx, client)
149 if r is None:
150 raise ConfigError('no remote for role %s' % client)
151 if cname.endswith('.'):
152 cname += r.hostname
153 names[cname] = r.ip_address
154
155 remote_names[remote] = names
156
157 testdir = misc.get_testdir(ctx)
158 resolv_bak = '/'.join((testdir, 'resolv.bak'))
159 resolv_tmp = '/'.join((testdir, 'resolv.tmp'))
160
161 # run subtasks for each unique remote
162 subtasks = []
163 for remote, cnames in remote_names.items():
164 subtasks.extend([ lambda r=remote: install_dnsmasq(r) ])
165 subtasks.extend([ lambda r=remote: backup_resolv(r, resolv_bak) ])
166 subtasks.extend([ lambda r=remote: replace_resolv(r, resolv_tmp) ])
167 subtasks.extend([ lambda r=remote, cn=cnames: setup_dnsmasq(r, testdir, cn) ])
168
169 with contextutil.nested(*subtasks):
170 yield