def connect(self, socket = None): """ Connects to the notify socket. However, if we can't, it's not considered an error. We just return False. """ self.close() if socket is None: if "NOTIFY_SOCKET" not in os.environ: return False socket = os.environ["NOTIFY_SOCKET"] self._client = NotifyClient(socket, onClose = lambda which,exc: self.close(), onError = lambda which,exc: debug("{0} error, notifications disabled".format(socket))) try: yield from self._client.run() except OSError as ex: debug("could not connect to notify socket '{0} ({1})".format(socket, ex)) self.close() return False return True
def configFromCommandSpec(cls, spec, user = None, default = None, extra_settings = None, disable_console_log = False): """ A command specification (typically specified with the --config=<file_or_dir> command line option) is used to create a configuration object. The target may be either a file or a directory. If it is a file, then the file itself will be the only configuration read. If it is a directory, then a search is made for any top-level files which end in .conf or .yaml, and those will be combined according to lexicographic order. If the configuration path is a relative path, then it is relative to either the root directory, or the home directory of the given user. This allows a user-specific configuration to automatically take effect if desired. """ frombase = '/' if user: frombase = lookup_user(user).pw_dir trypath = os.path.join(frombase, spec) debug("TRY CONFIG PATH: {0}".format(trypath)) if not os.path.exists(trypath): return cls(default = default) else: os.environ[ENV_CONFIG_DIR] = os.path.dirname(trypath) if os.path.isdir(trypath): return cls(*[os.path.join(trypath, f) for f in sorted(os.listdir(trypath)) if f.endswith('.yaml') or f.endswith('.conf')], default = default, uid = user, extra_settings = extra_settings, disable_console_log = disable_console_log) return cls(trypath, default = default, uid = user, extra_settings = extra_settings, disable_console_log = disable_console_log)
def _no_processes(self, ignore_service_state = False): if not (ignore_service_state or self._services_started): return # do not react during system initialization self._all_killed = True if not self._killing_system: if not self.detect_exit: return if self._family: ss = self._family.get_scheduled_services() if ss: warn("system will remain active since there are scheduled services: " + ", ".join(s.name for s in ss)) return # Passed all checks, now kill system self.notify.stopping() debug("Final termination phase.") self._services_started = False if self._kill_future and not self._kill_future.cancelled(): self._kill_future.cancel() self.activate(self._final_system_stop())
def send(self, name, val): if name not in self._lev: return self._sent.add(name) if self._client: debug("queueing '{0}={1}' to notify socket '{2}'".format(name, val, self._client.socket_name)) asyncio.async(self._do_send("{0}={1}".format(name, val)))
def _do_waitpid_all(self): # Because of signal coalescing, we must keep calling waitpid() as # long as we're able to reap a child. while True: try: pid, status = os.waitpid(-1, os.WNOHANG) debug("REAP pid={0},status={1}".format(pid,status)) except ChildProcessError: # No more child processes exist. if self._had_children: debug("no child processes present") self.events.onNoProcesses() return else: self._had_children = True if pid == 0: # A child process is still alive. return returncode = ProcStatus(status) with self._lock: try: callback, args = self._callbacks.pop(pid) except KeyError: # unknown child if self._forks: # It may not be registered yet. self._zombies[pid] = returncode continue callback = None if callback is None: info( "Caught subprocess termination from unknown pid: " "%d -> %d", pid, returncode) else: callback(pid, returncode, *args)
def __init__(self, *args, default = None, uid = None, extra_settings = None, disable_console_log = False): """ Given one or more files, load our configuration. If no configuration is provided, then use the configuration specified by the default. """ debug("CONFIG INPUT (uid={1}): '{0}'".format(args, uid)) self.uid = uid self._conf = lazydict() for fn in args: if os.path.exists(fn): self._merge(yaml.load(open(fn, 'r').read().expandtabs())) if not self._conf and default: self._conf = lazydict(yaml.load(default)) validator(self._conf) if extra_settings: self.update_settings(extra_settings) s = self.get_settings() self.uid = s.get('uid', self.uid) self.gid = s.get('gid', self.gid) # Special case used by --no-console-log. It really was just easiest to do it this way # rather than try to build some special notion of "console logging" into the log services # backends. if disable_console_log: for k,v in self._conf.items(): if k.endswith('.logging'): if 'stdout' in v: del v['stdout'] if 'stderr' in v: del v['stderr']
def _kill_system_co(self): self.notify.stopping() self._cancel_pending() # Tell the family it's been nice. It's unlikely we won't have a process family, but # it's optional, so we should handle the situation. wait_done = False # indicates if shutdown_timeout has expired if self._family: for f in self._family.values(): yield from f.final_stop() # let normal shutdown happen if self._watcher.number_of_waiters > 0 and self._shutdown_timeout: debug( "still have {0} waiting, sleeping for shutdown_timeout={1}" .format(self._watcher.number_of_waiters, self._shutdown_timeout)) yield from asyncio.sleep(self._shutdown_timeout) wait_done = True try: os.kill(-1, signal.SIGTERM) # first try a sig term if self.send_sighup: os.kill(-1, signal.SIGHUP) except ProcessLookupError: debug( "No processes remain when attempting to kill system, just stop." ) self._no_processes(True) return if wait_done: # give a short wait just so the signals fire yield from asyncio.sleep(1) # these processes are unknowns else: yield from asyncio.sleep(self._shutdown_timeout) if self._all_killed: return info("Some processes remain after {0}secs. Forcing kill".format( self._shutdown_timeout)) try: os.kill(-1, signal.SIGKILL) except ProcessLookupError: debug("No processes when attempting to force quit") self._no_processes(True) return
def _kill_system_co(self): self.notify.stopping() self._cancel_pending() # Tell the family it's been nice. It's unlikely we won't have a process family, but # it's optional, so we should handle the situation. wait_done = False # indicates if shutdown_timeout has expired if self._family: for f in self._family.values(): yield from f.final_stop() # let normal shutdown happen if self._watcher.number_of_waiters > 0 and self._shutdown_timeout: debug("still have {0} waiting, sleeping for shutdown_timeout={1}".format(self._watcher.number_of_waiters, self._shutdown_timeout)) yield from asyncio.sleep(self._shutdown_timeout) wait_done = True try: os.kill(-1, signal.SIGTERM) # first try a sig term if self.send_sighup: os.kill(-1, signal.SIGHUP) except ProcessLookupError: debug("No processes remain when attempting to kill system, just stop.") self._no_processes(True) return if wait_done: # give a short wait just so the signals fire yield from asyncio.sleep(1) # these processes are unknowns else: yield from asyncio.sleep(self._shutdown_timeout) if self._all_killed: return info("Some processes remain after {0}secs. Forcing kill".format(self._shutdown_timeout)) try: os.kill(-1, signal.SIGKILL) except ProcessLookupError: debug("No processes when attempting to force quit") self._no_processes(True) return
def dump(self): debug('FULL CONFIGURATION: {0}'.format(self._conf))
def logdebug(self, *args, **kwargs): debug(*args, facility=self.syslog_facility, **kwargs)