Add a new function to wait for arnied to be ready
authorSamir Aguiar <samir.aguiar@intra2net.com>
Thu, 12 Aug 2021 12:54:14 +0000 (09:54 -0300)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Fri, 10 Sep 2021 11:57:58 +0000 (13:57 +0200)
Usually code depending on arnied checks whether its process is running
before proceeding, which can lead to a race condition when it is not
yet fully started. The new function makes use of a novel CLI option
in the arnied helper that blocks execution until arnied is indeed
ready to be used.

src/arnied_wrapper.py
test/test_arnied_wrapper.py

index 73b242a..991e2a4 100644 (file)
@@ -62,6 +62,8 @@ from . import sysmisc
 
 #: default set_cnf binary
 BIN_SET_CNF = "/usr/intranator/bin/set_cnf"
+#: default arnied_helper binary
+BIN_ARNIED_HELPER = "/usr/intranator/bin/arnied_helper"
 #: default location for template configuration files
 SRC_CONFIG_DIR = "."
 #: default location for dumped configuration files
@@ -102,7 +104,6 @@ def run_cmd(cmd="", ignore_errors=False, vm=None, timeout=60):
 def verify_running(process='arnied', timeout=60, vm=None):
     """
     Verify if a given process is running via 'pgrep'.
-    Normally this is used to check if arnied is running.
 
     :param str process: process to verify if running
     :param int timeout: run verification timeout
@@ -141,7 +142,7 @@ def accept_licence(vm=None):
     cmd = 'echo "LICENSE_ACCEPTED,0: \\"1\\"" | set_cnf'
     result = run_cmd(cmd=cmd, ignore_errors=True, vm=vm)
     log.debug(result)
-    cmd = "/usr/intranator/bin/arnied_helper --wait-for-program-end GENERATE"
+    cmd = f"{BIN_ARNIED_HELPER} --wait-for-program-end GENERATE"
     run_cmd(cmd=cmd, vm=vm)
 
 
@@ -287,7 +288,7 @@ def email_transfer(vm=None):
     :param vm: vm to run on if running on a guest instead of the host
     :type vm: VM object or None
     """
-    cmd = "/usr/intranator/bin/arnied_helper --transfer-mail"
+    cmd = f"{BIN_ARNIED_HELPER} --transfer-mail"
     result = run_cmd(cmd=cmd, vm=vm)
     log.debug(result)
 
@@ -370,7 +371,7 @@ def wait_for_run(program, timeout=300, retries=10, vm=None):
     log.info("Waiting for program %s to finish with timeout %i",
              program, timeout)
     for i in range(retries):
-        cmd = "/usr/intranator/bin/arnied_helper --is-scheduled-or-running " \
+        cmd = f"{BIN_ARNIED_HELPER} --is-scheduled-or-running " \
             + program.upper()
         check_scheduled = run_cmd(cmd=cmd, ignore_errors=True, vm=vm)
         if check_scheduled.returncode == 0:
@@ -379,8 +380,23 @@ def wait_for_run(program, timeout=300, retries=10, vm=None):
     else:
         log.warning("The program %s was not scheduled and is not running", program)
         return
-    cmd = "/usr/intranator/bin/arnied_helper --wait-for-program-end " \
-        + program.upper() + " --wait-for-program-timeout " + str(timeout)
+    cmd = f"{BIN_ARNIED_HELPER} --wait-for-program-end " \
+          f"{program.upper()} --wait-for-program-timeout {timeout}"
+    # add one second to make sure arnied_helper is finished when we expire
+    result = run_cmd(cmd=cmd, vm=vm, timeout=timeout+1)
+    log.debug(result.stdout)
+
+
+def wait_for_arnied(timeout=60, vm=None):
+    """
+    Wait for arnied socket to be ready.
+
+    :param int timeout: maximum number of seconds to wait
+    :param vm: vm to run on if running on a guest instead of the host
+    :type vm: VM object or None
+    """
+    cmd = f"{BIN_ARNIED_HELPER} --wait-for-arnied-socket " \
+          f"--wait-for-arnied-socket-timeout {timeout}"
     # add one second to make sure arnied_helper is finished when we expire
     result = run_cmd(cmd=cmd, vm=vm, timeout=timeout+1)
     log.debug(result.stdout)
