]> git.llucax.com Git - software/pymin.git/commitdiff
Add ComposedSubHandler (and their List and Dict flavors) to services.util.
authorLeandro Lucarella <llucax@gmail.com>
Tue, 16 Oct 2007 03:28:21 +0000 (00:28 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Tue, 16 Oct 2007 03:28:21 +0000 (00:28 -0300)
Add a class to handle List and Dict subhandlers contained in another
parent object. DnsHandler and IpHandler are updated to use them.

get_devices() function was "promoted" to services.util too, with a little
signature change.

pymin/services/dns/__init__.py
pymin/services/ip/__init__.py
pymin/services/ip/templates/ip_add
pymin/services/ip/templates/ip_del
pymin/services/util.py

index 32e177221607975ac820a0ad4774cb0a2f96ef3b..d34291e66c18c7270bf2b9dcc8271e111a9c3059 100644 (file)
 # TODO COMMENT
 from os import path
 from os import unlink
-from new import instancemethod
 
 from pymin.seqtools import Sequence
 from pymin.dispatcher import handler, HandlerError, Handler
 from pymin.services.util import Restorable, ConfigWriter, InitdHandler, \
                                 TransactionalHandler, ParametersHandler, \
-                                SubHandler, call
-
-__ALL__ = ('DnsHandler', 'Error',
-            'ZoneError', 'ZoneNotFoundError', 'ZoneAlreadyExistsError',
-            'HostError', 'HostAlreadyExistsError', 'HostNotFoundError',
-            'MailExchangeError', 'MailExchangeAlreadyExistsError',
-            'MailExchangeNotFoundError', 'NameServerError',
-            'NameServerAlreadyExistsError', 'NameServerNotFoundError')
-
-template_dir = path.join(path.dirname(__file__), 'templates')
-
-
-class Error(HandlerError):
-    r"""
-    Error(command) -> Error instance :: Base DnsHandler exception class.
-
-    All exceptions raised by the DnsHandler inherits from this one, so you can
-    easily catch any DnsHandler exception.
-
-    message - A descriptive error message.
-    """
-    pass
-
-class ZoneError(Error, KeyError):
-    r"""
-    ZoneError(zonename) -> ZoneError instance
-
-    This is the base exception for all zone related errors.
-    """
-
-    def __init__(self, zonename):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Zone error: "%s"' % zonename
-
-class ZoneNotFoundError(ZoneError):
-    r"""
-    ZoneNotFoundError(hostname) -> ZoneNotFoundError instance
-
-    This exception is raised when trying to operate on a zone that doesn't
-    exists.
-    """
-
-    def __init__(self, zonename):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'zone not found: "%s"' % zonename
-
-class ZoneAlreadyExistsError(ZoneError):
-    r"""
-    ZoneAlreadyExistsError(hostname) -> ZoneAlreadyExistsError instance
-
-    This exception is raised when trying to add a zonename that already exists.
-    """
-
-    def __init__(self, zonename):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Zone already exists: "%s"' % zonename
-
-class HostError(Error, KeyError):
-    r"""
-    HostError(hostname) -> HostError instance
-
-    This is the base exception for all host related errors.
-    """
-
-    def __init__(self, hostname):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Host error: "%s"' % hostname
-
-class HostAlreadyExistsError(HostError):
-    r"""
-    HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
-
-    This exception is raised when trying to add a hostname that already exists.
-    """
-
-    def __init__(self, hostname):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Host already exists: "%s"' % hostname
-
-class HostNotFoundError(HostError):
-    r"""
-    HostNotFoundError(hostname) -> HostNotFoundError instance.
-
-    This exception is raised when trying to operate on a hostname that doesn't
-    exists.
-    """
-
-    def __init__(self, hostname):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Host not found: "%s"' % hostname
-
-class MailExchangeError(Error, KeyError):
-    r"""
-    MailExchangeError(hostname) -> MailExchangeError instance.
-
-    This is the base exception for all mail exchange related errors.
-    """
-
-    def __init__(self, mx):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Mail Exchange error: "%s"' % mx
-
-class MailExchangeAlreadyExistsError(MailExchangeError):
-    r"""
-    MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError.
-
-    This exception is raised when trying to add a mail exchange that already exists.
-    """
-
-    def __init__(self, mx):
-        r"Initialize the object. See class documentation for more info."
-        self.message = u'Mail Exchange already exists: "%s"' % mx
-
-class MailExchangeNotFoundError(MailExchangeError):
-    r"""
-    MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance.
-
-    This exception is raised when trying to operate on a mail exchange that
-    doesn't exists.
-    """
-
-    def __init__(self, mx):
-        r"Initialize the object. See class documentation for more info."
-        self.message = 'Mail Exchange not found: "%s"' % mx
-
-class NameServerError(Error, KeyError):
-    r"""
-    NameServerError(ns) -> NameServerError instance
-
-    This is the base exception for all name server related errors.
-    """
-
-    def __init__(self, ns):
-        r"Initialize the object. See class documentation for more info."
-        self.message = 'Name Server error: "%s"' % ns
-
-class NameServerAlreadyExistsError(NameServerError):
-    r"""
-    NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError.
-
-    This exception is raised when trying to add a name server that already
-    exists.
-    """
-
-    def __init__(self, ns):
-        r"Initialize the object. See class documentation for more info."
-        self.message = 'Name server already exists: "%s"' % ns
-
-class NameServerNotFoundError(NameServerError):
-    r"""
-    NameServerNotFoundError(hostname) -> NameServerNotFoundError instance.
-
-    This exception is raised when trying to operate on a name server that
-    doesn't exists.
-    """
-
-    def __init__(self, ns):
-        r"Initialize the object. See class documentation for more info."
-        self.message = 'Mail Exchange not found: "%s"' % ns
+                                DictComposedSubHandler, DictSubHandler, call
 
+__ALL__ = ('DnsHandler',)
 
 class Host(Sequence):
     def __init__(self, name, ip):
         self.name = name
         self.ip = ip
-
+    def update(self, ip=None):
+        if ip is not None: self.ip = ip
     def as_tuple(self):
         return (self.name, self.ip)
 
-class HostHandler(SubHandler):
-
+class HostHandler(DictComposedSubHandler):
     handler_help = u"Manage DNS hosts"
-
-    @handler(u'Adds a host to a zone')
-    def add(self, name, hostname, ip):
-        if not name in self.parent.zones:
-            raise ZoneNotFoundError(name)
-        if hostname in self.parent.zones[name].hosts:
-            raise HostAlreadyExistsError(hostname)
-        self.parent.zones[name].hosts[hostname] = Host(hostname, ip)
-        self.parent.zones[name].mod = True
-
-    @handler(u'Updates a host ip in a zone')
-    def update(self, name, hostname, ip):
-        if not name in self.parent.zones:
-            raise ZoneNotFoundError(name)
-        if not hostname in self.parent.zones[name].hosts:
-             raise HostNotFoundError(name)
-        self.parent.zones[name].hosts[hostname].ip = ip
-        self.parent.zones[name].mod = True
-
-    @handler(u'Deletes a host from a zone')
-    def delete(self, name, hostname):
-        if not name in self.parent.zones:
-            raise ZoneNotFoundError(name)
-        if not hostname in self.parent.zones[name].hosts:
-             raise HostNotFoundError(name)
-        del self.parent.zones[name].hosts[hostname]
-        self.parent.zones[name].mod = True
-
-    @handler(u'Lists hosts')
-    def list(self):
-        return self.parent.zones.keys()
-
-    @handler(u'Get insormation about all hosts')
-    def show(self):
-        return self.parent.zones.values()
-
+    _comp_subhandler_cont = 'zones'
+    _comp_subhandler_attr = 'hosts'
+    _comp_subhandler_class = Host
 
 class MailExchange(Sequence):
-
     def __init__(self, mx, prio):
         self.mx = mx
         self.prio = prio
-
+    def update(self, prio=None):
+        if prio is not None: self.prio = prio
     def as_tuple(self):
         return (self.mx, self.prio)
 
-class MailExchangeHandler(SubHandler):
-
+class MailExchangeHandler(DictComposedSubHandler):
     handler_help = u"Manage DNS mail exchangers (MX)"
-
-    @handler(u'Adds a mail exchange to a zone')
-    def add(self, zonename, mx, prio):
-        if not zonename in self.parent.zones:
-            raise ZoneNotFoundError(zonename)
-        if mx in self.parent.zones[zonename].mxs:
-            raise MailExchangeAlreadyExistsError(mx)
-        self.parent.zones[zonename].mxs[mx] = MailExchange(mx, prio)
-        self.parent.zones[zonename].mod = True
-
-    @handler(u'Updates a mail exchange priority')
-    def update(self, zonename, mx, prio):
-        if not zonename in self.parent.zones:
-            raise ZoneNotFoundError(zonename)
-        if not mx in self.parent.zones[zonename].mxs:
-            raise MailExchangeNotFoundError(mx)
-        self.parent.zones[zonename].mxs[mx].prio = prio
-        self.parent.zones[zonename].mod = True
-
-    @handler(u'Deletes a mail exchange from a zone')
-    def delete(self, zonename, mx):
-        if not zonename in self.parent.zones:
-            raise ZoneNotFoundError(zonename)
-        if not mx in self.parent.zones[zonename].mxs:
-            raise MailExchangeNotFoundError(mx)
-        del self.parent.zones[zonename].mxs[mx]
-        self.parent.zones[zonename].mod = True
-
-    @handler(u'Lists mail exchangers')
-    def list(self):
-        return self.parent.zones.keys()
-
-    @handler(u'Get information about all mail exchangers')
-    def show(self):
-        return self.parent.zones.values()
-
+    _comp_subhandler_cont = 'zones'
+    _comp_subhandler_attr = 'mxs'
+    _comp_subhandler_class = MailExchange
 
 class NameServer(Sequence):
-
     def __init__(self, name):
         self.name = name
-
     def as_tuple(self):
-        return (self.name)
-
-class NameServerHandler(SubHandler):
+        return (self.name,)
 
+class NameServerHandler(DictComposedSubHandler):
     handler_help = u"Manage DNS name servers (NS)"
-
-    @handler(u'Adds a name server to a zone')
-    def add(self, zone, ns):
-        if not zone in self.parent.zones:
-            raise ZoneNotFoundError(zone)
-        if ns in self.parent.zones[zone].nss:
-            raise NameServerAlreadyExistsError(ns)
-        self.parent.zones[zone].nss[ns] = NameServer(ns)
-        self.parent.zones[zone].mod = True
-
-    @handler(u'Deletes a name server from a zone')
-    def delete(self, zone, ns):
-        if not zone in self.parent.zones:
-            raise ZoneNotFoundError(zone)
-        if not ns in self.parent.zones[zone].nss:
-            raise NameServerNotFoundError(ns)
-        del self.parent.zones[zone].nss[ns]
-        self.parent.zones[zone].mod = True
-
-    @handler(u'Lists name servers')
-    def list(self):
-        return self.parent.zones.keys()
-
-    @handler(u'Get information about all name servers')
-    def show(self):
-        return self.parent.zones.values()
-
+    _comp_subhandler_cont = 'zones'
+    _comp_subhandler_attr = 'nss'
+    _comp_subhandler_class = NameServer
 
 class Zone(Sequence):
     def __init__(self, name):
@@ -312,50 +60,16 @@ class Zone(Sequence):
         self.hosts = dict()
         self.mxs = dict()
         self.nss = dict()
-        self.new = False
-        self.mod = False
-        self.dele = False
-
+        self._add = False
+        self._update = False
+        self._delete = False
     def as_tuple(self):
         return (self.name, self.hosts, self.mxs, self.nss)
 
-class ZoneHandler(SubHandler):
-    r"""ZoneHandler(parent.zones) -> ZoneHandler instance :: Handle a list of zones.
-
-    This class is a helper for DnsHandler to do all the work related to zone
-    administration.
-
-    parent - The parent service handler.
-    """
-
+class ZoneHandler(DictSubHandler):
     handler_help = u"Manage DNS zones"
-
-    @handler(u'Adds a zone')
-    def add(self, name):
-        if name in self.parent.zones:
-            if self.parent.zones[name].dele == True:
-                self.parent.zones[name].dele = False
-            else:
-                raise ZoneAlreadyExistsError(name)
-        self.parent.zones[name] = Zone(name)
-        self.parent.zones[name].mod = True
-        self.parent.zones[name].new = True
-
-    @handler(u'Deletes a zone')
-    def delete(self, name):
-        r"delete(name) -> None :: Delete a zone from the zone list."
-        if not name in self.parent.zones:
-            raise ZoneNotFoundError(name)
-        self.parent.zones[name].dele = True
-
-    @handler(u'Lists zones')
-    def list(self):
-        return self.parent.zones.keys()
-
-    @handler(u'Get information about all zones')
-    def show(self):
-        return self.parent.zones.values()
-
+    _cont_subhandler_attr = 'zones'
+    _cont_subhandler_class = Zone
 
 class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
                  ParametersHandler):
