[Supervisor-checkins] r828 - in supervisor/trunk: . src/supervisor src/supervisor/tests

Chris McDonough chrism at agendaless.com
Sun Nov 23 14:36:01 EST 2008


Author: Chris McDonough <chrism at agendaless.com>
Date: Sun Nov 23 14:36:00 2008
New Revision: 828

Log:
Merge chrism-30-merge branch to trunk.


Modified:
   supervisor/trunk/CHANGES.txt
   supervisor/trunk/src/supervisor/options.py
   supervisor/trunk/src/supervisor/rpcinterface.py
   supervisor/trunk/src/supervisor/supervisorctl.py
   supervisor/trunk/src/supervisor/supervisord.py
   supervisor/trunk/src/supervisor/tests/base.py
   supervisor/trunk/src/supervisor/tests/test_options.py
   supervisor/trunk/src/supervisor/tests/test_rpcinterfaces.py
   supervisor/trunk/src/supervisor/tests/test_supervisorctl.py
   supervisor/trunk/src/supervisor/tests/test_supervisord.py
   supervisor/trunk/src/supervisor/xmlrpc.py

Modified: supervisor/trunk/CHANGES.txt
==============================================================================
--- supervisor/trunk/CHANGES.txt	(original)
+++ supervisor/trunk/CHANGES.txt	Sun Nov 23 14:36:00 2008
@@ -1,5 +1,69 @@
 Next Release
 
+  - Add ``reread``, ``update``, and ``avail`` commands based on Anders
+    Quist's ``online_config_reload.diff`` patch.  This patch extends
+    the "add" and "drop" commands with automagical behavior::
+
+      In supervisorctl:
+
+        supervisor> status
+        bar                              RUNNING    pid 14864, uptime 18:03:42
+        baz                              RUNNING    pid 23260, uptime 0:10:16
+        foo                              RUNNING    pid 14866, uptime 18:03:42
+        gazonk                           RUNNING    pid 23261, uptime 0:10:16
+        supervisor> avail
+        bar                              in use    auto      999:999
+        baz                              in use    auto      999:999
+        foo                              in use    auto      999:999
+        gazonk                           in use    auto      999:999
+        quux                             avail     auto      999:999
+
+      Now we add this to our conf:
+
+        [group:zegroup]
+        programs=baz,gazonk
+
+      Then we reread conf:
+
+        supervisor> reread
+        baz: disappeared
+        gazonk: disappeared
+        quux: available
+        zegroup: available
+        supervisor> avail
+        bar                              in use    auto      999:999
+        foo                              in use    auto      999:999
+        quux                             avail     auto      999:999
+        zegroup:baz                      avail     auto      999:999
+        zegroup:gazonk                   avail     auto      999:999
+        supervisor> status
+        bar                              RUNNING    pid 14864, uptime 18:04:18
+        baz                              RUNNING    pid 23260, uptime 0:10:52
+        foo                              RUNNING    pid 14866, uptime 18:04:18
+        gazonk                           RUNNING    pid 23261, uptime 0:10:52
+
+      The magic make-it-so command:
+
+        supervisor> update
+        baz: stopped
+        baz: removed process group
+        gazonk: stopped
+        gazonk: removed process group
+        zegroup: added process group
+        quux: added process group
+        supervisor> status
+        bar                              RUNNING    pid 14864, uptime 18:04:43
+        foo                              RUNNING    pid 14866, uptime 18:04:43
+        quux                             RUNNING    pid 23561, uptime 0:00:02
+        zegroup:baz                      RUNNING    pid 23559, uptime 0:00:02
+        zegroup:gazonk                   RUNNING    pid 23560, uptime 0:00:02
+        supervisor> avail
+        bar                              in use    auto      999:999
+        foo                              in use    auto      999:999
+        quux                             in use    auto      999:999
+        zegroup:baz                      in use    auto      999:999
+        zegroup:gazonk                   in use    auto      999:999
+
   - Fix bug with symptom "KeyError: 'process_name'" when using a logfile name 
     including documented 'process_name' Python string expansions.
 

Modified: supervisor/trunk/src/supervisor/options.py
==============================================================================
--- supervisor/trunk/src/supervisor/options.py	(original)
+++ supervisor/trunk/src/supervisor/options.py	Sun Nov 23 14:36:00 2008
@@ -75,6 +75,9 @@
     pass
 
 class Options:
