pvl.hosts.config: refactor
authorTero Marttila <tero.marttila@aalto.fi>
Thu, 26 Feb 2015 17:24:13 +0200
changeset 504 ee0a3dcacb95
parent 503 a56456f901e8
child 505 e5a76c404679
pvl.hosts.config: refactor
pvl/hosts/config.py
pvl/hosts/tests.py
--- a/pvl/hosts/config.py	Thu Feb 26 16:38:31 2015 +0200
+++ b/pvl/hosts/config.py	Thu Feb 26 17:24:13 2015 +0200
@@ -51,19 +51,6 @@
     def __str__ (self):
         return "{self.config}:{self.error.line_number}: {self.error.message}".format(self=self)        
 
-def apply_host_expand (options, range, name, domain, **params):
-    """
-        Expand a templated host item.
-    """
-
-    name = pvl.dns.parse_generate_field(name)
-    
-    # expand all fields
-    params = {param: pvl.dns.parse_generate_field(value) for param, value in params.iteritems()}
-
-    for i in range:
-        yield name(i), {param: value(i) for param, value in params.iteritems()}
-
 def parse_expand(name):
     """
         Parse a name containing an optional expansion part.
@@ -88,37 +75,42 @@
 
     return name, range
 
-def parse_host_config(config):
+def parse_config_field(field):
     """
         Parse structured config fields.
+
+            [<extension> ":"] <field> ["." <instance>]
     """
 
-    for field, value in config.iteritems():
-        if ':' in field :
-            extension, field = field.split(':', 1)
-        else:
-            extension = None
+    if ':' in field :
+        extension, field = field.split(':', 1)
+    else:
+        extension = None
 
-        if '.' in field :
-            field, instance = field.split('.')
-        else :
-            instance = None
-        
-        yield extension, field, instance, value
+    if '.' in field :
+        field, instance = field.split('.')
+    else :
+        instance = None
+    
+    return extension, field, instance
 
-def apply_host(options, name, domain, config):
+def apply_host (name, domain, config):
     """
-        Returns Host from a given (expanded) config section.
-
-        Parses config field names:
-
-            [<extension> ":"] <field> ["." <instance>]
+        Return Host from an (expanded) config section.
+            
+            name        - (expanded) name of host
+            domain      - domain for host
+            config      - host config fields to parse
+        
+        Raises ValueError.
     """
 
     fields = { }
     extensions = fields['extensions'] = { }
 
-    for extension, field, instance, value in parse_host_config(config):
+    for field, value in config.iteritems():
+        extension, field, instance = parse_config_field(field)
+
         if extension:
             f = extensions.setdefault(extension, {})
         else:
@@ -132,20 +124,21 @@
         elif isinstance(f[field], dict):
             f.setdefault(field, {})[instance] = value
         else:
-            raise HostConfigError(None, "{name}: override instanced {field} value: {value}".format(name=name, field=field, value=value))
-
+            raise ValueError("override instanced {field} value: {value}".format(field=field, value=value))
+    
     return Host.build(name, domain, **fields)
 
-def apply_host_config (options, parent, name, **config):
+def apply_hosts (parent, name, config):
     """
         Yield Hosts from a given config section.
