Browse Source

Add clearProcessLog and clearAllProcessLogs along with supervisorctl clear command.

Chris McDonough 19 years ago
parent
commit
725d23019c
5 changed files with 215 additions and 2 deletions
  1. 1 1
      TODO.txt
  2. 38 0
      rpc.py
  3. 1 1
      sample.conf
  4. 58 0
      src/supervisor/supervisorctl.py
  5. 117 0
      src/supervisor/tests.py

+ 1 - 1
TODO.txt

@@ -1,7 +1,7 @@
 - Figure out why supervisord sends SIGKILL to processes at shutdown time
   so quickly (and why they report they got SIGINT).
 
-- Clear process logs.
+- Create named process log files after we setuid.
 
 - Tail main log.
 

+ 38 - 0
rpc.py

@@ -577,6 +577,44 @@ class SupervisorNamespaceRPCInterface:
 
         return True
 
+    def clearAllProcessLogs(self):
+        """ Clear all process log files
+
+        @return boolean result      Always return true
+        """
+        self._update('clearAllProcessLogs')
+        results  = []
+        callbacks = []
+
+        processnames = self.supervisord.processes.keys()
+        processnames.sort()
+        
+        for processname in processnames:
+            callbacks.append((processname, self.clearProcessLog))
+
+        def clearall():
+            if not callbacks:
+                return results
+
+            name, callback = callbacks.pop(0)
+            try:
+                callback(name)
+            except RPCError, e:
+                results.append({'name':name, 'status':e.code,
+                                'description':e.text})
+            else:
+                results.append({'name':name, 'status':Faults.SUCCESS,
+                                'description':'OK'})
+            
+            if callbacks:
+                return NOT_DONE_YET
+
+            return results
+        
+        clearall.delay = 0.05
+        clearall.rpcinterface = self
+        return clearall # deferred
+
     def _rotateMainLog(self):
         """ Rotate the main supervisord log (for debugging/testing) """
         self._update('_rotateMainLog')

+ 1 - 1
sample.conf

@@ -25,7 +25,7 @@ backofflimit=3              ; (child process restart seconds;default 3)
 ;directory=/tmp              ; (default is not to cd during start)
 
 [supervisorctl]
-serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL to specify a domain socket
+serverurl=unix://supervisor.sock ; use a unix:// URL to specify a domain socket
 ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
 ;username=chris              ; should be same as xmlrpc_username if set
 ;password=123                ; should be same as xmlrpc_password if set

+ 58 - 0
src/supervisor/supervisorctl.py

@@ -462,6 +462,64 @@ class Controller(cmd.Cmd):
     def help_reload(self):
         self._output("reload \t\tRestart the remote supervisord.")
 
+    def _clearresult(self, code, processname, default=None):
+        template = '%s: ERROR (%s)'
+        if code == rpc.Faults.BAD_NAME:
+            return template % (processname, 'no such process')
+        elif code == rpc.Faults.FAILED:
+            return template % (processname, 'failed')
+        elif code == rpc.Faults.SUCCESS:
+            return '%s: cleared' % processname
+        return default
+
+    def do_clear(self, arg):
+        if not self._upcheck():
+            return
+
+        processnames = arg.strip().split()
+
+        if not processnames:
+            self._output('Error: clear requires a process name')
+            self.help_clear()
+            return
+
+        supervisor = self._get_supervisor()
+
+        if 'all' in processnames:
+            results = supervisor.clearAllProcessLogs()
+            for result in results:
+                name = result['name']
+                code = result['status']
+                result = self._clearresult(code, name)
+                if result is None:
+                    # assertion
+                    raise ValueError('Unknown result code %s for %s' %
+                                     (code, name))
+                else:
+                    self._output(result)
+
+        else:
+
+            for processname in processnames:
+                try:
+                    result = supervisor.clearProcessLog(processname)
+                except xmlrpclib.Fault, e:
+                    error = self._clearresult(e.faultCode, processname)
+                    if error is not None:
+                        self._output(error)
+                    else:
+                        raise
+                else:
+                    if result == True:
+                        self._output('%s: cleared' % processname)
+                    else:
+                        raise # assertion
+
+    def help_clear(self):
+        self._output("clear <processname>\t\t\tClear a process' log file.")
+        self._output("clear <processname> <processname>\tclear multiple "
+                     "process log files")
+        self._output("clear all\t\t\t\tClear all process log files")
 
 def main(args=None, options=None):
     if options is None:

+ 117 - 0
src/supervisor/tests.py

@@ -653,6 +653,55 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         interface.clearProcessLog('foo')
         self.assertEqual(process.logsremoved, True)
 
