Michal 'vorner' Vaner 12 years ago
parent
commit
03b60b9a5c

+ 52 - 8
src/bin/cfgmgr/plugins/datasrc_config_plugin.py

@@ -16,20 +16,64 @@
 from isc.config.module_spec import module_spec_from_file
 from isc.util.file import path_search
 from bind10_config import PLUGIN_PATHS
+import isc.dns
+import isc.datasrc
+import json
+import os.path
+import copy
+
 spec = module_spec_from_file(path_search('datasrc.spec', PLUGIN_PATHS))
 
 def check(config):
     """
     Check the configuration.
     """
-    # TODO: Once we have solved ticket #2051, create the list and
-    # fill it with the configuration. We probably want to have some way
-    # to not load the data sources, just the configuration. It could
-    # be hacked together by subclassing ConfigurableClientList and
-    # having empty getDataSource method. But it looks like a hack and it
-    # won't really check the params configuration.
-    #
-    # For now, we let everything pass.
+    # Check the data layout first
+    errors=[]
+    if not spec.validate_config(False, config, errors):
+        return ' '.join(errors)
+
+    classes = config.get('classes')
+    # Nothing passed here
+    if classes is None:
+        return None
+
+    for rr_class_str in classes:
+        try:
+            rr_class = isc.dns.RRClass(rr_class_str)
+        except isc.dns.InvalidRRClass as irc:
+            return "The class '" + rr_class_str + "' is invalid"
+
+        dlist = isc.datasrc.ConfigurableClientList(rr_class)
+        # We get a copy here, as we are going to mangle the configuration.
+        # But we don't want our changes to propagate outside, to the real
+        # configuration.
+        client_config = copy.deepcopy(classes.get(rr_class_str))
+
+        for client in client_config:
+            if client['type'] == 'MasterFiles':
+                if not client.get('cache-enable', False):
+                    return 'The cache must be enabled in MasterFiles type'
+                params = client.get('params')
+                if type(params) != dict:
+                    return 'Params of MasterFiles must be a named set'
+                for name in params:
+                    try:
+                        isc.dns.Name(name)
+                    except Exception as e: # There are many related exceptions
+                        return str(e)
+                    if not os.path.exists(params[name]):
+                        return "Master file " + params[name] + " does not exist"
+                # We remove the list of zones locally. We already checked them,
+                # and the client list would have skipped them anyway, as we
+                # forbid cache. But it would produce a warning and we don't
+                # want that here.
+                client['params'] = {}
+
+        try:
+            dlist.configure(json.dumps(client_config), False)
+        except isc.datasrc.Error as dse:
+            return str(dse)
     return None
 
 def load():

+ 125 - 2
src/bin/cfgmgr/plugins/tests/datasrc_test.py

@@ -16,12 +16,36 @@
 # Make sure we can load the module, put it into path
 import sys
 import os
+import unittest
+import json
 sys.path.extend(os.environ["B10_TEST_PLUGIN_DIR"].split(':'))
+import isc.log
 
 import datasrc_config_plugin
-import unittest
 
 class DatasrcTest(unittest.TestCase):
+    def reject(self, config):
+        """
+        Just a shortcut to check the config is rejected.
+        """
+        old = json.dumps(config)
+        self.assertIsNotNone(datasrc_config_plugin.check({"classes":
+                                                         config}))
+        # There's some data mangling inside the plugin. Check it does
+        # not propagate out, as it could change the real configuration.
+        self.assertEqual(old, json.dumps(config))
+
+    def accept(self, config):
+        """
+        Just a shortcut to check the config is accepted.
+        """
+        old = json.dumps(config)
+        self.assertIsNone(datasrc_config_plugin.check({"classes":
+                                                      config}))
+        # There's some data mangling inside the plugin. Check it does
+        # not propagate out, as it could change the real configuration.
+        self.assertEqual(old, json.dumps(config))
+
     def test_load(self):
         """
         Checks the entry point returns the correct values.
@@ -32,5 +56,104 @@ class DatasrcTest(unittest.TestCase):
         # The plugin stores it's spec
         self.assertEqual(spec, datasrc_config_plugin.spec)
 
+    def test_empty(self):
+        """
+        Check an empty input is OK.
+        """
+        self.accept({})
+
+    def test_invalid_spec(self):
+        """
+        Check it rejects stuff that is not well-formed according
+        to the spec.
+        """
+        self.reject("test")
+        self.reject(13)
+        self.reject([])
+        self.reject({"IN": {}})
+        self.reject({"IN": [{"bad-name": True}]})
+
+    def test_class(self):
+        """
+        The class is rejected, if it is wrong.
+        """
+        self.reject({"BAD": []})
+        self.reject({"": []})
+        # But with a good one, it works
+        for c in ["IN", "CH", "HS"]:
+            self.accept({c: []})
+
+    def test_mem_ok(self):
+        """
+        Test we accept an in-memory data source. It doesn't really matter
+        which one it is. We just want to make sure we accept something
+        and this one does not need any kind of path mangling to find
+        plugins.
+        """
+        self.accept({"IN": [{
+            "type": "MasterFiles",
+            "cache-enable": True,
+            "params": {}
+        }]})
+
+    def test_dstype_bad(self):
+        """
+        The configuration is correct by the spec, but it would be rejected
+        by the client list. Check we reject it.
+        """
+        self.reject({"IN": [{
+            "type": "No such type"
+        }]})
+
+    def test_invalid_mem_params(self):
+        """
+        The client list skips in-memory sources. So we check it locally that
+        invalid things are rejected.
+        """
+        # The 'params' key is mandatory for MasterFiles
+        self.reject({"IN": [{
+            "type": "MasterFiles",
+            "cache-enable": True
+        }]})
+        # The cache must be enabled
+        self.reject({"IN": [{
+            "type": "MasterFiles",
+            "cache-enable": False,
+            "params": {}
+        }]})
+        self.reject({"IN": [{
+            "type": "MasterFiles",
+            "params": {}
+        }]})
+        # Bad params type
+        self.reject({"IN": [{
+            "type": "MasterFiles",
+            "cache-enable": True,
+            "params": []
+        }]})
+        # Bad name
+        self.reject({"IN": [{
+            "type": "MasterFiles",
+            "cache-enable": True,
+            "params": {
+                "example....org.": '/file/does/not/exist'
+            }
+        }]})
+
+    def test_no_such_file_mem(self):
+        """
+        We also check the existance of master files. Not the actual content,
+        though.
+        """
+        self.reject({"IN": [{
+            "type": "MasterFiles",
+            "cache-enable": True,
+            "params": {
+                "example.org.": '/file/does/not/exist'
+            }
+        }]})
+
 if __name__ == '__main__':
-        unittest.main()
+    isc.log.init("bind10")
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()

+ 2 - 0
src/lib/python/isc/config/module_spec.py

@@ -386,6 +386,8 @@ def _validate_format(spec, value, errors):
     return True
 
 def _validate_item(spec, full, data, errors):
+    if spec.get('item_type') == 'any':
+        return True
     if not _validate_type(spec, data, errors):
         return False
     elif type(data) == list: