Parcourir la source

[2967] convert to general datasrc config

Paul Selkirk il y a 12 ans
Parent
commit
23059244dc

+ 71 - 44
src/bin/zonemgr/tests/zonemgr_test.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010-2013  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,7 @@ import tempfile
 from zonemgr import *
 from isc.testutils.ccsession_mock import MockModuleCCSession
 from isc.notify import notify_out
+from isc.datasrc import ZoneFinder
 
 ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
 ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
@@ -36,6 +37,9 @@ LOWERBOUND_RETRY = 5
 REFRESH_JITTER = 0.10
 RELOAD_JITTER = 0.75
 
+rdata_net = 'a.example.net. root.example.net. 2009073106 7200 3600 2419200 21600'
+rdata_org = 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600'
+
 TEST_SQLITE3_DBFILE = os.getenv("TESTDATAOBJDIR") + '/initdb.file'
 
 class ZonemgrTestException(Exception):
@@ -47,6 +51,9 @@ class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
         ConfigData.__init__(self, module_spec)
         MockModuleCCSession.__init__(self)
 
+    def add_remote_config_by_name(self, name, callback):
+        pass
+
     def rpc_call(self, command, module, instance="*", to="*", params=None):
         if module not in ("Auth", "Xfrin"):
             raise ZonemgrTestException("module name not exist")
@@ -57,6 +64,47 @@ class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
         else:
             return "unknown", False
 
+class MockDataSourceClient():
+    '''A simple mock data source client.'''
+    def find_zone(self, zone_name):
+        '''Mock version of find_zone().'''
+        return (isc.datasrc.DataSourceClient.SUCCESS, self)
+
+    def find(self, name, rrtype, options=ZoneFinder.FIND_DEFAULT):
+        '''Mock ZoneFinder.find().
+
+        It returns the predefined SOA RRset to queries for SOA of the common
+        test zone name.  It also emulates some unusual cases for special
+        zone names.
+
+        '''
+        if name == Name('example.net'):
+            rdata = Rdata(RRType.SOA, RRClass.IN, rdata_net)
+        elif name == 'example.org.':
+            rdata = Rdata(RRType.SOA, RRClass.IN, rdata_org)
+        else:
+            return (ZoneFinder.NXDOMAIN, None, 0)
+        rrset = RRset(name, RRClass.IN, RRType.SOA, RRTTL(3600))
+        rrset.add_rdata(rdata)
+        return (ZoneFinder.SUCCESS, rrset, 0)
+
+class MockDataSrcClientsMgr():
+    def __init__(self):
+        # Default faked result of get_client_list, customizable by tests
+        self.found_datasrc_client_list = self
+
+        # Default faked result of find(), customizable by tests
+        self.found_datasrc_client = MockDataSourceClient()
+
+    def get_client_list(self, rrclass):
+        return self.found_datasrc_client_list
+
+    def find(self, zone_name, want_exact_match, want_finder):
+        """Pretending find method on the object returned by get_client_list"""
+        if issubclass(type(self.found_datasrc_client), Exception):
+            raise self.found_datasrc_client
+        return self.found_datasrc_client, None, None
+
 class MyZonemgrRefresh(ZonemgrRefresh):
     def __init__(self):
         self._master_socket, self._slave_socket = socket.socketpair()
@@ -66,19 +114,8 @@ class MyZonemgrRefresh(ZonemgrRefresh):
         self._reload_jitter = 0.75
         self._refresh_jitter = 0.25
 
