]> git.llucax.com Git - software/pymin.git/commitdiff
Merge branch 'master' of or3st3s@baryon.com.ar:workspace/pymin into suse
authorjack2 <jack2@linux-48bh.site>
Sat, 27 Oct 2007 17:45:19 +0000 (14:45 -0300)
committerjack2 <jack2@linux-48bh.site>
Sat, 27 Oct 2007 17:45:19 +0000 (14:45 -0300)
Conflicts:

config.py

14 files changed:
TODO
config.py
pymin/dispatcher.py
pymin/eventloop.py
pymin/pymindaemon.py
pymin/services/dhcp/__init__.py
pymin/services/dns/__init__.py
pymin/services/firewall/__init__.py
pymin/services/ip/__init__.py
pymin/services/nat/__init__.py
pymin/services/ppp/__init__.py
pymin/services/proxy/__init__.py
pymin/services/util.py
pymin/services/vrrp/__init__.py

diff --git a/TODO b/TODO
index 3438f7e8d8f80c7820786bfd089278f58369df4d..fd3e0ce653474d0a263d745864405b69e6f1cf7d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,17 +1,6 @@
 
 Ideas / TODO:
 
-* Revisar interacción entre firewall y nat que ambos usan iptables. Es probable
-  que al manipular reglas por número de índice se complique todo porque tengo
-  indices separados por tipo de regla, entonces si pongo borrar la 2 tal vez es
-  la 13 en vez de la dos porque hay otras 11 reglas de otros sub-servicios que
-  usan iptables. Tal vez la solución simple es hacer algo como:
-  router firewall add [regla]
-  router nat masq add [masq]
-  router nat forward add [port]
-  router nat snat add [snat]
-  (u organizándolo de otra forma pero que tengan todos un root en común)
-
 * Agregar soporte de opciones de línea de comando/archivo de conf para:
   * Dry run.
   * Seleccionar servicios a usar.
@@ -20,31 +9,19 @@ Ideas / TODO:
   * Paths.
 
 * SubHandlers:
-  * ComposeDictSubHandler con soporte de dirty/del/add (para ip y DNS).
-  * Agregar SimpleDictSubHandler? (que no use una clase, que use un dict
-    de strings directamente, para Proxy Users por ej.). Ídem List.
   * Agregar SetSubHandler? (para Proxy Hosts)
 
 * Agregar logging.
 
 * Agregar validación con formencode.
 
-* Ver como manejar la información sobre si un servicio está andando o no. Si se
-  agrega una acción 'status' para ver el estado y si ese estado se saca de posta
-  de /proc o si es un estado interno y se asume que los servicios no se caen (no
-  creo que sea una buena idea esto último). Además habría que ver cuando arranca
-  el pymin, si se inician servicios automáticamente o no y si la info de qué
-  servicios iniciar o no es persistente y si puede configurarla el usuario.
+* Hacer que el estado sobre si un servicio está andando o no sea más confiable
+  que un simple flag interno (en caso de ver que realmente esté corriendo,
+  probablemente sea una buena idea que haya un flag que indique si hay que
+  levantarlo en el inicio).
 
 * No usar comandos con templates, porque después si no hay que ejecutarlos con
   un shell (porque el template devuelve un string todo grande) y hay que andar
   teniendo cuidado de escapar las cosas (y hay riesgos de seguridad de shell
   injection).
 
-Estas cosas quedan sujetas a necesitada y a definición del protocolo.
-Para mí lo ideal es que el protocolo de red sea igual que la consola del
-usuario, porque después de todo no va a ser más que eso, mandar comanditos.
-
-Por otro lado, el cliente de consola, por que no es el cliente web pero
-accedido via ssh usando un navegador de texto como w3m???
-
index ee3c174cf3dd0eb6c02506e2c15c71c47f4e84d2..db3151b2f93a4125e33cacd519736e817a33a5a4 100644 (file)
--- a/config.py
+++ b/config.py
@@ -10,17 +10,25 @@ pickle_path = join(base_path, 'pickle')
 config_path = join(base_path, 'config')
 
 class Root(Handler):
-    ip = IpHandler(
-        pickle_dir = join(pickle_path, 'ip'),
-        config_dir = join(config_path, 'ip'))
 
     firewall = FirewallHandler(
         pickle_dir = join(pickle_path, 'firewall'),
         config_dir = '/tmp')
 
