BIND 10 master, updated. 82706fc52eca0eef8ef9af269ce55b68ff79dc6a [master] Merge branch 'trac2020'
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Jun 14 16:59:11 UTC 2012
The branch, master has been updated
via 82706fc52eca0eef8ef9af269ce55b68ff79dc6a (commit)
via 1fa35a8bca4eb2f8a515516a69c79354dd355e69 (commit)
via 43feaa8658342eff0c2ddc9b33ef185a6826411e (commit)
via f5abe6318c5b84383cde4f74e79a6de0cfca87e4 (commit)
via 5077b7bfbcd4e9ef7ce16cd3dca603e2582fd945 (commit)
via 31408c8c110f7bbd95bbbea411cc8aee30eaf6b6 (commit)
via 27ab58d1ad4771ec5e71d41e43fb836310bd4d4b (commit)
via 926983df95ba480878a289f34a95449bb8ebde21 (commit)
via eddd2459c8298b1693e3b38c0d6ebebdb237102f (commit)
via 57c8937e293f8849ac962c419027347b3b1afdb3 (commit)
via 202bac3be117c0c93029b7cca008d3d2c6b854a7 (commit)
via 3a21a6c5b9f01618ad2b8d16ecaad8e2cbe880ce (commit)
from c7da3e0c3ab187eb6f4293eea34f65deef0cd249 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 82706fc52eca0eef8ef9af269ce55b68ff79dc6a
Merge: c7da3e0 1fa35a8
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Thu Jun 14 09:56:18 2012 -0700
[master] Merge branch 'trac2020'
-----------------------------------------------------------------------
Summary of changes:
src/bin/ddns/ddns.py.in | 134 +++++++++++++--
src/bin/ddns/ddns_messages.mes | 65 +++++++
src/bin/ddns/tests/ddns_test.py | 177 +++++++++++++++++---
src/lib/python/isc/ddns/tests/session_tests.py | 20 +--
src/lib/python/isc/ddns/tests/zone_config_tests.py | 25 +--
src/lib/python/isc/ddns/zone_config.py | 11 +-
6 files changed, 363 insertions(+), 69 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index 4f7903a..76ffca0 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -25,6 +25,7 @@ import isc.ddns.session
from isc.ddns.zone_config import ZoneConfig
from isc.ddns.logger import ClientFormatter, ZoneFormatter
from isc.config.ccsession import *
+from isc.config.module_spec import ModuleSpecError
from isc.cc import SessionError, SessionTimeout, ProtocolError
import isc.util.process
import isc.util.cio.socketsession
@@ -34,6 +35,7 @@ from isc.server_common.dns_tcp import DNSTCPContext
from isc.datasrc import DataSourceClient
from isc.server_common.auth_command import auth_loadzone_command
import select
+import time
import errno
from isc.log_messages.ddns_messages import *
@@ -67,24 +69,22 @@ else:
SPECFILE_PATH = SPECFILE_PATH.replace("${prefix}", PREFIX)
if "B10_FROM_BUILD" in os.environ:
- AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
SOCKET_FILE_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
else:
SOCKET_FILE_PATH = os.environ["B10_FROM_BUILD"]
else:
SOCKET_FILE_PATH = bind10_config.DATA_PATH
- AUTH_SPECFILE_PATH = SPECFILE_PATH
SPECFILE_LOCATION = SPECFILE_PATH + "/ddns.spec"
SOCKET_FILE = SOCKET_FILE_PATH + '/ddns_socket'
-AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + '/auth.spec'
-isc.util.process.rename()
-
-# Cooperating modules
-XFROUT_MODULE_NAME = 'Xfrout'
+# Cooperating or dependency modules
AUTH_MODULE_NAME = 'Auth'
+XFROUT_MODULE_NAME = 'Xfrout'
+ZONEMGR_MODULE_NAME = 'Zonemgr'
+
+isc.util.process.rename()
class DDNSConfigError(Exception):
'''An exception indicating an error in updating ddns configuration.
@@ -143,15 +143,23 @@ def get_datasrc_client(cc_session):
file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
datasrc_config = '{ "database_file": "' + file + '"}'
try:
- return HARDCODED_DATASRC_CLASS, DataSourceClient('sqlite3',
- datasrc_config)
+ return (HARDCODED_DATASRC_CLASS,
+ DataSourceClient('sqlite3', datasrc_config), file)
except isc.datasrc.Error as ex:
class DummyDataSourceClient:
def __init__(self, ex):
self.__ex = ex
def find_zone(self, zone_name):
raise isc.datasrc.Error(self.__ex)
- return HARDCODED_DATASRC_CLASS, DummyDataSourceClient(ex)
+ return (HARDCODED_DATASRC_CLASS, DummyDataSourceClient(ex), file)
+
+def add_pause(sec):
+ '''Pause a specified period for inter module synchronization.
+
+ This is a trivial wrapper of time.sleep, but defined as a separate function
+ so tests can customize it.
+ '''
+ time.sleep(sec)
class DDNSServer:
# The number of TCP clients that can be handled by the server at the same
@@ -181,8 +189,23 @@ class DDNSServer:
self._cc.get_default_value('zones'))
self._cc.start()
+ # Internal attributes derived from other modules. They will be
+ # initialized via dd_remote_xxx below and will be kept updated
+ # through their callbacks. They are defined as 'protected' so tests
+ # can examine them; but they are essentially private to the class.
+ #
+ # Datasource client used for handling update requests: when set,
+ # should a tuple of RRClass and DataSourceClient. Constructed and
+ # maintained based on auth configuration.
+ self._datasrc_info = None
+ # A set of secondary zones, retrieved from zonemgr configuration.
+ self._secondary_zones = None
+
# Get necessary configurations from remote modules.
- self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
+ for mod in [(AUTH_MODULE_NAME, self.__auth_config_handler),
+ (ZONEMGR_MODULE_NAME, self.__zonemgr_config_handler)]:
+ self.__add_remote_module(mod[0], mod[1])
+ # This should succeed as long as cfgmgr is up.
isc.server_common.tsig_keyring.init_keyring(self._cc)
self._shutdown = False
@@ -256,6 +279,88 @@ class DDNSServer:
answer = create_answer(1, "Unknown command: " + str(cmd))
return answer
+ def __add_remote_module(self, mod_name, callback):
+ '''Register interest in other module's config with a callback.'''
+
+ # Due to startup timing, add_remote_config can fail. We could make it
+ # more sophisticated, but for now we simply retry a few times, each
+ # separated by a short period (3 times and 1 sec, arbitrary chosen,
+ # and hardcoded for now). In practice this should be more than
+ # sufficient, but if it turns out to be a bigger problem we can
+ # consider more elegant solutions.
+ for n_try in range(0, 3):
+ try:
+ # by_name() version can fail with ModuleSpecError in getting
+ # the module spec because cfgmgr returns a "successful" answer
+ # with empty data if it cannot find the specified module.
+ # This seems to be a deviant behavior (see Trac #2039), but
+ # we need to deal with it.
+ self._cc.add_remote_config_by_name(mod_name, callback)
+ return
+ except (ModuleSpecError, ModuleCCSessionError) as ex:
+ logger.warn(DDNS_GET_REMOTE_CONFIG_FAIL, mod_name, n_try + 1,
+ ex)
+ last_ex = ex
+ add_pause(1)
+ raise last_ex
+
+ def __auth_config_handler(self, new_config, module_config):
+ logger.info(DDNS_RECEIVED_AUTH_UPDATE)
+
+ # If we've got the config before and the new config doesn't update
+ # the DB file, there's nothing we should do with it.
+ # Note: there seems to be a bug either in bindctl or cfgmgr, and
+ # new_config can contain 'database_file' even if it's not really
+ # updated. We still perform the check so we can avoid redundant
+ # resetting when the bug is fixed. The redundant reset itself is not
+ # good, but such configuration update should not happen so often and
+ # it should be acceptable in practice.
+ if self._datasrc_info is not None and \
+ not 'database_file' in new_config:
+ return
+ rrclass, client, db_file = get_datasrc_client(self._cc)
+ self._datasrc_info = (rrclass, client)
+ logger.info(DDNS_AUTH_DBFILE_UPDATE, db_file)
+
+ def __zonemgr_config_handler(self, new_config, module_config):
+ logger.info(DDNS_RECEIVED_ZONEMGR_UPDATE)
+
+ # If we've got the config before and the new config doesn't update
+ # the secondary zone list, there's nothing we should do with it.
+ # (Same note as that for auth's config applies)
+ if self._secondary_zones is not None and \
+ not 'secondary_zones' in new_config:
+ return
+
+ # Get the latest secondary zones. Use get_remote_config_value() so
+ # it can work for both the initial default case and updates.
+ sec_zones, _ = self._cc.get_remote_config_value(ZONEMGR_MODULE_NAME,
+ 'secondary_zones')
+ new_secondary_zones = set()
+ try:
+ # Parse the new config and build a new list of secondary zones.
+ # Unfortunately, in the current implementation, even an observer
+ # module needs to perform full validation. This should be changed
+ # so that only post-validation (done by the main module) config is
+ # delivered to observer modules, but until it's supported we need
+ # to protect ourselves.
+ for zone_spec in sec_zones:
+ zname = Name(zone_spec['name'])
+ # class has the default value in case it's unspecified.
+ # ideally this should be merged within the config module, but
+ # the current implementation doesn't esnure that, so we need to
+ # subsitute it ourselves.
+ if 'class' in zone_spec:
+ zclass = RRClass(zone_spec['class'])
+ else:
+ zclass = RRClass(module_config.get_default_value(
+ 'secondary_zones/class'))
+ new_secondary_zones.add((zname, zclass))
+ self._secondary_zones = new_secondary_zones
+ logger.info(DDNS_SECONDARY_ZONES_UPDATE, len(self._secondary_zones))
+ except Exception as ex:
+ logger.error(DDNS_SECONDARY_ZONES_UPDATE_FAIL, ex)
+
def trigger_shutdown(self):
'''Initiate a shutdown sequence.
@@ -366,9 +471,8 @@ class DDNSServer:
# Let an update session object handle the request. Note: things around
# ZoneConfig will soon be substantially revised. For now we don't
# bother to generalize it.
- datasrc_class, datasrc_client = get_datasrc_client(self._cc)
- zone_cfg = ZoneConfig([], datasrc_class, datasrc_client,
- self._zone_config)
+ zone_cfg = ZoneConfig(self._secondary_zones, self._datasrc_info[0],
+ self._datasrc_info[1], self._zone_config)
update_session = self._UpdateSessionClass(self.__request_msg,
remote_addr, zone_cfg)
result, zname, zclass = update_session.handle()
@@ -605,7 +709,7 @@ def main(ddns_server=None):
logger.info(DDNS_STOPPED_BY_KEYBOARD)
except SessionError as e:
logger.error(DDNS_CC_SESSION_ERROR, str(e))
- except ModuleCCSessionError as e:
+ except (ModuleSpecError, ModuleCCSessionError) as e:
logger.error(DDNS_MODULECC_SESSION_ERROR, str(e))
except DDNSConfigError as e:
logger.error(DDNS_CONFIG_ERROR, str(e))
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index aa26059..61311bc 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -25,6 +25,12 @@ There was a low-level error when we tried to accept an incoming connection
connections we already have, but this connection is dropped. The reason
is logged.
+% DDNS_AUTH_DBFILE_UPDATE updated auth DB file to %1
+b10-ddns was notified of updates to the SQLite3 DB file that b10-auth
+uses for the underlying data source and on which b10-ddns needs to
+make updates. b10-ddns then updated its internal setup so further
+updates would be made on the new DB.
+
% DDNS_CC_SESSION_ERROR error reading from cc channel: %1
There was a problem reading from the command and control channel. The
most likely cause is that the msgq process is not running.
@@ -53,6 +59,29 @@ authoritative server shuts down, the connection would get closed. It also
can mean the system is busy and can't keep up or that the other side got
confused and sent bad data.
+% DDNS_GET_REMOTE_CONFIG_FAIL failed to get %1 module configuration %2 times: %3
+b10-ddns tried to get configuration of some remote modules for its
+operation, but it failed. The most likely cause of this is that the
+remote module has not fully started up and b10-ddns couldn't get the
+configuration in a timely fashion. b10-ddns attempts to retry it a
+few times, imposing a short delay, hoping it eventually succeeds if
+it's just a timing issue. The number of total failed attempts is also
+logged. If it reaches an internal threshold b10-ddns considers it a
+fatal error and terminates. Even in that case, if b10-ddns is
+configured as a "dispensable" component (which is the default), the
+parent bind10 process will restart it, and there will be another
+chance of getting the remote configuration successfully. These are
+not the optimal behavior, but it's believed to be sufficient in
+practice (there would normally be no failure in the first place). If
+it really causes an operational trouble other than having a few of
+these log messages, please submit a bug report; there can be several
+ways to make it more sophisticated. Another, less likely reason for
+having this error is because the remote modules are not actually
+configured to run. If that's the case fixing the configuration should
+solve the problem - either by making sure the remote module will run
+or by not running b10-ddns (without these remote modules b10-ddns is
+not functional, so there's no point in running it in this case).
+
% DDNS_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
There was a problem in the lower level module handling configuration and
control commands. This could happen for various reasons, but the most likely
@@ -66,10 +95,21 @@ requests from it. The file descriptor number and the address where the request
comes from is logged. The connection is over a unix domain socket and is likely
coming from a b10-auth process.
+% DDNS_RECEIVED_AUTH_UPDATE received configuration updates from auth server
+b10-ddns is notified of updates to b10-auth configuration
+(including a report of the initial configuration) that b10-ddns might
+be interested in.
+
% DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received
The ddns process received a shutdown command from the command channel
and will now shut down.
+% DDNS_RECEIVED_ZONEMGR_UPDATE received configuration updates from zonemgr
+b10-ddns is notified of updates to b10-zonemgr's configuration
+(including a report of the initial configuration). It may possibly
+contain changes to the secondary zones, in which case b10-ddns will
+update its internal copy of that configuration.
+
% DDNS_REQUEST_PARSE_FAIL failed to parse update request: %1
b10-ddns received an update request via b10-auth, but the received
data failed to pass minimum validation: it was either broken wire
@@ -117,6 +157,31 @@ with the client address.
The ddns process has successfully started and is now ready to receive commands
and updates.
+% DDNS_SECONDARY_ZONES_UPDATE updated secondary zone list (%1 zones are listed)
+b10-ddns has successfully updated the internal copy of secondary zones
+obtained from b10-zonemgr, based on a latest update to zonemgr's
+configuration. The number of newly configured (unique) secondary
+zones is logged.
+
+% DDNS_SECONDARY_ZONES_UPDATE_FAIL failed to update secondary zone list: %1
+An error message. b10-ddns was notified of updates to a list of
+secondary zones from b10-zonemgr and tried to update its own internal
+copy of the list, but it failed. This can happen if the configuration
+contains an error, and b10-zonemgr should also reject that update.
+Unfortunately, in the current implementation there is no way to ensure
+that both zonemgr and ddns have consistent information when an update
+contains an error; further, as of this writing zonemgr has a bug that
+it could partially update the list of secondary zones if part of the
+list has an error (see Trac ticket #2038). b10-ddns still keeps
+running with the previous configuration, but it's strongly advisable
+to check log messages from zonemgr, and if it indicates there can be
+inconsistent state, it's better to restart the entire BIND 10 system
+(just restarting b10-ddns wouldn't be enough, because zonemgr can have
+partially updated configuration due to bug #2038). The log message
+contains an error description, but it's intentionally kept simple as
+it's primarily a matter of zonemgr. To know the details of the error,
+log messages of zonemgr should be consulted.
+
% DDNS_SESSION session arrived on file descriptor %1
A debug message, informing there's some activity on the given file descriptor.
It will be either a request or the file descriptor will be closed. See
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index 70fb63d..b0b34ca 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -21,7 +21,10 @@ from isc.acl.acl import ACCEPT
import isc.util.cio.socketsession
from isc.cc.session import SessionTimeout, SessionError, ProtocolError
from isc.datasrc import DataSourceClient
-from isc.config.ccsession import create_answer
+from isc.config import module_spec_from_file
+from isc.config.config_data import ConfigData
+from isc.config.ccsession import create_answer, ModuleCCSessionError
+from isc.config.module_spec import ModuleSpecError
from isc.server_common.dns_tcp import DNSTCPContext
import ddns
import errno
@@ -56,6 +59,11 @@ TEST_TSIG_KEYRING.add(TEST_TSIG_KEY)
# Another TSIG key not in the keyring, making verification fail
BAD_TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
+# Incorporate it so we can use the real default values of zonemgr config
+# in the tests.
+ZONEMGR_MODULE_SPEC = module_spec_from_file(
+ os.environ["B10_FROM_BUILD"] + "/src/bin/zonemgr/zonemgr.spec")
+
class FakeSocket:
"""
A fake socket. It only provides a file number, peer name and accept method.
@@ -208,6 +216,13 @@ class MyCCSession(isc.config.ConfigData):
self._sendmsg_exception = None # will be raised from sendmsg if !None
self._recvmsg_exception = None # will be raised from recvmsg if !None
+ # Attributes to handle (faked) remote configurations
+ self.__callbacks = {} # record callbacks for updates to remote confs
+ self._raise_mods = {} # map of module to exceptions to be triggered
+ # on add_remote. settable by tests.
+ self._auth_config = {} # faked auth cfg, settable by tests
+ self._zonemgr_config = {} # faked zonemgr cfg, settable by tests
+
def start(self):
'''Called by DDNSServer initialization, but not used in tests'''
self._started = True
@@ -222,8 +237,27 @@ class MyCCSession(isc.config.ConfigData):
"""
return FakeSocket(1)
- def add_remote_config(self, spec_file_name):
- pass
+ def add_remote_config_by_name(self, module_name, update_callback=None):
+ # If a list of exceptions is given for the module, raise the front one,
+ # removing that exception from the list (so the list length controls
+ # how many (and which) exceptions should be raised on add_remote).
+ if module_name in self._raise_mods.keys() and \
+ len(self._raise_mods[module_name]) != 0:
+ ex = self._raise_mods[module_name][0]
+ self._raise_mods[module_name] = self._raise_mods[module_name][1:]
+ raise ex('Failure requesting remote config data')
+
+ if update_callback is not None:
+ self.__callbacks[module_name] = update_callback
+ if module_name is 'Auth':
+ if module_name in self.__callbacks:
+ # ddns implementation doesn't use the 2nd element, so just
+ # setting it to None
+ self.__callbacks[module_name](self._auth_config, None)
+ if module_name is 'Zonemgr':
+ if module_name in self.__callbacks:
+ self.__callbacks[module_name](self._zonemgr_config,
+ ConfigData(ZONEMGR_MODULE_SPEC))
def get_remote_config_value(self, module_name, item):
if module_name == "Auth" and item == "database_file":
@@ -233,6 +267,14 @@ class MyCCSession(isc.config.ConfigData):
return [], True # default
else:
return self.auth_datasources, False
+ if module_name == 'Zonemgr' and item == 'secondary_zones':
+ if item in self._zonemgr_config:
+ return self._zonemgr_config[item], False
+ else:
+ seczone_default = \
+ ConfigData(ZONEMGR_MODULE_SPEC).get_default_value(
+ 'secondary_zones')
+ return seczone_default, True
def group_sendmsg(self, msg, group):
# remember the passed parameter, and return dummy sequence
@@ -306,12 +348,15 @@ class TestDDNSServer(unittest.TestCase):
self.__tcp_sock = FakeSocket(10, socket.IPPROTO_TCP)
self.__tcp_ctx = DNSTCPContext(self.__tcp_sock)
self.__tcp_data = b'A' * 12 # dummy, just the same size as DNS header
+ # some tests will override this, which will be restored in tearDown:
+ self.__orig_add_pause = ddns.add_pause
def tearDown(self):
ddns.select.select = select.select
ddns.isc.util.cio.socketsession.SocketSessionReceiver = \
isc.util.cio.socketsession.SocketSessionReceiver
isc.server_common.tsig_keyring = self.orig_tsig_keyring
+ ddns.add_pause = self.__orig_add_pause
def test_listen(self):
'''
@@ -422,6 +467,112 @@ class TestDDNSServer(unittest.TestCase):
acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)]
self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT))
+ def test_datasrc_config(self):
+ # By default (in our faked config) it should be derived from the
+ # test data source
+ rrclass, datasrc_client = self.ddns_server._datasrc_info
+ self.assertEqual(RRClass.IN(), rrclass)
+ self.assertEqual(DataSourceClient.SUCCESS,
+ datasrc_client.find_zone(Name('example.org'))[0])
+
+ # emulating an update. calling add_remote_config_by_name is a
+ # convenient faked way to invoke the callback. We set the db file
+ # to a bogus one; the current implementation will create an unusable
+ # data source client.
+ self.__cc_session.auth_db_file = './notexistentdir/somedb.sqlite3'
+ self.__cc_session._auth_config = \
+ {'database_file': './notexistentdir/somedb.sqlite3'}
+ self.__cc_session.add_remote_config_by_name('Auth')
+ rrclass, datasrc_client = self.ddns_server._datasrc_info
+ self.assertEqual(RRClass.IN(), rrclass)
+ self.assertRaises(isc.datasrc.Error,
+ datasrc_client.find_zone, Name('example.org'))
+
+ # Check the current info isn't changed if the new config doesn't
+ # update it.
+ info_orig = self.ddns_server._datasrc_info
+ self.ddns_server._datasrc_info = 42 # dummy value, should be kept.
+ self.__cc_session._auth_config = {'other_config': 'value'}
+ self.__cc_session.add_remote_config_by_name('Auth')
+ self.assertEqual(42, self.ddns_server._datasrc_info)
+ self.ddns_server._datasrc_info = info_orig
+
+ def test_secondary_zones_config(self):
+ # By default it should be an empty list
+ self.assertEqual(set(), self.ddns_server._secondary_zones)
+
+ # emulating an update.
+ self.__cc_session._zonemgr_config = {'secondary_zones': [
+ {'name': TEST_ZONE_NAME_STR, 'class': TEST_RRCLASS_STR}]}
+ self.__cc_session.add_remote_config_by_name('Zonemgr')
+
+ # The new set of secondary zones should be stored.
+ self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)},
+ self.ddns_server._secondary_zones)
+
+ # Similar to the above, but 'class' is unspecified. The default value
+ # should be used.
+ self.__cc_session._zonemgr_config = {'secondary_zones': [
+ {'name': TEST_ZONE_NAME_STR}]}
+ self.__cc_session.add_remote_config_by_name('Zonemgr')
+ self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)},
+ self.ddns_server._secondary_zones)
+
+ # The given list has a duplicate. The resulting set should unify them.
+ self.__cc_session._zonemgr_config = {'secondary_zones': [
+ {'name': TEST_ZONE_NAME_STR, 'class': TEST_RRCLASS_STR},
+ {'name': TEST_ZONE_NAME_STR, 'class': TEST_RRCLASS_STR}]}
+ self.__cc_session.add_remote_config_by_name('Zonemgr')
+ self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)},
+ self.ddns_server._secondary_zones)
+
+ # Check the 2ndary zones aren't changed if the new config doesn't
+ # update it.
+ seczones_orig = self.ddns_server._secondary_zones
+ self.ddns_server._secondary_zones = 42 # dummy value, should be kept.
+ self.__cc_session._zonemgr_config = {}
+ self.__cc_session.add_remote_config_by_name('Zonemgr')
+ self.assertEqual(42, self.ddns_server._secondary_zones)
+ self.ddns_server._secondary_zones = seczones_orig
+
+ # If the update config is broken, the existing set should be intact.
+ self.__cc_session._zonemgr_config = {'secondary_zones': [
+ {'name': 'good.example', 'class': TEST_RRCLASS_STR},
+ {'name': 'badd..example', 'class': TEST_RRCLASS_STR}]}
+ self.__cc_session.add_remote_config_by_name('Zonemgr')
+ self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)},
+ self.ddns_server._secondary_zones)
+
+ def __check_remote_config_fail(self, mod_name, num_ex, expected_ex):
+ '''Subroutine for remote_config_fail test.'''
+
+ # fake pause function for inspection and to avoid having timeouts
+ added_pause = []
+ ddns.add_pause = lambda sec: added_pause.append(sec)
+
+ # In our current implementation, there will be up to 3 tries of
+ # adding the module, each separated by a 1-sec pause. If all attempts
+ # fail the exception will be propagated.
+ exceptions = [expected_ex for i in range(0, num_ex)]
+ self.__cc_session._raise_mods = {mod_name: exceptions}
+ if num_ex >= 3:
+ self.assertRaises(expected_ex, ddns.DDNSServer, self.__cc_session)
+ else:
+ ddns.DDNSServer(self.__cc_session)
+ self.assertEqual([1 for i in range(0, num_ex)], added_pause)
+
+ def test_remote_config_fail(self):
+ # If getting config of Auth or Zonemgr fails on construction of
+ # DDNServer, it should result in an exception and a few times
+ # of retries. We test all possible cases, changing the number of
+ # raised exceptions and the type of exceptions that can happen,
+ # which should also cover the fatal error case.
+ for i in range(0, 4):
+ self.__check_remote_config_fail('Auth', i, ModuleCCSessionError)
+ self.__check_remote_config_fail('Auth', i, ModuleSpecError)
+ self.__check_remote_config_fail('Zonemgr', i, ModuleCCSessionError)
+ self.__check_remote_config_fail('Zonemgr', i, ModuleSpecError)
+
def test_shutdown_command(self):
'''Test whether the shutdown command works'''
self.assertFalse(self.ddns_server._shutdown)
@@ -1178,26 +1329,6 @@ class TestConfig(unittest.TestCase):
ddns.SOCKET_FILE)
self.assertEqual(os.environ["B10_FROM_SOURCE"] +
"/src/bin/ddns/ddns.spec", ddns.SPECFILE_LOCATION)
- self.assertEqual(os.environ["B10_FROM_BUILD"] +
- "/src/bin/auth/auth.spec",
- ddns.AUTH_SPECFILE_LOCATION)
-
- def test_get_datasrc_client(self):
- # The test sqlite DB should contain the example.org zone.
- rrclass, datasrc_client = ddns.get_datasrc_client(self.__ccsession)
- self.assertEqual(RRClass.IN(), rrclass)
- self.assertEqual(DataSourceClient.SUCCESS,
- datasrc_client.find_zone(Name('example.org'))[0])
-
- def test_get_datasrc_client_fail(self):
- # DB file is in a non existent directory, and creatng the client
- # will fail. get_datasrc_client will return a dummy client, which
- # will subsequently make find_zone() fail.
- self.__ccsession.auth_db_file = './notexistentdir/somedb.sqlite3'
- rrclass, datasrc_client = ddns.get_datasrc_client(self.__ccsession)
- self.assertEqual(RRClass.IN(), rrclass)
- self.assertRaises(isc.datasrc.Error,
- datasrc_client.find_zone, Name('example.org'))
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index 6f6c6ba..0239bb1 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -200,7 +200,7 @@ class SessionTestBase(unittest.TestCase):
self._acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
REQUEST_LOADER.load([{"action": "ACCEPT"}])}
self._session = UpdateSession(self._update_msg, TEST_CLIENT4,
- ZoneConfig([], TEST_RRCLASS,
+ ZoneConfig(set(), TEST_RRCLASS,
self._datasrc_client,
self._acl_map))
self._session._get_update_zone()
@@ -327,7 +327,7 @@ class SessionTest(SessionTestBase):
msg = create_update_msg(zones=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
RRType.SOA())])
session = UpdateSession(msg, TEST_CLIENT4,
- ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ ZoneConfig({(TEST_ZONE_NAME, TEST_RRCLASS)},
TEST_RRCLASS, self._datasrc_client))
self.assertEqual(UPDATE_ERROR, session.handle()[0])
self.check_response(session.get_message(), Rcode.NOTIMP())
@@ -336,7 +336,7 @@ class SessionTest(SessionTestBase):
'''Common test sequence for the 'notauth' test'''
msg = create_update_msg(zones=[Question(zname, zclass, RRType.SOA())])
session = UpdateSession(msg, TEST_CLIENT4,
- ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ ZoneConfig({(TEST_ZONE_NAME, TEST_RRCLASS)},
TEST_RRCLASS, self._datasrc_client))
self.assertEqual(UPDATE_ERROR, session.handle()[0])
self.check_response(session.get_message(), Rcode.NOTAUTH())
@@ -360,7 +360,7 @@ class SessionTest(SessionTestBase):
msg = create_update_msg(zones=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
RRType.SOA())])
session = UpdateSession(msg, TEST_CLIENT4,
- ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ ZoneConfig({(TEST_ZONE_NAME, TEST_RRCLASS)},
TEST_RRCLASS,
BadDataSourceClient()))
self.assertEqual(UPDATE_ERROR, session.handle()[0])
@@ -617,7 +617,7 @@ class SessionTest(SessionTestBase):
from 'prerequisites'. Then checks if __check_prerequisites()
returns the Rcode specified in 'expected'.'''
msg = create_update_msg([TEST_ZONE_RECORD], prerequisites)
- zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ zconfig = ZoneConfig(set(), TEST_RRCLASS, self._datasrc_client,
self._acl_map)
session = UpdateSession(msg, TEST_CLIENT4, zconfig)
session._get_update_zone()
@@ -643,7 +643,7 @@ class SessionTest(SessionTestBase):
from 'updates'. Then checks if __do_prescan()
returns the Rcode specified in 'expected'.'''
msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
- zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ zconfig = ZoneConfig(set(), TEST_RRCLASS, self._datasrc_client,
self._acl_map)
session = UpdateSession(msg, TEST_CLIENT4, zconfig)
session._get_update_zone()
@@ -663,7 +663,7 @@ class SessionTest(SessionTestBase):
from 'updates'. Then checks if __handle()
results in a response with rcode 'expected'.'''
msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
- zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ zconfig = ZoneConfig(set(), TEST_RRCLASS, self._datasrc_client,
self._acl_map)
session = UpdateSession(msg, TEST_CLIENT4, zconfig)
@@ -1479,7 +1479,7 @@ class SessionACLTest(SessionTestBase):
'''
# create a separate session, with default (empty) ACL map.
session = UpdateSession(self._update_msg,
- TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+ TEST_CLIENT4, ZoneConfig(set(), TEST_RRCLASS,
self._datasrc_client))
# then the request should be rejected.
self.assertEqual((UPDATE_ERROR, None, None), session.handle())
@@ -1508,7 +1508,7 @@ class SessionACLTest(SessionTestBase):
# If the message doesn't contain TSIG, it doesn't match the ACCEPT
# ACL entry, and the request should be rejected.
session = UpdateSession(self._update_msg,
- TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+ TEST_CLIENT4, ZoneConfig(set(), TEST_RRCLASS,
self._datasrc_client,
acl_map))
self.assertEqual((UPDATE_ERROR, None, None), session.handle())
@@ -1517,7 +1517,7 @@ class SessionACLTest(SessionTestBase):
# If the message contains TSIG, it should match the ACCEPT
# ACL entry, and the request should be granted.
session = UpdateSession(create_update_msg(tsig_key=TEST_TSIG_KEY),
- TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+ TEST_CLIENT4, ZoneConfig(set(), TEST_RRCLASS,
self._datasrc_client,
acl_map))
self.assertEqual((UPDATE_SUCCESS, TEST_ZONE_NAME, TEST_RRCLASS),
diff --git a/src/lib/python/isc/ddns/tests/zone_config_tests.py b/src/lib/python/isc/ddns/tests/zone_config_tests.py
index 4efd1c1..7facb48 100644
--- a/src/lib/python/isc/ddns/tests/zone_config_tests.py
+++ b/src/lib/python/isc/ddns/tests/zone_config_tests.py
@@ -55,7 +55,7 @@ class ZoneConfigTest(unittest.TestCase):
'''Some basic tests for the ZoneConfig class.'''
def setUp(self):
self.__datasrc_client = FakeDataSourceClient()
- self.zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ self.zconfig = ZoneConfig({(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)},
TEST_RRCLASS, self.__datasrc_client)
def test_find_zone(self):
@@ -87,34 +87,27 @@ class ZoneConfigTest(unittest.TestCase):
TEST_RRCLASS)))
# zone class doesn't match (but zone name matches)
self.__datasrc_client.set_find_result(DataSourceClient.SUCCESS)
- zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ zconfig = ZoneConfig({(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)},
RRClass.CH(), self.__datasrc_client)
self.assertEqual((ZONE_NOTFOUND, None),
(zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
# similar to the previous case, but also in the secondary list
- zconfig = ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ zconfig = ZoneConfig({(TEST_ZONE_NAME, TEST_RRCLASS)},
RRClass.CH(), self.__datasrc_client)
self.assertEqual((ZONE_NOTFOUND, None),
(zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
# check some basic tests varying the secondary list.
# empty secondary list doesn't cause any disruption.
- zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client)
+ zconfig = ZoneConfig(set(), TEST_RRCLASS, self.__datasrc_client)
self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS))
- # adding some mulitle tuples, including subdomainof the test zone name,
- # and the same zone name but a different class
- zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
+ # adding some mulitle tuples, including subdomain of the test zone
+ # name, and the same zone name but a different class
+ zconfig = ZoneConfig({(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
(Name('example'), TEST_RRCLASS),
(Name('sub.example.org'), TEST_RRCLASS),
- (TEST_ZONE_NAME, RRClass.CH())],
- TEST_RRCLASS, self.__datasrc_client)
- self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
- self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS))
- # secondary zone list has a duplicate entry, which is just
- # (effecitivey) ignored
- zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
- (TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ (TEST_ZONE_NAME, RRClass.CH())},
TEST_RRCLASS, self.__datasrc_client)
self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS))
@@ -122,7 +115,7 @@ class ZoneConfigTest(unittest.TestCase):
class ACLConfigTest(unittest.TestCase):
def setUp(self):
self.__datasrc_client = FakeDataSourceClient()
- self.__zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ self.__zconfig = ZoneConfig({(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)},
TEST_RRCLASS, self.__datasrc_client)
def test_get_update_acl(self):
diff --git a/src/lib/python/isc/ddns/zone_config.py b/src/lib/python/isc/ddns/zone_config.py
index 388770c..848eac1 100644
--- a/src/lib/python/isc/ddns/zone_config.py
+++ b/src/lib/python/isc/ddns/zone_config.py
@@ -22,6 +22,9 @@ ZONE_NOTFOUND = -1 # Zone isn't found in find_zone()
ZONE_PRIMARY = 0 # Primary zone
ZONE_SECONDARY = 1 # Secondary zone
+# The default ACL if unspecifed on construction of ZoneConfig.
+DEFAULT_ACL = REQUEST_LOADER.load([{"action": "REJECT"}])
+
class ZoneConfig:
'''A temporary helper class to encapsulate zone related configuration.
@@ -38,7 +41,7 @@ class ZoneConfig:
'''Constructor.
Parameters:
- - secondaries: a list of 2-element tuples. Each element is a pair
+ - secondaries: a set of 2-element tuples. Each element is a pair
of isc.dns.Name and isc.dns.RRClass, and identifies a single
secondary zone.
- datasrc_class: isc.dns.RRClass object. Specifies the RR class
@@ -53,12 +56,10 @@ class ZoneConfig:
ACL will be applied to all zones, which is to reject any requests.
'''
- self.__secondaries = set()
- for (zname, zclass) in secondaries:
- self.__secondaries.add((zname, zclass))
+ self.__secondaries = secondaries
self.__datasrc_class = datasrc_class
self.__datasrc_client = datasrc_client
- self.__default_acl = REQUEST_LOADER.load([{"action": "REJECT"}])
+ self.__default_acl = DEFAULT_ACL
self.__acl_map = acl_map
def find_zone(self, zone_name, zone_class):
More information about the bind10-changes
mailing list