pvl/dhcp/rule.py
author Tero Marttila <terom@paivola.fi>
Tue, 10 Mar 2015 00:11:43 +0200
changeset 739 5149c39f3dfc
parent 214 e314447c5621
permissions -rw-r--r--
pvl.hosts: improve HostExtension support enough to move boot= into pvl.hosts.dhcp
"""
    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',             action='store_true',
            help="dhcp plugin instance by network")

    parser.add_option('--dhcp-gateway',             action='store_true',
            help="dhcp plugin instance by gateway")
   
    return parser

def apply (options) :
    """
        Return DHCPRule from options.
    """

    if options.dhcp_rules :
        return DHCPRule.load(options, open(options.dhcp_rules))
    else :
        return DHCPRule()

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, options, file, name=None) :
        """
            Load from config file.
        """
        
        config = configobj.ConfigObj(file)

        return cls.load_section(options, name, config)

    @classmethod
    def load_section (cls, options, name, section) :
        """
            Rule from sub-sections and section.
        """

        # recurse
        rules = tuple(cls.load_section(options, subsection, section[subsection]) for subsection in section.sections)

        # rule
        attrs = dict((name, section[name]) for name in section.scalars)

        try :
            return cls.config(options, name, rules, **attrs)

        except ValueError as ex :
            raise ValueError("[%s] %s" % (name, ex))

    @classmethod
    def config (cls, options, name, rules, gateway=None, network=None, interval=None) :
        """
            Rule from section.
        """

        if interval :
            log.warn("%s: interval: not implemented", name)

        if network and options.dhcp_network :
            network = IPv4Network(network)
        else :
            network = None

        if gateway and options.dhcp_gateway :
            gateway = gateway
        else :
            gateway = None

        return cls(name, rules,
                gateway     = gateway, 
                network     = network,
        )

    def __init__ (self, name=None, 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 not in 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 ''