-    dhcp = DhcpHandler(
-        pickle_dir = join(pickle_path, 'dhcp'),
-        config_dir = '/etc')
+    nat = NatHandler(pickle_dir = join(pickle_path, 'nat'))
+
+    ppp = PppHandler(
+        pickle_dir = join(pickle_path, 'ppp'),
+        config_dir = {
+            'pap-secrets':  '/etc/ppp',
+            'chap-secrets': '/etc/ppp',
+            'options.X':    '/etc/ppp',
+            'nameX':        '/etc/ppp/peers',
+        })
+
+    ip = IpHandler(
+        pickle_dir = join(pickle_path, 'ip'),
+        config_dir = join(config_path, 'ip'))
 
     dns = DnsHandler(
         pickle_dir = join(pickle_path, 'dns'),
@@ -29,7 +37,9 @@ class Root(Handler):
             'zoneX.zone': '/var/lib/named',
         })
 
-    nat = NatHandler(pickle_dir = join(pickle_path, 'nat'))
+    dhcp = DhcpHandler(
+        pickle_dir = join(pickle_path, 'dhcp'),
+        config_dir = '/etc')
 
     proxy = ProxyHandler(
         pickle_dir = join(pickle_path, 'proxy'),
@@ -40,15 +50,6 @@ class Root(Handler):
         config_dir = join(config_path, 'vrrp'),
         pid_dir    = '/var/run')
 
