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