Explorar o código

rpc -> xmlrpc

Chris McDonough %!s(int64=19) %!d(string=hai) anos
pai
achega
7096947ccc
Modificáronse 6 ficheiros con 356 adicións e 73 borrados
  1. 1 2
      TODO.txt
  2. 1 1
      src/supervisor/http.py
  3. 19 18
      src/supervisor/supervisorctl.py
  4. 51 52
      src/supervisor/tests.py
  5. 283 0
      src/supervisor/web.py
  6. 1 0
      rpc.py

+ 1 - 2
TODO.txt

@@ -34,8 +34,7 @@
 
 - supervisorctl command history
 
-- test startup as root
-
 - stop logging all RPC requests in info mode when we ship
 
+- Test on Linux.
 

+ 1 - 1
src/supervisor/http.py

@@ -637,7 +637,7 @@ def make_http_server(supervisord):
     else:
         raise ValueError('Cannot determine socket type %r' % family)
 
-    from rpc import supervisor_xmlrpc_handler
+    from xmlrpc import supervisor_xmlrpc_handler
     from web import supervisor_ui_handler
     xmlrpchandler = supervisor_xmlrpc_handler(supervisord)
     tailhandler = logtail_handler(supervisord)

+ 19 - 18
src/supervisor/supervisorctl.py

@@ -38,9 +38,6 @@ import cmd
 import sys
 import time
 import getpass
-import supervisord
-import rpc
-from options import ClientOptions
 import xmlrpclib
 import urllib2
 import fcntl
@@ -51,6 +48,10 @@ import time
 import datetime
 import urlparse
 
+from options import ClientOptions
+import supervisord
+import xmlrpc
+
 class Controller(cmd.Cmd):
 
     def __init__(self, options, completekey='tab', stdin=None, stdout=None):
@@ -204,7 +205,7 @@ class Controller(cmd.Cmd):
         try:
             output = supervisor.readProcessLog(processname, -bytes, 0)
         except xmlrpclib.Fault, e:
-            if e.faultCode == rpc.Faults.FAILED:
+            if e.faultCode == xmlrpc.Faults.FAILED:
                 self._output("Error: Log file doesn't yet exist on server")
         else:
             self._output(output)
@@ -273,7 +274,7 @@ class Controller(cmd.Cmd):
                 try:
                     info = supervisor.getProcessInfo(processname)
                 except xmlrpclib.Fault, e:
-                    if e.faultCode == rpc.Faults.BAD_NAME:
+                    if e.faultCode == xmlrpc.Faults.BAD_NAME:
                         self._output('No such process %s' % processname)
                     else:
                         raise
@@ -293,15 +294,15 @@ class Controller(cmd.Cmd):
 
     def _startresult(self, code, processname, default=None):
         template = '%s: ERROR (%s)'
-        if code == rpc.Faults.BAD_NAME:
+        if code == xmlrpc.Faults.BAD_NAME:
             return template % (processname,'no such process')
-        elif code == rpc.Faults.ALREADY_STARTED:
+        elif code == xmlrpc.Faults.ALREADY_STARTED:
             return template % (processname,'already started')
-        elif code == rpc.Faults.SPAWN_ERROR:
+        elif code == xmlrpc.Faults.SPAWN_ERROR:
             return template % (processname, 'spawn error')
-        elif code == rpc.Faults.ABNORMAL_TERMINATION:
+        elif code == xmlrpc.Faults.ABNORMAL_TERMINATION:
             return template % (processname, 'abornal termination')
-        elif code == rpc.Faults.SUCCESS:
+        elif code == xmlrpc.Faults.SUCCESS:
             return '%s: started' % processname
         
         return default
@@ -358,11 +359,11 @@ class Controller(cmd.Cmd):
 
     def _stopresult(self, code, processname, default=None):
         template = '%s: ERROR (%s)'
-        if code == rpc.Faults.BAD_NAME:
+        if code == xmlrpc.Faults.BAD_NAME:
             return template % (processname, 'no such process')
-        elif code == rpc.Faults.NOT_RUNNING:
+        elif code == xmlrpc.Faults.NOT_RUNNING:
             return template % (processname, 'not running')
