From 9903c48f3a44fb5105866643ffe9d935e13fe3d9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Oct 2017 23:46:10 +0200 Subject: [PATCH] Be able to give lock to son processes detached by systemctl (#367) * Give lock to systemctl sons * Get the 'need_lock' flag from services.yml * Don't need the lock for enable/disable and other stuff --- data/templates/yunohost/services.yml | 1 + src/yunohost/service.py | 57 ++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 514cf5258..fb8c076f9 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -47,6 +47,7 @@ yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: status: service + need_lock: true nslcd: status: service log: /var/log/syslog diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 2ea953873..5401a1fab 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -39,10 +39,10 @@ from moulinette.utils import log, filesystem from yunohost.hook import hook_callback - BASE_CONF_PATH = '/home/yunohost.conf' BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') +MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') @@ -493,7 +493,8 @@ def _run_service_command(action, service): service -- Service name """ - if service not in _get_services().keys(): + services = _get_services() + if service not in services.keys(): raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service)) cmd = None @@ -505,8 +506,23 @@ def _run_service_command(action, service): else: raise ValueError("Unknown action '%s'" % action) + need_lock = (services[service].get('need_lock') or False) \ + and action in ['start', 'stop', 'restart', 'reload'] + try: - ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + # Launch the command + logger.debug("Running '%s'" % cmd) + p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT) + # If this command needs a lock (because the service uses yunohost + # commands inside), find the PID and add a lock for it + if need_lock: + PID = _give_lock(action, service, p) + # Wait for the command to complete + p.communicate() + # Remove the lock if one was given + if need_lock and PID != 0: + _remove_lock(PID) + except subprocess.CalledProcessError as e: # TODO: Log output? logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd))) @@ -514,6 +530,41 @@ def _run_service_command(action, service): return True +def _give_lock(action, service, p): + + # Depending of the action, systemctl calls the PID differently :/ + if action == "start" or action == "restart": + systemctl_PID_name = "MainPID" + else: + systemctl_PID_name = "ControlPID" + + cmd_get_son_PID ="systemctl show %s -p %s" % (service, systemctl_PID_name) + son_PID = 0 + # As long as we did not found the PID and that the command is still running + while son_PID == 0 and p.poll() == None: + # Call systemctl to get the PID + # Output of the command is e.g. ControlPID=1234 + son_PID = subprocess.check_output(cmd_get_son_PID.split()) \ + .strip().split("=")[1] + son_PID = int(son_PID) + time.sleep(1) + + # If we found a PID + if son_PID != 0: + # Append the PID to the lock file + logger.debug("Giving a lock to PID %s for service %s !" + % (str(son_PID), service)) + filesystem.append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) + + return son_PID + +def _remove_lock(PID_to_remove): + + PIDs = filesystem.read_file(MOULINETTE_LOCK).split("\n") + PIDs_to_keep = [ PID for PID in PIDs if int(PID) != PID_to_remove ] + filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep)) + + def _get_services(): """ Get a dict of managed services with their parameters