+    stderr = sys.stderr
+    stdout = sys.stdout
+    exit = sys.exit
 
     uid = gid = None
 
@@ -124,13 +127,13 @@
         if help.find("%s") > 0:
             help = help.replace("%s", self.progname)
         print help,
-        sys.exit(0)
+        self.exit(0)
 
     def usage(self, msg):
         """Print a brief error message to stderr and exit(2)."""
-        sys.stderr.write("Error: %s\n" % str(msg))
-        sys.stderr.write("For help, use %s -h\n" % self.progname)
-        sys.exit(2)
+        self.stderr.write("Error: %s\n" % str(msg))
+        self.stderr.write("For help, use %s -h\n" % self.progname)
+        self.exit(2)
 
     def add(self,
             name=None,                  # attribute name on self
@@ -292,7 +295,7 @@
 
         self.process_config_file()
 
-    def process_config_file(self):
+    def process_config_file(self, do_usage=True):
         # Process config file
         if not hasattr(self.configfile, 'read'):
             self.here = os.path.abspath(os.path.dirname(self.configfile))
@@ -300,7 +303,12 @@
         try:
             self.read_config(self.configfile)
         except ValueError, msg:
-            self.usage(str(msg))
+            if do_usage:
+                # if this is not called from an RPC method, run usage and exit.
+                self.usage(str(msg))
+            else:
+                # if this is called from an RPC method, raise an error
+                raise ValueError(msg)
 
         # Copy config options to attributes of self.  This only fills
         # in options that aren't already set from the command line.
@@ -471,27 +479,11 @@
 
         self.identifier = section.identifier
 
-    def diff_process_groups(self, new):
-        cur = self.process_group_configs
-
-        curdict = dict(zip([cfg.name for cfg in cur], cur))
-        newdict = dict(zip([cfg.name for cfg in new], new))
-
-        added   = [cand for cand in new if cand.name not in curdict]
-        removed = [cand for cand in cur if cand.name not in newdict]
-
-        changed = [cand for cand in new
-                   if cand != curdict.get(cand.name, cand)]
-
-        return added, changed, removed
-
-    def process_config_file(self):
-        Options.process_config_file(self)
+    def process_config_file(self, do_usage=True):
+        Options.process_config_file(self, do_usage=do_usage)
 
         new = self.configroot.supervisord.process_group_configs
-        changes = self.diff_process_groups(new)
         self.process_group_configs = new
-        return changes
 
     def read_config(self, fp):
         section = self.configroot.supervisord
@@ -501,7 +493,10 @@
             except (IOError, OSError):
                 raise ValueError("could not find config file %s" % fp)
         parser = UnhosedConfigParser()
-        parser.readfp(fp)
+        try:
+            parser.readfp(fp)
+        except ConfigParser.ParsingError, why:
+            raise ValueError(str(why))
 
         if parser.has_section('include'):
             if not parser.has_option('include', 'files'):
@@ -518,7 +513,10 @@
                 for filename in glob.glob(pattern):
                     self.parse_warnings.append(
                         'Included extra file "%s" during parsing' % filename)
-                    parser.read(filename)
+                    try:
+                        parser.read(filename)
+                    except ConfigParser.ParsingError, why:
+                        raise ValueError(str(why))
 
         sections = parser.sections()
         if not 'supervisord' in sections:
@@ -919,11 +917,11 @@
                 self.logger.info("set current directory: %r"
                                  % self.directory)
         os.close(0)
-        sys.stdin = sys.__stdin__ = open("/dev/null")
+        self.stdin = sys.stdin = sys.__stdin__ = open("/dev/null")
         os.close(1)
-        sys.stdout = sys.__stdout__ = open("/dev/null", "w")
+        self.stdout = sys.stdout = sys.__stdout__ = open("/dev/null", "w")
         os.close(2)
-        sys.stderr = sys.__stderr__ = open("/dev/null", "w")
+        self.stderr = sys.stderr = sys.__stderr__ = open("/dev/null", "w")
         os.setsid()
         os.umask(self.umask)
         # XXX Stevens, in his Advanced Unix book, section 13.3 (page

Modified: supervisor/trunk/src/supervisor/rpcinterface.py
==============================================================================
--- supervisor/trunk/src/supervisor/rpcinterface.py	(original)
+++ supervisor/trunk/src/supervisor/rpcinterface.py	Sun Nov 23 14:36:00 2008
@@ -162,6 +162,25 @@
         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.
 
@@ -433,6 +452,29 @@
         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']
 

Modified: supervisor/trunk/src/supervisor/supervisorctl.py
==============================================================================
--- supervisor/trunk/src/supervisor/supervisorctl.py	(original)
+++ supervisor/trunk/src/supervisor/supervisorctl.py	Sun Nov 23 14:36:00 2008
@@ -778,6 +778,70 @@
     def help_reload(self):
         self.ctl.output("reload \t\tRestart the remote supervisord.")
 
+    def _formatChanges(self, (added, changed, dropped)):
+        changedict = {}
+        for n, t in [(added, 'available'),
+                     (changed, 'changed'),
+                     (dropped, 'disappeared')]:
+            changedict.update(dict(zip(n, [t] * len(n))))
+
+        if changedict:
+            for name in sorted(changedict):
+                self.ctl.output("%s: %s" % (name, changedict[name]))
+        else:
+            self.ctl.output("No config updates to processes")
+
+    def _formatConfigInfo(self, configinfo):
+        if configinfo['group'] == configinfo['name']:
+            name = configinfo['group']
+        else:
+            name = "%s:%s" % (configinfo['group'], configinfo['name'])
+        formatted = { 'name': name }
+        if configinfo['inuse']:
+            formatted['inuse'] = 'in use'
+        else:
+            formatted['inuse'] = 'avail'
+        if configinfo['autostart']:
+            formatted['autostart'] = 'auto'
+        else:
+            formatted['autostart'] = 'manual'
+        formatted['priority'] = "%s:%s" % (configinfo['group_prio'],
+                                           configinfo['process_prio'])
+
+        template = '%(name)-32s %(inuse)-9s %(autostart)-9s %(priority)s'
+        return template % formatted
+
+    def do_avail(self, arg):
+        supervisor = self.ctl.get_supervisor()
+        try:
+            configinfo = supervisor.getAllConfigInfo()
+        except xmlrpclib.Fault, e:
+            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+                self.ctl.output('ERROR: supervisor shutting down')
+        else:
+            for pinfo in configinfo:
+                self.ctl.output(self._formatConfigInfo(pinfo))
+
+    def help_avail(self):
+        self.ctl.output("avail\t\t\tDisplay all configured processes")
+
+    def do_reread(self, arg):
+        supervisor = self.ctl.get_supervisor()
+        try:
+            result = supervisor.reloadConfig()
+        except xmlrpclib.Fault, e:
+            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+                self.ctl.output('ERROR: supervisor shutting down')
+            elif e.faultCode == xmlrpc.Faults.CANT_REREAD:
+                self.ctl.output('ERROR: %s' % e.faultString)
+            else:
+                raise
+        else:
+            self._formatChanges(result[0])
+
+    def help_reread(self):
+        self.ctl.output("reread \t\t\tReload the daemon's configuration files")
+
     def do_add(self, arg):
         names = arg.strip().split()
 
@@ -819,12 +883,55 @@
                 else:
                     raise
             else:
-                self.ctl.output("%s: removed" % name)
+                self.ctl.output("%s: removed process group" % name)
 
     def help_remove(self):
         self.ctl.output("remove <name> [...]\tRemoves process/group from "
                         "active config")
 
+    def do_update(self, arg):
+        def log(name, message):
+            self.ctl.output("%s: %s" % (name, message))
+
+        supervisor = self.ctl.get_supervisor()
+        try:
+            result = supervisor.reloadConfig()
+        except xmlrpclib.Fault, e:
+            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+                self.ctl.output('ERROR: already shutting down')
+                return
+            else:
+                raise e
+
+        added, changed, removed = result[0]
+
+        for gname in removed:
+            results = supervisor.stopProcessGroup(gname)
+            log(gname, "stopped")
+
+            fails = [res for res in results
+                     if res['status'] == xmlrpc.Faults.FAILED]
+            if fails:
+                log(gname, "has problems; not removing")
+                continue
+            supervisor.removeProcessGroup(gname)
+            log(gname, "removed process group")
+
+        for gname in changed:
+            results = supervisor.stopProcessGroup(gname)
+            log(gname, "stopped")
+
+            supervisor.removeProcessGroup(gname)
+            supervisor.addProcessGroup(gname)
+            log(gname, "updated process group")
+
+        for gname in added:
+            supervisor.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']

Modified: supervisor/trunk/src/supervisor/supervisord.py
==============================================================================
--- supervisor/trunk/src/supervisor/supervisord.py	(original)
+++ supervisor/trunk/src/supervisor/supervisord.py	Sun Nov 23 14:36:00 2008
@@ -108,6 +108,22 @@
         finally:
             self.options.cleanup()
 
+    def diff_to_active(self, new=None):
+        if not new:
+            new = self.options.process_group_configs
+        cur = [group.config for group in self.process_groups.values()]
+
+        curdict = dict(zip([cfg.name for cfg in cur], cur))
+        newdict = dict(zip([cfg.name for cfg in new], new))
+
+        added   = [cand for cand in new if cand.name not in curdict]
+        removed = [cand for cand in cur if cand.name not in newdict]
+
+        changed = [cand for cand in new
+                   if cand != curdict.get(cand.name, cand)]
+
+        return added, changed, removed
+
     def add_process_group(self, config):
         name = config.name
         if name not in self.process_groups:

Modified: supervisor/trunk/src/supervisor/tests/base.py
==============================================================================
--- supervisor/trunk/src/supervisor/tests/base.py	(original)
+++ supervisor/trunk/src/supervisor/tests/base.py	Sun Nov 23 14:36:00 2008
@@ -79,6 +79,9 @@
         self.realizeargs = args
         self.realizekw = kw
 
+    def process_config_file(self, do_usage=True):
+        pass
+
     def cleanup_fds(self):
         self.fds_cleaned_up = True
 
@@ -767,7 +770,7 @@
         raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
 
     def reloadConfig(self):
-        return [[['added'], ['changed'], ['dropped']]]
+        return [[['added'], ['changed'], ['removed']]]
 
     def addProcessGroup(self, name):
         from xmlrpclib import Fault

Modified: supervisor/trunk/src/supervisor/tests/test_options.py
==============================================================================
--- supervisor/trunk/src/supervisor/tests/test_options.py	(original)
+++ supervisor/trunk/src/supervisor/tests/test_options.py	Sun Nov 23 14:36:00 2008
@@ -20,7 +20,7 @@
 
 class OptionTests(unittest.TestCase):
 
-    def _makeOptions(self):
+    def _makeOptions(self, read_error=False):
         from cStringIO import StringIO
         from supervisor.options import Options
         from supervisor.datatypes import integer
@@ -28,12 +28,15 @@
         class MyOptions(Options):
             master = {
                 'other': 41 }
-            def __init__(self):
+            def __init__(self, read_error=read_error):
+                self.read_error = read_error
                 Options.__init__(self)
                 class Foo(object): pass
                 self.configroot = Foo()
 
             def read_config(self, fp):
+                if self.read_error:
+                    raise ValueError(self.read_error)
                 # Pretend we read it from file:
                 self.configroot.__dict__.update(self.default_map)
                 self.configroot.__dict__.update(self.master)
@@ -73,6 +76,24 @@
         options.process_config_file()
         self.assertEquals(options.other, 42)
 
+    def test_config_reload_do_usage_false(self):
+        options = self._makeOptions(read_error='error')
+        self.assertRaises(ValueError, options.process_config_file,
+                          False)
+
+    def test_config_reload_do_usage_true(self):
+        options = self._makeOptions(read_error='error')
+        from StringIO import StringIO
+        L = []
+        def exit(num):
+            L.append(num)
+        options.stderr = options.stdout = StringIO()
+        options.exit = exit
+        options.configroot.anoption = 1
+        options.configroot.other = 1
+        options.process_config_file(True)
+        self.assertEqual(L, [2])
+
     def test__set(self):
         from supervisor.options import Options
         options = Options()
@@ -405,94 +426,6 @@
         self.assertTrue(section.process_group_configs is
                         instance.process_group_configs)
 
-    def test_diff_add_remove(self):
-        options = self._makeOne()
-
-        pconfig = DummyPConfig(options, 'process1', 'process1')
-        group1 = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
-
-        pconfig = DummyPConfig(options, 'process2', 'process2')
-        group2 = DummyPGroupConfig(options, 'group2', pconfigs=[pconfig])
-
-        new = [group1, group2]
-
-        added, changed, removed = options.diff_process_groups([])
-        self.assertEqual(added, options.process_group_configs)
-        self.assertEqual(removed, [])
-
-        options.process_group_configs = list(new)
-        added, changed, removed = options.diff_process_groups(new)
-        self.assertEqual(added, [])
-        self.assertEqual(changed, [])
-        self.assertEqual(removed, [])
-
-        pconfig = DummyPConfig(options, 'process3', 'process3')
-        new_group1 = DummyPGroupConfig(options, pconfigs=[pconfig])
-
-        pconfig = DummyPConfig(options, 'process4', 'process4')
-        new_group2 = DummyPGroupConfig(options, pconfigs=[pconfig])
-
-        new = [group2, new_group1, new_group2]
-
-        added, changed, removed = options.diff_process_groups(new)
-        self.assertEqual(added, [new_group1, new_group2])
-        self.assertEqual(changed, [])
-        self.assertEqual(removed, [group1])
-
-    def test_diff_changed(self):
-        from supervisor.options import ProcessConfig, ProcessGroupConfig
-
-        options = self._makeOne()
-
-        def make_pconfig(name, command, **params):
-            result = {
-                'name': name, 'command': command,
-                'directory': None, 'umask': None, 'priority': 999, 'autostart': True,
-                'autorestart': True, 'startsecs': 10, 'startretries': 999,
-                'uid': None, 'stdout_logfile': None, 'stdout_capture_maxbytes': 0,
-                'stdout_logfile_backups': 0, 'stdout_logfile_maxbytes': 0,
-                'stderr_logfile': None, 'stderr_capture_maxbytes': 0,
-                'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,
-                'redirect_stderr': False,
-                'stopsignal': None, 'stopwaitsecs': 10,
-                'exitcodes': (0,2), 'environment': None, 'serverurl': None }
-            result.update(params)
-            return ProcessConfig(options, **result)
-
-        def make_gconfig(name, pconfigs):
-            return ProcessGroupConfig(options, name, 25, pconfigs)
-
-        pconfig = make_pconfig('process1', 'process1', uid='new')
-        group1 = make_gconfig('group1', [pconfig])
-
-        pconfig = make_pconfig('process2', 'process2')
-        group2 = make_gconfig('group2', [pconfig])
-        new = [group1, group2]
-
-        pconfig = make_pconfig('process1', 'process1', uid='old')
-        group3 = make_gconfig('group1', [pconfig])
-
-        pconfig = make_pconfig('process2', 'process2')
-        group4 = make_gconfig('group2', [pconfig])
-        options.process_group_configs = [group4, group3]
-
-        added, changed, removed = options.diff_process_groups(new)
-
-        self.assertEqual([added, removed], [[], []])
-        self.assertEqual(changed, [group1])
-
-        options = self._makeOne()
-        pconfig1 = make_pconfig('process1', 'process1')
-        pconfig2 = make_pconfig('process2', 'process2')
-        group1 = make_gconfig('group1', [pconfig1, pconfig2])
-        new = [group1]
-
-        options.process_group_configs = [make_gconfig('group1', [pconfig1])]
-
-        added, changed, removed = options.diff_process_groups(new)
-        self.assertEqual([added, removed], [[], []])
-        self.assertEqual(changed, [group1])
-
     def test_readFile_failed(self):
         from supervisor.options import readFile
         try:

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	Sun Nov 23 14:36:00 2008
@@ -218,6 +218,30 @@
         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
@@ -821,6 +845,31 @@
         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)

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	Sun Nov 23 14:36:00 2008
@@ -472,6 +472,65 @@
         self.assertEqual(result, None)
         self.assertEqual(options._server.supervisor._shutdown, True)
 