-        def get_zone_soa(zone_name, db_file):
-            if zone_name == 'example.net.':
-                return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
-                        'a.example.net. root.example.net. 2009073106 7200 3600 2419200 21600')
-            elif zone_name == 'example.org.':
-                return (1, 2, 'example.org.', 'example.org.sd.', 21600, 'SOA', None,
-                        'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600')
-            else:
-                return None
-        sqlite3_ds.get_zone_soa = get_zone_soa
-
-        ZonemgrRefresh.__init__(self, TEST_SQLITE3_DBFILE, self._slave_socket,
-                                FakeCCSession())
+        ZonemgrRefresh.__init__(self, self._slave_socket, FakeCCSession())
+        self._datasrc_clients_mgr = MockDataSrcClientsMgr()
         current_time = time.time()
         self._zonemgr_refresh_info = {
          ('example.net.', 'IN'): {
@@ -194,17 +231,14 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertRaises(KeyError, self.zone_refresh._get_zone_soa_rdata, ZONE_NAME_CLASS2_IN)
 
     def test_zonemgr_reload_zone(self):
+        global rdata_net
         soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
         # We need to restore this not to harm other tests
-        old_get_zone_soa = sqlite3_ds.get_zone_soa
-        def get_zone_soa(zone_name, db_file):
-            return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
-                    'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
-        sqlite3_ds.get_zone_soa = get_zone_soa
-
+        old_rdata_net = rdata_net
+        rdata_net = soa_rdata
         self.zone_refresh.zonemgr_reload_zone(ZONE_NAME_CLASS1_IN)
         self.assertEqual(soa_rdata, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_soa_rdata"])
-        sqlite3_ds.get_zone_soa = old_get_zone_soa
+        rdata_net = old_rdata_net
 
     def test_get_zone_notifier_master(self):
         notify_master = "192.168.1.1"
@@ -276,18 +310,13 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertRaises(ZonemgrTestException, self.zone_refresh._send_command, "Unknown", "Notify", None)
 
     def test_zonemgr_add_zone(self):
+        global rdata_net
         soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
         # This needs to be restored. The following test actually failed if we left
         # this unclean
-        old_get_zone_soa = sqlite3_ds.get_zone_soa
+        old_rdata_net = rdata_net
+        rdata_net = soa_rdata
         time1 = time.time()
-
-        def get_zone_soa(zone_name, db_file):
-            return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
-                    'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
-
-        sqlite3_ds.get_zone_soa = get_zone_soa
-
         self.zone_refresh._zonemgr_refresh_info = {}
         self.zone_refresh.zonemgr_add_zone(ZONE_NAME_CLASS1_IN)
         self.assertEqual(1, len(self.zone_refresh._zonemgr_refresh_info))
@@ -300,13 +329,15 @@ class TestZonemgrRefresh(unittest.TestCase):
         zone_timeout = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["next_refresh_time"]
         self.assertTrue((time1 + 900 * (1 - self.zone_refresh._reload_jitter)) <= zone_timeout)
         self.assertTrue(zone_timeout <= time2 + 900)
+        rdata_net = old_rdata_net
 
+        old_get_zone_soa = self.zone_refresh._get_zone_soa
         def get_zone_soa2(zone_name, db_file):
             return None
-        sqlite3_ds.get_zone_soa = get_zone_soa2
+        self.zone_refresh._get_zone_soa = get_zone_soa2
         self.zone_refresh.zonemgr_add_zone(ZONE_NAME_CLASS2_IN)
         self.assertTrue(self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS2_IN]["zone_soa_rdata"] is None)
-        sqlite3_ds.get_zone_soa = old_get_zone_soa
+        self.zone_refresh._get_zone_soa = old_get_zone_soa
 
     def test_zone_handle_notify(self):
         self.assertTrue(self.zone_refresh.zone_handle_notify(
@@ -327,11 +358,10 @@ class TestZonemgrRefresh(unittest.TestCase):
                 ZONE_NAME_CLASS3_IN, "127.0.0.1"))
 
     def test_zone_refresh_success(self):
+        global rdata_net
         soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
-        def get_zone_soa(zone_name, db_file):
-            return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
-                    'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
-        sqlite3_ds.get_zone_soa = get_zone_soa
+        old_rdata_net = rdata_net
+        rdata_net = soa_rdata
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
         self.zone_refresh.zone_refresh_success(ZONE_NAME_CLASS1_IN)
@@ -347,6 +377,7 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertTrue(last_refresh_time <= time2)
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ("example.test.", "CH"))
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ZONE_NAME_CLASS3_IN)
+        rdata_net = old_rdata_net
 
     def test_zone_refresh_fail(self):
         soa_rdata = 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600'
@@ -368,14 +399,14 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_CH)
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_IN)
 
-        old_get_zone_soa = sqlite3_ds.get_zone_soa
+        old_get_zone_soa = self.zone_refresh._get_zone_soa
         def get_zone_soa(zone_name, db_file):
             return None
-        sqlite3_ds.get_zone_soa = get_zone_soa
+        self.zone_refresh._get_zone_soa = get_zone_soa
         self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
         self.assertEqual(self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"],
                          ZONE_EXPIRED)
-        sqlite3_ds.get_zone_soa = old_get_zone_soa
+        self.zone_refresh._get_zone_soa = old_get_zone_soa
 
     def test_find_need_do_refresh_zone(self):
         time1 = time.time()