-        elif code == rpc.Faults.SUCCESS:
+        elif code == xmlrpc.Faults.SUCCESS:
             return '%s: stopped' % processname
         return default
 
@@ -452,7 +453,7 @@ class Controller(cmd.Cmd):
             try:
                 supervisor.shutdown()
             except xmlrpclib.Fault, e:
-                if e.faultCode == rpc.Faults.SHUTDOWN_STATE:
+                if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
                     self._output('ERROR: already shutting down')
             else:
                 self._output('Shut down')
@@ -472,7 +473,7 @@ class Controller(cmd.Cmd):
             try:
                 supervisor.restart()
             except xmlrpclib.Fault, e:
-                if e.faultCode == rpc.Faults.SHUTDOWN_STATE:
+                if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
                     self._output('ERROR: already shutting down')
             else:
                 self._output('Restarted supervisord')
@@ -482,11 +483,11 @@ class Controller(cmd.Cmd):
 
     def _clearresult(self, code, processname, default=None):
         template = '%s: ERROR (%s)'
-        if code == rpc.Faults.BAD_NAME:
+        if code == xmlrpc.Faults.BAD_NAME:
             return template % (processname, 'no such process')
-        elif code == rpc.Faults.FAILED:
+        elif code == xmlrpc.Faults.FAILED:
             return template % (processname, 'failed')
-        elif code == rpc.Faults.SUCCESS:
+        elif code == xmlrpc.Faults.SUCCESS:
             return '%s: cleared' % processname
         return default
 

+ 51 - 52
src/supervisor/tests.py

@@ -16,7 +16,7 @@ from StringIO import StringIO
 
 import supervisord
 import datatypes
-import rpc
+import xmlrpc
 import http
 from options import ServerOptions
 from supervisord import ProcessStates
@@ -189,7 +189,7 @@ class TestBase(unittest.TestCase):
     def _assertRPCError(self, code, callable, *args, **kw):
         try:
             callable(*args, **kw)
-        except rpc.RPCError, inst:
+        except xmlrpc.RPCError, inst:
             self.assertEqual(inst.code, code)
         else:
             raise AssertionError("Didnt raise")
@@ -197,7 +197,7 @@ class TestBase(unittest.TestCase):
 class MainXMLRPCInterfaceTests(TestBase):
 
     def _getTargetClass(self):
-        return rpc.RPCInterface
+        return xmlrpc.RPCInterface
 
     def _makeOne(self, *args, **kw):
         return self._getTargetClass()(*args, **kw)