@@ -389,19 +103,19 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
     _config_writer_files = ('named.conf', 'zoneX.zone')
     _config_writer_tpl_dir = path.join(path.dirname(__file__), 'templates')
 
-    def __init__(self, pickle_dir='.', config_dir={'named.conf':'.', 'zoneX.zone':'.'}):
+    def __init__(self, pickle_dir='.', config_dir='.'):
         r"Initialize DnsHandler object, see class documentation for details."
         self._persistent_dir = pickle_dir
         self._config_writer_cfg_dir = config_dir
-        self.mod = False
+        self._update = False
         self._config_build_templates()
         self._restore()
-        # FIXME self.mod = True
+        # FIXME self._update = True
         #if not self._restore():
         #r = self._restore()
         #print r
         #if not r:
-        #    self.mod = True
+        #    self._update = True
         self.host = HostHandler(self)
         self.zone = ZoneHandler(self)
         self.mx = MailExchangeHandler(self)
@@ -417,9 +131,8 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
         r"_write_config() -> None :: Generate all the configuration files."
         delete_zones = list()
         for a_zone in self.zones.values():
-            if a_zone.mod:
-                if not a_zone.new:
-                    # TODO freeze de la zona
+            if a_zone._update or a_zone._add:
+                if not a_zone._add:
                     call(('rndc', 'freeze', a_zone.name))
                 vars = dict(
                     zone = a_zone,
@@ -429,17 +142,16 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
                 )
                 self._write_single_config('zoneX.zone',
                                             self._zone_filename(a_zone), vars)