@@ -671,9 +702,8 @@ class TestZonemgr(unittest.TestCase):
         config_data3 = {"refresh_jitter" : 0.7}
         self.zonemgr.config_handler(config_data3)
         self.assertEqual(0.5, self.zonemgr._config_data.get("refresh_jitter"))
-        # The zone doesn't exist in database, simply skip loading soa for it and log an warning
-        self.zonemgr._zone_refresh = ZonemgrRefresh(TEST_SQLITE3_DBFILE, None,
-                                                    FakeCCSession())
+        # The zone doesn't exist in database, simply skip loading soa for it and log a warning
+        self.zonemgr._zone_refresh = ZonemgrRefresh(None, FakeCCSession())
         config_data1["secondary_zones"] = [{"name": "nonexistent.example",
                                             "class": "IN"}]
         self.assertEqual(self.zonemgr.config_handler(config_data1),
@@ -684,9 +714,6 @@ class TestZonemgr(unittest.TestCase):
                         is None)
         self.assertEqual(0.1, self.zonemgr._config_data.get("refresh_jitter"))
 
-    def test_get_db_file(self):
-        self.assertEqual(TEST_SQLITE3_DBFILE, self.zonemgr.get_db_file())
-
     def test_parse_cmd_params(self):
         params1 = {"zone_name" : "example.com.", "zone_class" : "CH",
                    "master" : "127.0.0.1"}

+ 119 - 32
src/bin/zonemgr/zonemgr.py.in

@@ -1,6 +1,6 @@
 #!@PYTHON@
 
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010-2013  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -40,6 +40,9 @@ from isc.config.ccsession import *
 import isc.util.process
 from isc.log_messages.zonemgr_messages import *
 from isc.notify import notify_out
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr, ConfigError
+from isc.datasrc import DataSourceClient, ZoneFinder
+from isc.dns import *
 
 # Initialize logging for called modules.
 isc.log.init("b10-zonemgr", buffer=True)
@@ -105,19 +108,25 @@ class ZonemgrRefresh:
     can be stopped by calling shutdown() in another thread.
     """
 
-    def __init__(self, db_file, slave_socket, module_cc_session):
-        self._mccs = module_cc_session
+    def __init__(self, slave_socket, module_cc):
+        self._module_cc = module_cc
         self._check_sock = slave_socket
-        self._db_file = db_file
         self._zonemgr_refresh_info = {}
         self._lowerbound_refresh = None
         self._lowerbound_retry = None
         self._max_transfer_timeout = None
         self._refresh_jitter = None
         self._reload_jitter = None
-        self.update_config_data(module_cc_session.get_full_config(),
-                                module_cc_session)
+        self.update_config_data(module_cc.get_full_config(),
+                                module_cc)
         self._running = False
+        # This is essentially private, but we allow tests to customize it.
+        self._datasrc_clients_mgr = DataSrcClientsMgr()
+        # data_sources configuration should be ready with cfgmgr, so this
+        # shouldn't fail; if it ever does we simply propagate the exception
+        # to terminate the program.
+        self._module_cc.add_remote_config_by_name('data_sources',
+                                                  self._datasrc_config_handler)
 
     def _random_jitter(self, max, jitter):
         """Imposes some random jitters for refresh and
@@ -229,26 +238,22 @@ class ZonemgrRefresh:
 
     def zonemgr_reload_zone(self, zone_name_class):
         """ Reload a zone."""
-        zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]),
-                                           self._db_file)
         self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = \
-            zone_soa[7]
+            self._get_zone_soa(zone_name_class[0], zone_name_class[1])
 
     def zonemgr_add_zone(self, zone_name_class):
         """ Add a zone into zone manager."""
-
         logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0],
                      zone_name_class[1])
         zone_info = {}