-    ppp = PppHandler(
-        pickle_dir = join(pickle_path, 'ppp'),
-        config_dir = {
-            'pap-secrets':  '/etc/ppp',
-            'chap-secrets': '/etc/ppp',
-            'options.X':    '/etc/ppp',
-            'nameX':        '/etc/ppp/peers',
-        })
-
 bind_addr = \
 (
     '',   # Bind IP ('' is ANY)
index 17075a3f1dfec8617432dd68fb532022f7299b2b..24f0d1f14e6a52b6f97fd49664d767b07382a99a 100644 (file)
@@ -216,6 +216,17 @@ class Handler:
             raise HelpNotFoundError(command)
         return handler.handler_help
 
+    def handle_timer(self):
+        r"""handle_timer() -> None :: Do periodic tasks.
+
+        By default we do nothing but calling handle_timer() on subhandlers.
+        """
+        for a in dir(self):
+            if a == 'parent': continue # Skip parents in SubHandlers
+            h = getattr(self, a)
+            if isinstance(h, Handler):
+                h.handle_timer()
+
 def parse_command(command):
     r"""parse_command(command) -> (args, kwargs) :: Parse a command.
 
index 06922a72fe9290bcfb5133700e49ae6187d0dc3c..b6be5a1003d891b7e857a166b9523647de3a3005 100644 (file)
@@ -7,6 +7,8 @@ Please see EventLoop class documentation for more info.
 """
 
 import select
+import errno
+import signal
 from select import POLLIN, POLLPRI, POLLERR
 
 __ALL__ = ('EventLoop', 'LoopInterruptedError')
@@ -35,8 +37,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
+
+# Alarm Signal handler
+def alarm_handler(signum, stack_frame):
+    global timeout
+    timeout = True
+
 class EventLoop:
-    r"""EventLoop(file[, handler]) -> EventLoop instance
+    r"""EventLoop(file[, timer[, handler[, timer_handler]]]) -> EventLoop.
 
     This class implements a simple event loop based on select module.
     It "listens" to activity a single 'file' object (a file, a pipe,
@@ -44,9 +54,12 @@ class EventLoop:
     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.
+
     This is a really simple example of usage using a hanlder callable:
 
-    >>> import os 
+    >>> import os
     >>> def handle(event_loop):
             data = os.read(event_loop.fileno, 100)
             os.write(1, 'Received message: %r\n' % data)
@@ -66,14 +79,16 @@ class EventLoop:
     >>>             self.stop()
     >>>         else:
     >>>             os.write(1, 'Received message: %r\n' % data)
-    >>> p = Test(0)
+    >>>     def handle_timer(self):
+    >>>         print time.strftime('%c')
+    >>> p = Test(0, timer=5)
     >>> p.loop()
 
     This example loops until the user enters a single "q", when stop()
     is called and the event loop is exited.
     """
 
-    def __init__(self, file, handler=None):
+    def __init__(self, file, handler=None, timer=None, timer_handler=None):
         r"""Initialize the EventLoop object.
 
         See EventLoop class documentation for more info.
@@ -81,7 +96,9 @@ class EventLoop:
         self.poll = select.poll()
         self._stop = False
         self.__register(file)
+        self.timer = timer
         self.handler = handler
+        self.timer_handler = timer_handler
 
     def __register(self, file):
         r"__register(file) -> None :: Register a new file for polling."
@@ -125,26 +142,44 @@ class EventLoop:
         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)
+            signal.alarm(self.timer)
         while True:
             try:
                 res = self.poll.poll()
             except select.error, e:
-                raise LoopInterruptedError(e)
-            if self.handler is not None:
-                self.handler(self)
+                # The error is not an interrupt caused by the alarm, then raise
+                if e.args[0] != errno.EINTR or not timeout:
+                    raise LoopInterruptedError(e)
+            # There was a timeout, so execute the timer handler
+            if timeout:
+                timeout = False
+                self.handle_timer()
+                signal.alarm(self.timer)
+            # Not a timeout, execute the regular handler
             else:
                 self.handle()
+            # Look if we have to stop
             if self._stop or once:
                 self._stop = False
                 break
 
     def handle(self):
         r"handle() -> None :: Abstract method to be overriden to handle events."
-        raise NotImplementedError
+        self.handler(self)
+
+    def handle_timer(self):
+        r"handle() -> None :: Abstract method to be overriden to handle events."
+        self.timer_handler(self)
 
 if __name__ == '__main__':
 
     import os
+    import time
 
     def handle(event_loop):
         data = os.read(event_loop.fileno, 100)
@@ -163,8 +198,10 @@ if __name__ == '__main__':
                 self.stop()
             else:
                 os.write(1, 'Received message: %r\n' % data)
+        def handle_timer(self):
+            print time.strftime('%c')
 
-    p = Test(0)
+    p = Test(0, timer=5)
 
     os.write(1, 'Say a lot of things, then press write just "q" to stop: ')
     p.loop()
index f7497536835ae72670cc2ca3acbcf55ba5d58ba3..8ed3f1576b93b2db18fd793e5e4b2e99fc1615e9 100644 (file)
@@ -37,7 +37,7 @@ class PyminDaemon(eventloop.EventLoop):
     >>> PyminDaemon(Root(), ('', 9999)).run()
     """
 
-    def __init__(self, root, bind_addr=('', 9999)):
+    def __init__(self, root, bind_addr=('', 9999), timer=1):
         r"""Initialize the PyminDaemon object.
 
         See PyminDaemon class documentation for more info.
@@ -47,7 +47,7 @@ class PyminDaemon(eventloop.EventLoop):
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         sock.bind(bind_addr)
         # Create EventLoop
-        eventloop.EventLoop.__init__(self, sock)
+        eventloop.EventLoop.__init__(self, sock, timer=timer)
         # Create Dispatcher
         #TODO root.pymin = PyminHandler()
         self.dispatcher = dispatcher.Dispatcher(root)
@@ -84,6 +84,10 @@ class PyminDaemon(eventloop.EventLoop):
             response += u'%d\n%s' % (len(result), result)
         self.file.sendto(response.encode('utf-8'), addr)
 
+    def handle_timer(self):
+        r"handle_timer() -> None :: Call handle_timer() on handlers."
+        self.dispatcher.root.handle_timer()
+
     def run(self):
         r"run() -> None :: Run the event loop (shortcut to loop())"
         try:
index 251553bde18f448f84d4864d654da37f97d10ed6..de1515b092c7955e4ee6970387f0545758693aea 100644 (file)
@@ -87,7 +87,7 @@ class DhcpHandler(Restorable, ConfigWriter, ReloadHandler, TransactionalHandler,
         self._persistent_dir = pickle_dir
         self._config_writer_cfg_dir = config_dir
         self._config_build_templates()
-        self._restore()
+        InitdHandler.__init__(self)
         self.host = HostHandler(self)
 
     def _get_config_vars(self, config_file):
index d34291e66c18c7270bf2b9dcc8271e111a9c3059..7d09c2fc9a4762a66702377e53375679563451ec 100644 (file)
@@ -109,13 +109,7 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
         self._config_writer_cfg_dir = config_dir
         self._update = False
         self._config_build_templates()
-        self._restore()
-        # FIXME self._update = True
-        #if not self._restore():
-        #r = self._restore()
-        #print r
-        #if not r:
-        #    self._update = True
+        InitdHandler.__init__(self)
         self.host = HostHandler(self)
         self.zone = ZoneHandler(self)
         self.mx = MailExchangeHandler(self)
@@ -132,7 +126,7 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
         delete_zones = list()
         for a_zone in self.zones.values():
             if a_zone._update or a_zone._add:
-                if not a_zone._add:
+                if not a_zone._add and self._service_running:
                     call(('rndc', 'freeze', a_zone.name))
                 vars = dict(
                     zone = a_zone,
@@ -143,7 +137,7 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
                 self._write_single_config('zoneX.zone',
                                             self._zone_filename(a_zone), vars)
                 a_zone._update = False
-                if not a_zone._add:
+                if not a_zone._add and self._service_running:
                     call(('rndc', 'thaw', a_zone.name))
                 else :
                     self._update = True
@@ -165,7 +159,8 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
         if self._update:
             self._write_single_config('named.conf')
             self._update = False
-            self.reload()
+            return False # Do reload
+        return True # we don't need to reload
 
 
 if __name__ == '__main__':
index 71a95481380b943e4957ed4d12e049067f0645f0..06b41915cd4a78b66a300dee08f0ce51eec666cc 100644 (file)
@@ -84,7 +84,7 @@ class FirewallHandler(Restorable, ConfigWriter, ServiceHandler,
 
     handler_help = u"Manage firewall service"
 
-    _persistent_attrs = 'rules'
+    _persistent_attrs = ['rules']
 
     _restorable_defaults = dict(rules=list())
 
@@ -101,7 +101,7 @@ class FirewallHandler(Restorable, ConfigWriter, ServiceHandler,
         self._service_restart = self._service_start
         self._service_reload = self._service_start
         self._config_build_templates()
-        self._restore()
+        ServiceHandler.__init__(self)
         self.rule = RuleHandler(self)
 
     def _get_config_vars(self, config_file):
index e626ab73fedf010ef30594763bc8a2990e575d4b..560e2f58747af7b498231ca132c5cc72aff726f6 100644 (file)
@@ -187,6 +187,7 @@ class IpHandler(Restorable, ConfigWriter, TransactionalHandler):
         self._config_writer_cfg_dir = config_dir
         self._config_build_templates()
         self._restore()
+        self._write_config()
         self.addr = AddressHandler(self)
         self.route = RouteHandler(self)
         self.dev = DeviceHandler(self)
@@ -225,9 +226,26 @@ class IpHandler(Restorable, ConfigWriter, TransactionalHandler):
                  ), shell=True)
 
 
+    def handle_timer(self):
+        self.refresh_devices()
+
+
+    def refresh_devices(self):
+        devices = get_network_devices()
+        #add not registered devices
+        for k,v in devices.items():
+            if k not in self.devices:
+                self.devices[k] = Device(k,v)
+        #delete dead devices
+        for k in self.devices.keys():
+            if k not in devices:
+                del self.devices[k]
+
+
+
 if __name__ == '__main__':
 
-    ip = IpHandler()
+    ip = IpHanlder()
     print '----------------------'
     ip.hop.add('201.21.32.53','eth0')
     ip.hop.add('205.65.65.25','eth1')
index 4724fba550b7bab420dd42bd2ef446ada7356c7d..a30f4c76dbabb2806781d10b7e67348cce5719f4 100644 (file)
@@ -6,7 +6,7 @@ from pymin.seqtools import Sequence
 from pymin.dispatcher import Handler, handler, HandlerError
 from pymin.services.util import Restorable, ConfigWriter, RestartHandler, \
                                 ReloadHandler, TransactionalHandler, \
-                                ListSubHandler, call
+                                ServiceHandler, ListSubHandler, call
 
 __ALL__ = ('NatHandler',)
 
@@ -180,8 +180,8 @@ class MasqHandler(ListSubHandler):
     _cont_subhandler_attr = 'masqs'
     _cont_subhandler_class = Masq
 
-class NatHandler(Restorable, ConfigWriter, RestartHandler, ReloadHandler,
-                      TransactionalHandler):
+class NatHandler(Restorable, ConfigWriter, ReloadHandler, ServiceHandler,
+                        TransactionalHandler):
     r"""NatHandler([pickle_dir[, config_dir]]) -> NatHandler instance.
 
     Handles NAT commands using iptables.
@@ -203,8 +203,8 @@ class NatHandler(Restorable, ConfigWriter, RestartHandler, ReloadHandler,
         masqs=list(),
     )
 
-    @handler(u'Start the service.')
-    def start(self):
+    def _service_start(self):
+        call(('iptables', '-t', 'nat', '-F'))
         for (index, port) in enumerate(self.ports):
             call(['iptables'] + port.as_call_list(index+1))
         for (index, snat) in enumerate(self.snats):
@@ -212,14 +212,15 @@ class NatHandler(Restorable, ConfigWriter, RestartHandler, ReloadHandler,
         for (index, masq) in enumerate(self.masqs):
             call(['iptables'] + masq.as_call_list(index+1))
 
-    @handler(u'Stop the service.')
-    def stop(self):
+    def _service_stop(self):
         call(('iptables', '-t', 'nat', '-F'))
 
+    _service_restart = _service_start
+
     def __init__(self, pickle_dir='.'):
         r"Initialize the object, see class documentation for details."
         self._persistent_dir = pickle_dir
-        self._restore()
+        ServiceHandler.__init__(self)
         self.forward = PortForwardHandler(self)
         self.snat = SNatHandler(self)
         self.masq = MasqHandler(self)
index 868b6d59330729a946309128ef91e85f99c9a31a..caf8f4ed1cd308f3b23525c81a75105dec6ef790 100644 (file)
@@ -1,11 +1,13 @@
 # vim: set encoding=utf-8 et sw=4 sts=4 :
 
+import os
 from os import path
+from signal import SIGTERM
 
 from pymin.seqtools import Sequence
 from pymin.dispatcher import Handler, handler, HandlerError
-from pymin.services.util import Restorable, ConfigWriter \
-                                ,TransactionalHandler, DictSubHandler, call
+from pymin.services.util import Restorable, ConfigWriter, ReloadHandler, \
+                                TransactionalHandler, DictSubHandler, call
 
 __ALL__ = ('PppHandler',)
 
@@ -32,6 +34,7 @@ class Connection(Sequence):
         self.username = username
         self.password = password
         self.type = type
+        self._running = False
         if type == 'OE':
             if not 'device' in kw:
                 raise ConnectionError('Bad arguments for type=OE')
@@ -70,11 +73,11 @@ class ConnectionHandler(DictSubHandler):
     _cont_subhandler_attr = 'conns'
     _cont_subhandler_class = Connection
 
-class PppHandler(Restorable, ConfigWriter, TransactionalHandler):
+class PppHandler(Restorable, ConfigWriter, ReloadHandler, TransactionalHandler):
 
     handler_help = u"Manage ppp service"
 
-    _persistent_attrs = ('conns')
+    _persistent_attrs = ['conns']
 
     _restorable_defaults = dict(
         conns  = dict(),
@@ -89,32 +92,76 @@ class PppHandler(Restorable, ConfigWriter, TransactionalHandler):
         self._config_writer_cfg_dir = config_dir
         self._config_build_templates()
         self._restore()
+        for conn in self.conns.values():
+            if conn._running:
+                conn._running = False
+                self.start(conn.name)
         self.conn = ConnectionHandler(self)
 
-    @handler('Starts the service')
-    def start(self, name):
-        if name in self.conns:
-            call(['pppd','call', name],stdout=None, stderr=None)
-            #print ('pon', name)
-        else:
-            raise ConnectionNotFoundError(name)
-
-    @handler('Stops the service')
-    def stop(self, name):
+    @handler(u'Start one or all the connections.')
+    def start(self, name=None):
+        names = [name]
+        if name is None:
+            names = self.conns.keys()
+        for name in names:
+            if name in self.conns:
+                if not self.conns[name]._running:
+                    call(('pppd', 'call', name))
+                    self.conns[name]._running = True
+                    self._dump_attr('conns')
+            else:
+                raise ConnectionNotFoundError(name)
+
+    @handler(u'Stop one or all the connections.')
+    def stop(self, name=None):
+        names = [name]
+        if name is None:
+            names = self.conns.keys()
+        for name in names:
+            if name in self.conns:
+                if self.conns[name]._running:
+                    call(('poff', name))
+                    if path.exists('/var/run/ppp-' + name + '.pid'):
+                        pid = file('/var/run/ppp-' + name + '.pid').readline()
+                        try:
+                            os.kill(int(pid.strip()), SIGTERM)
+                        except OSError:
+                            pass # XXX report error?
+                    self.conns[name]._running = False
+                    self._dump_attr('conns')
+            else:
+                raise ConnectionNotFoundError(name)
+
+    @handler(u'Restart one or all the connections (even disconnected ones).')
+    def restart(self, name=None):
+        names = [name]
+        if name is None:
+            names = self.conns.keys()
+        for name in names:
+            self.stop(name)
+            self.start(name)
+
+    @handler(u'Restart only one or all the already running connections.')
+    def reload(self, name=None):
+        r"reload() -> None :: Reload the configuration of the service."
+        names = [name]
+        if name is None:
+            names = self.conns.keys()
+        for name in names:
+            if self.conns[name]._running:
+                self.stop(name)
+                self.start(name)
+
+    @handler(u'Tell if the service is running.')
+    def running(self, name=None):
+        r"reload() -> None :: Reload the configuration of the service."
+        if name is None:
+            return [c.name for c in self.conns.values() if c._running]
         if name in self.conns:
-            if path.exists('/var/run/ppp-' + name + '.pid'):
-                pid = file('/var/run/ppp-' + name + '.pid').readline().strip()
-                call(['kill',pid],stdout=None, stderr=None)
-            #print ('poff', name)
+            return int(self.conns[name]._running)
         else:
             raise ConnectionNotFoundError(name)
 
-    @handler('Reloads the service')
-    def reload(self):
-        for conn in self.conns.values():
-            self.stop(conn.name)
-            self.start(conn.name)
-
     def _write_config(self):
         r"_write_config() -> None :: Generate all the configuration files."
         #guardo los pass que van el pap-secrets
@@ -139,6 +186,7 @@ class PppHandler(Restorable, ConfigWriter, TransactionalHandler):
 
 
 if __name__ == '__main__':
+
     p = PppHandler()
     p.conn.add('ppp_c','nico','nico',type='PPP',device='tty0')
     p.conn.add('pppoe_c','fede','fede',type='OE',device='tty1')
@@ -146,3 +194,4 @@ if __name__ == '__main__':
     p.commit()
     print p.conn.list()
     print p.conn.show()
+
index 715b0ab2ac6a4b3e597498151a43ac2d81272fef..a02e6c58df2e7c69a8d8508f2029c84c23f461c6 100644 (file)
@@ -70,7 +70,7 @@ class ProxyHandler(Restorable, ConfigWriter, InitdHandler,
         self._persistent_dir = pickle_dir
         self._config_writer_cfg_dir = config_dir
         self._config_build_templates()
-        self._restore()
+        InitdHandler.__init__(self)
         self.host = HostHandler(self)
         self.user = UserHandler(self)
 
index d1788fc76cf65f7ec9167896d5b4e836b1a682c3..abf30e9ce097956ecf0543b98461da6c4df5abc4 100644 (file)
@@ -154,18 +154,26 @@ class ContainerNotFoundError(ContainerError):
 
 
 def get_network_devices():
-    p = subprocess.Popen(('ip', 'link', 'list'), stdout=subprocess.PIPE,
+    p = subprocess.Popen(('ip', '-o', 'link'), stdout=subprocess.PIPE,
                                                     close_fds=True)
     string = p.stdout.read()
     p.wait()
     d = dict()
-    i = string.find('eth')
-    while i != -1:
-        eth = string[i:i+4]
-        m = string.find('link/ether', i+4)
-        mac = string[ m+11 : m+11+17]
-        d[eth] = mac
-        i = string.find('eth', m+11+17)
+    devices = string.splitlines()
+    for dev in devices:
+        mac = ''
+        if dev.find('link/ether') != -1:
+            i = dev.find('link/ether')
+            mac = dev[i+11 : i+11+17]
+            i = dev.find(':',2)
+            name = dev[3: i]
+            d[name] = mac
+        elif dev.find('link/ppp') != -1:
+            i = dev.find('link/ppp')
+            mac =  '00:00:00:00:00:00'
+            i = dev.find(':',2)
+            name = dev[3 : i]
+            d[name] = mac
     return d
 
 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -294,9 +302,6 @@ class Restorable(Persistent):
         r"_restore() -> bool :: Restore persistent data or create a default."
         try:
             self._load()
-            # TODO tener en cuenta servicios que hay que levantar y los que no
-            if hasattr(self, 'commit'): # TODO deberia ser reload y/o algo para comandos
-                self.commit()
             return True
         except IOError:
             for (k, v) in self._restorable_defaults.items():
@@ -308,8 +313,6 @@ class Restorable(Persistent):
             self._dump()
             if hasattr(self, '_write_config'):
                 self._write_config()
-            if hasattr(self, 'reload'):
-                self.reload()
             return False
 
 class ConfigWriter:
@@ -437,7 +440,7 @@ class ConfigWriter:
             self._write_single_config(t)
 
 
-class ServiceHandler(Handler):
+class ServiceHandler(Handler, Restorable):
     r"""ServiceHandler([start[, stop[, restart[, reload]]]]) -> ServiceHandler.
 
     This is a helper class to inherit from to automatically handle services
@@ -468,26 +471,63 @@ class ServiceHandler(Handler):
                                                     reload=reload).items():
             if action is not None:
                 setattr(self, '_service_%s' % name, action)
+        self._persistent_attrs = list(self._persistent_attrs)
+        self._persistent_attrs.append('_service_running')
+        if '_service_running' not in self._restorable_defaults:
+            self._restorable_defaults['_service_running'] = False
+        self._restore()
+        if self._service_running:
+            self._service_running = False
+            self.start()
 
     @handler(u'Start the service.')
     def start(self):
         r"start() -> None :: Start the service."
-        call(self._service_start)
+        if not self._service_running:
+            if callable(self._service_start):
+                self._service_start()
+            else:
+                call(self._service_start)
+            self._service_running = True
+            self._dump_attr('_service_running')
 
     @handler(u'Stop the service.')
     def stop(self):
         r"stop() -> None :: Stop the service."
-        call(self._service_stop)
+        if self._service_running:
+            if callable(self._service_stop):
+                self._service_stop()
+            else:
+                call(self._service_stop)
+            self._service_running = False
+            self._dump_attr('_service_running')
 
     @handler(u'Restart the service.')
     def restart(self):
         r"restart() -> None :: Restart the service."
-        call(self._service_restart)
+        if callable(self._service_restart):
+            self._service_restart()
+        else:
+            call(self._service_restart)
+        self._service_running = True
+        self._dump_attr('_service_running')
 
     @handler(u'Reload the service config (without restarting, if possible).')
     def reload(self):
         r"reload() -> None :: Reload the configuration of the service."
-        call(self._service_reload)
+        if self._service_running:
+            if callable(self._service_reload):
+                self._service_reload()
+            else:
+                call(self._service_reload)
+
+    @handler(u'Tell if the service is running.')
+    def running(self):
+        r"reload() -> None :: Reload the configuration of the service."
+        if self._service_running:
+            return 1
+        else:
+            return 0
 
 class RestartHandler(Handler):
     r"""RestartHandler() -> RestartHandler :: Provides generic restart command.
@@ -513,9 +553,11 @@ class ReloadHandler(Handler):
     @handler(u'Reload the service config (alias to restart).')
     def reload(self):
         r"reload() -> None :: Reload the configuration of the service."
-        self.restart()
+        if hasattr(self, '_service_running') and self._service_running:
+            self.restart()
 
-class InitdHandler(Handler):
+class InitdHandler(ServiceHandler):
+    # TODO update docs, declarative style is depracated
     r"""InitdHandler([initd_name[, initd_dir]]) -> InitdHandler.
 
     This is a helper class to inherit from to automatically handle services
@@ -544,26 +586,11 @@ class InitdHandler(Handler):
             self._initd_name = initd_name
         if initd_dir is not None:
             self._initd_dir = initd_dir
-
-    @handler(u'Start the service.')
-    def start(self):
-        r"start() -> None :: Start the service."
-        call((path.join(self._initd_dir, self._initd_name), 'start'))
-
-    @handler(u'Stop the service.')
-    def stop(self):
-        r"stop() -> None :: Stop the service."
-        call((path.join(self._initd_dir, self._initd_name), 'stop'))
-
-    @handler(u'Restart the service.')
-    def restart(self):
-        r"restart() -> None :: Restart the service."
-        call((path.join(self._initd_dir, self._initd_name), 'restart'))
-
-    @handler(u'Reload the service config (without restarting, if possible).')
-    def reload(self):
-        r"reload() -> None :: Reload the configuration of the service."
-        call((path.join(self._initd_dir, self._initd_name), 'reload'))
+        actions = dict()
+        for action in ('start', 'stop', 'restart', 'reload'):
+            actions[action] = (path.join(self._initd_dir, self._initd_name),
+                                action)
+        ServiceHandler.__init__(self, **actions)
 
 class TransactionalHandler(Handler):
     r"""Handle command transactions providing a commit and rollback commands.
@@ -586,9 +613,10 @@ class TransactionalHandler(Handler):
         r"commit() -> None :: Commit the changes and reload the service."
         if hasattr(self, '_dump'):
             self._dump()
+        unchanged = False
         if hasattr(self, '_write_config'):
-            self._write_config()
-        if hasattr(self, 'reload'):
+            unchanged = self._write_config()
+        if not unchanged and hasattr(self, 'reload'):
             self.reload()
 
     @handler(u'Discard all the uncommited changes.')
@@ -627,6 +655,8 @@ class ParametersHandler(Handler):
         if not param in self.params:
             raise ParameterNotFoundError(param)
         self.params[param] = value
+        if hasattr(self, '_update'):
+            self._update = True
 
     @handler(u'Get a service parameter.')
     def get(self, param):
index 580d4e69a23ec42540e65b6b1345601c3fa76770..403cd364fdac153229282e8fb6230cc921680eb3 100644 (file)
@@ -1,18 +1,24 @@
 # vim: set encoding=utf-8 et sw=4 sts=4 :
 
+import os
 from os import path
+from signal import SIGTERM
 from subprocess import Popen, PIPE
 
 from pymin.seqtools import Sequence
 from pymin.dispatcher import Handler, handler, HandlerError
-from pymin.services.util import Restorable, TransactionalHandler, ParametersHandler, call
+from pymin.services.util import Restorable, TransactionalHandler, \
+                                ReloadHandler, RestartHandler, \
+                                ServiceHandler, ParametersHandler, call
 
 __ALL__ = ('VrrpHandler',)
 
-class VrrpHandler(Restorable, ParametersHandler, TransactionalHandler):
+class VrrpHandler(Restorable, ParametersHandler, ReloadHandler, RestartHandler,
+                        ServiceHandler, TransactionalHandler):
+
     handler_help = u"Manage VRRP service"
 
-    _persistent_attrs = 'params'
+    _persistent_attrs = ['params']
 
     _restorable_defaults = dict(
                             params = dict( ipaddress='192.168.0.1',
@@ -22,37 +28,31 @@ class VrrpHandler(Restorable, ParametersHandler, TransactionalHandler):
                                     ),
                             )
 
-    def __init__(self, pickle_dir='.', config_dir='.', pid_dir='.'):
-        self._persistent_dir = pickle_dir
-        self._pid_dir = pid_dir
-        self._restore()
-
-    @handler('Starts the service')
-    def start(self):
+    def _service_start(self):
         if self.params['prio'] != '':
-            call(('/usr/local/bin/vrrpd','-i',self.params['dev'],'-v',self.params['id'],'-p',self.params['prio'],self.params['ipaddress']))
-            #print ('vrrpd','-i',self.params['dev'],'-v',self.params['id'],'-p',self.params['prio'],self.params['ipaddress'])
+            call(('vrrp', '-i', self.params['dev'], '-v', self.params['id'],
+                    '-p', self.params['prio'], self.params['ipaddress']))
         else:
-            call(('/usr/local/bin/vrrpd','-i',self.params['dev'],'-v',self.params['id'],self.params['ipaddress']))
-            #print ('vrrpd','-i',self.params['dev'],'-v',self.params['id'],self.params['ipaddress'])
-
-    @handler('Stop the service')
-    def stop(self):
-        try :
-            pid = 'vrrpd' + '_' + self.params['dev'] + '_' + self.params['id'] + '.pid'
-            f = file(path.join(self._pid_dir, pid ), 'r')
-            call(('kill',f.read().strip('\n')))
-            #print('kill','<',f.read())
-        except IOError:
+            call(('vrrp', '-i', self.params['dev'], '-v', self.params['id'], \
+                    self.params['ipaddress']))
+
+    def _service_stop(self):
+        try:
+            pid_filename = 'vrrpd_%(dev)s_%(id)s.pid' % self.params
+            pid = file(path.join(self._pid_dir, pid_filename )).read().strip()
+            os.kill(int(pid), SIGTERM)
+        except (IOError, OSError):
+            # TODO log
             pass
 
-    @handler('Reloads the service')
-    def reload(self):
-        self.stop()
-        self.start()
+    def __init__(self, pickle_dir='.', config_dir='.', pid_dir='.'):
+        self._persistent_dir = pickle_dir
+        self._pid_dir = pid_dir
+        ServiceHandler.__init__(self)
 
 
 if __name__ == '__main__':
     v = VrrpHandler()
     v.set('prio','10')
     v.commit()
+