From: jack2 Date: Sat, 27 Oct 2007 17:45:19 +0000 (-0300) Subject: Merge branch 'master' of or3st3s@baryon.com.ar:workspace/pymin into suse X-Git-Url: https://git.llucax.com/software/pymin.git/commitdiff_plain/c3862dc0f135a52327fbf9511c22392555e84f3f?hp=29ad7f7c872353579d714ac13d0640b86df9b248 Merge branch 'master' of or3st3s@baryon.com.ar:workspace/pymin into suse Conflicts: config.py --- diff --git a/TODO b/TODO index 3438f7e..fd3e0ce 100644 --- 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??? - diff --git a/config.py b/config.py index ee3c174..db3151b 100644 --- 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) diff --git a/pymin/dispatcher.py b/pymin/dispatcher.py index 17075a3..24f0d1f 100644 --- a/pymin/dispatcher.py +++ b/pymin/dispatcher.py @@ -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. diff --git a/pymin/eventloop.py b/pymin/eventloop.py index 06922a7..b6be5a1 100644 --- a/pymin/eventloop.py +++ b/pymin/eventloop.py @@ -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() diff --git a/pymin/pymindaemon.py b/pymin/pymindaemon.py index f749753..8ed3f15 100644 --- a/pymin/pymindaemon.py +++ b/pymin/pymindaemon.py @@ -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: 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 71a9548..06b4191 100644 --- a/pymin/services/firewall/__init__.py +++ b/pymin/services/firewall/__init__.py @@ -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): diff --git a/pymin/services/ip/__init__.py b/pymin/services/ip/__init__.py index e626ab7..560e2f5 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) @@ -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') 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 868b6d5..caf8f4e 100644 --- a/pymin/services/ppp/__init__.py +++ b/pymin/services/ppp/__init__.py @@ -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() + 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 d1788fc..abf30e9 100644 --- a/pymin/services/util.py +++ b/pymin/services/util.py @@ -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): diff --git a/pymin/services/vrrp/__init__.py b/pymin/services/vrrp/__init__.py index 580d4e6..403cd36 100644 --- a/pymin/services/vrrp/__init__.py +++ b/pymin/services/vrrp/__init__.py @@ -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() +