pvl/hosts/host.py
changeset 739 5149c39f3dfc
parent 738 3104fdf7ea26
child 740 74352351d6f5
--- 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