[Supervisor-checkins] r879 - in supervisor/trunk: . src/supervisor src/supervisor/skel src/supervisor/tests
Mike Naberezny
mike at maintainable.com
Tue Jun 9 13:09:23 EDT 2009
Author: Mike Naberezny <mike at maintainable.com>
Date: Tue Jun 9 13:09:22 2009
New Revision: 879
Log:
- The configuration rereading functionality introduced in 3.0a7 has been
moved into its own RPC interface and supervisorctl plugin. These will
continue to be bundled with the main Supervisor package.
Users who upgraded to 3.0a7 and wish to continue using the reread
functionality must add these lines to their existing supervisord.conf:
[rpcinterface:supervisor_reread]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_reread_rpcinterface
[ctlplugin:supervisor_reread]
supervisor.ctl_factory = supervisor.supervisorctl:make_reread_controllerplugin
The lines above are now included in the sample configuration file
output by the ``echo_supervisord_conf`` console script.
Please note that due to this change, supervisorctl from 3.0a7 cannot
control the reread functions of 3.0a8 (and later).
Modified:
supervisor/trunk/CHANGES.txt
supervisor/trunk/TODO.txt
supervisor/trunk/src/supervisor/rpcinterface.py
supervisor/trunk/src/supervisor/skel/sample.conf
supervisor/trunk/src/supervisor/supervisorctl.py
supervisor/trunk/src/supervisor/tests/base.py
supervisor/trunk/src/supervisor/tests/test_rpcinterfaces.py
supervisor/trunk/src/supervisor/tests/test_supervisorctl.py
Modified: supervisor/trunk/CHANGES.txt
==============================================================================
--- supervisor/trunk/CHANGES.txt (original)
+++ supervisor/trunk/CHANGES.txt Tue Jun 9 13:09:22 2009
@@ -6,7 +6,27 @@
- Removed the test suite for the ``memmon`` console script, which was
moved to the Superlance package in 3.0a7.
-3.0a7
+ - Added release dates to CHANGES.txt.
+
+ - The configuration rereading functionality introduced in 3.0a7 has been
+ moved into its own RPC interface and supervisorctl plugin. These will
+ continue to be bundled with the main Supervisor package.
+
+ Users who upgraded to 3.0a7 and wish to continue using the reread
+ functionality must add these lines to their existing supervisord.conf:
+
+ [rpcinterface:supervisor_reread]
+ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_reread_rpcinterface
+ [ctlplugin:supervisor_reread]
+ supervisor.ctl_factory = supervisor.supervisorctl:make_reread_controllerplugin
+
+ The lines above are now included in the sample configuration file
+ output by the ``echo_supervisord_conf`` console script.
+
+ Please note that due to this change, supervisorctl from 3.0a7 cannot
+ control the reread functions of 3.0a8 (and later).
+
+3.0a7 (2009-05-24)
- We now bundle our own patched version of Medusa contributed by Jason
Kirtland to allow Supervisor to run on Python 2.6. This was done
@@ -166,14 +186,14 @@
supervisor.getPID() on the XML-RPC interface or a new
"pid" command on supervisorctl.
-3.0a6
+3.0a6 (2008-04-07)
- The RotatingFileLogger had a race condition in its doRollover
method whereby a file might not actually exist despite a call to
os.path.exists on the line above a place where we try to remove
it. We catch the exception now and ignore the missing file.
-3.0a5
+3.0a5 (2008-03-13)
- Supervisorctl now supports persistent readline history. To
enable, add "history_file = <pathname>" to the '[supervisorctl']
@@ -184,7 +204,7 @@
single semicolon; they will be executed in order as you would
expect.
-3.0a4
+3.0a4 (2008-01-30)
- 3.0a3 broke Python 2.3 backwards compatibility.
@@ -377,7 +397,7 @@
stopAllProcesses() now take an optional "wait" argument that defaults
to True for parity with the start methods.
-3.0a3
+3.0a3 (2007-10-02)
- Supervisorctl now reports a better error message when the main
supervisor XML-RPC namespace is not registered. Thanks to
@@ -620,7 +640,7 @@
- Updated ez_setup.py to one that knows about setuptools 0.6c7.
-3.0a2
+3.0a2 (2007-08-24)
- Fixed the README.txt example for defining the supervisor RPC
interface in the configuration file. Thanks to Drew Perttula.
@@ -685,7 +705,7 @@
leakage. We now require a version of meld3 that does not appear
to leak memory from its C extensions (0.6.3).
-3.0a1
+3.0a1 (2007-08-16)
- Default config file comment documented 10 secs as default for
'startsecs' value in process config, in reality it was 1 sec.
@@ -803,7 +823,7 @@
Agendless Consulting to add the event notification features and
extensible XML-RPC namespaces feature to supervisor.
-2.2b1
+2.2b1 (2007-03-31)
- Individual program configuration sections can now specify an
environment.
@@ -812,7 +832,7 @@
version of the supervisor2 package which the remote supervisord
process is using.
-2.1
+2.1 (2007-03-17)
- When supervisord was invoked more than once, and its configuration
was set up to use a UNIX domain socket as the HTTP server, the
@@ -856,13 +876,7 @@
readProcessLog() to read chunks and tailProcessLog() to tail.
(thanks to Mike Naberezny).
-2.1b2
-
- - Added new tailProcessLog() command to the XML-RPC API that
- is more efficient for just tailing than the existing
- readProcessLog() command (Mike Naberezny).
-
-2.1b1
+2.1b1 (2006-08-30)
- "supervisord -h" and "supervisorctl -h" did not work (traceback
instead of showing help view (thanks to Damjan from Macedonia for
@@ -894,7 +908,7 @@
RUNNING -> EXITED -> STARTING. This has no real negative effect,
but should be fixed for correctness.
-2.0
+2.0 (2006-08-30)
- pidfile written in daemon mode had incorrect pid.
@@ -909,7 +923,7 @@
- New "environment" config file option allows you to add environment
variable values to supervisord environment from config file.
-2.0b1
+2.0b1 (2006-07-12)
- fundamental rewrite based on 1.0.6, use distutils (only) for
installation, use ConfigParser rather than ZConfig, use HTTP for
Modified: supervisor/trunk/TODO.txt
==============================================================================
--- supervisor/trunk/TODO.txt (original)
+++ supervisor/trunk/TODO.txt Tue Jun 9 13:09:22 2009
@@ -2,16 +2,6 @@
their error handling but not actual operation. We should add some additional
tests to verify their operation for completeness.
-- Finish process group reloading that was merged into trunk. We decided to
- implement addProcessGroup() such that at the time it is called, it reads the
- config from disk and then adds the group if found. It will have no other
- side effects and will not require a sort of "refresh" command to be called
- prior. We decided that a supervisorctl "avail" command will not be
- implemented. We decided that we are not going to implement any kind of DWIM
- or "smart" config reloading in the foreseeable future and will remove the
- process group "diffing" functionality that attempts to compare the runtime
- config to that on disk.
-
- Add an option that allows numprocs for an existing process group to be
adjusted at runtime. Requested by Roger Hoover.
Modified: supervisor/trunk/src/supervisor/rpcinterface.py
==============================================================================
--- supervisor/trunk/src/supervisor/rpcinterface.py (original)
+++ supervisor/trunk/src/supervisor/rpcinterface.py Tue Jun 9 13:09:22 2009
@@ -165,56 +165,6 @@
self.supervisord.options.mood = SupervisorStates.RESTARTING
return True
- def reloadConfig(self):
- """
- Reload configuration
-
- @return boolean result always return True unless error
- """
- self._update('reloadConfig')
- try:
- self.supervisord.options.process_config_file(do_usage=False)
- except ValueError, 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 addProcessGroup(self, name):
- """ Update the config for a running process from config file.
-
- @param string name name of process group to add
- @return boolean result true if successful
- """
- self._update('addProcessGroup')
-
- for config in self.supervisord.options.process_group_configs:
- if config.name == name:
- result = self.supervisord.add_process_group(config)
- if not result:
- raise RPCError(Faults.ALREADY_ADDED, name)
- return True
- raise RPCError(Faults.BAD_NAME, name)
-
- def removeProcessGroup(self, name):
- """ Remove a stopped process from the active configuration.
-
- @param string name name of process group to remove
- @return boolean result Indicates wether the removal was successful
- """
- self._update('removeProcessGroup')
- if name not in self.supervisord.process_groups:
- raise RPCError(Faults.BAD_NAME, name)
-
- result = self.supervisord.remove_process_group(name)
- if not result:
- raise RPCError(Faults.STILL_RUNNING)
- return True
-
def _getAllProcesses(self, lexical=False):
# if lexical is true, return processes sorted in lexical order,
# otherwise, sort in priority order
@@ -455,29 +405,6 @@
killall.rpcinterface = self
return killall # deferred
- def getAllConfigInfo(self):
- """ Get info about all availible process configurations. Each record
- represents a single process (i.e. groups get flattened).
-
- @return array result An array of process config info records
- """
- self._update('getAllConfigInfo')
-
- configinfo = []
- for gconfig in self.supervisord.options.process_group_configs:
- inuse = gconfig.name in self.supervisord.process_groups
- for pconfig in gconfig.process_configs:
- configinfo.append(
- { 'name': pconfig.name,
- 'group': gconfig.name,
- 'inuse': inuse,
- 'autostart': pconfig.autostart,
- 'group_prio': gconfig.priority,
- 'process_prio': pconfig.priority })
-
- configinfo.sort()
- return configinfo
-
def _interpretProcessInfo(self, info):
state = info['state']
@@ -863,7 +790,94 @@
def isNotRunning(process):
return not isRunning(process)
+
+class RereadNamespaceRPCInterface:
+ def __init__(self, supervisord):
+ self.supervisord = supervisord
+
+ def _update(self, text):
+ self.update_text = text # for unit tests, mainly
+ if self.supervisord.options.mood < SupervisorStates.RUNNING:
+ raise RPCError(Faults.SHUTDOWN_STATE)
+
+ def getAllConfigInfo(self):
+ """ Get info about all availible process configurations. Each record
+ represents a single process (i.e. groups get flattened).
+
+ @return array result An array of process config info records
+ """
+ self._update('getAllConfigInfo')
+
+ configinfo = []
+ for gconfig in self.supervisord.options.process_group_configs:
+ inuse = gconfig.name in self.supervisord.process_groups
+ for pconfig in gconfig.process_configs:
+ configinfo.append(
+ { 'name': pconfig.name,
+ 'group': gconfig.name,
+ 'inuse': inuse,
+ 'autostart': pconfig.autostart,
+ 'group_prio': gconfig.priority,
+ 'process_prio': pconfig.priority })
+
+ configinfo.sort()
+ return configinfo
+
+ def reloadConfig(self):
+ """
+ Reload configuration
+
+ @return boolean result always return True unless error
+ """
+ self._update('reloadConfig')
+ try:
+ self.supervisord.options.process_config_file(do_usage=False)
+ except ValueError, 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 addProcessGroup(self, name):
+ """ Update the config for a running process from config file.
+
+ @param string name name of process group to add
+ @return boolean result true if successful
+ """
+ self._update('addProcessGroup')
+
+ for config in self.supervisord.options.process_group_configs:
+ if config.name == name:
+ result = self.supervisord.add_process_group(config)
+ if not result:
+ raise RPCError(Faults.ALREADY_ADDED, name)
+ return True
+ raise RPCError(Faults.BAD_NAME, name)
+
+ def removeProcessGroup(self, name):
+ """ Remove a stopped process from the active configuration.
+
+ @param string name name of process group to remove
+ @return boolean result Indicates wether the removal was successful
+ """
+ self._update('removeProcessGroup')
+ if name not in self.supervisord.process_groups:
+ raise RPCError(Faults.BAD_NAME, name)
+
+ result = self.supervisord.remove_process_group(name)
+ if not result:
+ raise RPCError(Faults.STILL_RUNNING)
+ return True
+
+
# this is not used in code but referenced via an entry point in the conf file
def make_main_rpcinterface(supervisord):
return SupervisorNamespaceRPCInterface(supervisord)
+# this is not used in code but referenced via an entry point in the conf file
+def make_reread_rpcinterface(supervisord):
+ return RereadNamespaceRPCInterface(supervisord)
Modified: supervisor/trunk/src/supervisor/skel/sample.conf
==============================================================================
--- supervisor/trunk/src/supervisor/skel/sample.conf (original)
+++ supervisor/trunk/src/supervisor/skel/sample.conf Tue Jun 9 13:09:22 2009
@@ -30,12 +30,20 @@
;environment=KEY=value ; (key value pairs to add to environment)
;strip_ansi=false ; (strip ansi escape codes in logs; def. false)
-; the below section must remain in the config file for RPC
-; (supervisorctl/web interface) to work, additional interfaces may be
+; the section [rpcinterface:supervisor] must remain in the config file
+; for RPC (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+; the sections [rpcinterface:supervisor_reread] and [ctlplugin:supervisor_reread]
+; enable supervisor's ability to reread its configuration after it has started.
+; remove these two sections if you want to disable this functionality.
+[rpcinterface:supervisor_reread]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_reread_rpcinterface
+[ctlplugin:supervisor_reread]
+supervisor.ctl_factory = supervisor.supervisorctl:make_reread_controllerplugin
+
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
Modified: supervisor/trunk/src/supervisor/supervisorctl.py
==============================================================================
--- supervisor/trunk/src/supervisor/supervisorctl.py (original)
+++ supervisor/trunk/src/supervisor/supervisorctl.py Tue Jun 9 13:09:22 2009
@@ -790,6 +790,142 @@
def help_reload(self):
self.ctl.output("reload \t\tRestart the remote supervisord.")
+ def _clearresult(self, result):
+ name = result['name']
+ code = result['status']
+ template = '%s: ERROR (%s)'
+ if code == xmlrpc.Faults.BAD_NAME:
+ return template % (name, 'no such process')
+ elif code == xmlrpc.Faults.FAILED:
+ return template % (name, 'failed')
+ elif code == xmlrpc.Faults.SUCCESS:
+ return '%s: cleared' % name
+ raise ValueError('Unknown result code %s for %s' % (code, name))
+
+ def do_clear(self, arg):
+ if not self.ctl.upcheck():
+ return
+
+ names = arg.strip().split()
+
+ if not names:
+ self.ctl.output('Error: clear requires a process name')
+ self.help_clear()
+ return
+
+ supervisor = self.ctl.get_supervisor()
+
+ if 'all' in names:
+ results = supervisor.clearAllProcessLogs()
+ for result in results:
+ result = self._clearresult(result)
+ self.ctl.output(result)
+
+ else:
+
+ for name in names:
+ try:
+ result = supervisor.clearProcessLogs(name)
+ except xmlrpclib.Fault, e:
+ error = self._clearresult({'status':e.faultCode,
+ 'name':name,
+ 'description':e.faultString})
+ self.ctl.output(error)
+ else:
+ self.ctl.output('%s: cleared' % name)
+
+ def help_clear(self):
+ self.ctl.output("clear <name>\t\tClear a process' log files.")
+ self.ctl.output(
+ "clear <name> <name>\tClear multiple process' log files")
+ self.ctl.output("clear all\t\tClear all process' log files")
+
+ def do_open(self, arg):
+ url = arg.strip()
+ parts = urlparse.urlparse(url)
+ if parts[0] not in ('unix', 'http'):
+ self.ctl.output('ERROR: url must be http:// or unix://')
+ return
+ self.ctl.options.serverurl = url
+ self.do_status('')
+
+ def help_open(self):
+ self.ctl.output("open <url>\tConnect to a remote supervisord process.")
+ self.ctl.output("\t\t(for UNIX domain socket, use unix:///socket/path)")
+
+ def do_version(self, arg):
+ if not self.ctl.upcheck():
+ return
+ supervisor = self.ctl.get_supervisor()
+ self.ctl.output(supervisor.getSupervisorVersion())
+
+ def help_version(self):
+ self.ctl.output(
+ "version\t\t\tShow the version of the remote supervisord "
+ "process")
+
+ def do_fg(self,args=None):
+ if not self.ctl.upcheck():
+ return
+ if not args:
+ self.ctl.output('Error: no process name supplied')
+ self.help_fg()
+ return
+ args = args.split()
+ if len(args) > 1:
+ self.ctl.output('Error: too many process names supplied')
+ return
+ program = args[0]
+ supervisor = self.ctl.get_supervisor()
+ try:
+ info = supervisor.getProcessInfo(program)
+ except xmlrpclib.Fault, msg:
+ if msg.faultCode == xmlrpc.Faults.BAD_NAME:
+ self.ctl.output('Error: bad process name supplied')
+ return
+ # for any other fault
+ self.ctl.output(str(msg))
+ return
+ if not info['state'] == states.ProcessStates.RUNNING:
+ self.ctl.output('Error: process not running')
+ return
+ # everything good; continue
+ try:
+ a = fgthread(program,self.ctl)
+ # this thread takes care of
+ # the output/error messages
+ a.start()
+ while True:
+ # this takes care of the user input
+ inp = raw_input() + '\n'
+ try:
+ supervisor.sendProcessStdin(program, inp)
+ except xmlrpclib.Fault, msg:
+ if msg.faultCode == xmlrpc.Faults.NOT_RUNNING:
+ self.ctl.output('Process got killed')
+ self.ctl.output('Exiting foreground')
+ a.kill()
+ return
+ info = supervisor.getProcessInfo(program)
+ if not info['state'] == states.ProcessStates.RUNNING:
+ self.ctl.output('Process got killed')
+ self.ctl.output('Exiting foreground')
+ a.kill()
+ return
+ continue
+ except (KeyboardInterrupt, EOFError):
+ a.kill()
+ self.ctl.output('Exiting foreground')
+ return
+
+ def help_fg(self,args=None):
+ self.ctl.output('fg <process>\tConnect to a process in foreground mode')
+ self.ctl.output('Press Ctrl+C to exit foreground')
+
+class RereadControllerPlugin(ControllerPluginBase):
+ name = 'reread'
+ listener = None # for unit tests
+
def _formatChanges(self, (added, changed, dropped)):
changedict = {}
for n, t in [(added, 'available'),
@@ -824,9 +960,9 @@
return template % formatted
def do_avail(self, arg):
- supervisor = self.ctl.get_supervisor()
+ supervisor_reread = self.ctl.get_server_proxy('supervisor_reread')
try:
- configinfo = supervisor.getAllConfigInfo()
+ configinfo = supervisor_reread.getAllConfigInfo()
except xmlrpclib.Fault, e:
if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
self.ctl.output('ERROR: supervisor shutting down')
@@ -838,9 +974,9 @@
self.ctl.output("avail\t\t\tDisplay all configured processes")
def do_reread(self, arg):
- supervisor = self.ctl.get_supervisor()
+ supervisor_reread = self.ctl.get_server_proxy('supervisor_reread')
try:
- result = supervisor.reloadConfig()
+ result = supervisor_reread.reloadConfig()
except xmlrpclib.Fault, e:
if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
self.ctl.output('ERROR: supervisor shutting down')
@@ -857,10 +993,15 @@
def do_add(self, arg):
names = arg.strip().split()
- supervisor = self.ctl.get_supervisor()
+ if not names:
+ self.ctl.output("ERROR: add requires a process group name")
+ self.help_add()
+ return
+
+ supervisor_reread = self.ctl.get_server_proxy('supervisor_reread')
for name in names:
try:
- supervisor.addProcessGroup(name)
+ supervisor_reread.addProcessGroup(name)
except xmlrpclib.Fault, e:
if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
self.ctl.output('ERROR: shutting down')
@@ -881,12 +1022,19 @@
def do_remove(self, arg):
names = arg.strip().split()
- supervisor = self.ctl.get_supervisor()
+ if not names:
+ self.ctl.output("ERROR: remove requires a process group name")
+ self.help_add()
+ return
+
+ supervisor_reread = self.ctl.get_server_proxy('supervisor_reread')
for name in names:
try:
- result = supervisor.removeProcessGroup(name)
+ result = supervisor_reread.removeProcessGroup(name)
except xmlrpclib.Fault, e:
- if e.faultCode == xmlrpc.Faults.STILL_RUNNING:
+ if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+ self.ctl.output('ERROR: shutting down')
+ elif e.faultCode == xmlrpc.Faults.STILL_RUNNING:
self.ctl.output('ERROR: process/group still running: %s'
% name)
elif e.faultCode == xmlrpc.Faults.BAD_NAME:
@@ -906,11 +1054,13 @@
self.ctl.output("%s: %s" % (name, message))
supervisor = self.ctl.get_supervisor()
+ supervisor_reread = self.ctl.get_server_proxy('supervisor_reread')
+
try:
- result = supervisor.reloadConfig()
+ result = supervisor_reread.reloadConfig()
except xmlrpclib.Fault, e:
if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
- self.ctl.output('ERROR: already shutting down')
+ self.ctl.output('ERROR: shutting down')
return
else:
raise e
@@ -933,148 +1083,20 @@
results = supervisor.stopProcessGroup(gname)
log(gname, "stopped")
- supervisor.removeProcessGroup(gname)
- supervisor.addProcessGroup(gname)
+ supervisor_reread.removeProcessGroup(gname)
+ supervisor_reread.addProcessGroup(gname)
log(gname, "updated process group")
for gname in added:
- supervisor.addProcessGroup(gname)
+ supervisor_reread.addProcessGroup(gname)
log(gname, "added process group")
def help_update(self):
self.ctl.output("update\t\tReload config and add/remove as necessary")
- def _clearresult(self, result):
- name = result['name']
- code = result['status']
- template = '%s: ERROR (%s)'
- if code == xmlrpc.Faults.BAD_NAME:
- return template % (name, 'no such process')
- elif code == xmlrpc.Faults.FAILED:
- return template % (name, 'failed')
- elif code == xmlrpc.Faults.SUCCESS:
- return '%s: cleared' % name
- raise ValueError('Unknown result code %s for %s' % (code, name))
-
- def do_clear(self, arg):
- if not self.ctl.upcheck():
- return
-
- names = arg.strip().split()
-
- if not names:
- self.ctl.output('Error: clear requires a process name')
- self.help_clear()
- return
-
- supervisor = self.ctl.get_supervisor()
-
- if 'all' in names:
- results = supervisor.clearAllProcessLogs()
- for result in results:
- result = self._clearresult(result)
- self.ctl.output(result)
-
- else:
-
- for name in names:
- try:
- result = supervisor.clearProcessLogs(name)
- except xmlrpclib.Fault, e:
- error = self._clearresult({'status':e.faultCode,
- 'name':name,
- 'description':e.faultString})
- self.ctl.output(error)
- else:
- self.ctl.output('%s: cleared' % name)
-
- def help_clear(self):
- self.ctl.output("clear <name>\t\tClear a process' log files.")
- self.ctl.output(
- "clear <name> <name>\tClear multiple process' log files")
- self.ctl.output("clear all\t\tClear all process' log files")
-
- def do_open(self, arg):
- url = arg.strip()
- parts = urlparse.urlparse(url)
- if parts[0] not in ('unix', 'http'):
- self.ctl.output('ERROR: url must be http:// or unix://')
- return
- self.ctl.options.serverurl = url
- self.do_status('')
-
- def help_open(self):
- self.ctl.output("open <url>\tConnect to a remote supervisord process.")
- self.ctl.output("\t\t(for UNIX domain socket, use unix:///socket/path)")
-
- def do_version(self, arg):
- if not self.ctl.upcheck():
- return
- supervisor = self.ctl.get_supervisor()
- self.ctl.output(supervisor.getSupervisorVersion())
-
- def help_version(self):
- self.ctl.output(
- "version\t\t\tShow the version of the remote supervisord "
- "process")
+def make_reread_controllerplugin(controller, **config):
+ return RereadControllerPlugin(controller)
- def do_fg(self,args=None):
- if not self.ctl.upcheck():
- return
- if not args:
- self.ctl.output('Error: no process name supplied')
- self.help_fg()
- return
- args = args.split()
- if len(args) > 1:
- self.ctl.output('Error: too many process names supplied')
- return
- program = args[0]
- supervisor = self.ctl.get_supervisor()
- try:
- info = supervisor.getProcessInfo(program)
- except xmlrpclib.Fault, msg:
- if msg.faultCode == xmlrpc.Faults.BAD_NAME:
- self.ctl.output('Error: bad process name supplied')
- return
- # for any other fault
- self.ctl.output(str(msg))
- return
- if not info['state'] == states.ProcessStates.RUNNING:
- self.ctl.output('Error: process not running')
- return
- # everything good; continue
- try:
- a = fgthread(program,self.ctl)
- # this thread takes care of
- # the output/error messages
- a.start()
- while True:
- # this takes care of the user input
- inp = raw_input() + '\n'
- try:
- supervisor.sendProcessStdin(program, inp)
- except xmlrpclib.Fault, msg:
- if msg.faultCode == xmlrpc.Faults.NOT_RUNNING:
- self.ctl.output('Process got killed')
- self.ctl.output('Exiting foreground')
- a.kill()
- return
- info = supervisor.getProcessInfo(program)
- if not info['state'] == states.ProcessStates.RUNNING:
- self.ctl.output('Process got killed')
- self.ctl.output('Exiting foreground')
- a.kill()
- return
- continue
- except (KeyboardInterrupt, EOFError):
- a.kill()
- self.ctl.output('Exiting foreground')
- return
-
- def help_fg(self,args=None):
- self.ctl.output('fg <process>\tConnect to a process in foreground mode')
- self.ctl.output('Press Ctrl+C to exit foreground')
def main(args=None, options=None):
if options is None:
Modified: supervisor/trunk/src/supervisor/tests/base.py
==============================================================================
--- supervisor/trunk/src/supervisor/tests/base.py (original)
+++ supervisor/trunk/src/supervisor/tests/base.py Tue Jun 9 13:09:22 2009
@@ -588,18 +588,20 @@
class DummyRPCServer:
def __init__(self):
self.supervisor = DummySupervisorRPCNamespace()
+ self.supervisor_reread = DummyRereadRPCNamespace()
self.system = DummySystemRPCNamespace()
class DummySystemRPCNamespace:
pass
class DummySupervisorRPCNamespace:
+ processes = []
+
_restartable = True
_restarted = False
_shutdown = False
_readlog_error = False
-
from supervisor.process import ProcessStates
all_process_info = [
{
@@ -838,6 +840,43 @@
raise Fault(self._readlog_error, '')
return 'mainlogdata'
+class DummyRereadRPCNamespace:
+ def __init__(self):
+ self.added_process_groups = []
+ self.removed_process_groups = []
+ self.processes = [] # xxx hack
+
+ def addProcessGroup(self, name):
+ from xmlrpclib import Fault
+ from supervisor import xmlrpc
+ if name == 'SHUTDOWN_STATE':
+ raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
+ elif name == 'ALREADY_ADDED':
+ raise Fault(xmlrpc.Faults.ALREADY_ADDED, '')
+ elif name == 'BAD_NAME':
+ raise Fault(xmlrpc.Faults.BAD_NAME, '')
+ else:
+ self.processes.append(name) # xxx hack
+ self.added_process_groups.append(name)
+ return True
+
+ def removeProcessGroup(self, name):
+ from xmlrpclib import Fault
+ from supervisor import xmlrpc
+ if name == 'SHUTDOWN_STATE':
+ raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
+ elif name == 'STILL_RUNNING':
+ raise Fault(xmlrpc.Faults.STILL_RUNNING, '')
+ elif name == 'BAD_NAME':
+ raise Fault(xmlrpc.Faults.BAD_NAME, '')
+ else:
+ self.removed_process_groups.append(name)
+ return True
+
+ def reloadConfig(self):
+ return [[['added'], ['changed'], ['removed']]]
+
+
class DummyPGroupConfig:
def __init__(self, options, name='whatever', priority=999, pconfigs=None):
self.options = options
Modified: supervisor/trunk/src/supervisor/tests/test_rpcinterfaces.py
==============================================================================
--- supervisor/trunk/src/supervisor/tests/test_rpcinterfaces.py (original)
+++ supervisor/trunk/src/supervisor/tests/test_rpcinterfaces.py Tue Jun 9 13:09:22 2009
@@ -30,8 +30,24 @@
else:
raise AssertionError("Didnt raise")
-class MainXMLRPCInterfaceTests(TestBase):
+class TopLevelFunctionTests(TestBase):
+ def test_factory_for_main_rpc_interface(self):
+ from supervisor import rpcinterface
+ supervisor = DummySupervisor()
+ interface = rpcinterface.make_main_rpcinterface(supervisor)
+
+ expected = rpcinterface.SupervisorNamespaceRPCInterface
+ self.assertTrue(isinstance(interface, expected))
+
+ def test_factory_for_reread_rpc_interface(self):
+ from supervisor import rpcinterface
+ supervisor = DummySupervisor()
+ interface = rpcinterface.make_reread_rpcinterface(supervisor)
+
+ expected = rpcinterface.RereadNamespaceRPCInterface
+ self.assertTrue(isinstance(interface, expected))
+class MainXMLRPCInterfaceTests(TestBase):
def _getTargetClass(self):
from supervisor import xmlrpc
return xmlrpc.RootRPCInterface
@@ -217,99 +233,6 @@
value = interface.restart()
self.assertEqual(value, True)
self.assertEqual(supervisord.options.mood, 0)
-
- def test_reloadConfig(self):
- options = DummyOptions()
- supervisord = DummySupervisor(options)
- interface = self._makeOne(supervisord)
-
- changes = [ [DummyPGroupConfig(options, 'added')],
- [DummyPGroupConfig(options, 'changed')],
- [DummyPGroupConfig(options, 'dropped')] ]
-
- supervisord.diff_to_active = lambda : changes
-
- value = interface.reloadConfig()
- self.assertEqual(value, [[['added'], ['changed'], ['dropped']]])
-
- def test_reloadConfig_process_config_file_raises_ValueError(self):
- from supervisor import xmlrpc
- options = DummyOptions()
- def raise_exc(*arg, **kw):
- raise ValueError('foo')
- options.process_config_file = raise_exc
- supervisord = DummySupervisor(options)
- interface = self._makeOne(supervisord)
- self._assertRPCError(xmlrpc.Faults.CANT_REREAD, interface.reloadConfig)
-
- def test_addProcessGroup(self):
- from supervisor.supervisord import Supervisor
- from supervisor import xmlrpc
- options = DummyOptions()
- supervisord = Supervisor(options)
- pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
- gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
- supervisord.options.process_group_configs = [gconfig]
-
- interface = self._makeOne(supervisord)
-
- result = interface.addProcessGroup('group1')
- self.assertTrue(result)
- self.assertEqual(supervisord.process_groups.keys(), ['group1'])
-
- self._assertRPCError(xmlrpc.Faults.ALREADY_ADDED,
- interface.addProcessGroup, 'group1')
- self.assertEqual(supervisord.process_groups.keys(), ['group1'])
-
- self._assertRPCError(xmlrpc.Faults.BAD_NAME,
- interface.addProcessGroup, 'asdf')
- self.assertEqual(supervisord.process_groups.keys(), ['group1'])
-
- def test_removeProcessGroup(self):
- from supervisor.supervisord import Supervisor
- options = DummyOptions()
- supervisord = Supervisor(options)
- pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
- gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
- supervisord.options.process_group_configs = [gconfig]
-
- interface = self._makeOne(supervisord)
-
- interface.addProcessGroup('group1')
- result = interface.removeProcessGroup('group1')
- self.assertTrue(result)
- self.assertEqual(supervisord.process_groups.keys(), [])
-
- def test_removeProcessGroup_bad_name(self):
- from supervisor.supervisord import Supervisor
- from supervisor import xmlrpc
- options = DummyOptions()
- supervisord = Supervisor(options)
- pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
- gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
- supervisord.options.process_group_configs = [gconfig]
-
- interface = self._makeOne(supervisord)
-
- self._assertRPCError(xmlrpc.Faults.BAD_NAME,
- interface.removeProcessGroup, 'asdf')
-
- def test_removeProcessGroup_still_running(self):
- from supervisor.supervisord import Supervisor
- from supervisor import xmlrpc
- options = DummyOptions()
- supervisord = Supervisor(options)
- pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
- gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
- supervisord.options.process_group_configs = [gconfig]
- process = DummyProcessGroup(gconfig)
- process.unstopped_processes = [123]
- supervisord.process_groups = {'group1':process}
- interface = self._makeOne(supervisord)
- self._assertRPCError(xmlrpc.Faults.STILL_RUNNING,
- interface.removeProcessGroup, 'group1')
-
-
def test_startProcess_already_started(self):
from supervisor import xmlrpc
options = DummyOptions()
@@ -845,31 +768,6 @@
self.assertEqual(result[1]['status'], Faults.SUCCESS)
self.assertEqual(result[1]['description'], 'OK')
- def test_getAllConfigInfo(self):
- options = DummyOptions()
- supervisord = DummySupervisor(options, 'foo')
-
- pconfig1 = DummyPConfig(options, 'process1', __file__)
- pconfig2 = DummyPConfig(options, 'process2', __file__)
- gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig1, pconfig2])
- supervisord.process_groups = {'group1': DummyProcessGroup(gconfig)}
- supervisord.options.process_group_configs = [gconfig]
-
- interface = self._makeOne(supervisord)
- configs = interface.getAllConfigInfo()
- self.assertEqual(configs, [{ 'group': 'group1',
- 'name': 'process1',
- 'inuse': True,
- 'autostart': True,
- 'process_prio': 999,
- 'group_prio': 999 },
- { 'group': 'group1',
- 'name': 'process2',
- 'inuse': True,
- 'autostart': True,
- 'process_prio': 999,
- 'group_prio': 999 }])
-
def test__interpretProcessInfo(self):
supervisord = DummySupervisor()
interface = self._makeOne(supervisord)
@@ -1589,13 +1487,10 @@
L = []
def callback(event):
L.append(event)
-
- try:
- events.callbacks[:] = [(events.RemoteCommunicationEvent, callback)]
- result = interface.sendRemoteCommEvent('foo', 'bar')
- finally:
- events.callbacks[:] = []
- events.clear()
+
+ events.subscribe(events.RemoteCommunicationEvent, callback)
+ result = interface.sendRemoteCommEvent('foo', 'bar')
+ events.clear()
self.assertTrue(result)
self.assertEqual(len(L), 1)
@@ -1612,20 +1507,142 @@
L = []
def callback(event):
L.append(event)
-
- try:
- events.callbacks[:] = [(events.RemoteCommunicationEvent, callback)]
- result = interface.sendRemoteCommEvent(u'fi\xed once', u'fi\xed twice')
- finally:
- events.callbacks[:] = []
- events.clear()
+
+ events.subscribe(events.RemoteCommunicationEvent, callback)
+ result = interface.sendRemoteCommEvent(u'fi\xed 1', u'fi\xed 2')
+ events.clear()
self.assertTrue(result)
self.assertEqual(len(L), 1)
event = L[0]
- self.assertEqual(event.type, 'fi\xc3\xad once')
- self.assertEqual(event.data, 'fi\xc3\xad twice')
+ self.assertEqual(event.type, 'fi\xc3\xad 1')
+ self.assertEqual(event.data, 'fi\xc3\xad 2')
+
+class RereadNamespaceXMLRPCInterfaceTests(TestBase):
+ def _getTargetClass(self):
+ from supervisor import rpcinterface
+ return rpcinterface.RereadNamespaceRPCInterface
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def test_getAllConfigInfo(self):
+ options = DummyOptions()
+ supervisord = DummySupervisor(options, 'foo')
+
+ pconfig1 = DummyPConfig(options, 'process1', __file__)
+ pconfig2 = DummyPConfig(options, 'process2', __file__)
+ gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig1, pconfig2])
+ supervisord.process_groups = {'group1': DummyProcessGroup(gconfig)}
+ supervisord.options.process_group_configs = [gconfig]
+
+ interface = self._makeOne(supervisord)
+ configs = interface.getAllConfigInfo()
+ self.assertEqual(configs, [{ 'group': 'group1',
+ 'name': 'process1',
+ 'inuse': True,
+ 'autostart': True,
+ 'process_prio': 999,
+ 'group_prio': 999 },
+ { 'group': 'group1',
+ 'name': 'process2',
+ 'inuse': True,
+ 'autostart': True,
+ 'process_prio': 999,
+ 'group_prio': 999 }])
+
+ def test_reloadConfig(self):
+ options = DummyOptions()
+ supervisord = DummySupervisor(options)
+ interface = self._makeOne(supervisord)
+
+ changes = [ [DummyPGroupConfig(options, 'added')],
+ [DummyPGroupConfig(options, 'changed')],
+ [DummyPGroupConfig(options, 'dropped')] ]
+
+ supervisord.diff_to_active = lambda : changes
+
+ value = interface.reloadConfig()
+ self.assertEqual(value, [[['added'], ['changed'], ['dropped']]])
+
+ def test_reloadConfig_process_config_file_raises_ValueError(self):
+ from supervisor import xmlrpc
+ options = DummyOptions()
+ def raise_exc(*arg, **kw):
+ raise ValueError('foo')
+ options.process_config_file = raise_exc
+ supervisord = DummySupervisor(options)
+ interface = self._makeOne(supervisord)
+ self._assertRPCError(xmlrpc.Faults.CANT_REREAD, interface.reloadConfig)
+
+ def test_addProcessGroup(self):
+ from supervisor.supervisord import Supervisor
+ from supervisor import xmlrpc
+ options = DummyOptions()
+ supervisord = Supervisor(options)
+ pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
+ gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
+ supervisord.options.process_group_configs = [gconfig]
+
+ interface = self._makeOne(supervisord)
+
+ result = interface.addProcessGroup('group1')
+ self.assertTrue(result)
+ self.assertEqual(supervisord.process_groups.keys(), ['group1'])
+
+ self._assertRPCError(xmlrpc.Faults.ALREADY_ADDED,
+ interface.addProcessGroup, 'group1')
+ self.assertEqual(supervisord.process_groups.keys(), ['group1'])
+
+ self._assertRPCError(xmlrpc.Faults.BAD_NAME,
+ interface.addProcessGroup, 'asdf')
+ self.assertEqual(supervisord.process_groups.keys(), ['group1'])
+
+ def test_removeProcessGroup(self):
+ from supervisor.supervisord import Supervisor
+ options = DummyOptions()
+ supervisord = Supervisor(options)
+ pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
+ gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
+ supervisord.options.process_group_configs = [gconfig]
+
+ interface = self._makeOne(supervisord)
+
+ interface.addProcessGroup('group1')
+ result = interface.removeProcessGroup('group1')
+ self.assertTrue(result)
+ self.assertEqual(supervisord.process_groups.keys(), [])
+
+ def test_removeProcessGroup_bad_name(self):
+ from supervisor.supervisord import Supervisor
+ from supervisor import xmlrpc
+ options = DummyOptions()
+ supervisord = Supervisor(options)
+ pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
+ gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
+ supervisord.options.process_group_configs = [gconfig]
+
+ interface = self._makeOne(supervisord)
+
+ self._assertRPCError(xmlrpc.Faults.BAD_NAME,
+ interface.removeProcessGroup, 'asdf')
+
+ def test_removeProcessGroup_still_running(self):
+ from supervisor.supervisord import Supervisor
+ from supervisor import xmlrpc
+ options = DummyOptions()
+ supervisord = Supervisor(options)
+ pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)
+ gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
+ supervisord.options.process_group_configs = [gconfig]
+ process = DummyProcessGroup(gconfig)
+ process.unstopped_processes = [123]
+ supervisord.process_groups = {'group1':process}
+ interface = self._makeOne(supervisord)
+ self._assertRPCError(xmlrpc.Faults.STILL_RUNNING,
+ interface.removeProcessGroup, 'group1')
+
class SystemNamespaceXMLRPCInterfaceTests(TestBase):
def _getTargetClass(self):
Modified: supervisor/trunk/src/supervisor/tests/test_supervisorctl.py
==============================================================================
--- supervisor/trunk/src/supervisor/tests/test_supervisorctl.py (original)
+++ supervisor/trunk/src/supervisor/tests/test_supervisorctl.py Tue Jun 9 13:09:22 2009
@@ -511,137 +511,310 @@
self.assertEqual(result, None)
self.assertEqual(options._server.supervisor._shutdown, True)
- def test__formatChanges(self):
+ def test_pid(self):
plugin = self._makeOne()
- # Don't explode, plz
- plugin._formatChanges([['added'], ['changed'], ['removed']])
- plugin._formatChanges([[], [], []])
+ result = plugin.do_pid('')
+ options = plugin.ctl.options
+ self.assertEqual(result, None)
+ lines = plugin.ctl.stdout.getvalue().split('\n')
+ self.assertEqual(len(lines), 2)
+ self.assertEqual(lines[0], str(options._server.supervisor.getPID()))
- def test_reread(self):
+ def test_maintail_toomanyargs(self):
plugin = self._makeOne()
- calls = []
- plugin._formatChanges = lambda x: calls.append(x)
- result = plugin.do_reread(None)
- self.assertEqual(result, None)
- self.assertEqual(calls[0], [['added'], ['changed'], ['removed']])
+ result = plugin.do_maintail('foo bar')
+ val = plugin.ctl.stdout.getvalue()
+ self.failUnless(val.startswith('Error: too many'), val)
- def test_reread_Fault(self):
+ def test_maintail_minus_string_fails(self):
+ plugin = self._makeOne()
+ result = plugin.do_maintail('-wrong')
+ val = plugin.ctl.stdout.getvalue()
+ self.failUnless(val.startswith('Error: bad argument -wrong'), val)
+
+ def test_maintail_wrong(self):
+ plugin = self._makeOne()
+ result = plugin.do_maintail('wrong')
+ val = plugin.ctl.stdout.getvalue()
+ self.failUnless(val.startswith('Error: bad argument wrong'), val)
+
+ def test_maintail_dashf(self):
plugin = self._makeOne()
+ plugin.listener = DummyListener()
+ result = plugin.do_maintail('-f')
+ errors = plugin.listener.errors
+ self.assertEqual(len(errors), 1)
+ error = errors[0]
+ self.assertEqual(plugin.listener.closed,
+ 'http://localhost:92491/mainlogtail')
+ self.assertEqual(error[0],
+ 'http://localhost:92491/mainlogtail')
+ for msg in ('Cannot connect', 'socket.error', '32', 'Broken pipe'):
+ self.assertTrue(msg in error[1])
+
+ def test_maintail_nobytes(self):
+ plugin = self._makeOne()
+ result = plugin.do_maintail('')
+ self.assertEqual(plugin.ctl.stdout.getvalue(), 'mainlogdata\n')
+
+ def test_maintail_dashbytes(self):
+ plugin = self._makeOne()
+ result = plugin.do_maintail('-100')
+ self.assertEqual(plugin.ctl.stdout.getvalue(), 'mainlogdata\n')
+
+ def test_maintail_readlog_error_nofile(self):
+ plugin = self._makeOne()
+ supervisor_rpc = plugin.ctl.get_supervisor()
from supervisor import xmlrpc
- import xmlrpclib
- def raise_fault(*arg, **kw):
- raise xmlrpclib.Fault(xmlrpc.Faults.CANT_REREAD, 'cant')
- plugin.ctl.options._server.supervisor.reloadConfig = raise_fault
- plugin.do_reread(None)
+ supervisor_rpc._readlog_error = xmlrpc.Faults.NO_FILE
+ result = plugin.do_maintail('-100')
self.assertEqual(plugin.ctl.stdout.getvalue(),
- 'ERROR: cant\n')
+ 'supervisord: ERROR (no log file)\n')
- def test__formatConfigInfo(self):
- info = { 'group': 'group1',
- 'name': 'process1',
- 'inuse': True,
- 'autostart': True,
- 'process_prio': 999,
- 'group_prio': 999 }
+ def test_maintail_readlog_error_failed(self):
plugin = self._makeOne()
- result = plugin._formatConfigInfo(info)
- self.assertTrue('in use' in result)
- info = { 'group': 'group1',
- 'name': 'process1',
- 'inuse': False,
- 'autostart': False,
- 'process_prio': 999,
- 'group_prio': 999 }
- result = plugin._formatConfigInfo(info)
- self.assertTrue('avail' in result)
+ supervisor_rpc = plugin.ctl.get_supervisor()
+ from supervisor import xmlrpc
+ supervisor_rpc._readlog_error = xmlrpc.Faults.FAILED
+ result = plugin.do_maintail('-100')
+ self.assertEqual(plugin.ctl.stdout.getvalue(),
+ 'supervisord: ERROR (unknown error reading log)\n')
- def test_avail(self):
- calls = []
+ def test_fg_too_few_args(self):
plugin = self._makeOne()
+ result = plugin.do_fg('')
+ lines = plugin.ctl.stdout.getvalue().split('\n')
+ self.assertEqual(result, None)
+ self.assertEqual(lines[0], 'Error: no process name supplied')
- class FakeSupervisor(object):
+ def test_fg_too_many_args(self):
+ plugin = self._makeOne()
+ result = plugin.do_fg('foo bar')
+ line = plugin.ctl.stdout.getvalue()
+ self.assertEqual(result, None)
+ self.assertEqual(line, 'Error: too many process names supplied\n')
+
+ def test_fg_badprocname(self):
+ plugin = self._makeOne()
+ result = plugin.do_fg('BAD_NAME')
+ line = plugin.ctl.stdout.getvalue()
+ self.assertEqual(result, None)
+ self.assertEqual(line, 'Error: bad process name supplied\n')
+
+ def test_fg_procnotrunning(self):
+ plugin = self._makeOne()
+ result = plugin.do_fg('bar')
+ line = plugin.ctl.stdout.getvalue()
+ self.assertEqual(result, None)
+ self.assertEqual(line, 'Error: process not running\n')
+ result = plugin.do_fg('baz_01')
+ lines = plugin.ctl.stdout.getvalue().split('\n')
+ self.assertEqual(result, None)
+ self.assertEqual(lines[-2], 'Error: process not running')
+
+class TestRereadControllerPlugin(unittest.TestCase):
+ def _getTargetClass(self):
+ from supervisor.supervisorctl import RereadControllerPlugin
+ return RereadControllerPlugin
+
+ def _makeOne(self, *arg, **kw):
+ klass = self._getTargetClass()
+ options = DummyClientOptions()
+ ctl = DummyController(options)
+ plugin = klass(ctl, *arg, **kw)
+ return plugin
+
+ def test_avail_handles_shutdown_state_fault(self):
+ plugin = self._makeOne()
+
+ class DummyRereadInterface:
def getAllConfigInfo(self):
- return [{ 'group': 'group1', 'name': 'process1',
+ import xmlrpclib
+ from supervisor.xmlrpc import Faults
+ raise xmlrpclib.Fault(Faults.SHUTDOWN_STATE, 'bye')
+
+ server = plugin.ctl.options._server
+ server.supervisor_reread = DummyRereadInterface()
+
+ result = plugin.do_avail('')
+ self.assertEqual(result, None)
+
+ self.assertEqual(plugin.ctl.stdout.getvalue(),
+ 'ERROR: supervisor shutting down\n')
+
+ def test_avail_displays_formatted_config_info(self):
+ plugin = self._makeOne()
+
+ class DummyRereadInterface:
+ def __init__(self):
+ first = {'group': 'group1', 'name': 'process1',
+ 'inuse': False, 'autostart': False,
+ 'process_prio': 999, 'group_prio': 999}
+ second = {'group': 'group2', 'name': 'process2',
'inuse': False, 'autostart': False,
- 'process_prio': 999, 'group_prio': 999 }]
+ 'process_prio': 999, 'group_prio': 999}
+ self.info = [first, second]
+
+ def getAllConfigInfo(self):
+ return self.info
+
+ server = plugin.ctl.options._server
+ interface = DummyRereadInterface()
+ server.supervisor_reread = interface
- plugin.ctl.get_supervisor = lambda : FakeSupervisor()
- plugin.ctl.output = calls.append
result = plugin.do_avail('')
self.assertEqual(result, None)
- def test_add(self):
+ expected = plugin._formatConfigInfo(interface.info[0]) + "\n" + \
+ plugin._formatConfigInfo(interface.info[1]) + "\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ def test_add_displays_error_and_help_when_given_no_args(self):
plugin = self._makeOne()
- result = plugin.do_add('foo')
+
+ result = plugin.do_add(' ') # extra whitespace intentional
self.assertEqual(result, None)
- supervisor = plugin.ctl.options._server.supervisor
- self.assertEqual(supervisor.processes, ['foo'])
- def test_add_already_added(self):
+ output = plugin.ctl.stdout.getvalue()
+ self.assertTrue('ERROR: add requires a process group name' in output)
+ self.assertTrue('add <name>' in output)
+
+ def test_add_will_add_a_single_process_group(self):
+ plugin = self._makeOne()
+
+ result = plugin.do_add(' foo') # extra whitespace intentional
+ self.assertEqual(result, None)
+
+ expected = 'foo: added process group\n'
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ interface = plugin.ctl.options._server.supervisor_reread
+ self.assertEqual(interface.added_process_groups, ['foo'])
+
+ def test_add_will_adds_multiple_process_groups(self):
+ plugin = self._makeOne()
+
+ result = plugin.do_add(' foo bar ') # extra whitespace intentional
+ self.assertEqual(result, None)
+
+ expected = "foo: added process group\n" \
+ "bar: added process group\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ interface = plugin.ctl.options._server.supervisor_reread
+ self.assertEqual(interface.added_process_groups, ['foo', 'bar'])
+
+ def test_add_handles_shutdown_state_fault(self):
+ plugin = self._makeOne()
+
+ result = plugin.do_add('SHUTDOWN_STATE')
+ self.assertEqual(result, None)
+
+ expected = "ERROR: shutting down\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ def test_add_handles_already_added_fault(self):
plugin = self._makeOne()
+
result = plugin.do_add('ALREADY_ADDED')
self.assertEqual(result, None)
- supervisor = plugin.ctl.options._server.supervisor
- self.assertEqual(plugin.ctl.stdout.getvalue(),
- 'ERROR: process group already active\n')
- def test_add_bad_name(self):
+ expected = "ERROR: process group already active\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ def test_add_handles_bad_name_fault(self):
plugin = self._makeOne()
+
result = plugin.do_add('BAD_NAME')
self.assertEqual(result, None)
- supervisor = plugin.ctl.options._server.supervisor
- self.assertEqual(plugin.ctl.stdout.getvalue(),
- 'ERROR: no such process/group: BAD_NAME\n')
- def test_remove(self):
+ expected = "ERROR: no such process/group: BAD_NAME\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ def test_remove_will_remove_a_single_process_group(self):
plugin = self._makeOne()
- supervisor = plugin.ctl.options._server.supervisor
- supervisor.processes = ['foo']
- result = plugin.do_remove('foo')
+
+ result = plugin.do_remove(' foo') # extra whitespace intentional
self.assertEqual(result, None)
- self.assertEqual(supervisor.processes, [])
- def test_remove_bad_name(self):
+ expected = "foo: removed process group\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ interface = plugin.ctl.options._server.supervisor_reread
+ self.assertEqual(interface.removed_process_groups, ['foo'])
+
+ def test_remove_will_remove_multiple_process_groups(self):
plugin = self._makeOne()
- supervisor = plugin.ctl.options._server.supervisor
- supervisor.processes = ['foo']
- result = plugin.do_remove('BAD_NAME')
+
+ result = plugin.do_remove(' foo bar ') # extra whitespace intentional
+ self.assertEqual(result, None)
+
+ expected = "foo: removed process group\n" \
+ "bar: removed process group\n"
+ self.assertEqual(plugin.ctl.stdout.getvalue(), expected)
+
+ interface = plugin.ctl.options._server.supervisor_reread
+ self.assertEqual(interface.removed_process_groups, ['foo', 'bar'])
+
+ def test_remove_handles_shutdown_state_fault(self):
+ plugin = self._makeOne()
+
+ result = plugin.do_remove('SHUTDOWN_STATE')
self.assertEqual(result, None)
+
self.assertEqual(plugin.ctl.stdout.getvalue(),
- 'ERROR: no such process/group: BAD_NAME\n')
+ 'ERROR: shutting down\n')
- def test_remove_still_running(self):
+ def test_remove_handles_still_running_fault(self):
plugin = self._makeOne()
- supervisor = plugin.ctl.options._server.supervisor
- supervisor.processes = ['foo']
+
result = plugin.do_remove('STILL_RUNNING')
self.assertEqual(result, None)
+
self.assertEqual(plugin.ctl.stdout.getvalue(),
'ERROR: process/group still running: STILL_RUNNING\n')
- def test_update_not_on_shutdown(self):
+ def test_remove_handles_bad_name_fault(self):
plugin = self._makeOne()
- supervisor = plugin.ctl.options._server.supervisor
- def reloadConfig():
- from supervisor import xmlrpc
- import xmlrpclib
- raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, 'blah')
- supervisor.reloadConfig = reloadConfig
- supervisor.processes = ['removed']
- plugin.do_update('')
- self.assertEqual(supervisor.processes, ['removed'])
+
+ result = plugin.do_remove('BAD_NAME')
+ self.assertEqual(result, None)
+
+ self.assertEqual(plugin.ctl.stdout.getvalue(),
+ 'ERROR: no such process/group: BAD_NAME\n')
+
+ def test_update_handles_shutdown_state_fault(self):
+ plugin = self._makeOne()
+
+ class DummyRereadInterface:
+ def reloadConfig(self):
+ from supervisor import xmlrpc
+ import xmlrpclib
+ raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, 'bye')
+
+ server = plugin.ctl.options._server
+ interface = DummyRereadInterface()
+ server.supervisor_reread = interface
+
+ result = plugin.do_update('')
+ self.assertEqual(result, None)
+
+ self.assertEquals(plugin.ctl.stdout.getvalue(),
+ 'ERROR: shutting down\n')
def test_update_added_procs(self):
plugin = self._makeOne()
+ supervisor_reread = plugin.ctl.options._server.supervisor_reread
supervisor = plugin.ctl.options._server.supervisor
+ supervisor_reread.processes = supervisor.processes
calls = []
def reloadConfig():
return [[['new_proc'], [], []]]
- supervisor.reloadConfig = reloadConfig
+ supervisor_reread.reloadConfig = reloadConfig
plugin.do_update('')
- self.assertEqual(supervisor.processes, ['new_proc'])
+ self.assertEqual(supervisor_reread.processes, ['new_proc'])
def test_update_changed_procs(self):
from supervisor import xmlrpc
@@ -649,14 +822,16 @@
plugin = self._makeOne()
supervisor = plugin.ctl.options._server.supervisor
+ supervisor_reread = plugin.ctl.options._server.supervisor_reread
+ supervisor.processes = supervisor_reread.processes = []
calls = []
def reloadConfig():
return [[[], ['changed_group'], []]]
- supervisor.reloadConfig = reloadConfig
+ supervisor_reread.reloadConfig = reloadConfig
supervisor.startProcess = lambda x: calls.append(('start', x))
- supervisor.addProcessGroup('changed_group') # fake existence
+ supervisor_reread.addProcessGroup('changed_group') # fake existence
results = [{'name': 'changed_process',
'group': 'changed_group',
'status': xmlrpc.Faults.SUCCESS,
@@ -669,7 +844,7 @@
plugin.do_update('')
self.assertEqual(calls, [('stop', 'changed_group')])
- supervisor.addProcessGroup('changed_group') # fake existence
+ supervisor_reread.addProcessGroup('changed_group') # fake existence
calls[:] = []
results[:] = [{'name': 'changed_process1',
'group': 'changed_group',
@@ -683,7 +858,7 @@
plugin.do_update('')
self.assertEqual(calls, [('stop', 'changed_group')])
- supervisor.addProcessGroup('changed_group') # fake existence
+ supervisor_reread.addProcessGroup('changed_group') # fake existence
calls[:] = []
results[:] = [{'name': 'changed_process1',
'group': 'changed_group',
@@ -702,16 +877,17 @@
plugin = self._makeOne()
supervisor = plugin.ctl.options._server.supervisor
+ supervisor_reread = plugin.ctl.options._server.supervisor_reread
def reloadConfig():
return [[[], [], ['removed_group']]]
- supervisor.reloadConfig = reloadConfig
+ supervisor_reread.reloadConfig = reloadConfig
results = [{'name': 'removed_process',
'group': 'removed_group',
'status': xmlrpc.Faults.SUCCESS,
'description': 'blah'}]
- supervisor.processes = ['removed_group']
+ supervisor.processes = supervisor_reread.processes = ['removed_group']
def stopProcessGroup(name):
return results
@@ -724,7 +900,7 @@
'group': 'removed_group',
'status': xmlrpc.Faults.NOT_RUNNING,
'description': 'blah'}]
- supervisor.processes = ['removed_group']
+ supervisor.processes = supervisor_reread.processes = ['removed_group']
plugin.do_update('')
self.assertEqual(supervisor.processes, [])
@@ -733,111 +909,55 @@
'group': 'removed_group',
'status': xmlrpc.Faults.FAILED,
'description': 'blah'}]
- supervisor.processes = ['removed_group']
+ supervisor.processes = supervisor_reread.processes = ['removed_group']
plugin.do_update('')
self.assertEqual(supervisor.processes, ['removed_group'])
- def test_pid(self):
- plugin = self._makeOne()
- result = plugin.do_pid('')
- options = plugin.ctl.options
- self.assertEqual(result, None)
- lines = plugin.ctl.stdout.getvalue().split('\n')
- self.assertEqual(len(lines), 2)
- self.assertEqual(lines[0], str(options._server.supervisor.getPID()))
-
- def test_maintail_toomanyargs(self):
- plugin = self._makeOne()
- result = plugin.do_maintail('foo bar')
- val = plugin.ctl.stdout.getvalue()
- self.failUnless(val.startswith('Error: too many'), val)
-
- def test_maintail_minus_string_fails(self):
- plugin = self._makeOne()
- result = plugin.do_maintail('-wrong')
- val = plugin.ctl.stdout.getvalue()
- self.failUnless(val.startswith('Error: bad argument -wrong'), val)
-
- def test_maintail_wrong(self):
- plugin = self._makeOne()
- result = plugin.do_maintail('wrong')
- val = plugin.ctl.stdout.getvalue()
- self.failUnless(val.startswith('Error: bad argument wrong'), val)
-
- def test_maintail_dashf(self):
- plugin = self._makeOne()
- plugin.listener = DummyListener()
- result = plugin.do_maintail('-f')
- errors = plugin.listener.errors
- self.assertEqual(len(errors), 1)
- error = errors[0]
- self.assertEqual(plugin.listener.closed,
- 'http://localhost:92491/mainlogtail')
- self.assertEqual(error[0],
- 'http://localhost:92491/mainlogtail')
- for msg in ('Cannot connect', 'socket.error', '32', 'Broken pipe'):
- self.assertTrue(msg in error[1])
-
- def test_maintail_nobytes(self):
- plugin = self._makeOne()
- result = plugin.do_maintail('')
- self.assertEqual(plugin.ctl.stdout.getvalue(), 'mainlogdata\n')
-
- def test_maintail_dashbytes(self):
+ def test__formatChanges(self):
plugin = self._makeOne()
- result = plugin.do_maintail('-100')
- self.assertEqual(plugin.ctl.stdout.getvalue(), 'mainlogdata\n')
+ # Don't explode, plz
+ plugin._formatChanges([['added'], ['changed'], ['removed']])
+ plugin._formatChanges([[], [], []])
- def test_maintail_readlog_error_nofile(self):
+ def test_reread(self):
plugin = self._makeOne()
- supervisor_rpc = plugin.ctl.get_supervisor()
- from supervisor import xmlrpc
- supervisor_rpc._readlog_error = xmlrpc.Faults.NO_FILE
- result = plugin.do_maintail('-100')
- self.assertEqual(plugin.ctl.stdout.getvalue(),
- 'supervisord: ERROR (no log file)\n')
+ calls = []
+ plugin._formatChanges = lambda x: calls.append(x)
+ result = plugin.do_reread(None)
+ self.assertEqual(result, None)
+ self.assertEqual(calls[0], [['added'], ['changed'], ['removed']])
- def test_maintail_readlog_error_failed(self):
+ def test_reread_Fault(self):
plugin = self._makeOne()
- supervisor_rpc = plugin.ctl.get_supervisor()
from supervisor import xmlrpc
- supervisor_rpc._readlog_error = xmlrpc.Faults.FAILED
- result = plugin.do_maintail('-100')
+ import xmlrpclib
+ def raise_fault(*arg, **kw):
+ raise xmlrpclib.Fault(xmlrpc.Faults.CANT_REREAD, 'cant')
+ plugin.ctl.options._server.supervisor_reread.reloadConfig = raise_fault
+ plugin.do_reread(None)
self.assertEqual(plugin.ctl.stdout.getvalue(),
- 'supervisord: ERROR (unknown error reading log)\n')
-
- def test_fg_too_few_args(self):
- plugin = self._makeOne()
- result = plugin.do_fg('')
- lines = plugin.ctl.stdout.getvalue().split('\n')
- self.assertEqual(result, None)
- self.assertEqual(lines[0], 'Error: no process name supplied')
-
- def test_fg_too_many_args(self):
- plugin = self._makeOne()
- result = plugin.do_fg('foo bar')
- line = plugin.ctl.stdout.getvalue()
- self.assertEqual(result, None)
- self.assertEqual(line, 'Error: too many process names supplied\n')
+ 'ERROR: cant\n')
- def test_fg_badprocname(self):
+ def test__formatConfigInfo(self):
+ info = { 'group': 'group1',
+ 'name': 'process1',
+ 'inuse': True,
+ 'autostart': True,
+ 'process_prio': 999,
+ 'group_prio': 999 }
plugin = self._makeOne()
- result = plugin.do_fg('BAD_NAME')
- line = plugin.ctl.stdout.getvalue()
- self.assertEqual(result, None)
- self.assertEqual(line, 'Error: bad process name supplied\n')
+ result = plugin._formatConfigInfo(info)
+ self.assertTrue('in use' in result)
+ info = { 'group': 'group1',
+ 'name': 'process1',
+ 'inuse': False,
+ 'autostart': False,
+ 'process_prio': 999,
+ 'group_prio': 999 }
+ result = plugin._formatConfigInfo(info)
+ self.assertTrue('avail' in result)
- def test_fg_procnotrunning(self):
- plugin = self._makeOne()
- result = plugin.do_fg('bar')
- line = plugin.ctl.stdout.getvalue()
- self.assertEqual(result, None)
- self.assertEqual(line, 'Error: process not running\n')
- result = plugin.do_fg('baz_01')
- lines = plugin.ctl.stdout.getvalue().split('\n')
- self.assertEqual(result, None)
- self.assertEqual(lines[-2], 'Error: process not running')
class DummyListener:
def __init__(self):
More information about the Supervisor-checkins
mailing list