]> git.llucax.com Git - software/pymin.git/blobdiff - pymin/pymindaemon.py
Add host name related validators to pymin.validation (refs #20)
[software/pymin.git] / pymin / pymindaemon.py
index ccb943dd48d4ebcd030c5a12aa94b9f3ebb853a1..6e080cb6e27deba19067b60bd469edb33f31924a 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,6 +73,7 @@ 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
         # Timer timeout time
         self.timer = timer
         # Create and bind socket
@@ -50,20 +82,25 @@ class PyminDaemon(eventloop.EventLoop):
         sock.bind(bind_addr)
         # Signal handling
         def quit(loop, signum):
         sock.bind(bind_addr)
         # Signal handling
         def quit(loop, signum):
-            print "Shuting down ..."
+            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):
             loop.stop() # tell main event loop to stop
         def reload_config(loop, signum):
-            print "Reloading configuration..."
+            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)
             # 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
         eventloop.EventLoop.__init__(self, sock, signals={
                 signal.SIGINT: quit,
                 signal.SIGTERM: quit,
                 signal.SIGUSR1: reload_config,
                 signal.SIGALRM: timer,
         # Create EventLoop
         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()
             })
         # Create Dispatcher
         #TODO root.pymin = PyminHandler()
@@ -72,23 +109,29 @@ class PyminDaemon(eventloop.EventLoop):
     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:
         try:
             result = self.dispatcher.dispatch(unicode(msg, 'utf-8'))
             if result is not None:
-                result = serializer.serialize(result)
-            response = u'OK '
+                result = unicode(serializer.serialize(result), 'utf-8')
+            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 = unicode(serializer.serialize(e.error_dict), 'utf-8')
+            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):
@@ -97,6 +140,7 @@ 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)
         # Start the timer
         self.handle_timer()
         signal.alarm(self.timer)
@@ -104,16 +148,31 @@ class PyminDaemon(eventloop.EventLoop):
         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