]> git.llucax.com Git - software/pymin.git/blobdiff - pymin/pymindaemon.py
Move services outside the "static" pymin modules structure (refs #27).
[software/pymin.git] / pymin / pymindaemon.py
index 8ed3f1576b93b2db18fd793e5e4b2e99fc1615e9..6f4fa736534147539873a93d2be8b5f3dcf3a635 100644 (file)
@@ -10,11 +10,14 @@ command-line.
 
 import signal
 import socket
 
 import signal
 import socket
+import formencode
+import logging ; log = logging.getLogger('pymin.pymindaemon')
 
 from pymin.dispatcher import handler
 from pymin import dispatcher
 from pymin import eventloop
 from pymin import serializer
 
 from pymin.dispatcher import handler
 from pymin import dispatcher
 from pymin import eventloop
 from pymin import serializer
+from pymin import procman
 
 class PyminDaemon(eventloop.EventLoop):
     r"""PyminDaemon(root, bind_addr) -> PyminDaemon instance
 
 class PyminDaemon(eventloop.EventLoop):
     r"""PyminDaemon(root, bind_addr) -> PyminDaemon instance
@@ -35,6 +38,34 @@ class PyminDaemon(eventloop.EventLoop):
             def test(self, *args):
                 print 'test:', args
     >>> PyminDaemon(Root(), ('', 9999)).run()
             def test(self, *args):
                 print 'test:', args
     >>> PyminDaemon(Root(), ('', 9999)).run()
+
+    The daemon then will be listening to messages to UDP port 9999. Messages
+    will be dispatcher throgh the pymin.dispatcher mechanism. If all goes ok,
+    an OK response is sent. If there was a problem, an ERROR response is sent.
+
+    The general syntax of responses is::
+
+        (OK|ERROR) LENGTH
+        CSV MESSAGE
+
+    So, first is a response code (OK or ERROR), then it comes the length of
+    the CSV MESSAGE (the bufer needed to receive the rest of the message).
+
+    CSV MESSAGE is the body of the response, which it could be void (if lenght
+    is 0), a simple string (a CVS with only one column and row), a single row
+    or a full "table" (a CSV with multiple rows and columns).
+
+    There are 2 kind of errors considered "normal": dispatcher.Error and
+    formencode.Invalid. In general, response bodies of errors are simple
+    strings, except, for example, for formencode.Invalid errors where an
+    error_dict is provided. In that case the error is a "table", where the
+    first colunm is the name of an invalid argument, and the second is the
+    description of the error for that argument. Any other kind of exception
+    raised by the handlers will return an ERROR response with the description
+    "Internal server error".
+
+    All messages (requests and responses) should be UTF-8 encoded and the CVS
+    responses are formated in "Excel" format, as known by the csv module.
     """
 
     def __init__(self, root, bind_addr=('', 9999), timer=1):
     """
 
     def __init__(self, root, bind_addr=('', 9999), timer=1):
@@ -42,46 +73,65 @@ class PyminDaemon(eventloop.EventLoop):
 
         See PyminDaemon class documentation for more info.
         """
 
         See PyminDaemon class documentation for more info.
         """
+        log.debug(u'PyminDaemon(%r, %r, %r)', root, bind_addr, timer)
+        # Timer timeout time
+        self.timer = timer
         # Create and bind socket
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         sock.bind(bind_addr)
         # Create and bind socket
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         sock.bind(bind_addr)
+        # Signal handling
+        def quit(loop, signum):
+            log.debug(u'PyminDaemon quit() handler: signal %r', signum)
+            log.info(u'Shutting down...')
+            loop.stop() # tell main event loop to stop
+        def reload_config(loop, signum):
+            log.debug(u'PyminDaemon reload_config() handler: signal %r', signum)
+            log.info(u'Reloading configuration...')
+            # TODO iterate handlers list propagating reload action
+        def timer(loop, signum):
+            loop.handle_timer()
+            signal.alarm(loop.timer)
+        def child(loop, signum):
+            procman.sigchild_handler(signum)
         # Create EventLoop
         # Create EventLoop
-        eventloop.EventLoop.__init__(self, sock, timer=timer)
+        eventloop.EventLoop.__init__(self, sock, signals={
+                signal.SIGINT: quit,
+                signal.SIGTERM: quit,
+                signal.SIGUSR1: reload_config,
+                signal.SIGALRM: timer,
+                signal.SIGCHLD: child,
+            })
         # Create Dispatcher
         #TODO root.pymin = PyminHandler()
         self.dispatcher = dispatcher.Dispatcher(root)
         # Create Dispatcher
         #TODO root.pymin = PyminHandler()
         self.dispatcher = dispatcher.Dispatcher(root)
-        # Signal handling
-        def quit(signum, frame):
-            print "Shuting down ..."
-            self.stop() # tell main event loop to stop
-        def reload_config(signum, frame):
-            print "Reloading configuration..."
-            # TODO iterate handlers list propagating reload action
-        signal.signal(signal.SIGINT, quit)
-        signal.signal(signal.SIGTERM, quit)
-        signal.signal(signal.SIGUSR1, reload_config)
 
     def handle(self):
         r"handle() -> None :: Handle incoming events using the dispatcher."
         (msg, addr) = self.file.recvfrom(65535)
 
     def handle(self):
         r"handle() -> None :: Handle incoming events using the dispatcher."
         (msg, addr) = self.file.recvfrom(65535)
+        log.debug(u'PyminDaemon.handle: message %r from %r', msg, addr)
+        response = u'ERROR'
         try:
             result = self.dispatcher.dispatch(unicode(msg, 'utf-8'))
             if result is not None:
                 result = serializer.serialize(result)
         try:
             result = self.dispatcher.dispatch(unicode(msg, 'utf-8'))
             if result is not None:
                 result = serializer.serialize(result)
-            response = u'OK '
+            response = u'OK'
         except dispatcher.Error, e:
             result = unicode(e) + u'\n'
         except dispatcher.Error, e:
             result = unicode(e) + u'\n'
-            response = u'ERROR '
+        except formencode.Invalid, e:
+            if e.error_dict:
+                result = serializer.serialize(e.error_dict)
+            else:
+                result = unicode(e) + u'\n'
         except Exception, e:
             import traceback
             result = u'Internal server error\n'
         except Exception, e:
             import traceback
             result = u'Internal server error\n'
-            traceback.print_exc() # TODO logging!
-            response = u'ERROR '
+            log.exception(u'PyminDaemon.handle: unhandled exception')
         if result is None:
         if result is None:
-            response += u'0\n'
+            response += u' 0\n'
         else:
         else:
-            response += u'%d\n%s' % (len(result), result)
+            response += u' %d\n%s' % (len(result), result)
+        log.debug(u'PyminDaemon.handle: response %r to %r', response, addr)
         self.file.sendto(response.encode('utf-8'), addr)
 
     def handle_timer(self):
         self.file.sendto(response.encode('utf-8'), addr)
 
     def handle_timer(self):
@@ -90,19 +140,39 @@ class PyminDaemon(eventloop.EventLoop):
 
     def run(self):
         r"run() -> None :: Run the event loop (shortcut to loop())"
 
     def run(self):
         r"run() -> None :: Run the event loop (shortcut to loop())"
+        log.debug(u'PyminDaemon.loop()')
+        # Start the timer
+        self.handle_timer()
+        signal.alarm(self.timer)
+        # Loop
         try:
             return self.loop()
         except eventloop.LoopInterruptedError, e:
         try:
             return self.loop()
         except eventloop.LoopInterruptedError, e:
+            log.debug(u'PyminDaemon.loop: interrupted')
             pass
 
 if __name__ == '__main__':
 
             pass
 
 if __name__ == '__main__':
 
+    logging.basicConfig(
+        level   = logging.DEBUG,
+        format  = '%(asctime)s %(levelname)-8s %(message)s',
+        datefmt = '%H:%M:%S',
+    )
+
+    class Scheme(formencode.Schema):
+        mod = formencode.validators.OneOf(['upper', 'lower'], if_empty='lower')
+        ip = formencode.validators.CIDR
+
     class Root(dispatcher.Handler):
         @handler(u"Print all the arguments, return nothing.")
         def test(self, *args):
             print 'test:', args
         @handler(u"Echo the message passed as argument.")
     class Root(dispatcher.Handler):
         @handler(u"Print all the arguments, return nothing.")
         def test(self, *args):
             print 'test:', args
         @handler(u"Echo the message passed as argument.")
-        def echo(self, message):
+        def echo(self, message, mod=None, ip=None):
+            vals = Scheme.to_python(dict(mod=mod, ip=ip))
+            mod = vals['mod']
+            ip = vals['ip']
+            message = getattr(message, mod)()
             print 'echo:', message
             return message
 
             print 'echo:', message
             return message