+    def test__formatChanges(self):
+        plugin = self._makeOne()
+        # Don't explode, plz
+        plugin._formatChanges([['added'], ['changed'], ['removed']])
+        plugin._formatChanges([[], [], []])
+
+    def test_reread(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']])
+
+    def test_reread_Fault(self):
+        plugin = self._makeOne()
+        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)
+        self.assertEqual(plugin.ctl.stdout.getvalue(),
+                         'ERROR: cant\n')
+
+    def test__formatConfigInfo(self):
+        info = { 'group': 'group1',
+                 'name': 'process1',
+                 'inuse': True,
+                 'autostart': True,
+                 'process_prio': 999,
+                 'group_prio': 999 }
+        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)
+
+    def test_avail(self):
+        calls = []
+        plugin = self._makeOne()
+
+        class FakeSupervisor(object):
+            def getAllConfigInfo(self):
+                return [{ 'group': 'group1', 'name': 'process1',
+                          'inuse': False, 'autostart': False,
+                          'process_prio': 999, 'group_prio': 999 }]
+
+        plugin.ctl.get_supervisor = lambda : FakeSupervisor()
+        plugin.ctl.output = calls.append
+        result = plugin.do_avail('')
+        self.assertEqual(result, None)
+
     def test_add(self):
         plugin = self._makeOne()
         result = plugin.do_add('foo')