-                a_zone.mod = False
-                if not a_zone.new:
-                    # TODO unfreeze de la zona
+                a_zone._update = False
+                if not a_zone._add:
                     call(('rndc', 'thaw', a_zone.name))
                 else :
-                    self.mod = True
-                    a_zone.new = False
-            if a_zone.dele:
+                    self._update = True
+                    a_zone._add = False
+            if a_zone._delete:
                 #borro el archivo .zone
                 try:
-                    self.mod = True
+                    self._update = True
                     unlink(self._zone_filename(a_zone))
                 except OSError:
                     #la excepcion pude darse en caso que haga un add de una zona y
@@ -450,9 +162,9 @@ class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
         for z in delete_zones:
             del self.zones[z]
         #archivo general
-        if self.mod:
+        if self._update:
             self._write_single_config('named.conf')
-            self.mod = False
+            self._update = False
             self.reload()
 
 
@@ -497,7 +209,8 @@ if __name__ == '__main__':
     dns.commit()
 
     print 'ZONAS :', dns.zone.show()
-    print 'HOSTS :', dns.host.show()
+    for z in dns.zones:
+        print 'HOSTS from', z, ':', dns.host.show(z)
 
     #test zone errors
     #try:
@@ -505,9 +218,12 @@ if __name__ == '__main__':
     #except ZoneNotFoundError, inst:
     #    print 'Error: ', inst
 
