Commit | Line | Data |
---|---|---|
11cbb815 PD |
1 | # The software in this package is distributed under the GNU General |
2 | # Public License version 2 (with a special exception described below). | |
3 | # | |
4 | # A copy of GNU General Public License (GPL) is included in this distribution, | |
5 | # in the file COPYING.GPL. | |
6 | # | |
7 | # As a special exception, if other files instantiate templates or use macros | |
8 | # or inline functions from this file, or you compile this file and link it | |
9 | # with other works to produce a work based on this file, this file | |
10 | # does not by itself cause the resulting work to be covered | |
11 | # by the GNU General Public License. | |
12 | # | |
13 | # However the source code for this file must still be made available | |
14 | # in accordance with section (3) of the GNU General Public License. | |
15 | # | |
16 | # This exception does not invalidate any other reasons why a work based | |
17 | # on this file might be covered by the GNU General Public License. | |
18 | # | |
19 | # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com> | |
20 | ||
f49f6323 PD |
21 | """ |
22 | ||
23 | SUMMARY | |
24 | ------------------------------------------------------ | |
4965c436 | 25 | Interaction with central arnied daemon. |
f49f6323 | 26 | |
4965c436 PD |
27 | All functions (except :py:func:`schedule` result in calling a binary |
28 | (either :py:data:`BIN_ARNIED_HELPER` or *tell-connd*). | |
f49f6323 | 29 | |
4965c436 | 30 | For changes of configuration (*set_cnf*, *get_cnf*), refer to :py:mod:`pyi2ncommon.cnfvar`. |
f49f6323 | 31 | |
4965c436 | 32 | Copyright: Intra2net AG |
f49f6323 | 33 | |
f49f6323 PD |
34 | |
35 | INTERFACE | |
36 | ------------------------------------------------------ | |
37 | ||
38 | """ | |
39 | ||
40 | import os | |
41 | import sys | |
42 | import time | |
43 | import re | |
44 | import subprocess | |
45 | import shutil | |
46 | import tempfile | |
47 | import logging | |
3de8b4d8 | 48 | log = logging.getLogger('pyi2ncommon.arnied_wrapper') |
f49f6323 | 49 | |
30521dad | 50 | |
f1ca3964 SA |
51 | #: default arnied_helper binary |
52 | BIN_ARNIED_HELPER = "/usr/intranator/bin/arnied_helper" | |
f49f6323 PD |
53 | |
54 | ||
dfa7c024 | 55 | def run_cmd(cmd="", ignore_errors=False, vm=None, timeout=60): |
f49f6323 PD |
56 | """ |
57 | Universal command run wrapper. | |
58 | ||
59 | :param str cmd: command to run | |
60 | :param bool ignore_errors: whether not to raise error on command failure | |
61 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 62 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
dfa7c024 | 63 | :param int timeout: amount of seconds to wait for the program to run |
2dd87078 CH |
64 | :returns: command result output where output (stdout/stderr) is bytes |
65 | (encoding dependent on environment and command given) | |
66 | :rtype: :py:class:`subprocess.CompletedProcess` | |
f49f6323 PD |
67 | :raises: :py:class:`OSError` if command failed and cannot be ignored |
68 | """ | |
69 | if vm is not None: | |
dfa7c024 | 70 | status, stdout = vm.session.cmd_status_output(cmd, timeout=timeout) |
f49f6323 PD |
71 | stdout = stdout.encode() |
72 | stderr = b"" | |
73 | if status != 0: | |
74 | stderr = stdout | |
75 | stdout = b"" | |
76 | if not ignore_errors: | |
77 | raise subprocess.CalledProcessError(status, cmd, stderr=stderr) | |
2dd87078 CH |
78 | return subprocess.CompletedProcess(cmd, status, |
79 | stdout=stdout, stderr=stderr) | |
f49f6323 | 80 | else: |
2dd87078 CH |
81 | return subprocess.run(cmd, check=not ignore_errors, shell=True, |
82 | capture_output=True) | |
f49f6323 PD |
83 | |
84 | ||
85 | def verify_running(process='arnied', timeout=60, vm=None): | |
86 | """ | |
87 | Verify if a given process is running via 'pgrep'. | |
f49f6323 PD |
88 | |
89 | :param str process: process to verify if running | |
90 | :param int timeout: run verification timeout | |
91 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 92 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
93 | :raises: :py:class:`RuntimeError` if process is not running |
94 | """ | |
95 | platform_str = "" | |
96 | if vm is not None: | |
97 | vm.verify_alive() | |
98 | platform_str = " on %s" % vm.name | |
99 | for i in range(timeout): | |
100 | log.info("Checking whether %s is running%s (%i\%i)", | |
101 | process, platform_str, i, timeout) | |
102 | result = run_cmd(cmd="pgrep -l -x %s" % process, | |
103 | ignore_errors=True, vm=vm) | |
104 | if result.returncode == 0: | |
105 | log.debug(result) | |
106 | return | |
107 | time.sleep(1) | |
108 | raise RuntimeError("Process %s does not seem to be running" % process) | |
109 | ||
110 | ||
111 | # Basic functionality | |
112 | ||
113 | ||
f49f6323 PD |
114 | def go_online(provider_id, wait_online=True, timeout=60, vm=None): |
115 | """ | |
116 | Go online with the given provider id. | |
117 | ||
118 | :param provider_id: provider to go online with | |
119 | :type provider_id: int | |
120 | :param wait_online: whether to wait until online | |
121 | :type wait_online: bool | |
7628bc48 | 122 | :param int timeout: Seconds to wait in :py:func:`wait_for_online` |
f49f6323 | 123 | :param vm: vm to run on if running on a guest instead of the host |
f4c8f40e | 124 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
125 | |
126 | .. seealso:: :py:func:`go_offline`, :py:func:`wait_for_online` | |
127 | """ | |
128 | log.info("Switching to online mode with provider %d", provider_id) | |
129 | ||
f49f6323 PD |
130 | cmd = 'tell-connd --online P%i' % provider_id |
131 | result = run_cmd(cmd=cmd, vm=vm) | |
132 | log.debug(result) | |
133 | ||
134 | if wait_online: | |
135 | wait_for_online(provider_id, timeout=timeout, vm=vm) | |
136 | ||
137 | ||
138 | def go_offline(wait_offline=True, vm=None): | |
139 | """ | |
140 | Go offline. | |
141 | ||
142 | :param wait_offline: whether to wait until offline | |
143 | :type wait_offline: bool | |
144 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 145 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
146 | |
147 | .. seealso:: :py:func:`go_online`, :py:func:`wait_for_offline` | |
148 | """ | |
149 | cmd = 'tell-connd --offline' | |
150 | result = run_cmd(cmd=cmd, vm=vm) | |
151 | log.debug(result) | |
152 | ||
153 | if wait_offline: | |
154 | if wait_offline is True: | |
155 | wait_for_offline(vm=vm) | |
156 | else: | |
157 | wait_for_offline(wait_offline, vm=vm) | |
158 | ||
159 | ||
160 | def wait_for_offline(timeout=60, vm=None): | |
161 | """ | |
162 | Wait for arnied to signal we are offline. | |
163 | ||
164 | :param int timeout: maximum timeout for waiting | |
165 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 166 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
167 | """ |
168 | _wait_for_online_status('offline', None, timeout, vm) | |
169 | ||
170 | ||
171 | def wait_for_online(provider_id, timeout=60, vm=None): | |
172 | """ | |
173 | Wait for arnied to signal we are online. | |
174 | ||
175 | :param provider_id: provider to go online with | |
176 | :type provider_id: int | |
177 | :param int timeout: maximum timeout for waiting | |
178 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 179 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
180 | """ |
181 | _wait_for_online_status('online', provider_id, timeout, vm) | |
182 | ||
183 | ||
184 | def _wait_for_online_status(status, provider_id, timeout, vm): | |
185 | # Don't use tell-connd --status here since the actual | |
186 | # ONLINE signal to arnied is transmitted | |
187 | # asynchronously via arnieclient_muxer. | |
188 | ||
189 | if status == 'online': | |
190 | expected_output = 'DEFAULT: 2' | |
191 | set_status_func = lambda: go_online(provider_id, False, vm) | |
192 | elif status == 'offline': | |
193 | expected_output = 'DEFAULT: 0' | |
194 | set_status_func = lambda: go_offline(False, vm) | |
195 | else: | |
196 | raise ValueError('expect status "online" or "offline", not "{0}"!' | |
197 | .format(status)) | |
198 | ||
199 | log.info("Waiting for arnied to be {0} within {1} seconds" | |
200 | .format(status, timeout)) | |
201 | ||
202 | for i in range(timeout): | |
203 | # arnied might invalidate the connd "connection barrier" | |
204 | # after generate was running and switch to OFFLINE (race condition). | |
205 | # -> tell arnied every ten seconds to go online again | |
206 | if i % 10 == 0 and i != 0: | |
207 | set_status_func() | |
208 | ||
209 | cmd = '/usr/intranator/bin/get_var ONLINE' | |
210 | result = run_cmd(cmd=cmd, ignore_errors=True, vm=vm) | |
211 | log.debug(result) | |
212 | ||
213 | if expected_output in result.stdout.decode(): | |
214 | log.info("arnied is {0}. Continuing.".format(status)) | |
215 | return | |
216 | ||
217 | time.sleep(1) | |
218 | ||
219 | raise RuntimeError("We didn't manage to go {0} within {1} seconds\n" | |
220 | .format(status, timeout)) | |
221 | ||
222 | ||
f49f6323 PD |
223 | def email_transfer(vm=None): |
224 | """ | |
225 | Transfer all the emails using the guest tool arnied_helper. | |
226 | ||
227 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 228 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 | 229 | """ |
f1ca3964 | 230 | cmd = f"{BIN_ARNIED_HELPER} --transfer-mail" |
f49f6323 PD |
231 | result = run_cmd(cmd=cmd, vm=vm) |
232 | log.debug(result) | |
233 | ||
234 | ||
235 | def wait_for_email_transfer(timeout=300, vm=None): | |
236 | """ | |
237 | Wait until the mail queue is empty and all emails are sent. | |
238 | ||
239 | :param int timeout: email transfer timeout | |
240 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 241 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
242 | """ |
243 | for i in range(timeout): | |
244 | if i % 10 == 0: | |
245 | # Retrigger mail queue in case something is deferred | |
246 | # by an amavisd-new reconfiguration | |
247 | run_cmd(cmd='postqueue -f', vm=vm) | |
12d603bf | 248 | log.info('Waiting for SMTP queue to get empty (%i/%i s)', |
f49f6323 | 249 | i, timeout) |
68d12e8a | 250 | if not run_cmd(cmd='postqueue -j', vm=vm).stdout: |
12d603bf CH |
251 | log.debug('SMTP queue is empty') |
252 | return | |
f49f6323 | 253 | time.sleep(1) |
12d603bf CH |
254 | log.warning('Timeout reached but SMTP queue still not empty after {} s' |
255 | .format(timeout)) | |
f49f6323 PD |
256 | |
257 | ||
258 | def schedule(program, exec_time=0, optional_args="", vm=None): | |
259 | """ | |
260 | Schedule a program to be executed at a given unix time stamp. | |
261 | ||
262 | :param str program: program whose execution is scheduled | |
263 | :param int exec_time: scheduled time of program's execution | |
264 | :param str optional_args: optional command line arguments | |
265 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 266 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
267 | """ |
268 | log.info("Scheduling %s to be executed at %i", program, exec_time) | |
f49f6323 | 269 | schedule_dir = "/var/intranator/schedule" |
ef5c0c20 | 270 | # clean previous schedules of the same program |
f49f6323 PD |
271 | files = vm.session.cmd("ls " + schedule_dir).split() if vm else os.listdir(schedule_dir) |
272 | for file_name in files: | |
273 | if file_name.startswith(program.upper()): | |
274 | log.debug("Removing previous scheduled %s", file_name) | |
275 | if vm: | |
ef5c0c20 | 276 | vm.session.cmd("rm -f " + os.path.join(schedule_dir, file_name)) |
f49f6323 PD |
277 | else: |
278 | os.unlink(os.path.join(schedule_dir, file_name)) | |
ef5c0c20 SA |
279 | |
280 | contents = "%i\n%s\n" % (exec_time, optional_args) | |
281 | ||
282 | tmp_file = tempfile.NamedTemporaryFile(mode="w+", | |
283 | prefix=program.upper() + "_", | |
ef5c0c20 SA |
284 | delete=False) |
285 | log.debug("Created temporary file %s", tmp_file.name) | |
286 | tmp_file.write(contents) | |
287 | tmp_file.close() | |
288 | moved_tmp_file = os.path.join(schedule_dir, os.path.basename(tmp_file.name)) | |
289 | ||
f49f6323 | 290 | if vm: |
ef5c0c20 SA |
291 | vm.copy_files_to(tmp_file.name, moved_tmp_file) |
292 | os.remove(tmp_file.name) | |
f49f6323 PD |
293 | else: |
294 | shutil.move(tmp_file.name, moved_tmp_file) | |
ef5c0c20 | 295 | |
f49f6323 PD |
296 | log.debug("Moved temporary file to %s", moved_tmp_file) |
297 | ||
298 | ||
299 | def wait_for_run(program, timeout=300, retries=10, vm=None): | |
300 | """ | |
301 | Wait for a program using the guest arnied_helper tool. | |
302 | ||
303 | :param str program: scheduled or running program to wait for | |
304 | :param int timeout: program run timeout | |
305 | :param int retries: number of tries to verify that the program is scheduled or running | |
306 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 307 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f49f6323 PD |
308 | """ |
309 | log.info("Waiting for program %s to finish with timeout %i", | |
310 | program, timeout) | |
311 | for i in range(retries): | |
f1ca3964 | 312 | cmd = f"{BIN_ARNIED_HELPER} --is-scheduled-or-running " \ |
f49f6323 PD |
313 | + program.upper() |
314 | check_scheduled = run_cmd(cmd=cmd, ignore_errors=True, vm=vm) | |
315 | if check_scheduled.returncode == 0: | |
316 | break | |
317 | time.sleep(1) | |
80d82f13 PD |
318 | else: |
319 | log.warning("The program %s was not scheduled and is not running", program) | |
320 | return | |
f1ca3964 SA |
321 | cmd = f"{BIN_ARNIED_HELPER} --wait-for-program-end " \ |
322 | f"{program.upper()} --wait-for-program-timeout {timeout}" | |
323 | # add one second to make sure arnied_helper is finished when we expire | |
324 | result = run_cmd(cmd=cmd, vm=vm, timeout=timeout+1) | |
325 | log.debug(result.stdout) | |
326 | ||
327 | ||
328 | def wait_for_arnied(timeout=60, vm=None): | |
329 | """ | |
330 | Wait for arnied socket to be ready. | |
331 | ||
332 | :param int timeout: maximum number of seconds to wait | |
333 | :param vm: vm to run on if running on a guest instead of the host | |
f4c8f40e | 334 | :type vm: :py:class:`virttest.qemu_vm.VM` or None |
f1ca3964 SA |
335 | """ |
336 | cmd = f"{BIN_ARNIED_HELPER} --wait-for-arnied-socket " \ | |
337 | f"--wait-for-arnied-socket-timeout {timeout}" | |
3472b405 SA |
338 | # add one second to make sure arnied_helper is finished when we expire |
339 | result = run_cmd(cmd=cmd, vm=vm, timeout=timeout+1) | |
f49f6323 PD |
340 | log.debug(result.stdout) |
341 | ||
342 | ||
343 | # Configuration functionality | |
344 | ||
f49f6323 | 345 | |
80d82f13 | 346 | def wait_for_generate(timeout=300, vm=None): |
f49f6323 PD |
347 | """ |
348 | Wait for the 'generate' program to complete. | |
349 | ||
df036fbe | 350 | Arguments are similar to the ones from :py:func:`wait_for_run`. |
f49f6323 | 351 | """ |
80d82f13 PD |
352 | wait_for_run('generate', timeout=timeout, retries=1, vm=vm) |
353 | wait_for_run('generate_offline', timeout=timeout, retries=1, vm=vm) |