pvl/hosts/dhcp.py
author Tero Marttila <terom@paivola.fi>
Tue, 10 Mar 2015 00:11:43 +0200
changeset 739 5149c39f3dfc
parent 733 45bedeba92e5
permissions -rw-r--r--
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