+    from pymin.services.util import ItemNotFoundError, ItemAlreadyExistsError, \
+                                    ContainerNotFoundError
+
     try:
         dns.zone.delete('zone-sarasa')
-    except ZoneNotFoundError, inst:
+    except ItemNotFoundError, inst:
         print 'Error: ', inst
 
     #try:
@@ -515,33 +231,34 @@ if __name__ == '__main__':
     #except ZoneAlreadyExistsError, inst:
     #    print 'Error: ', inst
 
+
     #test hosts errors
     try:
         dns.host.update('zone-sarasa','kuak','192.68')
-    except ZoneNotFoundError, inst:
+    except ContainerNotFoundError, inst:
         print 'Error: ', inst
 
     try:
         dns.host.update('zona_loca.com','kuak','192.68')
-    except HostNotFoundError, inst:
+    except ItemNotFoundError, inst:
         print 'Error: ', inst
 
     try:
         dns.host.delete('zone-sarasa','lala')
-    except ZoneNotFoundError, inst:
+    except ContainerNotFoundError, inst:
         print 'Error: ', inst
 
     try:
         dns.host.delete('zona_loca.com','lala')
-    except HostNotFoundError, inst:
+    except ItemNotFoundError, inst:
         print 'Error: ', inst
 
     try:
         dns.host.add('zona','hostname_loco','192.168.0.23')
-    except ZoneNotFoundError, inst:
+    except ContainerNotFoundError, inst:
         print 'Error: ', inst
 
     try:
         dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
-    except HostAlreadyExistsError, inst:
+    except ItemAlreadyExistsError, inst:
         print 'Error: ', inst
index f967ae2d066abc23fd07fe13589cd84c3ea25d2d..94d681b7ccae135f6d5e6280f58f35ef10acb6c2 100644 (file)
@@ -6,191 +6,53 @@ from os import path
 from pymin.seqtools import Sequence
 from pymin.dispatcher import handler, HandlerError, Handler
 from pymin.services.util import Restorable, ConfigWriter, InitdHandler, \
-                                TransactionalHandler, SubHandler, call
-
-__ALL__ = ('IpHandler', 'Error','DeviceError', 'DeviceNotFoundError',
-           'RouteError', 'RouteNotFoundError', 'RouteAlreadyExistsError',
-           'AddressError', 'AddressNotFoundError', 'AddressAlreadyExistsError')
-
-class Error(HandlerError):
-    r"""
-    Error(command) -> Error instance :: Base IpHandler exception class.
-
-    All exceptions raised by the IpHandler inherits from this one, so you can
-    easily catch any IpHandler exception.
-
-    message - A descriptive error message.
-    """
-    pass
-
-class DeviceError(Error):
-
-    def __init__(self, device):
-        self.message = u'Device error : "%s"' % device
-
-class DeviceNotFoundError(DeviceError):
-
-    def __init__(self, device):
-        self.message = u'Device not found : "%s"' % device
-
-class AddressError(Error):
-
-    def __init__(self, addr):
-        self.message = u'Address error : "%s"' % addr
-
-class AddressNotFoundError(AddressError):
-
-    def __init__(self, address):
-        self.message = u'Address not found : "%s"' % address
-
-class AddressAlreadyExistsError(AddressError):
-
-    def __init__(self, address):
-        self.message = u'Address already exists : "%s"' % address
-
-class RouteError(Error):
-
-    def __init__(self, route):
-        self.message = u'Route error : "%s"' % route
-
-class RouteNotFoundError(RouteError):
-
-    def __init__(self, route):
-        self.message = u'Route not found : "%s"' % route
-
-class RouteAlreadyExistsError(RouteError):
-
-    def __init__(self, route):
-        self.message = u'Route already exists : "%s"' % route
+                                TransactionalHandler, SubHandler, call, \
+                                get_network_devices, ListComposedSubHandler, \
+                                DictComposedSubHandler
 
+__ALL__ = ('IpHandler',)
 
 class Route(Sequence):
-
     def __init__(self, net_addr, prefix, gateway):
         self.net_addr = net_addr
         self.prefix = prefix
         self.gateway = gateway
