Merge branch 'cnfvar-deprecations'
[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
PD
21"""
22
23SUMMARY
24------------------------------------------------------
4965c436 25Interaction with central arnied daemon.
f49f6323 26
4965c436
PD
27All functions (except :py:func:`schedule` result in calling a binary
28(either :py:data:`BIN_ARNIED_HELPER` or *tell-connd*).
f49f6323 29
4965c436 30For changes of configuration (*set_cnf*, *get_cnf*), refer to :py:mod:`pyi2ncommon.cnfvar`.
f49f6323 31
4965c436 32Copyright: Intra2net AG
f49f6323 33
f49f6323
PD
34
35INTERFACE
36------------------------------------------------------
37
38"""
39
40import os
41import sys
42import time
43import re
44import subprocess
45import shutil
46import tempfile
47import logging
3de8b4d8 48log = logging.getLogger('pyi2ncommon.arnied_wrapper')
f49f6323 49
30521dad 50
f1ca3964
SA
51#: default arnied_helper binary
52BIN_ARNIED_HELPER = "/usr/intranator/bin/arnied_helper"
f49f6323
PD
53
54
dfa7c024 55def 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
85def 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
114def 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
138def 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
160def 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
171def 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
184def _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
223def 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
235def 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
258def 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
299def 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
328def 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 346def wait_for_generate(timeout=300, vm=None):
f49f6323
PD
347 """
348 Wait for the 'generate' program to complete.
349
80d82f13 350 Arguments are similar to the ones from :py:method:`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)