]> git.llucax.com Git - software/pymin.git/commitdiff
Add support for service running status and restoring at startup.
authorLeandro Lucarella <llucax@gmail.com>
Mon, 22 Oct 2007 05:46:53 +0000 (02:46 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Mon, 22 Oct 2007 05:46:53 +0000 (02:46 -0300)
Now ServiceHandler stores a persistent status of the service, to know if
the service should be started at startup (or reloaded when reload command
is called). To do that it inherits from Restorable, so restarting and
committing is now not done at _restore() any more. InetdHandler now
inherits from ServiceHandler to take advantage of its features.

ServiceHandler now support callables _service_{start,stop,restart,reload}
attributes to ease the customization.

PppHandler is a special case where each connection has it own running
status. All start, stop, restart and reload takes the name argument as
optional. If a name is not provided, all connections are processed. On
restart, all connections are (re)started, on reload (and at startup), only the previous
running connectinos are restarted.

ConfigWriter's _write_config() now can return True to indicate that commit
command shouldn't reload the service.

VrrpHandler now uses python's kill to stop the service.

TODO
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 6641b978ef465ea225e7fdf30d78c6997eba644f..0bdd48eb56b3c2a296368fc8a39a419a6041b9c7 100644 (file)
--- a/TODO
+++ b/TODO
@@ -26,22 +26,13 @@ Ideas / TODO:
 
 * 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 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 9355e0f3bfce0773b2eecd255b178d32b0e005b4..06b41915cd4a78b66a300dee08f0ce51eec666cc 100644 (file)
@@ -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..9d256a294ad3020c9ff6073cba7ce53569c84456 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)
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 c759bc72338321f55c1ea1e7195f1d6b2602419a..91070e79cc652441b6909e36cda6a06a31939aab 100644 (file)
@@ -4,8 +4,8 @@ from os import path
 
 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 +32,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,7 +71,7 @@ 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"
 
@@ -89,30 +90,70 @@ 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):
+    @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(('pon', 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))
+                    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:
-            #call(('pon', name))
-            print ('pon', name)
+            return int(self.conns[name]._running)
         else:
             raise ConnectionNotFoundError(name)
 
-    @handler('Stops the service')
-    def stop(self, name):
-        if name in self.conns:
-            #call(('poff', name))
-            print ('poff', name)
-        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
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 f5f6b520fc1bf48f0ad8e227635970c0dfe8df50..a41882b6232b9a633dac4c1c0667018d85eb0876 100644 (file)
@@ -293,9 +293,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():
@@ -307,8 +304,6 @@ class Restorable(Persistent):
             self._dump()
             if hasattr(self, '_write_config'):
                 self._write_config()
-            if hasattr(self, 'reload'):
-                self.reload()
             return False
 
 class ConfigWriter:
@@ -436,7 +431,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
@@ -467,26 +462,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.
@@ -512,9 +544,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
@@ -543,26 +577,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.
@@ -585,9 +604,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.')
index 393f90c23ef7825e506942ec2bbf361f9ab7c6f2..c8563a699b6923f091ea50be67532ddf94121d26 100644 (file)
@@ -1,20 +1,26 @@
 # 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',)
 
 pid_filename = 'vrrp.pid'
 
-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',
@@ -24,33 +30,26 @@ 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(('vrrp','-i',self.params[dev],'-v',self.params[id],'-p',self.params[prio],self.params[ipaddress]))
-            print ('vrrp','-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(('vrrp','-i',self.params[dev],'-v',self.params[id],self.params[ipaddress]))
-            print ('vrrp','-i',self.params['dev'],'-v',self.params['id'],self.params['ipaddress'])
-
-    @handler('Stop the service')
-    def stop(self):
-        try :
-            f = file(path.join(self._pid_dir, pid_filename ), 'r')
-            #call(('kill','<',f.read()))
-            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 = 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__':