@@ -521,6 +580,125 @@
         self.assertEqual(plugin.ctl.stdout.getvalue(),
                          'ERROR: process/group still running: STILL_RUNNING\n')
 
+    def test_update_not_on_shutdown(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'])
+
+    def test_update_added_procs(self):
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+
+        calls = []
+        def reloadConfig():
+            return [[['new_proc'], [], []]]
+        supervisor.reloadConfig = reloadConfig
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, ['new_proc'])
+
+    def test_update_changed_procs(self):
+        from supervisor import xmlrpc
+        import xmlrpclib
+
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+
+        calls = []
+        def reloadConfig():
+            return [[[], ['changed_group'], []]]
+        supervisor.reloadConfig = reloadConfig
+        supervisor.startProcess = lambda x: calls.append(('start', x))
+
+        supervisor.addProcessGroup('changed_group') # fake existence
+        results = [{'name':        'changed_process',
+                    'group':       'changed_group',
+                    'status':      xmlrpc.Faults.SUCCESS,
+                    'description': 'blah'}]
+        def stopProcessGroup(name):
+            calls.append(('stop', name))
+            return results
+        supervisor.stopProcessGroup = stopProcessGroup
+
+        plugin.do_update('')
+        self.assertEqual(calls, [('stop', 'changed_group')])
+
+        supervisor.addProcessGroup('changed_group') # fake existence
+        calls[:] = []
+        results[:] = [{'name':        'changed_process1',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.NOT_RUNNING,
+                       'description': 'blah'},
+                      {'name':        'changed_process2',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.FAILED,
+                       'description': 'blah'}]
+
+        plugin.do_update('')
+        self.assertEqual(calls, [('stop', 'changed_group')])
+
+        supervisor.addProcessGroup('changed_group') # fake existence
+        calls[:] = []
+        results[:] = [{'name':        'changed_process1',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.FAILED,
+                       'description': 'blah'},
+                      {'name':        'changed_process2',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.SUCCESS,
+                       'description': 'blah'}]
+
+        plugin.do_update('')
+        self.assertEqual(calls, [('stop', 'changed_group')])
+
+    def test_update_removed_procs(self):
+        from supervisor import xmlrpc
+
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+
+        def reloadConfig():
+            return [[[], [], ['removed_group']]]
+        supervisor.reloadConfig = reloadConfig
+
+        results = [{'name':        'removed_process',
+                    'group':       'removed_group',
+                    'status':      xmlrpc.Faults.SUCCESS,
+                    'description': 'blah'}]
+        supervisor.processes = ['removed_group']
+
+        def stopProcessGroup(name):
+            return results
+        supervisor.stopProcessGroup = stopProcessGroup
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, [])
+
+        results[:] = [{'name':        'removed_process',
+                       'group':       'removed_group',
+                       'status':      xmlrpc.Faults.NOT_RUNNING,
+                       'description': 'blah'}]
+        supervisor.processes = ['removed_group']
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, [])
+
+        results[:] = [{'name':        'removed_process',
+                       'group':       'removed_group',
+                       'status':      xmlrpc.Faults.FAILED,
+                       'description': 'blah'}]
+        supervisor.processes = ['removed_group']
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, ['removed_group'])
+
     def test_pid(self):
         plugin = self._makeOne()
         result = plugin.do_pid('')

