From: Leandro Lucarella Date: Thu, 27 Dec 2007 15:53:10 +0000 (-0300) Subject: Merge branch 'procman' X-Git-Url: https://git.llucax.com/software/pymin.git/commitdiff_plain/55efba31a241826ed31597b14f2b5eb9efe808f1?ds=sidebyside;hp=-c Merge branch 'procman' * procman: Bugfix: set the catched signal to None *after* calling the signal handler. Improve vrrp restorable defaults readability. Use procman.restart() to restart vrrp service. Add ProcessManager.restart() method to block until the restart is done. Use procman to manage vrrp service. Bugfix: raise a KeyError, don't return it. Bugfix: call ProcessInfo.stop() in the right way (without parameters). Bugfix: use self instead of an unbinded pi object when killing a process. Remove unused ProcessInfo.last_return attribute. Bugfix: use correct module for simbol ECHILD. Handle SIGCHLD in PyminDaemon. Add a global ProcessManager instance and functions to procman module. Implement timer in PyminDaemon using EventLoop signal handling. Support general signals handling in EventLoop. Replace time.sleep() for signal.pause() in the ProcessManager test. Improve ProcessManager to manage registerable named services. Add a ProcessManager class to manage processes. Improve ProcessManager to manage registerable named services. Add a ProcessManager class to manage processes. Conflicts: pymin/eventloop.py pymin/pymindaemon.py pymin/services/vrrp/__init__.py --- 55efba31a241826ed31597b14f2b5eb9efe808f1 diff --combined pymin/eventloop.py index c9f6ade,fe3c4ec..0f72012 --- a/pymin/eventloop.py +++ b/pymin/eventloop.py @@@ -10,7 -10,6 +10,7 @@@ import selec import errno import signal from select import POLLIN, POLLPRI, POLLERR +import logging ; log = logging.getLogger('pymin.eventloop') __ALL__ = ('EventLoop', 'LoopInterruptedError') @@@ -38,16 -37,16 +38,16 @@@ class LoopInterruptedError(RuntimeError r"str(obj) -> String representation." return 'Loop interrupted: %s' % self.select_error - # Flag to know if a timer was expired - timeout = False + # Flag to know if a signal was caught + signals = list() # Alarm Signal handler - def alarm_handler(signum, stack_frame): - global timeout - timeout = True + def signal_handler(signum, stack_frame): + global signals + signals.append(signum) class EventLoop: - r"""EventLoop(file[, timer[, handler[, timer_handler]]]) -> EventLoop. + r"""EventLoop(file[, handler[, signals]]]) -> EventLoop. This class implements a simple event loop based on select module. It "listens" to activity a single 'file' object (a file, a pipe, @@@ -55,8 -54,11 +55,11 @@@ function (or the handle() method if you prefer subclassing) every time the file is ready for reading (or has an error). - If a 'timer' is supplied, then the timer_handler() function object - (or the handle_timer() method) is called every 'timer' seconds. + 'signals' is a dictionary with signals to be handled by the loop, + where keys are signal numbers and values are callbacks (which takes + 2 arguments, first the event loop that captured the signal, and then + the captured signal number). Callbacks can be None if all signals + are handled by the handle_signal() member function. This is a really simple example of usage using a hanlder callable: @@@ -76,38 -78,55 +79,56 @@@ >>> class Test(EventLoop): >>> def handle(self): >>> data = os.read(self.fileno, 100) - >>> if data == 'q\n': - >>> self.stop() - >>> else: - >>> os.write(1, 'Received message: %r\n' % data) - >>> def handle_timer(self): - >>> print time.strftime('%c') - >>> p = Test(0, timer=5) + >>> os.write(1, 'Received message: %r\n' % data) + >>> def handle_signal(self, signum): + >>> os.write(1, 'Signal %d received, stopping\n' % signum) + >>> self.stop() + >>> p = Test(0, signals={signal.SIGTERM: None, signal.SIGINT: None}) >>> p.loop() - This example loops until the user enters a single "q", when stop() - is called and the event loop is exited. + This example loops until the user enter interrupts the program (by + pressing Ctrl-C) or untile the program is terminated by a TERM signal + (kill) when stop() is called and the event loop is exited. """ - def __init__(self, file, handler=None, timer=None, timer_handler=None): + def __init__(self, file, handler=None, signals=None): r"""Initialize the EventLoop object. See EventLoop class documentation for more info. """ - log.debug(u'EventLoop(%r, %r, %r, %r)', file, handler, - timer, timer_handler) ++ log.debug(u'EventLoop(%r, %r, %r)', file, handler, signals) self.poll = select.poll() self._stop = False self.__register(file) - self.timer = timer self.handler = handler - self.timer_handler = timer_handler + self.signals = dict() + if signals is None: + signals = dict() + for (signum, sighandler) in signals.items(): + self.set_signal(signum, sighandler) def __register(self, file): r"__register(file) -> None :: Register a new file for polling." self._file = file self.poll.register(self.fileno, POLLIN | POLLPRI | POLLERR) + def set_signal(self, signum, sighandler): + prev = self.signals.get(signum, None) + # If the signal was not already handled, handle it + if signum not in self.signals: + signal.signal(signum, signal_handler) + self.signals[signum] = sighandler + return prev + + def get_signal_handler(self, signum): + return self.signals[signum] + + def unset_signal(self, signum): + prev = self.signals[signum] + # Restore the default handler + signal.signal(signum, signal.SIG_DFL) + return prev + def set_file(self, file): r"""set_file(file) -> None :: New file object to be monitored @@@ -137,7 -156,6 +158,7 @@@ The event loop will be interrupted as soon as the current handler finishes. """ + log.debug(u'EventLoop.stop()') self._stop = True def loop(self, once=False): @@@ -146,54 -164,38 +167,49 @@@ Wait for events and handle then when they arrive. If once is True, then only 1 event is processed and then this method returns. """ + log.debug(u'EventLoop.loop(%s)', once) - # Flag modified by the signal handler - global timeout - # If we use a timer, we set up the signal - if self.timer is not None: - signal.signal(signal.SIGALRM, alarm_handler) - self.handle_timer() - signal.alarm(self.timer) + # List of pending signals + global signals while True: try: + log.debug(u'EventLoop.loop: polling') res = self.poll.poll() except select.error, e: - # The error is not an interrupt caused by the alarm, then raise - if e.args[0] != errno.EINTR or not timeout: + # The error is not an interrupt caused by a signal, then raise + if e.args[0] != errno.EINTR or not signals: raise LoopInterruptedError(e) - # There was a timeout, so execute the timer handler - if timeout: - log.debug(u'EventLoop.loop: timer catched, handling...') - timeout = False - self.handle_timer() - signal.alarm(self.timer) - # Not a timeout, execute the regular handler - else: - log.debug(u'EventLoop.loop: no timeout, handle event') + # If we have signals to process, we just do it + have_signals = bool(signals) + while signals: - self.handle_signal(signals.pop(0)) ++ signum = signals.pop(0) ++ log.debug(u'EventLoop.loop: processing signal %d...', signum) ++ self.handle_signal(signum) + # No signals to process, execute the regular handler + if not have_signals: ++ log.debug(u'EventLoop.loop: processing event...') self.handle() - import os # Look if we have to stop if self._stop or once: + log.debug(u'EventLoop.loop: stopped') self._stop = False break def handle(self): - r"handle() -> None :: Abstract method to be overriden to handle events." + r"handle() -> None :: Handle file descriptor events." self.handler(self) - def handle_timer(self): - r"handle() -> None :: Abstract method to be overriden to handle events." - self.timer_handler(self) + def handle_signal(self, signum): + r"handle_signal(signum) -> None :: Handles signals." + self.signals[signum](self, signum) if __name__ == '__main__': + logging.basicConfig( + level = logging.DEBUG, + format = '%(asctime)s %(levelname)-8s %(message)s', + datefmt = '%H:%M:%S', + ) + import os import time @@@ -203,23 -205,21 +219,21 @@@ p = EventLoop(0, handle) -- os.write(1, 'Say something once: ') ++ os.write(1, 'Say something once:\n') p.loop(once=True) os.write(1, 'Great!\n') class Test(EventLoop): def handle(self): data = os.read(self.fileno, 100) - if data == 'q\n': - self.stop() - else: - os.write(1, 'Received message: %r\n' % data) - def handle_timer(self): - print time.strftime('%c') + os.write(1, 'Received message: %r\n' % data) + def handle_signal(self, signum): + os.write(1, 'Signal %d received, stopping\n' % signum) + self.stop() - p = Test(0, timer=5) + p = Test(0, signals={signal.SIGTERM: None, signal.SIGINT: None}) - os.write(1, 'Say a lot of things, then press write just "q" to stop: ') + os.write(1, 'Say a lot of things, then press Ctrl-C or kill me to stop: ') p.loop() os.write(1, 'Ok, bye!\n') diff --combined pymin/pymindaemon.py index a65f19c,3881857..067a727 --- a/pymin/pymindaemon.py +++ b/pymin/pymindaemon.py @@@ -10,12 -10,12 +10,13 @@@ command-line import signal import socket +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 import procman class PyminDaemon(eventloop.EventLoop): r"""PyminDaemon(root, bind_addr) -> PyminDaemon instance @@@ -43,33 -43,39 +44,43 @@@ 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 EventLoop - eventloop.EventLoop.__init__(self, sock, timer=timer) - # Create Dispatcher - #TODO root.pymin = PyminHandler() - self.dispatcher = dispatcher.Dispatcher(root) # Signal handling - def quit(signum, frame): + def quit(loop, signum): - print "Shuting down ..." + log.debug(u'PyminDaemon quit() handler: signal %r', signum) + log.info(u'Shutting down...') - self.stop() # tell main event loop to stop - def reload_config(signum, frame): + 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 - signal.signal(signal.SIGINT, quit) - signal.signal(signal.SIGTERM, quit) - signal.signal(signal.SIGUSR1, reload_config) + 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, + signal.SIGCHLD: child, + }) + # Create Dispatcher + #TODO root.pymin = PyminHandler() + self.dispatcher = dispatcher.Dispatcher(root) 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) try: result = self.dispatcher.dispatch(unicode(msg, 'utf-8')) if result is not None: @@@ -81,13 -87,12 +92,13 @@@ 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: response += u'0\n' else: 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): @@@ -96,21 -101,17 +107,25 @@@ 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: + log.debug(u'PyminDaemon.loop: interrupted') pass if __name__ == '__main__': + logging.basicConfig( + level = logging.DEBUG, + format = '%(asctime)s %(levelname)-8s %(message)s', + datefmt = '%H:%M:%S', + ) + class Root(dispatcher.Handler): @handler(u"Print all the arguments, return nothing.") def test(self, *args): diff --combined pymin/services/vrrp/__init__.py index 4739c52,9fdfd1a..f13db3e --- a/pymin/services/vrrp/__init__.py +++ b/pymin/services/vrrp/__init__.py @@@ -4,8 -4,8 +4,9 @@@ import o from os import path from signal import SIGTERM from subprocess import Popen, PIPE +import logging ; log = logging.getLogger('pymin.services.vrrp') + from pymin import procman from pymin.seqtools import Sequence from pymin.dispatcher import Handler, handler, HandlerError from pymin.services.util import Restorable, TransactionalHandler, \ @@@ -14,6 -14,8 +15,8 @@@ __ALL__ = ('VrrpHandler',) + # FIXME the the command should not use new parameters unless commit where called + # i.e. integrate commit with procman to update internal procman parameters. class VrrpHandler(Restorable, ParametersHandler, ReloadHandler, RestartHandler, ServiceHandler, TransactionalHandler): @@@ -22,51 -24,47 +25,57 @@@ _persistent_attrs = ['params'] _restorable_defaults = dict( - params = dict( ipaddress='192.168.0.1', - id = '1', - prio = '', - dev = 'eth0', - ), - ) + params = dict( + ipaddress = '192.168.0.1', + id = '1', + prio = '', + dev = 'eth0', + persist = True, + ), + ) + + @property + def _command(self): + command = ['vrrpd', '-i', self.params['dev'], '-v', self.params['id']] + if self.params['prio']: + command.extend(('-p', self.params['prio'])) + command.append(self.params['ipaddress']) + return command def _service_start(self): + log.debug(u'VrrpHandler._service_start()') - if self.params['prio'] != '': - call(('vrrp', '-i', self.params['dev'], '-v', self.params['id'], - '-p', self.params['prio'], self.params['ipaddress'])) - else: - call(('vrrp', '-i', self.params['dev'], '-v', self.params['id'], \ - self.params['ipaddress'])) + procinfo = procman.get('vrrp') + procinfo.command = self._command + procinfo.persist = self.params['persist'] + procman.start('vrrp') def _service_stop(self): + log.debug(u'VrrpHandler._service_stop()') - try: - pid_filename = 'vrrpd_%(dev)s_%(id)s.pid' % self.params - log.debug(u'VrrpHandler._service_stop: getting pid from %r', - pid_filename) - pid = file(path.join(self._pid_dir, pid_filename )).read() - pid = int(pid.strip()) - log.debug(u'VrrpHandler._service_stop: killing pid %r', pid) - os.kill(pid, SIGTERM) - except (IOError, OSError), e: - log.debug(u'VrrpHandler._service_stop: error %r', e) + procman.stop('vrrp') + + def _service_restart(self): + procinfo = procman.get('vrrp') + procinfo.command = self._command + procinfo.persist = self.params['persist'] + procman.restart('vrrp') def __init__(self, pickle_dir='.', config_dir='.', pid_dir='.'): + log.debug(u'VrrpHandler(%r, %r, $r)', pickle_dir, config_dir, pid_dir) self._persistent_dir = pickle_dir self._pid_dir = pid_dir + procman.register('vrrp', None) ServiceHandler.__init__(self) if __name__ == '__main__': + + logging.basicConfig( + level = logging.DEBUG, + format = '%(asctime)s %(levelname)-8s %(message)s', + datefmt = '%H:%M:%S', + ) + v = VrrpHandler() - v.set('prio','10') + v.set('prio', '10') v.commit()