-
+    def update(self, net_addr=None, prefix=None, gateway=None):
+        if net_addr is not None: self.net_addr = net_addr
+        if prefix is not None: self.prefix = prefix
+        if gateway is not None: self.gateway = gateway
     def as_tuple(self):
-        return(self.addr, self.prefix, self.gateway)
-
-    def __cmp__(self, other):
-        if self.net_addr == other.net_addr \
-                and self.prefix == other.prefix \
-                and self.gateway == other.gateway:
-            return 0
-        return cmp(id(self), id(other))
-
-class RouteHandler(SubHandler):
+        return(self.net_addr, self.prefix, self.gateway)
 
+class RouteHandler(ListComposedSubHandler):
     handler_help = u"Manage IP routes"
-
-    @handler(u'Adds a route to a device')
-    def add(self, device, net_addr, prefix, gateway):
-        if not device in self.parent.devices:
-            raise DeviceNotFoundError(device)
-        r = Route(net_addr, prefix, gateway)
-        try:
-            self.parent.devices[device].routes.index(r)
-            raise RouteAlreadyExistsError(net_addr + '/' + prefix + '->' + gateway)
-        except ValueError:
-            self.parent.devices[device].routes.append(r)
-
-    @handler(u'Deletes a route from a device')
-    def delete(self, device, net_addr, prefix, gateway):
-        if not device in self.parent.devices:
-            raise DeviceNotFoundError(device)
-        r = Route(net_addr, prefix, gateway)
-        try:
-            self.parent.devices[device].routes.remove(r)
-        except ValueError:
-            raise RouteNotFoundError(net_addr + '/' + prefix + '->' + gateway)
-
-    @handler(u'Flushes routes from a device')
-    def flush(self, device):
-        if not device in self.parent.devices:
-            raise DeviceNotFoundError(device)
-        self.parent.devices[device].routes = list()
-
-
-    @handler(u'List routes')
-    def list(self, device):
-        try:
-            k = self.parent.devices[device].routes.keys()
-        except ValueError:
-            k = list()
-        return k
-
-    @handler(u'Get information about all routes')
-    def show(self):
-        try:
-            k = self.parent.devices[device].routes.values()
-        except ValueError:
-            k = list()
-        return k
-
+    _comp_subhandler_cont = 'devices'
+    _comp_subhandler_attr = 'routes'
+    _comp_subhandler_class = Route
 
 class Address(Sequence):
-
-    def __init__(self, ip, prefix, broadcast):
+    def __init__(self, ip, netmask, broadcast=None):
         self.ip = ip
-        self.prefix = prefix
+        self.netmask = netmask
         self.broadcast = broadcast
-
+    def update(self, netmask=None, broadcast=None):
+        if netmask is not None: self.netmask = netmask
+        if broadcast is not None: self.broadcast = broadcast
     def as_tuple(self):
-        return (self.ip, self.prefix, self.broadcast)
-
-class AddressHandler(SubHandler):
+        return (self.ip, self.netmask, self.broadcast)
 
+class AddressHandler(DictComposedSubHandler):
     handler_help = u"Manage IP addresses"
-
-    @handler(u'Adds an address to a device')
-    def add(self, device, ip, prefix, broadcast='+'):
-        if not device in self.parent.devices:
-            raise DeviceNotFoundError(device)
-        if ip in self.parent.devices[device].addrs:
-            raise AddressAlreadyExistsError(ip)
-        self.parent.devices[device].addrs[ip] = Address(ip, prefix, broadcast)
-
-    @handler(u'Deletes an address from a device')
-    def delete(self, device, ip):
-        if not device in self.parent.devices:
-            raise DeviceNotFoundError(device)
-        if not ip in self.parent.devices[device].addrs:
-            raise AddressNotFoundError(ip)
-        del self.parent.devices[device].addrs[ip]
-
-    @handler(u'Flushes addresses from a device')
-    def flush(self, device):
-        if not device in self.parent.devices:
-            raise DeviceNotFoundError(device)
-        self.parent.devices[device].addrs = dict()
-
-    @handler(u'List all addresses from a device')
-    def list(self, device):
-        try:
-            k = self.parent.devices[device].addrs.keys()
-        except ValueError:
-            k = list()
-        return k
-
-    @handler(u'Get information about addresses from a device')
-    def show(self, device):
-        try:
-            k = self.parent.devices[device].addrs.values()
-        except ValueError:
-            k = list()
-        return k
-
+    _comp_subhandler_cont = 'devices'
+    _comp_subhandler_attr = 'addrs'
+    _comp_subhandler_class = Address
 
 class Device(Sequence):
-
     def __init__(self, name, mac):
         self.name = name
         self.mac = mac
         self.addrs = dict()
         self.routes = list()
-
     def as_tuple(self):
         return (self.name, self.mac)
 
@@ -208,40 +70,25 @@ class DeviceHandler(SubHandler):
 
     @handler(u'Bring the device up')
     def up(self, name):