-
-            options     global options
-            parent      parent filename/section containing this host item, or None.
-                        used for domain
-            name        name of the section (or file) containing this host item.
-                        used for hostname
-            **config    host parameters
+            
+            parent      - parent filename/section containing this host item, or None.
+                          used for domain
+            name        - name of the section (or file) containing this host item.
+                          used for hostname
+            config      - host parameters to parse
+        
+        Raises ValueError.
     """
     
     if '@' in name:
@@ -157,24 +150,29 @@
     elif parent:
         log.debug("%s: default domain to section: %s", name, parent)
         domain = parent
-    elif options.hosts_domain:
-        log.debug("%s: default domain to --hosts-domain: %s", name, options.hosts_domain)
-        domain = options.hosts_domain
     else:
-        raise HostConfigError(None, "{name}: no domain given".format(name=name))
+        # XXX: impossible?
+        raise ValueError("no domain given")
     
-    # expand 
+    # expand?
     name, range = parse_expand(name)
     
     if range:
-        configs_iter = apply_host_expand(options, range, name, domain, **config)
+        generate_name = pvl.dns.parse_generate_field(name)
+        
+        # expand all fields
+        generate_config = {field: pvl.dns.parse_generate_field(value) for field, value in config.iteritems()}
+
+        for i in range:
+            yield apply_host(generate_name(i), domain, 
+                    {field: value(i) for field, value in generate_config.iteritems()},
+            )
+
     else:
-        configs_iter = [(name, config)]
-    
-    for name, config in configs_iter:
-        yield apply_host(options, name, domain, config)
+        # single host
+        yield apply_host(name, domain, config)
 
-def apply_host_includes (options, config_path, include):
+def apply_hosts_includes (options, config_path, include):
     """
         Yield files from a given config's include=... value
     """
@@ -211,23 +209,23 @@
             log.info("%s: include: %s", config_path, path)
             yield open(path)
 
-def apply_hosts_config (options, path, name, config, defaults={}, parent=None):
+def apply_hosts_configs (options, path, name, config, parent=None, defaults={}):
     """
         Load hosts from a configobj.Section (which can be the top-level ConfigObj).
 
             options         global options
             path            filesystem path of file (for errors)
-            name            name of section/file
+            name            name of this section/file
             config          configobj.Section
+            parent          parent section from included files or --hosts-domain
             defaults        hierarchial section defaults
-            parent          optional parent section for included files
     """
     
     # items in this section
     section = dict(defaults)
     for scalar in config.scalars:
         section[scalar] = config[scalar]
-
+    
     if config.sections:
         # this is a top-level section that includes hosts
         log.info("%s: @%s", path, name)
@@ -235,7 +233,7 @@
         # process includes?
         includes = section.pop('include', '')
 
-        for file in apply_host_includes(options, path, includes):
+        for file in apply_hosts_includes(options, path, includes):
             # within our domain context
             for host in apply_hosts_file(options, file, parent=name):
                 yield host
@@ -244,7 +242,7 @@
         for section_name in config.sections:
             log.debug("%s: %s: %s", path, name, section_name)
 
-            for host in apply_hosts_config(options, path, section_name, config[section_name], defaults=section, parent=name):
+            for host in apply_hosts_configs(options, path, section_name, config[section_name], parent=name, defaults=section):
                 yield host
 
     elif parent:
@@ -252,15 +250,15 @@
         log.debug("%s: %s@%s", path, name, parent)
         
         try:
-            for host in apply_host_config(options, parent, name, **section):
+            for host in apply_hosts(parent, name, section):
                 log.info("%s: %s", path, host)
 
                 yield host
+        
+        except ValueError as error:
+            log.exception("%s: %s: %s", path, parent, name)
 
-        except HostConfigError as error:
-            # add in config path
-            error.config = path
-            raise
+            raise HostConfigError(path, "{parent}: {name}: {error}".format(parent=parent, name=name, error=error))
 
     else:
         raise HostConfigError(path, "No sections in config")
@@ -270,7 +268,7 @@
         Load Hosts from a file path.
 
             path:str        filesystem path
-            parent          included from given name
+            parent          domain from included file, or --hosts-domain
 
         Raises
             HostConfigError
@@ -290,9 +288,9 @@
     except configobj.ConfigObjError as ex:
         raise HostConfigObjError(path, ex)
     
-    return apply_hosts_config(options, path, name, config, parent=parent)
+    return apply_hosts_configs(options, path, name, config, parent=parent)
 
-def apply_hosts (options, files):
+def apply_hosts_files (options, files):
     """
         Load Hosts from files.
 
@@ -305,7 +303,7 @@
         except IOError as ex:
             raise HostConfigError(path, ex.strerror)
 
-        for host in apply_hosts_file(options, file):
+        for host in apply_hosts_file(options, file, parent=options.hosts_domain):
             yield host
 
 def apply (options, args):
@@ -317,7 +315,7 @@
     
     try:
         # load hosts from configs