@@ -404,7 +420,7 @@ def get_cnf(cnf_key, cnf_index=1, regex=".*", compact=False, timeout=30, vm=None
 
     If `cnf_index` is set to -1, retrieve and perform regex matching on all instances.
     """
-    verify_running(timeout=timeout, vm=vm)
+    wait_for_arnied(timeout=timeout, vm=vm)
     platform_str = ""
     if vm is not None:
         platform_str = " from %s" % vm.name
@@ -428,7 +444,7 @@ def get_cnf_id(cnf_key, value, timeout=30, vm=None):
     :returns: the cnf id or -1 if no such cnf variable
     :rtype: int
     """
-    verify_running(timeout=timeout, vm=vm)
+    wait_for_arnied(timeout=timeout, vm=vm)
     regex = "%s,(\d+): \"%s\"" % (cnf_key, value)
     cnf_id = get_cnf(cnf_key, cnf_index=-1, regex=regex, compact=True, vm=vm)
     if cnf_id is None:
@@ -453,7 +469,7 @@ def get_cnfvar(varname=None, instance=None, data=None, timeout=30, vm=None):
     :returns: the resulting "cnfvar" structure or None if the lookup fails or the result could not be parsed
     :rtype: cnfvar option
     """
-    verify_running(timeout=timeout, vm=vm)
+    wait_for_arnied(timeout=timeout, vm=vm)
     # firstly, build argv for get_cnf
     cmd = ["get_cnf", "-j"]
     if varname is not None:
@@ -502,7 +518,7 @@ def get_cnfvar_id(varname, data, timeout=30, vm=None):
     :returns: the cnf id or -1 if no such cnf variable
     :rtype: int
     """
-    verify_running(timeout=timeout, vm=vm)
+    wait_for_arnied(timeout=timeout, vm=vm)
     log.info("Extracting from arnied CNF_VAR %s with data %s",
              varname, data)
     cnf = get_cnfvar(varname=varname, data=data, vm=vm)
@@ -537,7 +553,7 @@ def unset_cnf(varname="", instance="", timeout=30, vm=None):
     :param vm: vm to run on if running on a guest instead of the host
     :type vm: VM object or None
     """
-    verify_running(timeout=timeout, vm=vm)
+    wait_for_arnied(timeout=timeout, vm=vm)
 
     cmd = "get_cnf %s %s | set_cnf -x" % (varname, instance)
     run_cmd(cmd=cmd, vm=vm)
@@ -563,7 +579,7 @@ def set_cnf(config_files, kind="cnf", timeout=30, vm=None):
     the config files will be copied there as temporary files before applying.
     """
     log.info("Setting arnied configuration")
-    verify_running(timeout=timeout, vm=vm)
+    wait_for_arnied(timeout=timeout, vm=vm)
 
     config_paths = prep_config_paths(config_files)
     for config_path in config_paths:
@@ -692,7 +708,7 @@ def set_cnf_pipe(cnf, timeout=30, block=False):
     This is obviously not generic but supposed to be run on the guest.
     """
     log.info("Setting arnied configuration through local pipe")
-    verify_running(timeout=timeout)
+    wait_for_arnied(timeout=timeout)
 
     st, out, exit = sysmisc.run_cmd_with_pipe([BIN_SET_CNF, "-j"], inp=str(cnf))
 
index ea41e82..57ab125 100755 (executable)
@@ -50,6 +50,9 @@ class DummyCmdOutputMapping(object):
         {"cmd": 'rm -f /var/intranator/schedule/UPDATE_VIRSCAN_NODIAL*', "stdout": b"", "returncode": 0},
 
         {"cmd": '/usr/intranator/bin/arnied_helper --transfer-mail', "stdout": b"", "returncode": 0},
+
+        {"cmd": '/usr/intranator/bin/arnied_helper --wait-for-arnied-socket --wait-for-arnied-socket-timeout 10', "stdout": b"", "returncode": 0},
+        {"cmd": '/usr/intranator/bin/arnied_helper --wait-for-arnied-socket --wait-for-arnied-socket-timeout 30', "stdout": b"", "returncode": 0},
     ]
     asserted_cmds = []
 
@@ -88,6 +91,10 @@ class ArniedWrapperTest(unittest.TestCase):
         with self.assertRaises(RuntimeError):
             arnied_wrapper.verify_running(timeout=1)
 
+    def test_wait_for_arnied(self):
+        DummyCmdOutputMapping.asserted_cmds = self.cmd_db
+        arnied_wrapper.wait_for_arnied(timeout=10)
+
     def test_accept_license(self):
         DummyCmdOutputMapping.asserted_cmds = self.cmd_db[1:3]
         arnied_wrapper.accept_licence()
@@ -101,7 +108,7 @@ class ArniedWrapperTest(unittest.TestCase):
         arnied_wrapper.go_online(1)
 
     def test_disable_virscan(self):
-        DummyCmdOutputMapping.asserted_cmds = self.cmd_db[6:14]
+        DummyCmdOutputMapping.asserted_cmds = self.cmd_db[6:]
         arnied_wrapper.disable_virscan()
 
     def test_email_transfer(self):