-        if name in self.devices:
+        if name in self.parent.devices:
             call(self.device_template.render(dev=name, action='up'), shell=True)
         else:
             raise DeviceNotFoundError(name)
 
     @handler(u'Bring the device down')
     def down(self, name):
-        if name in self.devices:
+        if name in self.parent.devices:
             call(self.device_template.render(dev=name, action='down'), shell=True)
         else:
             raise DeviceNotFoundError(name)
 
     @handler(u'List all devices')
     def list(self):
-        return self.devices.keys()
+        return self.parent.devices.keys()
 
     @handler(u'Get information about a device')
     def show(self):
-        return self.devices.items()
-
-
-def get_devices():
-    p = Popen(('ip', 'link', 'list'), stdout=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] = Device(eth, mac)
-        i = string.find('eth', m+11+17)
-    return d
+        return self.parent.devices.items()
 
 class IpHandler(Restorable, ConfigWriter, TransactionalHandler):
 
@@ -249,7 +96,8 @@ class IpHandler(Restorable, ConfigWriter, TransactionalHandler):
 
     _persistent_attrs = 'devices'
 
-    _restorable_defaults = dict(devices=get_devices())
+    _restorable_defaults = dict(devices=dict((dev, Device(dev, mac))
+                            for (dev, mac) in get_network_devices().items()))
 
     _config_writer_files = ('device', 'ip_add', 'ip_del', 'ip_flush',
                             'route_add', 'route_del', 'route_flush')
@@ -274,7 +122,7 @@ class IpHandler(Restorable, ConfigWriter, TransactionalHandler):
                 call(self._render_config('ip_add', dict(
                         dev = device.name,
                         addr = address.ip,
-                        prefix = address.prefix,
+                        netmask = address.netmask,
                         broadcast = address.broadcast,
                     )
                 ), shell=True)
@@ -299,7 +147,7 @@ if __name__ == '__main__':
     ip.route.add('eth0','192.168.0.0','24','192.168.0.1')
     ip.route.add('eth0','192.168.0.5','24','192.168.0.1')
     ip.commit()
-    ip.route.flush('eth0')
+    ip.route.clear('eth0')
     ip.commit()
     ip.addr.delete('eth0','192.168.0.23')
     ip.commit()
index 6bb6466c6c556b61a68223a4f80682f005f2ff4e..d6307b31b6cc90b62085b6dade97d8d93ff606f0 100644 (file)
@@ -1 +1 @@
-ip addr add dev ${dev} ${addr}/${prefix} broadcast ${broadcast}
\ No newline at end of file
+ip addr add dev ${dev} ${addr}/${netmask} broadcast ${broadcast}
\ No newline at end of file
index f57b925790072d61d2edb68eb8f9f73df8aa0305..0d2e223845026e9dce0fe5b3a3227a0844fe69f5 100644 (file)
@@ -1 +1 @@
-ip addr del dev ${dev} ${addr}/${prefix}
\ No newline at end of file
+ip addr del dev ${dev} ${addr}/${netmask}
\ No newline at end of file
index 9c96fcab36d6b56f645ccd6ffe5d565f1f72c67e..418616b7a99361df587f6ba3a52f8595bad7e864 100644 (file)
@@ -15,10 +15,13 @@ from pymin.dispatcher import Handler, handler, HandlerError, \
 #DEBUG = False
 DEBUG = True
 
-__ALL__ = ('ServiceHandler', 'RestartHandler', 'ReloadHandler', 'InitdHandler',
-            'SubHandler', 'DictSubHandler', 'ListSubHandler', 'Persistent',
-            'ConfigWriter', 'Error', 'ReturnNot0Error', 'ExecutionError',
-            'ItemError', 'ItemAlreadyExistsError', 'ItemNotFoundError', 'call')
+__ALL__ = ('Error', 'ReturnNot0Error', 'ExecutionError', 'ItemError',
+            'ItemAlreadyExistsError', 'ItemNotFoundError', 'ContainerError',
+            'ContainerNotFoundError', 'call', 'get_network_devices',
+            'Persistent', 'Restorable', 'ConfigWriter', 'ServiceHandler',
+            'RestartHandler', 'ReloadHandler', 'InitdHandler', 'SubHandler',
+            'DictSubHandler', 'ListSubHandler', 'ComposedSubHandler',
+            'ListComposedSubHandler', 'DictComposedSubHandler')
 
 class Error(HandlerError):
     r"""
@@ -126,6 +129,44 @@ class ItemNotFoundError(ItemError):
         r"Initialize the object. See class documentation for more info."
         self.message = u'Item not found: "%s"' % key
 
+class ContainerError(Error, KeyError):
+    r"""
+    ContainerError(key) -> ContainerError instance.
+
+    This is the base exception for all container related errors.
+    """
+
+    def __init__(self, key):
+        r"Initialize the object. See class documentation for more info."
+        self.message = u'Container error: "%s"' % key
+
+class ContainerNotFoundError(ContainerError):
+    r"""
+    ContainerNotFoundError(key) -> ContainerNotFoundError instance.
+
+    This exception is raised when trying to operate on an container that
+    doesn't exists.
+    """
+
+    def __init__(self, key):
+        r"Initialize the object. See class documentation for more info."
+        self.message = u'Container not found: "%s"' % key
+
+
+def get_network_devices():
+    p = subprocess.Popen(('ip', 'link', 'list'), 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)
+    return d
 
 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
             stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
@@ -749,25 +790,8 @@ class ContainerSubHandler(SubHandler):
 class ListSubHandler(ContainerSubHandler):
     r"""ListSubHandler(parent) -> ListSubHandler instance.
 
