]> git.llucax.com Git - software/pymin.git/commitdiff
Merge branch 'procman'
authorLeandro Lucarella <llucax@gmail.com>
Thu, 27 Dec 2007 15:53:10 +0000 (12:53 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Thu, 27 Dec 2007 15:53:10 +0000 (12:53 -0300)
* 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

1  2 
pymin/eventloop.py
pymin/pymindaemon.py
pymin/services/vrrp/__init__.py

diff --combined pymin/eventloop.py
index c9f6adeccecb653c284236af5550b7cf1acabf97,fe3c4eca57936743468be1d83a1c8a3fa3ded02d..0f720120730c1326c5ed852e35883ca96c18b344
@@@ -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,
      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:
  
      >>> 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
  
          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):
          Wait for events and handle then when they arrive. If once is True,
          then only 1 event is processed and then this method returns.
          """
-         # 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)
 +        log.debug(u'EventLoop.loop(%s)', once)
+         # 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
  
  
      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 a65f19cf57fa7d73f2f7f7bcaf6dbbf0f3227e12,3881857ea29c93d8276e5af33667900a08c1f510..067a727e265b42cd629f53deaa71fad585bb1020
@@@ -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
  
          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:
          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):
  
      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):
index 4739c528fc3e874f1299e1301f647826b4e34f9e,9fdfd1a526ce1755429fd9aca87ef8af0984b04e..f13db3ebf7b29f1b654c9230abc09397b11294ea
@@@ -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):
  
      _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):
-         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']))
 +        log.debug(u'VrrpHandler._service_start()')
+         procinfo = procman.get('vrrp')
+         procinfo.command = self._command
+         procinfo.persist = self.params['persist']
+         procman.start('vrrp')
  
      def _service_stop(self):
-         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)
 +        log.debug(u'VrrpHandler._service_stop()')
+         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()