--- a/pvl/hosts/host.py Mon Mar 09 23:31:13 2015 +0200
+++ b/pvl/hosts/host.py Tue Mar 10 00:11:43 2015 +0200
@@ -69,59 +69,6 @@
return ':'.join('%02x' % int(x, 16) for x in value.split(':'))
-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
- """
-
- # normalize to dict
- if not boot:
- boot = { }
- elif not isinstance(boot, dict):
- boot = { None: boot }
- else:
- boot = dict(boot)
-
- # support either an instanced dict or a plain str or a mixed instanced-with-plain-str
- boot_str = boot.pop(None, None)
-
- if not (set(boot) <= set(('filename', 'next-server', None))):
- raise ValueError("Invalid boot.*: {instances}".format(instances=' '.join(boot)))
-
- # any boot= given overrides boot.* fields
- if not boot_str:
- pass
- elif boot_str.startswith('/'):
- boot['filename'] = boot_str
-
- elif boot_str.endswith(':'):
- boot['next-server'] = boot_str[:-1]
-
- elif ':' in boot_str:
- boot['next-server'], boot['filename'] = boot_str.split(':', 1)
-
- else :
- raise ValueError("invalid boot={boot}".format(boot=boot_str))
-
- return boot
-
def parse_str(value):
"""
Normalize optional string value.
@@ -155,16 +102,32 @@
"""
EXTENSIONS = { }
+ EXTENSION_FIELDS = { }
@classmethod
- def build_extensions(cls, extensions):
- for extension, value in extensions.iteritems():
+ def parse_extensions(cls, extensions, extra):
+ """
+ Parse extensions and extension fields to yield
+ (HostExtension, field, value)
+ """
+
+ for extension, values in extensions.iteritems():
extension_cls = cls.EXTENSIONS.get(extension)
- if extension_cls:
- yield extension, extension_cls.build(**value)
- else:
+ if not extension_cls:
log.warning("skip unknown extension: %s", extension)
+ continue
+
+ for field, value in values.iteritems():
+ yield extension_cls, field, value
+
+ for field, value in extra.iteritems():
+ extension_cls = cls.EXTENSION_FIELDS.get(field)
+
+ if not extension_cls:
+ raise ValueError("unknown field: {field}".format(field=field))
+
+ yield extension_cls, field, value
@classmethod
def build (cls, name, domain,
@@ -175,8 +138,8 @@
alias=None, alias4=None, alias6=None,
forward=None, reverse=None,
down=None,
- boot=None,
extensions={ },
+ **extra
) :
"""
Return a Host initialized from data attributes.
@@ -184,6 +147,13 @@
This handles all string parsing to our data types.
"""
+ extension_classes = { }
+
+ for extension_cls, field, value in cls.parse_extensions(extensions, extra):
+ extension_classes.setdefault(extension_cls, dict())[field] = value
+
+ extensions = {extension_cls.EXTENSION: extension_cls.build(**params) for extension_cls, params in extension_classes.iteritems()}
+
return cls(name,
domain = domain,
ip4 = parse_ip(ip, ipaddr.IPv4Address),
@@ -197,8 +167,7 @@
forward = parse_str(forward),
reverse = parse_str(reverse),
down = parse_bool(down),
- boot = parse_dhcp_boot(boot),
- extensions = dict(cls.build_extensions(extensions)),
+ extensions = extensions,
)
def __init__ (self, name, domain,
@@ -209,7 +178,6 @@
alias=(), alias4=(), alias6=(),
forward=None, reverse=None,
down=None,
- boot=None,
extensions={},
):
"""
@@ -242,7 +210,6 @@
self.alias6 = alias6
self.owner = owner
self.location = location
- self.boot = boot
self.forward = forward
self.reverse = reverse
self.down = down
@@ -296,6 +263,9 @@
Provides default no-op behaviours for extension hooks.
"""
+ EXTENSION = None
+ EXTENSION_FIELDS = ()
+
def addresses (self):
"""
Yield additional (sublabel, ipaddr) records.
@@ -303,10 +273,12 @@
return ()
-def extension (cls):
+def register_extension (cls):
"""
Register an extension class
"""
Host.EXTENSIONS[cls.EXTENSION] = cls
+ for field in cls.EXTENSION_FIELDS:
+ Host.EXTENSION_FIELDS[field] = cls