-    This is a helper class to inherit from to automatically handle subcommands
-    that operates over a list parent attribute.
-
-    The list attribute to handle and the class of objects that it contains can
-    be defined by calling the constructor or in a more declarative way as
-    class attributes, like:
-
-    class TestHandler(ListSubHandler):
-        _cont_subhandler_attr = 'some_list'
-        _cont_subhandler_class = SomeClass
-
-    This way, the parent's some_list attribute (self.parent.some_list) will be
-    managed automatically, providing the commands: add, update, delete, get,
-    list and show. New items will be instances of SomeClass, which should
-    provide a cmp operator to see if the item is on the list and an update()
-    method, if it should be possible to modify it. If SomeClass has an _add,
-    _update or _delete attribute, it set them to true when the item is added,
-    updated or deleted respectively (in case that it's deleted, it's not
-    removed from the list, but it's not listed either).
+    ContainerSubHandler holding lists. See ComposedSubHandler documentation
+    for details.
     """
 
     @handler(u'Get how many items are in the list')
@@ -778,32 +802,209 @@ class ListSubHandler(ContainerSubHandler):
 class DictSubHandler(ContainerSubHandler):
     r"""DictSubHandler(parent) -> DictSubHandler instance.
 
-    This is a helper class to inherit from to automatically handle subcommands
-    that operates over a dict parent attribute.
+    ContainerSubHandler holding dicts. See ComposedSubHandler documentation
+    for details.
+    """
+
+    @handler(u'List all the items by key')
+    def list(self):
+        r"list() -> tuple :: List all the item keys."
+        return self._attr().keys()
 
-    The dict attribute to handle and the class of objects that it contains can
-    be defined by calling the constructor or in a more declarative way as
-    class attributes, like:
+class ComposedSubHandler(SubHandler):
+    r"""ComposedSubHandler(parent) -> ComposedSubHandler instance.
 
-    class TestHandler(DictSubHandler):
-        _cont_subhandler_attr = 'some_dict'
-        _cont_subhandler_class = SomeClass
+    This is a helper class to implement ListComposedSubHandler and
+    DictComposedSubHandler. You should not use it directly.
+
+    This class is usefull when you have a parent that has a dict (cont)
+    that stores some object that has an attribute (attr) with a list or
+    a dict of objects of some class. In that case, this class provides
+    automated commands to add, update, delete, get and show that objects.
+    This commands takes the cont (key of the dict for the object holding
+    the attr), and an index for access the object itself (in the attr
+    list/dict).
 
-    This way, the parent's some_dict attribute (self.parent.some_dict) will be
-    managed automatically, providing the commands: add, update, delete, get,
-    list and show. New items will be instances of SomeClass, which should
-    provide a constructor with at least the key value, an as_tuple() method
-    and an update() method,     if it should be possible to modify
+    The container object (cont) that holds a containers, the attribute of
+    that object that is the container itself, and the class of the objects
+    that it contains can be defined by calling the constructor or in a
+    more declarative way as class attributes, like:
+
+    class TestHandler(ComposedSubHandler):
+        _comp_subhandler_cont = 'some_cont'
+        _comp_subhandler_attr = 'some_attr'
+        _comp_subhandler_class = SomeClass
+
+    This way, the parent's some_cont attribute (self.parent.some_cont)
+    will be managed automatically, providing the commands: add, update,
+    delete, get and show for manipulating a particular instance that holds
+    of SomeClass. For example, updating an item at the index 5 is the same
+    (simplified) as doing parent.some_cont[cont][5].update().
+    SomeClass should provide a cmp operator to see if the item is on the
+    container and an update() method, if it should be possible to modify
     it. If SomeClass has an _add, _update or _delete attribute, it set
     them to true when the item is added, updated or deleted respectively
-    (in case that it's deleted, it's not removed from the dict, but it's
-    not listed either).
+    (in case that it's deleted, it's not removed from the container,
+    but it's not listed either). If the container objects
+    (parent.some_cont[cont]) has an _update attribute, it's set to True
+    when any add, update or delete command is executed.
+    """
+
+    def __init__(self, parent, cont=None, attr=None, cls=None):
+        r"Initialize the object, see the class documentation for details."
+        self.parent = parent
+        if cont is not None:
+            self._comp_subhandler_cont = cont
+        if attr is not None:
+            self._comp_subhandler_attr = attr
+        if cls is not None:
+            self._comp_subhandler_class = cls
+
+    def _cont(self):
+        return getattr(self.parent, self._comp_subhandler_cont)
+
+    def _attr(self, cont, attr=None):
+        if attr is None:
+            return getattr(self._cont()[cont], self._comp_subhandler_attr)
+        setattr(self._cont()[cont], self._comp_subhandler_attr, attr)
+
+    def _vattr(self, cont):
+        if isinstance(self._attr(cont), dict):
+            return dict([(k, i) for (k, i) in self._attr(cont).items()
+                    if not hasattr(i, '_delete') or not i._delete])
+        return [i for i in self._attr(cont)
+                if not hasattr(i, '_delete') or not i._delete]
+
+    @handler(u'Add a new item')
+    def add(self, cont, *args, **kwargs):
+        r"add(cont, ...) -> None :: Add an item to the list."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        item = self._comp_subhandler_class(*args, **kwargs)
+        if hasattr(item, '_add'):
+            item._add = True
+        key = item
+        if isinstance(self._attr(cont), dict):
+            key = item.as_tuple()[0]
+        # do we have the same item? then raise an error
+        if key in self._vattr(cont):
+            raise ItemAlreadyExistsError(item)
+        # do we have the same item, but logically deleted? then update flags
+        if key in self._attr(cont):
+            index = key
+            if not isinstance(self._attr(cont), dict):
+                index = self._attr(cont).index(item)
+            if hasattr(item, '_add'):
+                self._attr(cont)[index]._add = False
+            if hasattr(item, '_delete'):
+                self._attr(cont)[index]._delete = False
+        else: # it's *really* new
+            if isinstance(self._attr(cont), dict):
+                self._attr(cont)[key] = item
+            else:
+                self._attr(cont).append(item)
+        if hasattr(self._cont()[cont], '_update'):
+            self._cont()[cont]._update = True
+
+    @handler(u'Update an item')
+    def update(self, cont, index, *args, **kwargs):
+        r"update(cont, index, ...) -> None :: Update an item of the container."
+        # TODO make it right with metaclasses, so the method is not created
+        # unless the update() method really exists.
+        # TODO check if the modified item is the same of an existing one
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        if not isinstance(self._attr(cont), dict):
+            index = int(index) # TODO validation
+        if not hasattr(self._comp_subhandler_class, 'update'):
+            raise CommandNotFoundError(('update',))
+        try:
+            item = self._vattr(cont)[index]
+            item.update(*args, **kwargs)
+            if hasattr(item, '_update'):
+                item._update = True
+            if hasattr(self._cont()[cont], '_update'):
+                self._cont()[cont]._update = True
+        except LookupError:
+            raise ItemNotFoundError(index)
+
+    @handler(u'Delete an item')
+    def delete(self, cont, index):
+        r"delete(cont, index) -> None :: Delete an item of the container."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        if not isinstance(self._attr(cont), dict):
+            index = int(index) # TODO validation
+        try:
+            item = self._vattr(cont)[index]
+            if hasattr(item, '_delete'):
+                item._delete = True
+            else:
+                del self._attr(cont)[index]
+            if hasattr(self._cont()[cont], '_update'):
+                self._cont()[cont]._update = True
+            return item
+        except LookupError:
+            raise ItemNotFoundError(index)
+
+    @handler(u'Remove all items (use with care).')
+    def clear(self, cont):
+        r"clear(cont) -> None :: Delete all items of the container."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        if isinstance(self._attr(cont), dict):
+            self._attr(cont).clear()
+        else:
+            self._attr(cont, list())
+
+    @handler(u'Get information about an item')
+    def get(self, cont, index):
+        r"get(cont, index) -> item :: List all the information of an item."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        if not isinstance(self._attr(cont), dict):
+            index = int(index) # TODO validation
+        try:
+            return self._vattr(cont)[index]
+        except LookupError:
+            raise ItemNotFoundError(index)
+
+    @handler(u'Get information about all items')
+    def show(self, cont):
+        r"show(cont) -> list of items :: List all the complete items information."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        if isinstance(self._attr(cont), dict):
+            return self._attr(cont).values()
+        return self._vattr(cont)
+
+class ListComposedSubHandler(ComposedSubHandler):
+    r"""ListComposedSubHandler(parent) -> ListComposedSubHandler instance.
+
+    ComposedSubHandler holding lists. See ComposedSubHandler documentation
+    for details.
+    """
+
+    @handler(u'Get how many items are in the list')
+    def len(self, cont):
+        r"len(cont) -> int :: Get how many items are in the list."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        return len(self._vattr(cont))
+
+class DictComposedSubHandler(ComposedSubHandler):
+    r"""DictComposedSubHandler(parent) -> DictComposedSubHandler instance.
+
+    ComposedSubHandler holding dicts. See ComposedSubHandler documentation
+    for details.
     """
 
     @handler(u'List all the items by key')
-    def list(self):
-        r"list() -> tuple :: List all the item keys."
-        return self._attr().keys()
+    def list(self, cont):
+        r"list(cont) -> tuple :: List all the item keys."
+        if not cont in self._cont():
+            raise ContainerNotFoundError(cont)
+        return self._attr(cont).keys()
 
 
 if __name__ == '__main__':