-        hosts = list(apply_hosts(options, args))
+        hosts = list(apply_hosts_files(options, args))
     except HostConfigObjError as error:
         log.error("%s", error)
         log.error("\t%s", error.line_contents)
--- a/pvl/hosts/tests.py	Thu Feb 26 16:38:31 2015 +0200
+++ b/pvl/hosts/tests.py	Thu Feb 26 17:24:13 2015 +0200
@@ -33,7 +33,7 @@
  
     def testApplyHostsFileError(self):
         with self.assertRaises(config.HostConfigError):
-            list(config.apply_hosts(self.options, ['nonexistant']))
+            list(config.apply_hosts_files(self.options, ['nonexistant']))
 
     def testApplyHosts(self):
         conf_file = ConfFile('test', """
@@ -63,23 +63,23 @@
         ])
     
     def testApplyHostFqdn(self):
-        self.assertHostsEqual(config.apply_host_config(self.options, 'test', 'asdf@foo.test'), [
+        self.assertHostsEqual(config.apply_hosts('test', 'asdf@foo.test', { }), [
                 ('asdf@foo.test', dict()),
         ])
 
-        self.assertHostsEqual(config.apply_host_config(self.options, 'test', 'asdf.test2'), [
+        self.assertHostsEqual(config.apply_hosts('test', 'asdf.test2', { }), [
                 ('asdf.test2@', dict()),
         ])
 
     def testApplyHostExpand(self):
-        self.assertHostsEqual(config.apply_host_config(self.options, 'asdf', 'asdf{1-3}', ip='10.100.100.$'), [
-                ('asdf1@asdf', dict(ip=ipaddr.IPAddress('10.100.100.1'))),
-                ('asdf2@asdf', dict(ip=ipaddr.IPAddress('10.100.100.2'))),
-                ('asdf3@asdf', dict(ip=ipaddr.IPAddress('10.100.100.3'))),
+        self.assertHostsEqual(config.apply_hosts('test', 'asdf{1-3}', { 'ip': '10.100.100.$' }), [
+                ('asdf1@test', dict(ip=ipaddr.IPAddress('10.100.100.1'))),
+                ('asdf2@test', dict(ip=ipaddr.IPAddress('10.100.100.2'))),
+                ('asdf3@test', dict(ip=ipaddr.IPAddress('10.100.100.3'))),
         ])
 
     def testApplyHostConfigDict(self):
-        host = config.apply_host(self.options, 'foo', 'test', {
+        host = config.apply_host('foo', 'test', {
             'ethernet.eth0': '00:11:22:33:44:55',
         })
 
@@ -88,7 +88,7 @@
         ))
 
     def testApplyHostConfigDictMulti(self):
-        host = config.apply_host(self.options, 'foo', 'test', {
+        host = config.apply_host('foo', 'test', {
             'ethernet.eth0': '00:11:22:33:44:55',
             'ethernet.eth1': '00:11:22:33:44:66',
         })
@@ -100,15 +100,15 @@
                 }
         ))
    
-    def testApplyHostsConfigError(self):
-        with self.assertRaises(config.HostConfigError):
-            config.apply_host(self.options, 'foo', 'test', {
+    def testApplyHostsConfigErrorDict(self):
+        with self.assertRaises(ValueError):
+            config.apply_host('test', 'foo', {
                 'ethernet': 'foo',
                 'ethernet.eth0': 'bar',
             })
 
     def testApplyHostConfigExtensions(self):
-        host = config.apply_host(self.options, 'foo', 'test', {
+        host = config.apply_host('foo', 'test', {
             'link:50':          'foo@test',
             'link:uplink.49':   'bar@test',
         })
@@ -139,7 +139,9 @@
 
 class TestForwardZone(TestZoneMixin, unittest.TestCase):
     def testHostOutOfOrigin(self):
-        h = Host('host', 'domain', ip=ipaddr.IPAddress('10.0.0.1'))
+        h = Host.build('host', 'domain', 
+                ip  = '10.0.0.1',
+        )
 
         self.assertZoneEquals(zone.host_forward(h, 'test'), { })