@@ -211,12 +211,12 @@ class MainXMLRPCInterfaceTests(TestBase):
     def test_traverse(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
-        from rpc import traverse
-        self._assertRPCError(rpc.Faults.UNKNOWN_METHOD,
+        from xmlrpc import traverse
+        self._assertRPCError(xmlrpc.Faults.UNKNOWN_METHOD,
                              traverse, interface, 'notthere.hello', [])
-        self._assertRPCError(rpc.Faults.UNKNOWN_METHOD,
+        self._assertRPCError(xmlrpc.Faults.UNKNOWN_METHOD,
                              traverse, interface, 'supervisor._readFile', [])
-        self._assertRPCError(rpc.Faults.INCORRECT_PARAMETERS,
+        self._assertRPCError(xmlrpc.Faults.INCORRECT_PARAMETERS,
                              traverse, interface,
                              'supervisor.getIdentification', [1])
         self.assertEqual(
@@ -249,7 +249,7 @@ def makeSpew(unkillable=False):
 
 class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
     def _getTargetClass(self):
-        return rpc.SupervisorNamespaceRPCInterface
+        return xmlrpc.SupervisorNamespaceRPCInterface
 
     def _makeOne(self, *args, **kw):
         return self._getTargetClass()(*args, **kw)
@@ -260,14 +260,14 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         interface._update('foo')
         self.assertEqual(interface.update_text, 'foo')
         supervisord.state = SupervisorStates.SHUTDOWN
-        self._assertRPCError(rpc.Faults.SHUTDOWN_STATE, interface._update,
+        self._assertRPCError(xmlrpc.Faults.SHUTDOWN_STATE, interface._update,
                              'foo')
 
     def test_getVersion(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
         version = interface.getVersion()
-        self.assertEqual(version, rpc.RPC_VERSION)
+        self.assertEqual(version, xmlrpc.RPC_VERSION)
         self.assertEqual(interface.update_text, 'getVersion')
 
     def test_getIdentification(self):
@@ -291,7 +291,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
     def test_readLog_unreadable(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.NO_FILE, interface.readLog,
+        self._assertRPCError(xmlrpc.Faults.NO_FILE, interface.readLog,
                              offset=0, length=1)
 
     def test_readLog_badargs(self):
@@ -302,9 +302,9 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
             f = open(logfile, 'w+')
             f.write('x' * 2048)
             f.close()
-            self._assertRPCError(rpc.Faults.BAD_ARGUMENTS,
+            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,
                                  interface.readLog, offset=-1, length=1)
-            self._assertRPCError(rpc.Faults.BAD_ARGUMENTS,
+            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,
                                  interface.readLog, offset=-1,
                                  length=-1)
         finally:
@@ -334,7 +334,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
     def test_clearLog_unreadable(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.NO_FILE, interface.clearLog)
+        self._assertRPCError(xmlrpc.Faults.NO_FILE, interface.clearLog)
 
     def test_clearLog(self):
         supervisord = DummySupervisor()
@@ -377,13 +377,13 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         process.pid = 10
         supervisord = DummySupervisor({'foo':process})
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.ALREADY_STARTED,
+        self._assertRPCError(xmlrpc.Faults.ALREADY_STARTED,
                              interface.startProcess,'foo')
 
     def test_startProcess_badname(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.BAD_NAME,
+        self._assertRPCError(xmlrpc.Faults.BAD_NAME,
                              interface.startProcess,
                              'foo')
 
@@ -394,7 +394,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         process.spawnerr = 'abc'
         supervisord = DummySupervisor({'foo':process})
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.SPAWN_ERROR,
+        self._assertRPCError(xmlrpc.Faults.SPAWN_ERROR,
                              interface.startProcess,
                              'foo')
 
@@ -422,7 +422,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self.assertEqual(process.spawned, True)
         self.assertEqual(interface.update_text, 'startProcess')
         process.pid = 0
-        self._assertRPCError(rpc.Faults.ABNORMAL_TERMINATION,
+        self._assertRPCError(xmlrpc.Faults.ABNORMAL_TERMINATION,
                              callback, True)
     
     def test_startProcess_badtimeout(self):
@@ -431,7 +431,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         process = DummyProcess(options, config)
         supervisord = DummySupervisor({'foo':process})
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.BAD_ARGUMENTS,
+        self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,
                              interface.startProcess, 'foo', 'flee')
 
     def test_startAllProcesses(self):
@@ -465,7 +465,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
     def test_stopProcess_badname(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.BAD_NAME,
+        self._assertRPCError(xmlrpc.Faults.BAD_NAME,
                              interface.stopProcess, 'foo')
 
     def test_stopProcess(self):
@@ -595,7 +595,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         process = DummyProcess(options, config)
         supervisord = DummySupervisor({'process1':process})
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.NO_FILE,
+        self._assertRPCError(xmlrpc.Faults.NO_FILE,
                              interface.readProcessLog,
                              'process1', offset=0, length=1)
 
@@ -612,10 +612,10 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
             f = open(logfile, 'w+')
             f.write('x' * 2048)
             f.close()
-            self._assertRPCError(rpc.Faults.BAD_ARGUMENTS,
+            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,
                                  interface.readProcessLog,
                                  'process1', offset=-1, length=1)
-            self._assertRPCError(rpc.Faults.BAD_ARGUMENTS,
+            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,
                                  interface.readProcessLog, 'process1',
                                  offset=-1, length=-1)
         finally:
@@ -649,7 +649,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
     def test_clearProcessLog_bad_name(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
-        self._assertRPCError(rpc.Faults.BAD_NAME,
+        self._assertRPCError(xmlrpc.Faults.BAD_NAME,
                              interface.clearProcessLog,
                              'spew')
 
@@ -671,7 +671,7 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         processes = {'foo': process}
         supervisord = DummySupervisor(processes)
         interface = self._makeOne(supervisord)
-        self.assertRaises(rpc.RPCError, interface.clearProcessLog, 'foo')
+        self.assertRaises(xmlrpc.RPCError, interface.clearProcessLog, 'foo')
         
 
     def test_clearAllProcessLogs(self):
@@ -706,19 +706,19 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self.assertEqual(process2.logsremoved, False)
         self.assertEqual(len(results), 2)
         self.assertEqual(results[0], {'name':'bar',
-                                      'status':rpc.Faults.FAILED,
+                                      'status':xmlrpc.Faults.FAILED,
                                       'description':'FAILED: bar'})
         self.assertEqual(results[1], {'name':'foo',
-                                      'status':rpc.Faults.SUCCESS,
+                                      'status':xmlrpc.Faults.SUCCESS,
                                       'description':'OK'})
 
 class SystemNamespaceXMLRPCInterfaceTests(TestBase):
     def _getTargetClass(self):
-        return rpc.SystemNamespaceRPCInterface
+        return xmlrpc.SystemNamespaceRPCInterface
 
     def _makeOne(self):
         supervisord = DummySupervisor()
-        supervisor = rpc.SupervisorNamespaceRPCInterface(supervisord)
+        supervisor = xmlrpc.SupervisorNamespaceRPCInterface(supervisord)
         return self._getTargetClass()(
             [('supervisor', supervisor),
              ]
@@ -739,7 +739,7 @@ class SystemNamespaceXMLRPCInterfaceTests(TestBase):
 
     def test_methodSignature(self):
         interface = self._makeOne()
-        self._assertRPCError(rpc.Faults.SIGNATURE_UNSUPPORTED,
+        self._assertRPCError(xmlrpc.Faults.SIGNATURE_UNSUPPORTED,
                              interface.methodSignature,
                              ['foo.bar'])
         result = interface.methodSignature('system.methodSignature')
@@ -758,7 +758,7 @@ class SystemNamespaceXMLRPCInterfaceTests(TestBase):
             # Detect that here.
             try:
                 interface.methodSignature(k)
-            except rpc.RPCError:
+            except xmlrpc.RPCError:
                 raise AssertionError, ('methodSignature for %s raises '
                                        'RPCError (missing @return doc?)' % k)
 
@@ -862,7 +862,7 @@ class SystemNamespaceXMLRPCInterfaceTests(TestBase):
 
     def test_methodHelp(self):
         interface = self._makeOne()
-        self._assertRPCError(rpc.Faults.SIGNATURE_UNSUPPORTED,
+        self._assertRPCError(xmlrpc.Faults.SIGNATURE_UNSUPPORTED,
                              interface.methodHelp,
                              ['foo.bar'])
         help = interface.methodHelp('system.methodHelp')
@@ -1215,12 +1215,11 @@ class SubprocessTests(unittest.TestCase):
 
 class XMLRPCMarshallingTests(unittest.TestCase):
     def test_xmlrpc_marshal(self):
-        from rpc import xmlrpc_marshal
         import xmlrpclib
-        data = xmlrpc_marshal(1)
+        data = xmlrpc.xmlrpc_marshal(1)
         self.assertEqual(data, xmlrpclib.dumps((1,), methodresponse=True))
         fault = xmlrpclib.Fault(1, 'foo')
-        data = xmlrpc_marshal(fault)
+        data = xmlrpc.xmlrpc_marshal(fault)
         self.assertEqual(data, xmlrpclib.dumps(fault))
 
 class LogtailHandlerTests(unittest.TestCase):
@@ -1829,34 +1828,34 @@ class DummySupervisorRPCNamespace:
     def startProcess(self, name):
         from xmlrpclib import Fault
         if name == 'BAD_NAME':
-            raise Fault(rpc.Faults.BAD_NAME, 'BAD_NAME')
+            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')
         if name == 'ALREADY_STARTED':
-            raise Fault(rpc.Faults.ALREADY_STARTED, 'ALREADY_STARTED')
+            raise Fault(xmlrpc.Faults.ALREADY_STARTED, 'ALREADY_STARTED')
         if name == 'SPAWN_ERROR':
-            raise Fault(rpc.Faults.SPAWN_ERROR, 'SPAWN_ERROR')
+            raise Fault(xmlrpc.Faults.SPAWN_ERROR, 'SPAWN_ERROR')
         return True
 
     def startAllProcesses(self):
         return [
-            {'name':'foo', 'status': rpc.Faults.SUCCESS, 'description': 'OK'},
-            {'name':'foo2', 'status': rpc.Faults.SUCCESS, 'description': 'OK'},
-            {'name':'failed', 'status':rpc.Faults.SPAWN_ERROR,
+            {'name':'foo', 'status': xmlrpc.Faults.SUCCESS,'description': 'OK'},
+            {'name':'foo2', 'status':xmlrpc.Faults.SUCCESS,'description': 'OK'},
+            {'name':'failed', 'status':xmlrpc.Faults.SPAWN_ERROR,
              'description':'SPAWN_ERROR'}
             ]
 
     def stopProcess(self, name):
         from xmlrpclib import Fault
         if name == 'BAD_NAME':
-            raise Fault(rpc.Faults.BAD_NAME, 'BAD_NAME')
+            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')
         if name == 'NOT_RUNNING':
-            raise Fault(rpc.Faults.NOT_RUNNING, 'NOT_RUNNING')
+            raise Fault(xmlrpc.Faults.NOT_RUNNING, 'NOT_RUNNING')
         return True
     
     def stopAllProcesses(self):
         return [
-            {'name':'foo', 'status': rpc.Faults.SUCCESS, 'description': 'OK'},
-            {'name':'foo2', 'status': rpc.Faults.SUCCESS, 'description': 'OK'},
-            {'name':'failed', 'status':rpc.Faults.BAD_NAME,
+            {'name':'foo','status': xmlrpc.Faults.SUCCESS,'description': 'OK'},
+            {'name':'foo2', 'status':xmlrpc.Faults.SUCCESS,'description': 'OK'},
+            {'name':'failed', 'status':xmlrpc.Faults.BAD_NAME,
              'description':'FAILED'}
             ]
 
@@ -1865,26 +1864,26 @@ class DummySupervisorRPCNamespace:
             self._restarted = True
             return
         from xmlrpclib import Fault
-        raise Fault(rpc.Faults.SHUTDOWN_STATE, '')
+        raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
 
     def shutdown(self):
         if self._restartable:
             self._shutdown = True
             return
         from xmlrpclib import Fault
-        raise Fault(rpc.Faults.SHUTDOWN_STATE, '')
+        raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
 
     def clearProcessLog(self, name):
         from xmlrpclib import Fault
         if name == 'BAD_NAME':
-            raise Fault(rpc.Faults.BAD_NAME, 'BAD_NAME')
+            raise Fault(xmlrpc.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,
+            {'name':'foo', 'status':xmlrpc.Faults.SUCCESS,'description': 'OK'},
+            {'name':'foo2', 'status':xmlrpc.Faults.SUCCESS,'description': 'OK'},
+            {'name':'failed','status':xmlrpc.Faults.FAILED,
              'description':'FAILED'}
             ]
         

+ 283 - 0
src/supervisor/web.py

@@ -0,0 +1,283 @@
+import os
+import cgi
+import meld3
+import time
+from options import readFile
+from medusa import default_handler
+from medusa.http_server import http_date
+from medusa import producers
+
+class ViewContext:
+    def __init__(self, **kw):
+        self.__dict__.update(kw)
+
+class MeldView:
+    content_type = 'text/html'
+    def __init__(self, context):
+        self.context = context
+        template = self.context.template
+        if not os.path.isabs(template):
+            here = os.path.abspath(os.path.dirname(__file__))
+            template = os.path.join(here, template)
+        self.root = meld3.parse_xml(template)
+
+    def clone(self):
+        return self.root.clone()
+
+class TailView(MeldView):
+    def handle_query(self, query):
+        while query.startswith('?'):
+            query = query[1:]
+
+        params = cgi.parse_qs(query)
+        processname = params.get('processname',[None])[0]
+        return processname
+
+    def render(self):
+        supervisord = self.context.supervisord
+        request = self.context.request
+
+        path, params, query, fragment = request.split_uri()
+
+        if not query:
+            tail = 'No process name found'
+            processname = None
+        else:
+            processname = self.handle_query(query)
+            if not processname:
+                tail = 'No process name found'
+            else:
+                process = supervisord.processes.get(processname)
+                if process is None:
+                    tail = 'No such process %s' % processname
+                else:
+                    try:
+                        data = readFile(process.config.logfile, -1024, 0)
+                    except ValueError, e:
+                        tail = e.args[0]
+                    else:
+                        tail = data
+
+        root = self.clone()
+
+        title = root.findmeld('title')
+        title.content('Supervisor tail of process %s' % processname)
+        tailbody = root.findmeld('tailbody')
+        tailbody.content(tail)
+
+        refresh_anchor = root.findmeld('refresh_anchor')
+        if processname is not None:
+            refresh_anchor.attributes(href='tail.html?processname=%s' %
+                                      processname)
+        else:
+            refresh_anchor.deparent()
+
+        return root.write_xhtmlstring()
+
+class StatusView(MeldView):
+    def actions_for_process(self, process):
+        state = process.get_state()
+        from supervisord import ProcessStates
+        processname = process.config.name
+        start = {
+        'name':'Start',
+        'href':'index.html?processname=%s&action=start' % processname,
+        'target':None,
+        }
+        restart = {
+        'name':'Restart',
+        'href':'index.html?processname=%s&action=restart' % processname,
+        'target':None,
+        }
+        stop = {
+        'name':'Stop',
+        'href':'index.html?processname=%s&action=stop' % processname,
+        'target':None,
+        }
+        clearlog = {
+        'name':'Clear Log',
+        'href':'index.html?processname=%s&action=clearlog' % processname,
+        'target':None,
+        }
+        tailf = {
+        'name':'Tail -f',
+        'href':'logtail/%s' % processname,
+        'target':'_blank'
+        }
+        if state == ProcessStates.RUNNING:
+            actions = [restart, stop, clearlog, tailf]
+        elif state in (ProcessStates.STOPPED, ProcessStates.KILLED,
+                       ProcessStates.NOTSTARTED, ProcessStates.EXITED,
+                       ProcessStates.ERROR):
+            actions = [start, None, clearlog, tailf]
+        else:
+            actions = [None, None, clearlog, tailf]
+        return actions
+
+    def css_class_for_state(self, state):
+        from supervisord import ProcessStates
+        if state == ProcessStates.RUNNING:
+            return 'statusrunning'
+        elif state in (ProcessStates.KILLED, ProcessStates.ERROR):
+            return 'statuserror'
+        else:
+            return 'statusnominal'
+
+    def handle_query(self, query):
+        message = None
+        supervisord = self.context.supervisord
+
+        while query.startswith('?'):
+            query = query[1:]
+
+        params = cgi.parse_qs(query)
+        processname = params.get('processname',[None])[0]
+        action = params.get('action', [None])[0]
+
+        if action:
+            t = time.ctime()
+            if action == 'refresh':
+                message = 'Page refreshed at %s' % t
+
+            elif processname:
+                process = supervisord.processes.get(processname)
+                if process is None:
+                    message = 'No such process %s at %s' % (processname, t)
+                else:
+                    if action == 'stop':
+                        process.stop()
+                        message = 'Stopped %s at %s' % (processname, t)
+                    if action == 'restart':
+                        msg = process.stop()
+                        if not msg:
+                            # XXX busywait
+                            while not process.reportstatusmsg:
+                                supervisord.reap()
+                                supervisord.handle_procs_with_waitstatus()
+                            process.spawn()
+                            message = 'Restarted %s at %s' % (processname, t)
+                        else:
+                            message = msg
+                    if action == 'start':
+                        process.spawn()
+                        print "process pid", process.pid
+                        # XXX busywait
+                        time.sleep(.5)
+                        supervisord.reap()
+                        supervisord.handle_procs_with_waitstatus()
+                        message = 'Started %s at %s' % (processname, t)
+                    if action == 'clearlog':
+                        process.removelogs()
+                        message = 'Cleared log of %s at %s' % (processname, t)
+
+        return message
+    
+    def render(self):
+        supervisord = self.context.supervisord
+        request = self.context.request
+        message = None
+        path, params, query, fragment = request.split_uri()
+
+        if query:
+            message = self.handle_query(query)
+        
+        processnames = supervisord.processes.keys()
+        processnames.sort()
+        data = []
+        for processname in processnames:
+            process = supervisord.processes[processname]
+            state = process.get_state()
+            from supervisord import getProcessStateDescription
+            statedesc = getProcessStateDescription(state)
+            actions = self.actions_for_process(process)
+            data.append({'status':statedesc, 'name':processname,
+                         'actions':actions,
+                         'state':state})
+        
+        root = self.clone()
+
+        if message is not None:
+            statusarea = root.findmeld('statusmessage')
+            statusarea.attrib['class'] = 'statusmessage'
+            statusarea.content(message)
+            
+        iterator = root.findmeld('tr').repeat(data)
+        for element, item in iterator:
+            status_text = element.findmeld('status_text')
+            status_text.content(item['status'])
+            status_text.attrib['class'] = self.css_class_for_state(
+                item['state'])
+            anchor = element.findmeld('name_anchor')
+            processname = item['name']
+            anchor.attributes(href='tail.html?processname=%s' % processname)
+            anchor.content(processname)
+            actions = item['actions']
+            actionitem_td = element.findmeld('actionitem_td')
+            for element, actionitem in actionitem_td.repeat(actions):
+                if actionitem is None:
+                    element.content(' ', structure=True)
+                else:
+                    anchor = element.findmeld('actionitem_anchor')
+                    anchor.attributes(href=actionitem['href'])
+                    anchor.content(actionitem['name'])
+                    if actionitem['target']:
+                        anchor.attributes(target=actionitem['target'])
+
+        return root.write_xhtmlstring()
+
+VIEWS = {
+    'index.html': {
+          'template':'ui/status.html',
+          'view':StatusView
+          },
+    'tail.html': {
+           'template':'ui/tail.html',
+           'view':TailView,
+           },
+    }
+
+class supervisor_ui_handler(default_handler.default_handler):
+    IDENT = 'Supervisor Web UI HTTP Request Handler'
+
+    def __init__(self, filesystem, supervisord):
+        self.supervisord = supervisord
+        default_handler.default_handler.__init__(self, filesystem)
+
+    def handle_request(self, request):
+        if request.command not in self.valid_commands:
+            request.error (400) # bad request
+            return
+
+        path, params, query, fragment = request.split_uri()
+
+        if '%' in path:
+            path = cgi.unquote (path)
+
+        # strip off all leading slashes
+        while path and path[0] == '/':
+            path = path[1:]
+
+        if not path:
+            path = 'index.html'
+
+        viewdata = VIEWS.get(path)
+
+        if viewdata is not None:
+            context = ViewContext(template=viewdata['template'],
+                                  request=request,
+                                  supervisord=self.supervisord)
+            view = viewdata['view'](context)
+            rendering = view.render()
+            request['Last-Modified'] = http_date.build_http_date(time.time())
+            request['Content-Type'] = view.content_type
+            request['Pragma'] = 'no-cache'
+            request['Cache-Control'] = 'no-cache'
+            request['Expires'] = http_date.build_http_date(0)
+            request['Content-Length'] = len(rendering)
+            if request.command == 'GET':
+                request.push(producers.simple_producer(rendering))
+                request.done()
+                return
+
+        return default_handler.default_handler.handle_request(self, request)
+

+ 1 - 0
rpc.py

@@ -15,6 +15,7 @@ import traceback
 import StringIO
 import tempfile
 import errno
+
 from http import NOT_DONE_YET
 from options import readFile
 from options import gettags