-        zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]),
-                                           self._db_file)
+        zone_soa = self._get_zone_soa(zone_name_class[0], zone_name_class[1])
         if zone_soa is None:
             logger.warn(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
             zone_info["zone_soa_rdata"] = None
             zone_reload_time = 0.0
         else:
-            zone_info["zone_soa_rdata"] = zone_soa[7]
-            zone_reload_time = float(zone_soa[7].split(" ")[RETRY_OFFSET])
+            zone_info["zone_soa_rdata"] = zone_soa
+            zone_reload_time = float(zone_soa.split(" ")[RETRY_OFFSET])
         zone_info["zone_state"] = ZONE_OK
         zone_info["last_refresh_time"] = self._get_current_time()
         self._zonemgr_refresh_info[zone_name_class] = zone_info
@@ -258,6 +263,34 @@ class ZonemgrRefresh:
         self._set_zone_timer(zone_name_class, zone_reload_time,
                              self._reload_jitter * zone_reload_time)
 
+    def _get_zone_soa(self, zone_name, zone_class):
+        """Retrieve the current SOA RR of the zone to be transferred."""
+        # Identify the data source to which the zone content is transferred,
+        # and get the current zone SOA from the data source (if available).
+        # Note that we do this before spawning the zonemgr session thread.
+        # find() on the client list and use of ZoneFinder (in _get_zone_soa())
+        # should be completed within the same single thread.
+        datasrc_client = None
+        clist = self._datasrc_clients_mgr.get_client_list(zone_class)
+        if clist is None:
+            return None
+        try:
+            datasrc_client = clist.find(zone_name, True, False)[0]
+            if datasrc_client is None: # can happen, so log it separately.
+                logger.error(ZONEMGR_DATASRC_UNKNOWN,
+                             format_zone_str(zone_name, zone_class))
+                return None
+            zone_soa = _get_zone_soa(datasrc_client, Name(zone_name), RRClass(zone_class))
+            if (zone_soa == None):
+                return None
+            else:
+                return zone_soa.get_rdata()[0].to_text()
+        except isc.datasrc.Error as ex:
+            # rare case error. re-raise as ZonemgrException so it'll be logged
+            # in command_handler().
+            raise ZonemgrException('unexpected failure in datasrc module: ' +
+                                 str(ex))
+
     def _zone_is_expired(self, zone_name_class):
         """Judge whether a zone is expired or not."""
         zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).\
@@ -317,7 +350,7 @@ class ZonemgrRefresh:
     def _send_command(self, module_name, command_name, params):
         """Send command between modules."""
         try:
-            self._mccs.rpc_call(command_name, module_name, params=params)
+            self._module_cc.rpc_call(command_name, module_name, params=params)
         except socket.error:
             # FIXME: WTF? Where does socket.error come from? And how do we ever
             # dare ignore such serious error? It can only be broken link to
@@ -454,6 +487,20 @@ class ZonemgrRefresh:
         # Return the thread to anyone interested
         return self._thread
 
+    def _datasrc_config_handler(self, new_config, config_data):
+        """Configuration handler of the 'data_sources' module.
+
+        The actual handling is delegated to the DataSrcClientsMgr class;
+        this method is a simple wrapper.
+
+        This is essentially private, but implemented as 'protected' so tests
+        can refer to it; other external use is prohibited.
+        """
+        try:
+            self._datasrc_clients_mgr.reconfigure(new_config, config_data)
+        except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
+            logger.error(ZONEMGR_DATASRC_CONFIG_ERROR, ex)
+
     def shutdown(self):
         """
         Stop the run_timer() thread. Block until it finished. This must be
@@ -475,7 +522,7 @@ class ZonemgrRefresh:
         self._read_sock = None
         self._write_sock = None
 
-    def update_config_data(self, new_config, module_cc_session):
+    def update_config_data(self, new_config, module_cc):
         """ update ZonemgrRefresh config """
         # Get a new value, but only if it is defined (commonly used below)
         # We don't use "value or default", because if value would be
@@ -524,7 +571,7 @@ class ZonemgrRefresh:
                     # Currently we use an explicit get_default_value call
                     # in case the class hasn't been set. Alternatively, we
                     # could use
-                    # module_cc_session.get_value('secondary_zones[INDEX]/class')
+                    # module_cc.get_value('secondary_zones[INDEX]/class')
                     # To get either the value that was set, or the default if
                     # it wasn't set.
                     # But the real solution would be to make new_config a type
@@ -534,7 +581,7 @@ class ZonemgrRefresh:
                     if 'class' in secondary_zone:
                         rr_class = secondary_zone['class']
                     else:
-                        rr_class = module_cc_session.get_default_value(
+                        rr_class = module_cc.get_default_value(
                                         'secondary_zones/class')
                     # Convert rr_class to and from RRClass to check its value
                     try:
@@ -566,13 +613,11 @@ class Zonemgr:
     def __init__(self):
         self._zone_refresh = None
         self._setup_session()
-        self._db_file = self.get_db_file()
         # Create socket pair for communicating between main thread and zonemgr
         # timer thread
         self._master_socket, self._slave_socket = \
             socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
-        self._zone_refresh = ZonemgrRefresh(self._db_file, self._slave_socket,
-                                            self._module_cc)
+        self._zone_refresh = ZonemgrRefresh(self._slave_socket, self._module_cc)
         self._zone_refresh.run_timer()
 
         self._lock = threading.Lock()
@@ -592,17 +637,6 @@ class Zonemgr:
         self._config_data_check(self._config_data)
         self._module_cc.start()
 
-    def get_db_file(self):
-        db_file, is_default = \
-            self._module_cc.get_remote_config_value(AUTH_MODULE_NAME,
-                                                    "database_file")
-        # this too should be unnecessary, but currently the
-        # 'from build' override isn't stored in the config
-        # (and we don't have indirect python access to datasources yet)
-        if is_default and "B10_FROM_BUILD" in os.environ:
-            db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
-        return db_file
-
     def shutdown(self):
         """Shutdown the zonemgr process. The thread which is keeping track of
         zone timers should be terminated.