+    def test_clearProcessLog_failed(self):
+        pconfig = DummyPConfig('foo', 'foo')
+        options = DummyOptions()
+        process = DummyProcess(options, pconfig)
+        process.error_at_clear = True
+        processes = {'foo': process}
+        supervisord = DummySupervisor(processes)
+        interface = self._makeOne(supervisord)
+        self.assertRaises(rpc.RPCError, interface.clearProcessLog, 'foo')
+        
+
+    def test_clearAllProcessLogs(self):
+        pconfig = DummyPConfig('foo', 'foo')
+        pconfig2 = DummyPConfig('bar', 'bar')
+        options = DummyOptions()
+        process = DummyProcess(options, pconfig)
+        process2= DummyProcess(options, pconfig2)
+        processes = {'foo': process, 'bar':process2}
+        supervisord = DummySupervisor(processes)
+        interface = self._makeOne(supervisord)
+        callback = interface.clearAllProcessLogs()
+        callback()
+        callback()
+        self.assertEqual(process.logsremoved, True)
+        self.assertEqual(process2.logsremoved, True)
+
+    def test_clearAllProcessLogs_onefails(self):
+        pconfig = DummyPConfig('foo', 'foo')
+        pconfig2 = DummyPConfig('bar', 'bar')
+        options = DummyOptions()
+        process = DummyProcess(options, pconfig)
+        process2= DummyProcess(options, pconfig2)
+        process2.error_at_clear = True
+        processes = {'foo': process, 'bar':process2}
+        supervisord = DummySupervisor(processes)
+        interface = self._makeOne(supervisord)
+        callback = interface.clearAllProcessLogs()
+        callback()
+        results = callback()
+        self.assertEqual(process.logsremoved, True)
+        self.assertEqual(process2.logsremoved, False)
+        self.assertEqual(len(results), 2)
+        self.assertEqual(results[0], {'name':'bar',
+                                      'status':rpc.Faults.FAILED,
+                                      'description':'FAILED: bar'})
+        self.assertEqual(results[1], {'name':'foo',
+                                      'status':rpc.Faults.SUCCESS,
+                                      'description':'OK'})
+
     def test_readFile_failed(self):
         from rpc import _readFile
         supervisord = DummySupervisor()
@@ -1481,6 +1530,55 @@ baz            STOPPED    Jun 26 11:42 PM (OK)
         self.assertEqual(result, None)
         self.assertEqual(options._server.supervisor._shutdown, True)
 
+
+
+
+
+    def test_clear_fail(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_clear('')
+        self.assertEqual(result, None)
+        expected = "Error: clear requires a process name"
+        self.assertEqual(controller.stdout.getvalue().split('\n')[0], expected)
+
+    def test_clear_badname(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_clear('BAD_NAME')
+        self.assertEqual(result, None)
+        self.assertEqual(controller.stdout.getvalue(),
+                         'BAD_NAME: ERROR (no such process)\n')
+
+    def test_clear_one_success(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_clear('foo')
+        self.assertEqual(result, None)
+        self.assertEqual(controller.stdout.getvalue(), 'foo: cleared\n')
+
+    def test_clear_many(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_clear('foo bar')
+        self.assertEqual(result, None)
+        self.assertEqual(controller.stdout.getvalue(),
+                         'foo: cleared\nbar: cleared\n')
+
+    def test_clear_all(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_clear('all')
+        self.assertEqual(result, None)
+
+        self.assertEqual(controller.stdout.getvalue(),
+         'foo: cleared\nfoo2: cleared\nfailed: ERROR (failed)\n')
+
 class TailFProducerTests(unittest.TestCase):
     def _getTargetClass(self):
         from http import tail_f_producer
@@ -1543,8 +1641,11 @@ class DummyProcess:
         self.backoff_done = False
         self.spawned = True
         self.state = state
+        self.error_at_clear = False
 
     def removelogs(self):
+        if self.error_at_clear:
+            raise IOError('whatever')
         self.logsremoved = True
 
     def get_state(self):
@@ -1734,6 +1835,22 @@ class DummySupervisorRPCNamespace:
         from xmlrpclib import Fault
         raise Fault(rpc.Faults.SHUTDOWN_STATE, '')
 
+    def clearProcessLog(self, name):
+        from xmlrpclib import Fault
+        if name == 'BAD_NAME':
+            raise Fault(rpc.Faults.BAD_NAME, 'BAD_NAME')
+        return True
+
+    def clearAllProcessLogs(self):
+        return [
+            {'name':'foo', 'status': rpc.Faults.SUCCESS, 'description': 'OK'},
+            {'name':'foo2', 'status': rpc.Faults.SUCCESS, 'description': 'OK'},
+            {'name':'failed', 'status':rpc.Faults.FAILED,
+             'description':'FAILED'}
+            ]
+        
+        
+
 class DummySystemRPCNamespace:
     pass