From: Leandro Lucarella Date: Mon, 22 Oct 2007 05:46:53 +0000 (-0300) Subject: Add support for service running status and restoring at startup. X-Git-Url: https://git.llucax.com/software/pymin.git/commitdiff_plain/0dcb923d42a7489756eddd83a13a3059d8a24b2a?ds=inline Add support for service running status and restoring at startup. 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. --- diff --git a/TODO b/TODO index 6641b97..0bdd48e 100644 --- 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??? - diff --git a/pymin/services/dhcp/__init__.py b/pymin/services/dhcp/__init__.py index 251553b..de1515b 100644 --- a/pymin/services/dhcp/__init__.py +++ b/pymin/services/dhcp/__init__.py @@ -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): diff --git a/pymin/services/dns/__init__.py b/pymin/services/dns/__init__.py index d34291e..7d09c2f 100644 --- a/pymin/services/dns/__init__.py +++ b/pymin/services/dns/__init__.py @@ -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__': diff --git a/pymin/services/firewall/__init__.py b/pymin/services/firewall/__init__.py index 9355e0f..06b4191 100644 --- a/pymin/services/firewall/__init__.py +++ b/pymin/services/firewall/__init__.py @@ -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): diff --git a/pymin/services/ip/__init__.py b/pymin/services/ip/__init__.py index e626ab7..9d256a2 100644 --- a/pymin/services/ip/__init__.py +++ b/pymin/services/ip/__init__.py @@ -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) diff --git a/pymin/services/nat/__init__.py b/pymin/services/nat/__init__.py index 4724fba..a30f4c7 100644 --- a/pymin/services/nat/__init__.py +++ b/pymin/services/nat/__init__.py @@ -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) diff --git a/pymin/services/ppp/__init__.py b/pymin/services/ppp/__init__.py index c759bc7..91070e7 100644 --- a/pymin/services/ppp/__init__.py +++ b/pymin/services/ppp/__init__.py @@ -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 diff --git a/pymin/services/proxy/__init__.py b/pymin/services/proxy/__init__.py index 715b0ab..a02e6c5 100644 --- a/pymin/services/proxy/__init__.py +++ b/pymin/services/proxy/__init__.py @@ -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) diff --git a/pymin/services/util.py b/pymin/services/util.py index f5f6b52..a41882b 100644 --- a/pymin/services/util.py +++ b/pymin/services/util.py @@ -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.') diff --git a/pymin/services/vrrp/__init__.py b/pymin/services/vrrp/__init__.py index 393f90c..c8563a6 100644 --- a/pymin/services/vrrp/__init__.py +++ b/pymin/services/vrrp/__init__.py @@ -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__':