pvl.dhcp: split pvl.dhcp.rule.DHCPRule for use with leases
authorTero Marttila <terom@paivola.fi>
Sun, 10 Feb 2013 19:08:53 +0200
changeset 211 cf74bbb95d2b
parent 210 544a6a58ed28
child 212 c2bbde4007aa
pvl.dhcp: split pvl.dhcp.rule.DHCPRule for use with leases
pvl/dhcp/hosts.py
pvl/dhcp/leases.py
pvl/dhcp/rule.py
--- a/pvl/dhcp/hosts.py	Sun Feb 10 18:31:38 2013 +0200
+++ b/pvl/dhcp/hosts.py	Sun Feb 10 19:08:53 2013 +0200
@@ -132,122 +132,3 @@
             log.info("Insert: %s", attrs)
             self.insert(attrs)
 
-import configobj
-from pvl.verkko.utils import IPv4Address, IPv4Network
-
-class DHCPHostRule (object) :
-    """
-        A rule matching DHCP hosts.
-    """
-
-    @classmethod
-    def load (cls, file, name=None) :
-        """
-            Load from config file.
-        """
-        
-        config = configobj.ConfigObj(file)
-
-        return cls.load_section(name, config)
-
-    @classmethod
-    def load_section (cls, name, section) :
-        """
-            Rule from sub-sections and section.
-        """
-
-        # recurse
-        rules = tuple(cls.load_section(subsection, section[subsection]) for subsection in section.sections)
-
-        # rule
-        attrs = dict((name, section[name]) for name in section.scalars)
-
-        try :
-            return cls.config(name, rules, **attrs)
-
-        except ValueError as ex :
-            raise ValueError("[%s] %s" % (name, ex))
-
-    @classmethod
-    def config (cls, name, rules, gateway=None, network=None, interval=None) :
-        """
-            Rule from section.
-        """
-
-        if interval :
-            log.warn("%s: interval: not implemented", name)
-
-        if network :
-            network = IPv4Network(network)
-        else :
-            network = None
-
-        return cls(name, rules,
-                gateway     = gateway, 
-                network     = network,
-        )
-
-    @classmethod
-    def rule (cls, name, gateway=None, network=None) :
-        if network :
-            network = IPv4Network(network)
-        else :
-            network = None
-
-        return cls(name, (),
-                gateway     = gateway, 
-                network     = network,
-        )
-
-    def __init__ (self, name, rules, gateway=None, network=None) :
-        """
-            Match as name by gateway/network.
-        """
-
-        self.name = name
-        self.rules = rules
-
-        self.gateway = gateway
-        self.network = network
-
-        log.info("%s: gateway=%s, network=%s: %s", name, gateway, network, ' '.join(str(rule) for rule in rules))
-
-    def match (self, host) :
-        """
-            Match against given host.
-        """
-
-        if self.gateway and host.gw != self.gateway :
-            return False
-
-        if self.network and IPv4Address(host.ip) not in self.network :
-            return False
-        
-        return True
-
-    def apply (self, host) :
-        """
-            Match host against our rule and any sub rules, returning matching DHCPHostRule, or None.
-        """
-
-        match = self.match(host)
-
-        log.debug("%s: match: %s: %s", self, host, match)
-            
-        if not match :
-            return
-
-        for rule in self.rules :
-            apply = rule.apply(host)
-
-            if apply :
-                return apply
-        
-        log.debug("%s: apply: %s", self, host)
-        
-        return self
-
-    def __str__ (self) :
-        return self.name or ''
-
-
--- a/pvl/dhcp/leases.py	Sun Feb 10 18:31:38 2013 +0200
+++ b/pvl/dhcp/leases.py	Sun Feb 10 19:08:53 2013 +0200
@@ -8,6 +8,8 @@
 
 import logging; log = logging.getLogger('pvl.dhcp.leases')
 
+DHCPD_LEASES = '/var/lib/dhcp/dhcpd.leases'
+
 class DHCPLeasesParser (object) :
     """
         Simplistic parser for a dhcpd.leases file.
@@ -218,7 +220,7 @@
 
     lease_date_fmt = LEASE_DATE_FMT_DEFAULT
 
-    def __init__ (self, path) :
+    def __init__ (self, path=DHCPD_LEASES) :
         """
             path        - path to dhcpd.leases file
         """
@@ -416,7 +418,7 @@
             Iterate over all leases.
         """
 
-        return self.leases.itervalues()
+        return self._leases.itervalues()
 
     # utils
     def lease_state (self, lease) :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/dhcp/rule.py	Sun Feb 10 19:08:53 2013 +0200
