class NotifyProcess(SubProcess): process_timeout = 300 _fut_monitor = None _listener = None _ready_event = None def _close_listener(self): if self._listener: self._listener.close() self._listener = None @asyncio.coroutine def process_prepare_co(self, environ): if not self._listener: self._listener = NotifyListener('@/chaperone/' + self.service.name, onNotify=self._notify_received) yield from self._listener.run() environ['NOTIFY_SOCKET'] = self._listener.socket_name # Now, set up an event which is triggered upon ready self._ready_event = asyncio.Event() def _notify_timeout(self): service = self.service message = "notify service '{1}' did not receive ready notification after {2} second(s), {3}".format( service.type, service.name, self.process_timeout, "proceeding due to 'ignore_failures=True'" if service.ignore_failures else "terminating due to 'ignore_failures=False'") if not service.ignore_failures: self.terminate() raise ChProcessError(message) @asyncio.coroutine def reset(self, dependents=False, enable=False, restarts_ok=False): yield from super().reset(dependents, enable, restarts_ok) self._close_listener() @asyncio.coroutine def final_stop(self): yield from super().final_stop() self._close_listener() @asyncio.coroutine def process_started_co(self): if self._fut_monitor and not self._fut_monitor.cancelled(): self._fut_monitor.cancel() self._fut_monitor = None yield from self.do_startup_pause() self._fut_monitor = asyncio. async (self._monitor_service()) self.add_pending(self._fut_monitor) if self._ready_event: try: if not self.process_timeout: raise asyncio.TimeoutError() yield from asyncio.wait_for(self._ready_event.wait(), self.process_timeout) except asyncio.TimeoutError: self._ready_event = None self._notify_timeout() else: if self._ready_event: self._ready_event = None rc = self.returncode if rc is not None and not rc.normal_exit: if self.ignore_failures: warn( "{0} (ignored) failure on start-up with result '{1}'" .format(self.name, rc)) else: raise ChProcessError( "{0} failed with reported error {1}".format( self.name, rc), resultcode=rc) @asyncio.coroutine def _monitor_service(self): """ We only care about errors here. The rest is dealt with by having notifications occur. """ result = yield from self.wait() if isinstance(result, int) and result > 0: self._setready() # simulate ready self._ready_event = None self._close_listener() yield from self._abnormal_exit(result) def _notify_received(self, which, var, value): callfunc = getattr(self, "notify_" + var.upper(), None) #print("NOTIFY RECEIVED", var, value) if callfunc: callfunc(value) def _setready(self): if self._ready_event: self._ready_event.set() return True return False def notify_MAINPID(self, value): try: pid = int(value) except ValueError: self.logdebug("{0} got MAINPID={1}, but not a valid pid#", self.name, value) return self.pid = pid def notify_BUSERROR(self, value): code = ProcStatus(value) if not self._setready(): self.process_exit(code) else: self.returncode = code def notify_ERRNO(self, value): try: intval = int(value) except ValueError: self.logdebug("{0} got ERROR={1}, not a valid error code", self.name, value) return code = ProcStatus(intval << 8) if not self._setready(): self.process_exit(code) else: self.returncode = code def notify_READY(self, value): if value == "1": self._setready() def notify_STATUS(self, value): self.note = value @property def status(self): if self._ready_event: return "activating" return super().status
class SDNotifyExec: exitcode = 0 sockname = None listener = None parent = None timeout = None wait_mode = None verbose = False parent_client = None proxy_enabled = True INFO_MESSAGE = { 'READY': "READY={1}{2}", 'MAINPID': "Process PID (={1}) notification{2}", 'ERRNO': "Process ERROR (={1}) notification{2}", 'STATUS': "Status message = '{1}'{2}", 'default': "{0}={1}{2}", } def __init__(self, options): self.sockname = options['--socket'] if not self.sockname: self.sockname = "/tmp/sdnotify-proxy-{0}.sock".format(os.getpid()) self.proxy_enabled = parent_socket and not options['--noproxy'] if options['--wait-stop']: self.wait_mode = 'stop' elif options['--wait-ready']: self.wait_mode = 'ready' if options['--timeout'] and self.wait_mode: self.timeout = float(options['--timeout']) self.verbose = options['--verbose'] # Modify original environment os.environ['NOTIFY_SOCKET'] = self.sockname # Set up the environment, reparse the options, build the final command Environment.set_parse_parameters('%', '{') env = Environment() env['PID'] = str(os.getpid()) env['SOCKET_ARGS'] = options['--template'] or DEFAULT_TEMPLATE if parent_socket: env['ORIG_NOTIFY_SOCKET'] = parent_socket env = env.expanded() self.proc_args = shlex.split(env.expand(' '.join(maybe_quote(arg) for arg in [options['COMMAND']] + options['ARGS']))) self.listener = NotifyListener(self.sockname, onNotify = self.notify_received, onClose = self._parent_closed) loop.add_signal_handler(signal.SIGTERM, self._got_sig) loop.add_signal_handler(signal.SIGINT, self._got_sig) proctitle = '[sdnotify-exec]' try: from setproctitle import setproctitle setproctitle(proctitle) except ImportError: pass def info(self, msg): if self.verbose: print("info: " + msg) def _got_sig(self): self.kill_program() def kill_program(self, exitcode = None): if exitcode is not None: self.exitcode = exitcode loop.call_soon(self._really_kill) def _really_kill(self): self.listener.close() loop.stop() def _parent_closed(self, which, ex): if which == self.parent_client: self.proxy_enabled = False self.parent_client = None @asyncio.coroutine def _do_proxy_send(self, name, value): if not (parent_socket and self.proxy_enabled): return if not self.parent_client: self.parent_client = NotifyClient(parent_socket, onClose = self._parent_closed) yield from self.parent_client.run() yield from self.parent_client.send("{0}={1}".format(name, value)) def send_to_proxy(self, name, value): asyncio.ensure_future(self._do_proxy_send(name, value)) def notify_received(self, which, name, value): self.send_to_proxy(name, value) sent_info = False if self.wait_mode: if name == "READY" and value == "1": if self.wait_mode == 'ready': sent_info = True self.info("ready notification received (will exit)") self.kill_program(0) elif name == "ERRNO": sent_info = True self.info("error notification ({0}) received from {1}".format(value, self.proc_args[0])) self.kill_program(int(value)) elif name == "STOPPING" and value == "1": sent_info = True self.info("STOP notification received from {0} (will exit)".format(self.proc_args[0])) self.kill_program() if not sent_info: self.info(self.INFO_MESSAGE.get(name, self.INFO_MESSAGE['default']). format(name, value, ' (ignored but passed on)' if self.proxy_enabled else ' (ignored)')) @asyncio.coroutine def _notify_timeout(self): self.info("waiting {0} seconds for notification".format(self.timeout)) yield from asyncio.sleep(self.timeout) print("ERROR: Timeout exceeded while waiting for notification from '{0}'".format(self.proc_args[0])) self.kill_program(1) @asyncio.coroutine def _run_process(self): self.info('running: {0}'.format(self.proc_args[0])) create = asyncio.create_subprocess_exec(*self.proc_args, start_new_session=bool(self.wait_mode)) proc = yield from create if self.timeout: asyncio.ensure_future(self._notify_timeout()) exitcode = yield from proc.wait() if not self.exitcode: # may have arrived from ERRNO self.exitcode = exitcode @asyncio.coroutine def run(self): try: yield from self.listener.run() except ValueError as ex: print("Error while trying to create socket: " + str(ex)) self.kill_program() else: try: yield from self._run_process() except Exception as ex: print("Error running command: " + str(ex)) self.kill_program() # Command has executed, now determine our exit and proxy disposition if not self.wait_mode: self.info("program {0} exit({1}), terminating since --wait not specified".format(self.proc_args[0], self.exitcode)) self.kill_program()
class NotifyProcess(SubProcess): process_timeout = 300 _fut_monitor = None _listener = None _ready_event = None def _close_listener(self): if self._listener: self._listener.close() self._listener = None @asyncio.coroutine def process_prepare_co(self, environ): if not self._listener: self._listener = NotifyListener('@/chaperone/' + self.service.name, onNotify = self._notify_received) yield from self._listener.run() environ['NOTIFY_SOCKET'] = self._listener.socket_name # Now, set up an event which is triggered upon ready self._ready_event = asyncio.Event() def _notify_timeout(self): service = self.service message = "notify service '{1}' did not receive ready notification after {2} second(s), {3}".format( service.type, service.name, self.process_timeout, "proceeding due to 'ignore_failures=True'" if service.ignore_failures else "terminating due to 'ignore_failures=False'") if not service.ignore_failures: self.terminate() raise ChProcessError(message) @asyncio.coroutine def reset(self, dependents = False, enable = False, restarts_ok = False): yield from super().reset(dependents, enable, restarts_ok) self._close_listener() @asyncio.coroutine def final_stop(self): yield from super().final_stop() self._close_listener() @asyncio.coroutine def process_started_co(self): if self._fut_monitor and not self._fut_monitor.cancelled(): self._fut_monitor.cancel() self._fut_monitor = None yield from self.do_startup_pause() self._fut_monitor = asyncio.async(self._monitor_service()) self.add_pending(self._fut_monitor) if self._ready_event: try: if not self.process_timeout: raise asyncio.TimeoutError() yield from asyncio.wait_for(self._ready_event.wait(), self.process_timeout) except asyncio.TimeoutError: self._ready_event = None self._notify_timeout() else: if self._ready_event: self._ready_event = None rc = self.returncode if rc is not None and not rc.normal_exit: if self.ignore_failures: warn("{0} (ignored) failure on start-up with result '{1}'".format(self.name, rc)) else: raise ChProcessError("{0} failed with reported error {1}".format(self.name, rc), resultcode = rc) @asyncio.coroutine def _monitor_service(self): """ We only care about errors here. The rest is dealt with by having notifications occur. """ result = yield from self.wait() if isinstance(result, int) and result > 0: self._setready() # simulate ready self._ready_event = None self._close_listener() yield from self._abnormal_exit(result) def _notify_received(self, which, var, value): callfunc = getattr(self, "notify_" + var.upper(), None) #print("NOTIFY RECEIVED", var, value) if callfunc: callfunc(value) def _setready(self): if self._ready_event: self._ready_event.set() return True return False def notify_MAINPID(self, value): try: pid = int(value) except ValueError: self.logdebug("{0} got MAINPID={1}, but not a valid pid#", self.name, value) return self.pid = pid def notify_BUSERROR(self, value): code = ProcStatus(value) if not self._setready(): self.process_exit(code) else: self.returncode = code def notify_ERRNO(self, value): try: intval = int(value) except ValueError: self.logdebug("{0} got ERROR={1}, not a valid error code", self.name, value) return code = ProcStatus(intval << 8) if not self._setready(): self.process_exit(code) else: self.returncode = code def notify_READY(self, value): if value == "1": self._setready() def notify_STATUS(self, value): self.note = value @property def status(self): if self._ready_event: return "activating" return super().status
class SDNotifyExec: exitcode = 0 sockname = None listener = None parent = None timeout = None wait_mode = None verbose = False parent_client = None proxy_enabled = True INFO_MESSAGE = { 'READY': "READY={1}{2}", 'MAINPID': "Process PID (={1}) notification{2}", 'ERRNO': "Process ERROR (={1}) notification{2}", 'STATUS': "Status message = '{1}'{2}", 'default': "{0}={1}{2}", } def __init__(self, options): self.sockname = options['--socket'] if not self.sockname: self.sockname = "/tmp/sdnotify-proxy-{0}.sock".format(os.getpid()) self.proxy_enabled = parent_socket and not options['--noproxy'] if options['--wait-stop']: self.wait_mode = 'stop' elif options['--wait-ready']: self.wait_mode = 'ready' if options['--timeout'] and self.wait_mode: self.timeout = float(options['--timeout']) self.verbose = options['--verbose'] # Modify original environment os.environ['NOTIFY_SOCKET'] = self.sockname # Set up the environment, reparse the options, build the final command Environment.set_parse_parameters('%', '{') env = Environment() env['PID'] = str(os.getpid()) env['SOCKET_ARGS'] = options['--template'] or DEFAULT_TEMPLATE if parent_socket: env['ORIG_NOTIFY_SOCKET'] = parent_socket env = env.expanded() self.proc_args = shlex.split(env.expand(' '.join(maybe_quote(arg) for arg in [options['COMMAND']] + options['ARGS']))) self.listener = NotifyListener(self.sockname, onNotify = self.notify_received, onClose = self._parent_closed) loop.add_signal_handler(signal.SIGTERM, self._got_sig) loop.add_signal_handler(signal.SIGINT, self._got_sig) proctitle = '[sdnotify-exec]' try: from setproctitle import setproctitle setproctitle(proctitle) except ImportError: pass def info(self, msg): if self.verbose: print("info: " + msg) def _got_sig(self): self.kill_program() def kill_program(self, exitcode = None): if exitcode is not None: self.exitcode = exitcode loop.call_soon(self._really_kill) def _really_kill(self): self.listener.close() loop.stop() def _parent_closed(self, which, ex): if which == self.parent_client: self.proxy_enabled = False self.parent_client = None @asyncio.coroutine def _do_proxy_send(self, name, value): if not (parent_socket and self.proxy_enabled): return if not self.parent_client: self.parent_client = NotifyClient(parent_socket, onClose = self._parent_closed) yield from self.parent_client.run() yield from self.parent_client.send("{0}={1}".format(name, value)) def send_to_proxy(self, name, value): asyncio.async(self._do_proxy_send(name, value)) def notify_received(self, which, name, value): self.send_to_proxy(name, value) sent_info = False if self.wait_mode: if name == "READY" and value == "1": if self.wait_mode == 'ready': sent_info = True self.info("ready notification received (will exit)") self.kill_program(0) elif name == "ERRNO": sent_info = True self.info("error notification ({0}) received from {1}".format(value, self.proc_args[0])) self.kill_program(int(value)) elif name == "STOPPING" and value == "1": sent_info = True self.info("STOP notification received from {0} (will exit)".format(self.proc_args[0])) self.kill_program() if not sent_info: self.info(self.INFO_MESSAGE.get(name, self.INFO_MESSAGE['default']). format(name, value, ' (ignored but passed on)' if self.proxy_enabled else ' (ignored)')) @asyncio.coroutine def _notify_timeout(self): self.info("waiting {0} seconds for notification".format(self.timeout)) yield from asyncio.sleep(self.timeout) print("ERROR: Timeout exceeded while waiting for notification from '{0}'".format(self.proc_args[0])) self.kill_program(1) @asyncio.coroutine def _run_process(self): self.info('running: {0}'.format(self.proc_args[0])) create = asyncio.create_subprocess_exec(*self.proc_args, start_new_session=bool(self.wait_mode)) proc = yield from create if self.timeout: asyncio.async(self._notify_timeout()) exitcode = yield from proc.wait() if not self.exitcode: # may have arrived from ERRNO self.exitcode = exitcode @asyncio.coroutine def run(self): try: yield from self.listener.run() except ValueError as ex: print("Error while trying to create socket: " + str(ex)) self.kill_program() else: try: yield from self._run_process() except Exception as ex: print("Error running command: " + str(ex)) self.kill_program() # Command has executed, now determine our exit and proxy disposition if not self.wait_mode: self.info("program {0} exit({1}), terminating since --wait not specified".format(self.proc_args[0], self.exitcode)) self.kill_program()