@@ -743,6 +777,59 @@ class Zonemgr:
         finally:
             self._module_cc.send_stopping()
 
+# XXX copy from xfrin for now
+def format_zone_str(zone_name, zone_class):
+    """Helper function to format a zone name and class as a string of
+       the form '<name>/<class>'.
+       Parameters:
+       zone_name (isc.dns.Name) name to format
+       zone_class (isc.dns.RRClass) class to format
+    """
+    return zone_name.to_text(True) + '/' + str(zone_class)
+
+# XXX copy from xfrin for now
+def _get_zone_soa(datasrc_client, zone_name, zone_class):
+    """Retrieve the current SOA RR of the zone to be transferred.
+
+    This function is essentially private to the module, but will also
+    be called (or tweaked) from tests; no one else should use this
+    function directly.
+
+    The specified zone is expected to exist in the data source referenced
+    by the given datasrc_client at the point of the call to this function.
+    If this is not met ZonemgrException exception will be raised.
+
+    It will be used for various purposes in subsequent xfr protocol
+    processing.   It is validly possible that the zone is currently
+    empty and therefore doesn't have an SOA, so this method doesn't
+    consider it an error and returns None in such a case.  It may or
+    may not result in failure in the actual processing depending on
+    how the SOA is used.
+
+    When the zone has an SOA RR, this method makes sure that it's
+    valid, i.e., it has exactly one RDATA; if it is not the case
+    this method returns None.
+
+    """
+    # get the zone finder.  this must be SUCCESS (not even
+    # PARTIALMATCH) because we are specifying the zone origin name.
+    result, finder = datasrc_client.find_zone(zone_name)
+    if result != DataSourceClient.SUCCESS:
+        # The data source doesn't know the zone.  In the context of this
+        # function is called, this shouldn't happen.
+        raise ZonemgrException("unexpected result: zone %s doesn't exist" %
+                             format_zone_str(zone_name, zone_class))
+    result, soa_rrset, _ = finder.find(zone_name, RRType.SOA)
+    if result != ZoneFinder.SUCCESS:
+        logger.info(ZONEMGR_NO_SOA, format_zone_str(zone_name, zone_class))
+        return None
+    if soa_rrset.get_rdata_count() != 1:
+        logger.warn(ZONEMGR_MULTIPLE_SOA,
+                    format_zone_str(zone_name, zone_class),
+                    soa_rrset.get_rdata_count())
+        return None
+    return soa_rrset
+
 zonemgrd = None
 
 def signal_handler(signal, frame):

+ 11 - 1
src/bin/zonemgr/zonemgr_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,16 @@
 An error was encountered on the command channel.  The message indicates
 the nature of the error.
 
+% ZONEMGR_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
+Configuration for the global data sources is updated, but the update
+cannot be applied to zonemgr.  The zonemgr module will still keep running
+with the previous configuration, but the cause of the failure and
+other log messages must be carefully examined because if only zonemgr
+rejects the new configuration then the entire BIND 10 system will have
+inconsistent state among different modules.  If other modules accept
+the update but zonemgr produces this error, the zonemgr module should
+probably be restarted.
+
 % ZONEMGR_JITTER_TOO_BIG refresh_jitter is too big, setting to 0.5
 The value specified in the configuration for the refresh jitter is too large
 so its value has been set to the maximum of 0.5.