1 # The software in this package is distributed under the GNU General
2 # Public License version 2 (with a special exception described below).
4 # A copy of GNU General Public License (GPL) is included in this distribution,
5 # in the file COPYING.GPL.
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.
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.
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.
19 # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
24 ------------------------------------------------------
25 Interaction with central arnied daemon.
27 All functions (except :py:func:`schedule` result in calling a binary
28 (either :py:data:`BIN_ARNIED_HELPER` or *tell-connd*).
30 For changes of configuration (*set_cnf*, *get_cnf*), refer to :py:mod:`pyi2ncommon.cnfvar`.
32 Copyright: Intra2net AG
36 ------------------------------------------------------
48 log = logging.getLogger('pyi2ncommon.arnied_wrapper')
51 #: default arnied_helper binary
52 BIN_ARNIED_HELPER = "/usr/intranator/bin/arnied_helper"
55 def run_cmd(cmd="", ignore_errors=False, vm=None, timeout=60):
57 Universal command run wrapper.
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
62 :type vm: :py:class:`virttest.qemu_vm.VM` or None
63 :param int timeout: amount of seconds to wait for the program to run
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`
67 :raises: :py:class:`OSError` if command failed and cannot be ignored
70 status, stdout = vm.session.cmd_status_output(cmd, timeout=timeout)
71 stdout = stdout.encode()
77 raise subprocess.CalledProcessError(status, cmd, stderr=stderr)
78 return subprocess.CompletedProcess(cmd, status,
79 stdout=stdout, stderr=stderr)
81 return subprocess.run(cmd, check=not ignore_errors, shell=True,
85 def verify_running(process='arnied', timeout=60, vm=None):
87 Verify if a given process is running via 'pgrep'.
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
92 :type vm: :py:class:`virttest.qemu_vm.VM` or None
93 :raises: :py:class:`RuntimeError` if process is not running
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:
108 raise RuntimeError("Process %s does not seem to be running" % process)
111 # Basic functionality
114 def go_online(provider_id, wait_online=True, timeout=60, vm=None):
116 Go online with the given provider id.
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
122 :param int timeout: Seconds to wait in :py:func:`wait_for_online`
123 :param vm: vm to run on if running on a guest instead of the host
124 :type vm: :py:class:`virttest.qemu_vm.VM` or None
126 .. seealso:: :py:func:`go_offline`, :py:func:`wait_for_online`
128 log.info("Switching to online mode with provider %d", provider_id)
130 cmd = 'tell-connd --online P%i' % provider_id
131 result = run_cmd(cmd=cmd, vm=vm)
135 wait_for_online(provider_id, timeout=timeout, vm=vm)
138 def go_offline(wait_offline=True, vm=None):
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
145 :type vm: :py:class:`virttest.qemu_vm.VM` or None
147 .. seealso:: :py:func:`go_online`, :py:func:`wait_for_offline`
149 cmd = 'tell-connd --offline'
150 result = run_cmd(cmd=cmd, vm=vm)
154 if wait_offline is True:
155 wait_for_offline(vm=vm)
157 wait_for_offline(wait_offline, vm=vm)
160 def wait_for_offline(timeout=60, vm=None):
162 Wait for arnied to signal we are offline.
164 :param int timeout: maximum timeout for waiting
165 :param vm: vm to run on if running on a guest instead of the host
166 :type vm: :py:class:`virttest.qemu_vm.VM` or None
168 _wait_for_online_status('offline', None, timeout, vm)
171 def wait_for_online(provider_id, timeout=60, vm=None):
173 Wait for arnied to signal we are online.
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
179 :type vm: :py:class:`virttest.qemu_vm.VM` or None
181 _wait_for_online_status('online', provider_id, timeout, vm)
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.
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)
196 raise ValueError('expect status "online" or "offline", not "{0}"!'
199 log.info("Waiting for arnied to be {0} within {1} seconds"
200 .format(status, timeout))
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:
209 cmd = '/usr/intranator/bin/get_var ONLINE'
210 result = run_cmd(cmd=cmd, ignore_errors=True, vm=vm)
213 if expected_output in result.stdout.decode():
214 log.info("arnied is {0}. Continuing.".format(status))
219 raise RuntimeError("We didn't manage to go {0} within {1} seconds\n"
220 .format(status, timeout))
223 def email_transfer(vm=None):
225 Transfer all the emails using the guest tool arnied_helper.
227 :param vm: vm to run on if running on a guest instead of the host
228 :type vm: :py:class:`virttest.qemu_vm.VM` or None
230 cmd = f"{BIN_ARNIED_HELPER} --transfer-mail"
231 result = run_cmd(cmd=cmd, vm=vm)
235 def wait_for_email_transfer(timeout=300, vm=None):
237 Wait until the mail queue is empty and all emails are sent.
239 :param int timeout: email transfer timeout
240 :param vm: vm to run on if running on a guest instead of the host
241 :type vm: :py:class:`virttest.qemu_vm.VM` or None
243 for i in range(timeout):
245 # Retrigger mail queue in case something is deferred
246 # by an amavisd-new reconfiguration
247 run_cmd(cmd='postqueue -f', vm=vm)
248 log.info('Waiting for SMTP queue to get empty (%i/%i s)',
250 if not run_cmd(cmd='postqueue -j', vm=vm).stdout:
251 log.debug('SMTP queue is empty')
254 log.warning('Timeout reached but SMTP queue still not empty after {} s'
258 def schedule(program, exec_time=0, optional_args="", vm=None):
260 Schedule a program to be executed at a given unix time stamp.
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
266 :type vm: :py:class:`virttest.qemu_vm.VM` or None
268 log.info("Scheduling %s to be executed at %i", program, exec_time)
269 schedule_dir = "/var/intranator/schedule"
270 # clean previous schedules of the same program
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)
276 vm.session.cmd("rm -f " + os.path.join(schedule_dir, file_name))
278 os.unlink(os.path.join(schedule_dir, file_name))
280 contents = "%i\n%s\n" % (exec_time, optional_args)
282 tmp_file = tempfile.NamedTemporaryFile(mode="w+",
283 prefix=program.upper() + "_",
285 log.debug("Created temporary file %s", tmp_file.name)
286 tmp_file.write(contents)
288 moved_tmp_file = os.path.join(schedule_dir, os.path.basename(tmp_file.name))
291 vm.copy_files_to(tmp_file.name, moved_tmp_file)
292 os.remove(tmp_file.name)
294 shutil.move(tmp_file.name, moved_tmp_file)
296 log.debug("Moved temporary file to %s", moved_tmp_file)
299 def wait_for_run(program, timeout=300, retries=10, vm=None):
301 Wait for a program using the guest arnied_helper tool.
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
307 :type vm: :py:class:`virttest.qemu_vm.VM` or None
309 log.info("Waiting for program %s to finish with timeout %i",
311 for i in range(retries):
312 cmd = f"{BIN_ARNIED_HELPER} --is-scheduled-or-running " \
314 check_scheduled = run_cmd(cmd=cmd, ignore_errors=True, vm=vm)
315 if check_scheduled.returncode == 0:
319 log.warning("The program %s was not scheduled and is not running", program)
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)
328 def wait_for_arnied(timeout=60, vm=None):
330 Wait for arnied socket to be ready.
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
334 :type vm: :py:class:`virttest.qemu_vm.VM` or None
336 cmd = f"{BIN_ARNIED_HELPER} --wait-for-arnied-socket " \
337 f"--wait-for-arnied-socket-timeout {timeout}"
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)
340 log.debug(result.stdout)
343 # Configuration functionality
346 def wait_for_generate(timeout=300, vm=None):
348 Wait for the 'generate' program to complete.
350 Arguments are similar to the ones from :py:func:`wait_for_run`.
352 wait_for_run('generate', timeout=timeout, retries=1, vm=vm)
353 wait_for_run('generate_offline', timeout=timeout, retries=1, vm=vm)