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 ''