]>
Commit | Line | Data |
---|---|---|
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 | remote.write_file(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 | remote.write_file(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 |