From: Leandro Lucarella Date: Mon, 24 Sep 2007 17:04:42 +0000 (-0300) Subject: DhcpHandler documentation and improvements. X-Git-Url: https://git.llucax.com/software/pymin.git/commitdiff_plain/b8c5dbd52084b9a710c9881e327449f9eaec0bdb?hp=f2b9d4634807e38cec82fc1d2c4250b5e6106c5a DhcpHandler documentation and improvements. The path where to store pickled configuration and generated config files is configurable when constructing the instance. If there's a pickled configuration it's automatically loaded at construction time. If not, a new configuration based on defaults is dumped to disk. New methods are added: restart(), reload() and rollback() and DhcpHandler is published in the services module and included in the pymin daemon command tree. --- diff --git a/config.py b/config.py index 93a6b7b..f433170 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,7 @@ # vim: set et sts=4 sw=4 encoding=utf-8 : +from services import * + # XXX for testing only def test_func(*args): print 'func:', args @@ -7,6 +9,10 @@ def test_func(*args): routes = dict \ ( test = test_func, + dhcp = DhcpHandler( + pickle_dir = 'var/lib/pymin/pickle/dhcp', + config_dir = 'var/lib/pymin/config/dhcp', + ), ) bind_addr = \ diff --git a/services/__init__.py b/services/__init__.py index 8b13789..02829a4 100644 --- a/services/__init__.py +++ b/services/__init__.py @@ -1 +1,4 @@ +# vim: set encoding=utf-8 et sw=4 sts=4 : + +from services.dhcp import DhcpHandler diff --git a/services/dhcp/__init__.py b/services/dhcp/__init__.py index 941bccf..46b7d81 100644 --- a/services/dhcp/__init__.py +++ b/services/dhcp/__init__.py @@ -1,125 +1,255 @@ # vim: set encoding=utf-8 et sw=4 sts=4 : +from mako.template import Template +from mako.runtime import Context +from os import path try: import cPickle as pickle except ImportError: import pickle -from mako.template import Template -from mako.runtime import Context +__ALL__ = ('DhcpHandler',) + +pickle_ext = '.pkl' +pickle_vars = 'vars' +pickle_hosts = 'hosts' + +config_filename = 'dhcpd.conf' + +template_dir = path.join(path.dirname(__file__), 'templates') class Host: + r"""Host(name, ip, mac) -> Host instance :: Class representing a host. + + name - Host name, should be a fully qualified name, but no checks are done. + ip - IP assigned to the hostname. + mac - MAC address to associate to the hostname. + """ + def __init__(self, name, ip, mac): + r"Initialize Host object, see class documentation for details." self.name = name self.ip = ip self.mac = mac - def __repr__(self): - return 'Host(name="%s", ip="%s", mac="%s")' % ( - self.name, self.ip, self.mac) class HostHandler: + r"""HostHandler(hosts) -> HostHandler instance :: Handle a list of hosts. + + This class is a helper for DhcpHandler to do all the work related to hosts + administration. + + hosts - A dictionary with string keys (hostnames) and Host instances values. + """ def __init__(self, hosts): + r"Initialize HostHandler object, see class documentation for details." self.hosts = hosts def add(self, name, ip, mac): - #deberia indexar por hostname o por ip? o por mac? :) - # Mejor por nada... + r"add(name, ip, mac) -> None :: Add a host to the hosts list." + # XXX deberia indexar por hostname o por ip? o por mac? :) + # o por nada... Puedo tener un nombre con muchas IPs? Una IP con muchos + # nombres? Una MAC con muchas IP? una MAC con muchos nombre? Etc... self.hosts[name] = Host(name, ip, mac) def update(self, name, ip=None, mac=None): + r"update(name[, ip[, mac]]) -> None :: Update a host of the hosts list." + if not name in self.hosts: + raise KeyError('Host not found') if ip is not None: self.hosts[name].ip = ip if mac is not None: self.hosts[name].mac = mac def delete(self, name): + r"delete(name) -> None :: Delete a host of the hosts list." + if not name in self.hosts: + raise KeyError('Host not found') del self.hosts[name] def list(self): + r"""list() -> CSV string :: List all the hostnames. + + The list is returned as a single CSV line with all the hostnames. + """ return ','.join(self.hosts) def show(self): + r"""show() -> CSV string :: List all the complete hosts information. + + The hosts are returned as a CSV list with each host in a line, like: + hostname,ip,mac + """ hosts = self.hosts.values() return '\n'.join('%s,%s,%s' % (h.name, h.ip, h.mac) for h in hosts) class DhcpHandler: - r"""class that handles DHCP service using dhcpd program""" - - def __init__(self): - self.hosts = dict() - self.vars = dict( - domain_name = 'my_domain_name', - dns_1 = 'my_ns1', - dns_2 = 'my_ns2', - net_address = '192.168.0.0', - net_mask = '255.255.255.0', - net_start = '192.168.0.100', - net_end = '192.168.0.200', - net_gateway = '192.168.0.1', - ) + r"""DhcpHandler([pickle_dir[, config_dir]]) -> DhcpHandler instance. + + Handles DHCP service commands for the dhcpd program. + + pickle_dir - Directory where to write the persistent configuration data. + + config_dir - Directory where to store de generated configuration files. + + Both defaults to the current working directory. + """ + + def __init__(self, pickle_dir='.', config_dir='.'): + r"Initialize DhcpHandler object, see class documentation for details." + self.pickle_dir = pickle_dir + self.config_dir = config_dir + filename = path.join(template_dir, config_filename) + self.template = Template(filename=filename) + try: + self._load() + except IOError: + # This is the first time the handler is used, create a basic + # setup using some nice defaults + self.hosts = dict() + self.vars = dict( + domain_name = 'example.com', + dns_1 = 'ns1.example.com', + dns_2 = 'ns2.example.com', + net_address = '192.168.0.0', + net_mask = '255.255.255.0', + net_start = '192.168.0.100', + net_end = '192.168.0.200', + net_gateway = '192.168.0.1', + ) + self._dump() + self._write_config() self.host = HostHandler(self.hosts) def set(self, param, value): - if param in self.vars: - self.vars[param] = value - else: - raise KeyError("Parameter " + param + " not found") + r"set(param, value) -> None :: Set a DHCP parameter." + if not param in self.vars: + raise KeyError('Parameter ' + param + ' not found') + self.vars[param] = value def list(self): + r"""list() -> CSV string :: List all the parameter names. + + The list is returned as a single CSV line with all the names. + """ return ','.join(self.vars) def show(self): + r"""show() -> CSV string :: List all the parameters (with their values). + + The parameters are returned as a CSV list with each parameter in a + line, like: + name,value + """ return '\n'.join(('%s,%s' % (k, v) for (k, v) in self.vars.items())) def start(self): + r"start() -> None :: Start the DHCP service." #esto seria para poner en una interfaz #y seria el hook para arrancar el servicio pass def stop(self): + r"stop() -> None :: Stop the DHCP service." + #esto seria para poner en una interfaz + #y seria el hook para arrancar el servicio + pass + + def restart(self): + r"restart() -> None :: Restart the DHCP service." + #esto seria para poner en una interfaz + #y seria el hook para arrancar el servicio + pass + + def reload(self): + r"reload() -> None :: Reload the configuration of the DHCP service." #esto seria para poner en una interfaz #y seria el hook para arrancar el servicio pass def commit(self): + r"commit() -> None :: Commit the changes and reload the DHCP service." #esto seria para poner en una interfaz #y seria que hace el pickle deberia llamarse #al hacerse un commit - pickle.dump(self.vars, file('pickled/vars.pkl', 'wb'), 2) - pickle.dump(self.hosts, file('pickled/hosts.pkl', 'wb'), 2) - tpl = Template(filename='templates/dhcpd.conf') - ctx = Context(file('generated/dhcpd.conf', 'w'), - hosts=self.hosts.values(), **self.vars) - tpl.render_context(ctx) + self._dump() + self._write_config() + self.reload() + + def rollback(self): + r"rollback() -> None :: Discard the changes not yet commited." + self._load() + + def _dump(self): + r"_dump() -> None :: Dump all persistent data to pickle files." + # XXX podría ir en una clase base + self._dump_var(self.vars, pickle_vars) + self._dump_var(self.hosts, pickle_hosts) + + def _load(self): + r"_load() -> None :: Load all persistent data from pickle files." + # XXX podría ir en una clase base + self.vars = self._load_var(pickle_vars) + self.hosts = self._load_var(pickle_hosts) + + def _pickle_filename(self, name): + r"_pickle_filename() -> string :: Construct a pickle filename." + # XXX podría ir en una clase base + return path.join(self.pickle_dir, name) + pickle_ext + + def _dump_var(self, var, name): + r"_dump_var() -> None :: Dump a especific variable to a pickle file." + # XXX podría ir en una clase base + pickle.dump(var, file(self._pickle_filename(name), 'wb'), 2) + + def _load_var(self, name): + r"_load_var() -> object :: Load a especific pickle file." + # XXX podría ir en una clase base + return pickle.load(file(self._pickle_filename(name))) + + def _write_config(self): + r"_write_config() -> None :: Generate all the configuration files." + # XXX podría ir en una clase base, ver como generalizar variables a + # reemplazar en la template + out_file = file(path.join(self.config_dir, config_filename), 'w') + ctx = Context(out_file, hosts=self.hosts.values(), **self.vars) + self.template.render_context(ctx) if __name__ == '__main__': - config = DhcpHandler() + import os + + dhcp_handler = DhcpHandler() - config.host.add('my_name','192.168.0.102','00:12:ff:56') + def dump(): + print '-' * 80 + print 'Variables:', dhcp_handler.list() + print dhcp_handler.show() + print + print 'Hosts:', dhcp_handler.host.list() + print dhcp_handler.host.show() + print '-' * 80 - config.host.update('my_name','192.168.0.192','00:12:ff:56') + dump() - config.host.add('nico','192.168.0.188','00:00:00:00') + dhcp_handler.host.add('my_name','192.168.0.102','00:12:ff:56') - config.set('domain_name','baryon.com.ar') + dhcp_handler.host.update('my_name','192.168.0.192','00:12:ff:56') + + dhcp_handler.host.add('nico','192.168.0.188','00:00:00:00') + + dhcp_handler.set('domain_name','baryon.com.ar') try: - config.set('sarasa','baryon.com.ar') + dhcp_handler.set('sarasa','baryon.com.ar') except KeyError, e: print 'Error:', e - config.commit() - - print 'Variables:', config.list() - print config.show() + dhcp_handler.commit() - print 'Hosts:', config.host.list() - print config.host.show() + dump() - vars = pickle.load(file('pickled/vars.pkl')) - hosts = pickle.load(file('pickled/hosts.pkl')) - print 'Pickled vars:', vars - print 'Pickled hosts:', hosts + for f in (pickle_vars + pickle_ext, pickle_hosts + pickle_ext, + config_filename): + os.unlink(f)