1 # vim: set encoding=utf-8 et sw=4 sts=4 :
4 Python Administration Daemon.
6 Python Administration Daemon is an modular, extensible administration tool
7 to administrate a set of services remotely (or localy) throw a simple
14 import logging ; log = logging.getLogger('pymin.pymindaemon')
16 from pymin.dispatcher import handler
17 from pymin import dispatcher
18 from pymin import eventloop
19 from pymin import serializer
20 from pymin import procman
22 class PyminDaemon(eventloop.EventLoop):
23 r"""PyminDaemon(root, bind_addr) -> PyminDaemon instance
25 This class is well suited to run as a single process. It handles
26 signals for controlled termination (SIGINT and SIGTERM), as well as
27 a user signal to reload the configuration files (SIGUSR1).
29 root - the root handler. This is passed directly to the Dispatcher.
31 bind_addr - is a tuple of (ip, port) where to bind the UDP socket to.
33 Here is a simple usage example:
35 >>> from pymin import dispatcher
36 >>> class Root(dispatcher.Handler):
37 @handler('Test command.')
38 def test(self, *args):
40 >>> PyminDaemon(Root(), ('', 9999)).run()
42 The daemon then will be listening to messages to UDP port 9999. Messages
43 will be dispatcher throgh the pymin.dispatcher mechanism. If all goes ok,
44 an OK response is sent. If there was a problem, an ERROR response is sent.
46 The general syntax of responses is::
51 So, first is a response code (OK or ERROR), then it comes the length of
52 the CSV MESSAGE (the bufer needed to receive the rest of the message).
54 CSV MESSAGE is the body of the response, which it could be void (if lenght
55 is 0), a simple string (a CVS with only one column and row), a single row
56 or a full "table" (a CSV with multiple rows and columns).
58 There are 2 kind of errors considered "normal": dispatcher.Error and
59 formencode.Invalid. In general, response bodies of errors are simple
60 strings, except, for example, for formencode.Invalid errors where an
61 error_dict is provided. In that case the error is a "table", where the
62 first colunm is the name of an invalid argument, and the second is the
63 description of the error for that argument. Any other kind of exception
64 raised by the handlers will return an ERROR response with the description
65 "Internal server error".
67 All messages (requests and responses) should be UTF-8 encoded and the CVS
68 responses are formated in "Excel" format, as known by the csv module.
71 def __init__(self, root, bind_addr=('', 9999), timer=1):
72 r"""Initialize the PyminDaemon object.
74 See PyminDaemon class documentation for more info.
76 log.debug(u'PyminDaemon(%r, %r, %r)', root, bind_addr, timer)
79 # Create and bind socket
80 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
81 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
84 def quit(loop, signum):
85 log.debug(u'PyminDaemon quit() handler: signal %r', signum)
86 log.info(u'Shutting down...')
87 loop.stop() # tell main event loop to stop
88 def reload_config(loop, signum):
89 log.debug(u'PyminDaemon reload_config() handler: signal %r', signum)
90 log.info(u'Reloading configuration...')
91 # TODO iterate handlers list propagating reload action
92 def timer(loop, signum):
94 signal.alarm(loop.timer)
95 def child(loop, signum):
96 procman.sigchild_handler(signum)
98 eventloop.EventLoop.__init__(self, sock, signals={
100 signal.SIGTERM: quit,
101 signal.SIGUSR1: reload_config,
102 signal.SIGALRM: timer,
103 signal.SIGCHLD: child,
106 #TODO root.pymin = PyminHandler()
107 self.dispatcher = dispatcher.Dispatcher(root)
110 r"handle() -> None :: Handle incoming events using the dispatcher."
111 (msg, addr) = self.file.recvfrom(65535)
112 log.debug(u'PyminDaemon.handle: message %r from %r', msg, addr)
115 result = self.dispatcher.dispatch(unicode(msg, 'utf-8'))
116 if result is not None:
117 result = serializer.serialize(result)
119 except dispatcher.Error, e:
120 result = unicode(e) + u'\n'
121 except formencode.Invalid, e:
123 result = serializer.serialize(e.error_dict)
125 result = unicode(e) + u'\n'
128 result = u'Internal server error\n'
129 log.exception(u'PyminDaemon.handle: unhandled exception')
133 response += u' %d\n%s' % (len(result), result)
134 log.debug(u'PyminDaemon.handle: response %r to %r', response, addr)
135 self.file.sendto(response.encode('utf-8'), addr)
137 def handle_timer(self):
138 r"handle_timer() -> None :: Call handle_timer() on handlers."
139 self.dispatcher.root.handle_timer()
142 r"run() -> None :: Run the event loop (shortcut to loop())"
143 log.debug(u'PyminDaemon.loop()')
146 signal.alarm(self.timer)
150 except eventloop.LoopInterruptedError, e:
151 log.debug(u'PyminDaemon.loop: interrupted')
154 if __name__ == '__main__':
157 level = logging.DEBUG,
158 format = '%(asctime)s %(levelname)-8s %(message)s',
159 datefmt = '%H:%M:%S',
162 class Scheme(formencode.Schema):
163 mod = formencode.validators.OneOf(['upper', 'lower'], if_empty='lower')
164 ip = formencode.validators.CIDR
166 class Root(dispatcher.Handler):
167 @handler(u"Print all the arguments, return nothing.")
168 def test(self, *args):
170 @handler(u"Echo the message passed as argument.")
171 def echo(self, message, mod=None, ip=None):
172 vals = Scheme.to_python(dict(mod=mod, ip=ip))
175 message = getattr(message, mod)()
176 print 'echo:', message
179 PyminDaemon(Root()).run()