def memusage(self, name): info = self.getProcessInfo(name) if info['pid'] == 0: raise RPCError(Faults.NOT_RUNNING) return get_rss(info['pid'])
def onwait(): # check stopper if self.stopper.in_progress(): return NOT_DONE_YET if application.state != ApplicationStates.STOPPED: raise RPCError(Faults.ABNORMAL_TERMINATION, application_name) return True
def UpdateNumprocs(self, group_name): """ graceful process_group numprocs without restart all process when only numprocs changed. if numprocs increased, the operation will start (new_num - old_num) processes, if numprocs reduced, the operation will stop the last (new_num - old_num) processes @param string group_name Name of an existing process group """ try: self.supervisord.options.process_config(do_usage=False) except ValueError as msg: raise RPCError(Faults.CANT_REREAD, msg) group = self._getProcessGroup(group_name) old_config = self.supervisord.process_groups[group_name].config new_config = [ cfg for cfg in self.supervisord.options.process_group_configs if cfg.name == group_name ][0] if old_config == new_config: return json.dumps({"msg": "No need to update", "type": "error"}) else: if old_config.name != new_config.name or old_config.priority != new_config.priority: return json.dumps({ "msg": "Not only numprocs has changed: priority is difference", "type": "error" }) new_process_configs = new_config.process_configs old_process_configs = old_config.process_configs if len(old_process_configs) < len(new_process_configs): if self._issubset(old_process_configs, new_process_configs): return self._add_num( group_name, self._difference(new_process_configs, old_process_configs)) else: return json.dumps({ "msg": "Not only numprocs has changed", "type": "error" }) elif len(old_process_configs) > len(new_process_configs): if self._issubset(new_process_configs, old_process_configs): return self._reduce_num( group_name, self._difference(old_process_configs, new_process_configs)) else: return json.dumps({ "msg": "Not only numprocs has changed", "type": "error" }) # If other not only name, priority or numprocs has changed # Return an error else: return json.dumps({ "msg": "Other settings has changed, please use update", "type": "error" }) # Return something for xmlrpc lib return True
def _update(self, text): self.update_text = text # for unit tests, mainly state = self.supervisord.get_state() if state == SupervisorStates.SHUTDOWN: raise RPCError(Faults.SHUTDOWN_STATE)
def _getGroupAndProcess(self, name): # get process to start from name group_name, process_name = split_namespec(name) group = self.supervisord.process_groups.get(group_name) if group is None: raise RPCError(Faults.BAD_NAME, name) if process_name is None: return group, None process = group.processes.get(process_name) if process is None: raise RPCError(Faults.BAD_NAME, name) return group, process
def _getProcessGroup(self, name): """ Find a process group by its name """ group = self.supervisord.process_groups.get(name) if group is None: raise RPCError(SupervisorFaults.BAD_NAME, 'Unknown group: %s' % name) return group
def stopProcessGroup(self, name, wait=True): """ Stop all processes in the process group named 'name' @param string name The group name @param boolean wait Wait for each process to be fully stopped @return boolean result Always return true unless error. """ self._update('stopProcessGroup') group = self.supervisord.process_groups.get(name) if group is None: raise RPCError(Faults.BAD_NAME, name) processes = group.processes.values() processes.sort() processes = [(group, process) for process in processes] killall = make_allfunc(processes, isRunning, self.stopProcess, wait=wait) killall.delay = 0.05 killall.rpcinterface = self return killall # deferred
def stop_application(self, application_name, wait=True): """ Stop the application named application_name. *@param* ``str application_name``: the name of the application. *@param* ``bool wait``: wait for the application to be fully stopped. *@throws* ``RPCError``: * with code ``Faults.BAD_SUPVISORS_STATE`` if **Supvisors** is not in state ``OPERATION`` or ``CONCILIATION``, * with code ``Faults.BAD_NAME`` if application_name is unknown to **Supvisors**. *@return* ``bool``: always ``True`` unless error. """ self._check_operating_conciliation() # check application is known if application_name not in self.context.applications.keys(): raise RPCError(Faults.BAD_NAME, application_name) # do NOT check application state as there may be processes RUNNING although the application is declared STOPPED application = self.context.applications[application_name] done = self.stopper.stop_application(application) self.logger.debug('stop_application {} done={}'.format(application_name, done)) # wait until application fully STOPPED if wait and not done: def onwait(): # check stopper if self.stopper.in_progress(): return NOT_DONE_YET if application.state != ApplicationStates.STOPPED: raise RPCError(Faults.ABNORMAL_TERMINATION, application_name) return True onwait.delay = 0.5 return onwait # deferred # if done is True, nothing to do return not done
def addGroup(self, group_name, priority=999): """ Adds a new process group to the supervisor configuration. Args: group_name (str): The name of the group to add. priority (int, optional): The group priority. Defaults to 999. Returns: (boolean) True, unless an error is raised. """ self._update('addGroup') # make sure the group doesn't already exist if self.supervisord.process_groups.get(group_name) is not None: raise RPCError(Faults.ALREADY_ADDED, group_name) options = self.supervisord.options group_config = ProcessGroupConfig( options, group_name, priority, process_configs=[]) self.supervisord.options.process_group_configs.append(group_config) group_config.after_setuid() self.supervisord.process_groups[group_name] = group_config.make_group() return True
def log(self, message, level=supervisor.loggers.LevelsByName.INFO): """ Writes a message to the main supervisor log. Args: message (str): The message to write. level (str, optional): The log level to assign to the message. Defaults to supervisor.loggers.LevelsByName.INFO. Raises: RPCError: INCORRECT_PARAMETERS if an invalid log level is provided Returns: boolean: True, unless an error was raised. """ self._update('log') if isinstance(level, str): level = getattr( supervisor.loggers.LevelsByName, level.upper(), None) if supervisor.loggers.LOG_LEVELS_BY_NUM.get(level, None) is None: raise RPCError(Faults.INCORRECT_PARAMETERS) self.supervisord.options.logger.log(level, message) return True
def _readProcessLog(self, name, offset, length, channel): group, process = self._getGroupAndProcess(name) if process is None: raise RPCError(Faults.BAD_NAME, name) logfile = getattr(process.config, '%s_logfile' % channel) if logfile is None or not os.path.exists(logfile): raise RPCError(Faults.NO_FILE, logfile) try: return as_string(readFile(logfile, int(offset), int(length))) except ValueError as inst: why = inst.args[0] raise RPCError(getattr(Faults, why))
def signalProcessGroup(self, name, signal): """ Send a signal to all processes in the group named 'name' @param string name The group name @param string signal Signal to send, as name ('HUP') or number ('1') @return array """ group = self.supervisord.process_groups.get(name) self._update('signalProcessGroup') if group is None: raise RPCError(Faults.BAD_NAME, name) processes = list(group.processes.values()) processes.sort() processes = [(group, process) for process in processes] sendall = make_allfunc(processes, isRunning, self.signalProcess, signal=signal) result = sendall() self._update('signalProcessGroup') return result
def _extract_params(self, proc_name, proc_section): configfile = self.supervisord.options.configfile config = ConfigParser.RawConfigParser() config.read(configfile) req_params = ('workernames', 'tasks') opt_params = ('concurrency', 'loglevel', 'logfile', 'pidfile') params_dict = {} for param in req_params: try: params_dict[param] = config.get(proc_section, param) except ConfigParser.NoOptionError: raise RPCError(SupervisorFaults.BAD_ARGUMENTS) req_command = '{workernames} -A {tasks} '.format(**params_dict) opt_command = '' for param in opt_params: param_value = None try: param_value = config.get(proc_section, param) except ConfigParser.NoOptionError: pass if param_value is not None: opt_command += '--{param}={param_value} '.format( param=param, param_value=param_value) # Replace supervisor special chars to use celery wildcards command = req_command + opt_command.replace('%%', '%') return command
def reloadConfig(self): """ Reload the configuration. The result contains three arrays containing names of process groups: * `added` gives the process groups that have been added * `changed` gives the process groups whose contents have changed * `removed` gives the process groups that are no longer in the configuration @return array result [[added, changed, removed]] """ self._update('reloadConfig') try: self.supervisord.options.process_config(do_usage=False) except ValueError as msg: raise RPCError(Faults.CANT_REREAD, msg) added, changed, removed = self.supervisord.diff_to_active() added = [group.name for group in added] changed = [group.name for group in changed] removed = [group.name for group in removed] return [[added, changed, removed]] # cannot return len > 1, apparently
def startProcessGroup(self, name, wait=True): """ Start all processes in the group named 'name' @param string name The group name @param boolean wait Wait for each process to be fully started @return struct result A structure containing start statuses """ self._update('startProcessGroup') group = self.supervisord.process_groups.get(name) if group is None: raise RPCError(Faults.BAD_NAME, name) processes = group.processes.values() processes.sort() processes = [(group, process) for process in processes] startall = make_allfunc(processes, isNotRunning, self.startProcess, wait=wait) startall.delay = 0.05 startall.rpcinterface = self return startall # deferred
def addProgramToGroup(self, group_name, program_name, program_options): """ Adds a new program to an existing process group. Args: group_name (str): The name of the group to add this program to. program_name (str): The name of the program to add to supervisor. program_options (dict): The program configuration options. Returns: (boolean): True, unless an error is raised """ self._update('addProgramToGroup') group = self._get_process_group(group_name, True) # determine the process name based on the current instances # of this program. current_program_instances = self.numprocs.get(program_name) process_num = '%d' % (current_program_instances + 1) self._apply_process_num(program_options, process_num) # make a configparser instance for the program section_name = 'program:%s' % program_name parser = self._make_config_parser(section_name, program_options) # make the process configs from the parser instance options = self.supervisord.options try: new_configs = options.processes_from_section( parser, section_name, group_name) except ValueError as e: raise RPCError(Faults.INCORRECT_PARAMETERS, e) # make sure the new program doesn't already exist in the group for new_config in new_configs: for existing_config in group.config.process_configs: if new_config.name == existing_config.name: raise RPCError(Faults.BAD_NAME, new_config.name) # add the new program configuration(s) to the group group.config.process_configs.extend(new_configs) for new_config in new_configs: new_config.create_autochildlogs() group.processes[new_config.name] = new_config.make_process(group) self.numprocs.increment(program_name) return True
def start_args(self, namespec, extra_args='', wait=True): """ Start a local process. The behaviour is different from ``supervisor.startProcess`` as it sets the process state to ``FATAL`` instead of throwing an exception to the RPC client. This RPC makes it also possible to pass extra arguments to the program command line. *@param* ``str namespec``: the process namespec. *@param* ``str extra_args``: extra arguments to be passed to the command line of the program. *@param* ``bool wait``: wait for the process to be fully started. *@throws* ``RPCError``: * with code ``Faults.BAD_NAME`` if namespec is unknown to the local Supervisor, * with code ``Faults.BAD_EXTRA_ARGUMENTS`` if program is required or has a start sequence, * with code ``Faults.ALREADY_STARTED`` if process is ``RUNNING``, * with code ``Faults.ABNORMAL_TERMINATION`` if process could not be started. *@return* ``bool``: always ``True`` unless error. """ # WARN: do NOT check OPERATION (it is used internally in DEPLOYMENT) application, process = self._get_application_process(namespec) if extra_args and not process.accept_extra_arguments(): raise RPCError(Faults.BAD_EXTRA_ARGUMENTS, 'rules for namespec {} are not compatible with extra arguments in command line'.format(namespec)) # update command line in process config with extra_args try: self.info_source.update_extra_args(namespec, extra_args) except KeyError: # process is unknown to the local Supervisor # this should not happen as Supvisors checks the configuration before it sends this request self.logger.error('could not find {} in supervisord processes'.format(namespec)) raise RPCError(Faults.BAD_NAME, 'namespec {} unknown in this Supervisor instance'.format(namespec)) # start process with Supervisor internal RPC try: cb = self.info_source.supervisor_rpc_interface.startProcess(namespec, wait) except RPCError, why: self.logger.error('start_process {} failed: {}'.format(namespec, why)) if why.code in [Faults.NO_FILE, Faults.NOT_EXECUTABLE]: self.logger.warn('force supervisord internal state of {} to FATAL'.format(namespec)) # at this stage, process is known to the local Supervisor self.info_source.force_process_fatal(namespec, why.text) # else process is already started # this should not happen as Supvisors checks the process state before it sends this request # anyway raise exception again raise
def _get_process(self, namespec): """ Return the ProcessStatus corresponding to the namespec. A BAD_NAME exception is raised if the process is not found. """ try: process = self.context.processes[namespec] except KeyError: raise RPCError(Faults.BAD_NAME, 'process {} unknown in Supvisors'.format(namespec)) return process
def onwait(): # check stopper if self.stopper.in_progress(): return NOT_DONE_YET for process in processes: if process.running(): raise RPCError(Faults.ABNORMAL_TERMINATION, process.namespec()) return True
def addProgramToGroup(self, group_name, program_name, program_options): """ Add a new program to an existing process group. Depending on the numprocs option, this will result in one or more processes being added to the group. @param string group_name Name of an existing process group @param string program_name Name of the new process in the process table @param struct program_options Program options, same as in supervisord.conf @return boolean Always True unless error """ self._update('addProgramToGroup') group = self._getProcessGroup(group_name) # make configparser instance for program options section_name = 'program:%s' % program_name parser = self._makeConfigParser(section_name, program_options) # make process configs from parser instance options = self.supervisord.options try: new_configs = options.processes_from_section( parser, section_name, group_name) except ValueError as e: raise RPCError(SupervisorFaults.INCORRECT_PARAMETERS, e) # check new process names don't already exist in the config for new_config in new_configs: for existing_config in group.config.process_configs: if new_config.name == existing_config.name: raise RPCError( SupervisorFaults.BAD_NAME, "Duplicated process name:" + new_config.name) # add process configs to group group.config.process_configs.extend(new_configs) for new_config in new_configs: # the process group config already exists and its after_setuid hook # will not be called again to make the auto child logs for this process. new_config.create_autochildlogs() # add process instance group.processes[new_config.name] = new_config.make_process(group) return True
def _get_application(self, application_name): """ Return the ApplicationStatus corresponding to the application name. A BAD_NAME exception is raised if the application is not found. """ try: application = self.context.applications[application_name] except KeyError: raise RPCError(Faults.BAD_NAME, 'application {} unknown in Supvisors'.format(application_name)) return application
def _makeConfigParser(self, section_name, options): config = UnhosedConfigParser() try: config.add_section(section_name) for k, v in dict(options).items(): config.set(section_name, k, v) except (TypeError, ValueError): raise RPCError(SupervisorFaults.INCORRECT_PARAMETERS) return config
def addGroup(self, name, priority=999): """ Add a new, empty process group. @param string name Name for the new process group @param integer priority Group priority (same as supervisord.conf) @return boolean Always True unless error """ self._update('addGroup') # check group_name does not already exist if self.supervisord.process_groups.get(name) is not None: raise RPCError(SupervisorFaults.BAD_NAME, name) # check priority is sane try: int(priority) except ValueError, why: raise RPCError(SupervisorFaults.INCORRECT_PARAMETERS, why[0])
def _check_state(self, states): """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT in one of the states. """ if self.supvisors.fsm.state not in states: raise RPCError( Faults.BAD_SUPVISORS_STATE, 'Supvisors (state={}) not in state {} to perform request'. format(SupvisorsStates._to_string(self.supvisors.fsm.state), [SupvisorsStates._to_string(state) for state in states]))
def readLog(self, offset, length): """ Read length bytes from the main log starting at offset @param int offset offset to start reading from. @param int length number of bytes to read from the log. @return string result Bytes of log """ self._update('readLog') logfile = self.supervisord.options.logfile if logfile is None or not os.path.exists(logfile): raise RPCError(Faults.NO_FILE, logfile) try: return readFile(logfile, int(offset), int(length)) except ValueError, inst: why = inst.args[0] raise RPCError(getattr(Faults, why))
def _make_config_parser(self, section_name, options): config_parser = UnhosedConfigParser() try: config_parser.add_section(section_name) for key, value in dict(options).items(): config_parser.set(section_name, key, value) except (TypeError, ValueError) as e: return RPCError(SupervisorFaults.INCORRECT_PARAMETERS) return config_parser
def UpdateNumprocs(self, group_name): self._update('UpdateNumprocs') try: self.supervisord.options.process_config(do_usage=False) except ValueError as msg: raise RPCError(Faults.CANT_REREAD, msg) group = self._getProcessGroup(group_name) old_config = self.supervisord.process_groups[group_name].config new_config = [ cfg for cfg in self.supervisord.options.process_group_configs if cfg.name == group_name ][0] if old_config == new_config: return json.dumps({"msg": "No need to update", "type": "error"}) else: if old_config.name != new_config.name or old_config.priority != new_config.priority: return json.dumps({ "msg": "Not only numprocs has changed: priority is difference", "type": "error" }) new_process_configs = new_config.process_configs old_process_configs = old_config.process_configs if len(old_process_configs) < len(new_process_configs): if self._issubset(old_process_configs, new_process_configs): return self._add_num( group_name, self._difference(new_process_configs, old_process_configs)) else: return json.dumps({ "msg": "Not only numprocs has changed", "type": "error" }) elif len(old_process_configs) > len(new_process_configs): if self._issubset(new_process_configs, old_process_configs): return self._reduce_num( group_name, self._difference(old_process_configs, new_process_configs)) else: return json.dumps({ "msg": "Not only numprocs has changed", "type": "error" }) # If other not only name, priority or numprocs has changed # Return an error else: return json.dumps({ "msg": "Other settings has changed, please use update", "type": "error" }) # Return something for xmlrpc lib return True
def reloadConfig(self): """ Reload configuration @return boolean result always return True unless error """ self._update('reloadConfig') try: self.supervisord.options.process_config(do_usage=False) except ValueError, msg: raise RPCError(Faults.CANT_REREAD, msg)
def clearProcessLogs(self, name): """ Clear the stdout and stderr logs for the named process and reopen them. @param string name The name of the process (or 'group:name') @return boolean result Always True unless error """ self._update('clearProcessLogs') group, process = self._getGroupAndProcess(name) if process is None: raise RPCError(Faults.BAD_NAME, name) try: # implies a reopen process.removelogs() except (IOError, OSError): raise RPCError(Faults.FAILED, name) return True
def startit(): if not started: if process.get_state() in RUNNING_STATES: raise RPCError(Faults.ALREADY_STARTED, name) process.spawn() if process.spawnerr: raise RPCError(Faults.SPAWN_ERROR, name) # we use a list here to fake out lexical scoping; # using a direct assignment to 'started' in the # function appears to not work (symptom: 2nd or 3rd # call through, it forgets about 'started', claiming # it's undeclared). started.append(time.time()) if not wait or not startsecs: return True t = time.time() if t < started[0]: self.supervisord.options.logger.warn( "startProcess: Abnormal system time detected, assuming start OK" ) t = started[0] + startsecs runtime = (t - started[0]) state = process.get_state() if state not in (ProcessStates.STARTING, ProcessStates.RUNNING): raise RPCError(Faults.ABNORMAL_TERMINATION, name) if runtime < startsecs: return NOT_DONE_YET if state == ProcessStates.RUNNING: return True raise RPCError(Faults.ABNORMAL_TERMINATION, name)