pvl.hosts: improve HostExtension support enough to move boot= into pvl.hosts.dhcp
import pvl.dhcp.config
import pvl.hosts.host
def parse_dhcp_boot(boot):
"""
Parse the dhcp boot=... option
>>> print parse_dhcp_boot(None)
{}
>>> print parse_dhcp_boot({'filename': '/foo'})
{'filename': '/foo'}
>>> print parse_dhcp_boot({'filename': '/foo', 'next-server': 'bar'})
{'next-server': 'bar', 'filename': '/foo'}
>>> print parse_dhcp_boot('/foo')
{'filename': '/foo'}
>>> print parse_dhcp_boot('bar:/foo')
{'next-server': 'bar', 'filename': '/foo'}
>>> print parse_dhcp_boot('bar:')
{'next-server': 'bar'}
>>> print parse_dhcp_boot('foo')
Traceback (most recent call last):
...
ValueError: invalid boot=foo
"""
# unpack dict, or str
if not boot:
filename = next_server = None
boot_str = None
elif isinstance(boot, dict):
filename = boot.pop('filename', None)
next_server = boot.pop('next-server', None)
boot_str = boot.pop(None, None)
else:
filename = next_server = None
boot_str = boot
boot = None
if boot:
raise ValueError("Invalid boot.*: {instances}".format(instances=' '.join(boot)))
# any boot= given overrides boot.* fields
if not boot_str:
pass
elif boot_str.startswith('/'):
filename = boot_str
elif boot_str.endswith(':'):
next_server = boot_str[:-1]
elif ':' in boot_str:
next_server, filename = boot_str.split(':', 1)
else :
raise ValueError("invalid boot={boot}".format(boot=boot_str))
return next_server, filename
@pvl.hosts.host.register_extension
class HostDHCP(pvl.hosts.host.HostExtension):
EXTENSION = 'dhcp'
EXTENSION_FIELDS = (
'boot',
)
@classmethod
def build (cls,
boot = None,
subclass = None,
):
next_server, filename = parse_dhcp_boot(boot)
return cls(
filename = filename,
subclass = subclass,
next_server = next_server,
)
def __init__(self,
filename = None,
next_server = None,
subclass = None,
):
self.filename = filename
self.next_server = next_server
self.subclass = subclass
def dhcp_host_subclass (host, subclass, ethernet):
"""
Build a DHCP Item for declaring a subclass for a host.
"""
# hardware-type prefixed hardware-address
hardware = '1:' + ethernet
return pvl.dhcp.config.Block(None, [
('subclass', pvl.dhcp.config.String(subclass), pvl.dhcp.config.Field(hardware)),
])
class HostDHCPError(pvl.hosts.host.HostError):
pass
def dhcp_host_options (host, ethernet, dhcp=None):
"""
Yield specific dhcp.conf host { ... } items.
"""
yield 'option', 'host-name', host.name
yield 'hardware', 'ethernet', pvl.dhcp.config.Field(ethernet)
if host.ip4:
yield 'fixed-address', pvl.dhcp.config.Field(str(host.ip4))
if dhcp:
if dhcp.next_server:
yield 'next-server', dhcp.next_server
if dhcp.filename:
yield 'filename', dhcp.filename
def dhcp_host (host):
"""
Yield pvl.dhcp.config.Block's for given Host, with possible HostDHCP extensions.
"""
if not host.ethernet:
# nothing to be seen here
return
if host.owner :
comment = u"Owner: {host.owner}".format(host=host)
else:
comment = None
dhcp = host.extensions.get('dhcp')
for index, ethernet in host.ethernet.iteritems() :
if index:
name = '{host.name}-{index}'.format(host=host, index=index)
else:
name = '{host.name}'.format(host=host)
items = list(dhcp_host_options(host, ethernet, dhcp=dhcp))
yield pvl.dhcp.config.Block(('host', name), items, comment=comment)
if dhcp and dhcp.subclass:
yield dhcp_host_subclass(host, dhcp.subclass, ethernet)
def dhcp_hosts (hosts):
"""
Verifies that there are no dupliate hosts.
"""
blocks = { }
for host in hosts:
for block in dhcp_host(host):
if not block.key:
# TODO: check for unique Item-Blocks
pass
elif block.key in blocks:
raise HostDHCPError(host, "dhcp {block} conflict with {other}; hosts on multiple networks must use unique ethernet.XXX=... naming".format(block=block, other=blocks[block.key]))
else:
blocks[block.key] = host
yield block