Modified: supervisor/trunk/src/supervisor/tests/test_supervisord.py
==============================================================================
--- supervisor/trunk/src/supervisor/tests/test_supervisord.py	(original)
+++ supervisor/trunk/src/supervisor/tests/test_supervisord.py	Sun Nov 23 14:36:00 2008
@@ -173,6 +173,106 @@
         self.assertEqual(options.logger.data[0],
                          'received SIGUSR1 indicating nothing')
 
+    def test_diff_add_remove(self):
+        options = DummyOptions()
+        supervisord = self._makeOne(options)
+
+        pconfig = DummyPConfig(options, 'process1', 'process1')
+        group1 = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
+
+        pconfig = DummyPConfig(options, 'process2', 'process2')
+        group2 = DummyPGroupConfig(options, 'group2', pconfigs=[pconfig])
+
+        new = [group1, group2]
+
+        added, changed, removed = supervisord.diff_to_active()
+        self.assertEqual(added, [])
+        self.assertEqual(changed, [])
+        self.assertEqual(removed, [])
+
+        added, changed, removed = supervisord.diff_to_active(new)
+        self.assertEqual(added, new)
+        self.assertEqual(changed, [])
+        self.assertEqual(removed, [])
+
+        supervisord.options.process_group_configs = new
+        added, changed, removed = supervisord.diff_to_active()
+        self.assertEqual(added, new)
+
+        supervisord.add_process_group(group1)
+        supervisord.add_process_group(group2)
+
+        pconfig = DummyPConfig(options, 'process3', 'process3')
+        new_group1 = DummyPGroupConfig(options, pconfigs=[pconfig])
+
+        pconfig = DummyPConfig(options, 'process4', 'process4')
+        new_group2 = DummyPGroupConfig(options, pconfigs=[pconfig])
+
+        new = [group2, new_group1, new_group2]
+
+        added, changed, removed = supervisord.diff_to_active(new)
+        self.assertEqual(added, [new_group1, new_group2])
+        self.assertEqual(changed, [])
+        self.assertEqual(removed, [group1])
+
+    def test_diff_changed(self):
+        from supervisor.options import ProcessConfig, ProcessGroupConfig
+
+        options = DummyOptions()
+        supervisord = self._makeOne(options)
+
+        def make_pconfig(name, command, **params):
+            result = {
+                'name': name, 'command': command,
+                'directory': None, 'umask': None, 'priority': 999, 'autostart': True,
+                'autorestart': True, 'startsecs': 10, 'startretries': 999,
+                'uid': None, 'stdout_logfile': None, 'stdout_capture_maxbytes': 0,
+                'stdout_logfile_backups': 0, 'stdout_logfile_maxbytes': 0,
+                'stderr_logfile': None, 'stderr_capture_maxbytes': 0,
+                'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,
+                'redirect_stderr': False,
+                'stopsignal': None, 'stopwaitsecs': 10,
+                'exitcodes': (0,2), 'environment': None, 'serverurl': None }
+            result.update(params)
+            return ProcessConfig(options, **result)
+
+        def make_gconfig(name, pconfigs):
+            return ProcessGroupConfig(options, name, 25, pconfigs)
+
+        pconfig = make_pconfig('process1', 'process1', uid='new')
+        group1 = make_gconfig('group1', [pconfig])
+
+        pconfig = make_pconfig('process2', 'process2')
+        group2 = make_gconfig('group2', [pconfig])
+        new = [group1, group2]
+
+        pconfig = make_pconfig('process1', 'process1', uid='old')
+        group3 = make_gconfig('group1', [pconfig])
+
+        pconfig = make_pconfig('process2', 'process2')
+        group4 = make_gconfig('group2', [pconfig])
+        supervisord.add_process_group(group3)
+        supervisord.add_process_group(group4)
+
+        added, changed, removed = supervisord.diff_to_active(new)
+
+        self.assertEqual([added, removed], [[], []])
+        self.assertEqual(changed, [group1])
+
+        options = DummyOptions()
+        supervisord = self._makeOne(options)
+
+        pconfig1 = make_pconfig('process1', 'process1')
+        pconfig2 = make_pconfig('process2', 'process2')
+        group1 = make_gconfig('group1', [pconfig1, pconfig2])
+        new = [group1]
+
+        supervisord.add_process_group(make_gconfig('group1', [pconfig1]))
+
+        added, changed, removed = supervisord.diff_to_active(new)
+        self.assertEqual([added, removed], [[], []])
+        self.assertEqual(changed, [group1])
+
     def test_add_process_group(self):
         options = DummyOptions()
         pconfig = DummyPConfig(options, 'foo', 'foo', '/bin/foo')

Modified: supervisor/trunk/src/supervisor/xmlrpc.py
==============================================================================
--- supervisor/trunk/src/supervisor/xmlrpc.py	(original)
+++ supervisor/trunk/src/supervisor/xmlrpc.py	Sun Nov 23 14:36:00 2008
@@ -45,6 +45,7 @@
     SUCCESS = 80
     ALREADY_ADDED = 90
     STILL_RUNNING = 91
+    CANT_REREAD = 92
 
 def getFaultDescription(code):
     for faultname in Faults.__dict__:


More information about the Supervisor-checkins mailing list