@@ -0,0 +1,202 @@
+"""
+    Match DHCP clients by network.
+"""
+
+import optparse
+import configobj
+
+from pvl.verkko.utils import IPv4Address, IPv4Network
+
+import logging; log = logging.getLogger('pvl.dhcp.rule')
+
+def parser (parser) :
+    """
+        Optparse options for DHCPRule
+    """
+
+    parser.set_defaults(
+            dhcp_network        = [],
+            dhcp_gateway        = [],
+    )
+ 
+    parser = optparse.OptionGroup(parser, "DHCP host/lease matching")
+
+    parser.add_option('--dhcp-rules',               metavar='CONF',
+            help="dhcp plugin instances by network/gateway")
+
+    parser.add_option('--dhcp-network',             metavar='NET', action='append',
+            help="dhcp plugin instance by network")
+
+    parser.add_option('--dhcp-gateway',             metavar='NET', action='append',
+            help="dhcp plugin instance by gateway")
+   
+    return parser
+
+def apply_rules (options) :
+    """
+        Yield DHCPRules from options.
+    """
+
+    for gateway in options.dhcp_gateway :
+        yield DHCPRule.rule(gateway, gateway=gateway)
+
+    for network in options.dhcp_network :
+        yield DHCPRule.rule(network, network=network)
+
+    if options.dhcp_rules :
+        yield DHCPRule.load(open(options.dhcp_rules))
+
+def apply (options) :
+    """
+        Return DHCPRule from options.
+    """
+
+    return DHCPRule(None, tuple(apply_rules(options)))
+
+class DHCPRule (object) :
+    """
+        A rule matching DHCP hosts/leases by gateway/network.
+
+        Note that leases don't have a gateway, but they're assumed to be valid, so we just ignore any gateway criteria for for unknown gateways.
+    """
+
+
+    @classmethod
+    def load (cls, file, name=None) :
+        """
+            Load from config file.
+        """
+        
+        config = configobj.ConfigObj(file)
+
+        return cls.load_section(name, config)
+
+    @classmethod
+    def load_section (cls, name, section) :
+        """
+            Rule from sub-sections and section.
+        """
+
+        # recurse
+        rules = tuple(cls.load_section(subsection, section[subsection]) for subsection in section.sections)
+
+        # rule
+        attrs = dict((name, section[name]) for name in section.scalars)
+
+        try :
+            return cls.config(name, rules, **attrs)
+
+        except ValueError as ex :
+            raise ValueError("[%s] %s" % (name, ex))
+
+    @classmethod
+    def config (cls, name, rules, gateway=None, network=None, interval=None) :
+        """
+            Rule from section.
+        """
+
+        if interval :
+            log.warn("%s: interval: not implemented", name)
+
+        if network :
+            network = IPv4Network(network)
+        else :
+            network = None
+
+        return cls(name, rules,
+                gateway     = gateway, 
+                network     = network,
+        )
+
+    @classmethod
+    def rule (cls, name, gateway=None, network=None) :
+        if network :
+            network = IPv4Network(network)
+        else :
+            network = None
+
+        return cls(name, (),
+                gateway     = gateway, 
+                network     = network,
+        )
+
+    def __init__ (self, name, rules, gateway=None, network=None) :
+        """
+            Match as name by gateway/network.
+        """
+
+        self.name = name
+        self.rules = rules
+
+        self.gateway = gateway
+        self.network = network
+
+        log.info("%s: gateway=%s, network=%s: %s", name, gateway, network, ' '.join(str(rule) for rule in rules))
+
+    def match (self, host) :
+        """
+            Match against given host.
+        """
+
+        if self.gateway :
+            gateway = host.get('gw')
+
+            if gateway and gateway != self.gateway :
+                gateway = False
+
+        else :
+            gateway = None
+
+        if self.network :
+            network = host.get('ip')
+            
+            if network and network != self.network :
+                network = False
+        else :
+            network = None
+        
+        # decide
+        if network is False or gateway is False :
+            # negative
+            return False
+
+        elif network or gateway :
+            # (partial) positive
+            return True
+
+        else :
+            # unknown
+            return None
+
+    def apply (self, host) :
+        """
+            Match { gw: str, ip: IPv4Address } against our rule and any sub rules, returning matching DHCPHostRule, or None.
+        """
+
+        match = self.match(host)
+
+        log.debug("%s: match: %s: %s", self, host, match)
+            
+        if match is False :
+            # negative
+            return
+
+        for rule in self.rules :
+            apply = rule.apply(host)
+
+            if apply :
+                return apply
+        
+        log.debug("%s: apply: %s", self, host)
+        
+        if match :
+            # positive
+            return self
+        else :
+            # unknown
+            return None
+
+    def __str__ (self) :
+        return self.name or ''
+
+