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