Commit 8191aec0 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac2964'

parents f49e5405 09e6babe
......@@ -2624,7 +2624,7 @@ can use various data source backends.
There's also <varname>Auth/database_file</varname> configuration
variable, pointing to a SQLite3 database file. This is no longer
used by <command>b10-auth</command>, but it is left in place for
now, since other modules use it. Once <command>b10-xfrin</command>,
now, since other modules use it. Once <command>b10-zonemgr</command>,
<command>b10-xfrout</command> and <command>b10-ddns</command>
are ported to the new configuration, this will disappear. But for
now, make sure that if you use any of these modules, the new
......@@ -2731,10 +2731,23 @@ TODO
<section>
<title>Configuration for Incoming Zone Transfers</title>
<para>
In practice, you need to specify a list of secondary zones to
enable incoming zone transfers for these zones (you can still
trigger a zone transfer manually, without a prior configuration
(see below)).
In order to enable incoming zone transfers for a secondary
zone, you will first need to make the zone "exist" in some
data source.
One easy way to do this is to create an empty zone using the
<command>b10-loadzone</command> utility.
For example, this makes an empty zone (or empties any existing
content of the zone) "example.com" in the default data source
for <command>b10-loadzone</command> (which is SQLite3-based
data source):
<screen>$ <userinput>b10-loadzone <replaceable>-e</replaceable> <replaceable>example.com</replaceable></userinput></screen>
</para>
<para>
Next, you need to specify a list of secondary zones to
enable incoming zone transfers for these zones in most
practical cases (you can still trigger a zone transfer
manually, without a prior configuration (see below)).
</para>
<para>
......@@ -2749,6 +2762,17 @@ TODO
(We assume there has been no zone configuration before).
</para>
<note>
<simpara>
There is a plan to revise overall zone management
configuration (which are primary and secondary zones, which
data source they are stored, etc) so it can be configured
more consistently and in a unified way among various BIND 10 modules.
When it's done, part or all of the initial configuration
setup described in this section may be deprecated.
</simpara>
</note>
</section>
<section>
......
......@@ -52,6 +52,12 @@
<arg choice="req">zone name</arg>
<arg choice="req">zone file</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>b10-loadzone</command>
<arg><option>-e</option></arg>
<arg><option>other options</option></arg>
<arg choice="req">zone name</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
......@@ -61,6 +67,9 @@
in a BIND 10 ready data source format.
Master files are text files that contain DNS Resource Records
in text form.
This utility can also be used to create an empty zone on the
specified data source so the existence of the zone is recognized
in the data source without any content (resource records).
</para>
<note><simpara>Currently only the SQLITE3 data source is supported.
</simpara></note>
......@@ -104,6 +113,18 @@
old version will still remain accessible for other applications.
</para>
<para>
If the <command>-e</command> command line option is specified,
<command>b10-loadzone</command> does not take the zone name
argument.
In this case it creates an empty zone without any content
while the data source still recognizes the existence of the
zone.
If the specified zone already has some content, this mode of
operation will remove it (but the existence of the zone in the
data source will be still recognized).
</para>
</refsect1>
<refsect1>
......@@ -141,6 +162,18 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term>-e</term>
<listitem><para>
Create an empty zone, or empty existing zone content
instead of loading new one.
When this option is specified, the zone file command line
argument must not be provided.
The <command>-i</command> option has no effect, but it
does not cause a failure; it will be simply ignored.
</para></listitem>
</varlistentry>
<varlistentry>
<term>-i <replaceable class="parameter">report_interval</replaceable></term>
<listitem><para>
......
......@@ -66,6 +66,8 @@ Example: '{"database_file": "/path/to/dbfile/db.sqlite3"}'""",
parser.add_option("-d", "--debug", dest="debug_level",
type='int', action="store", default=None,
help="enable debug logs with the specified level [0-99]")
parser.add_option("-e", "--empty", dest="empty_zone",
action="store_true", help="empty zone content (no load)")
parser.add_option("-i", "--report-interval", dest="report_interval",
type='int', action="store",
default=LOAD_INTERVAL_DEFAULT,
......@@ -113,6 +115,7 @@ class LoadZoneRunner:
self._datasrc_type = None
self._log_severity = 'INFO'
self._log_debuglevel = 0
self._empty_zone = False
self._report_interval = LOAD_INTERVAL_DEFAULT
self._start_time = None
# This one will be used in (rare) cases where we want to allow tests to
......@@ -140,7 +143,8 @@ class LoadZoneRunner:
'''
usage_txt = \
'usage: %prog [options] -c datasrc_config zonename zonefile'
'usage: %prog [options] -c datasrc_config zonename zonefile\n' + \
' %prog [options] -c datasrc_config -e zonename'
parser = OptionParser(usage=usage_txt)
set_cmd_options(parser)
(options, args) = parser.parse_args(args=self.__command_args)
......@@ -174,15 +178,22 @@ class LoadZoneRunner:
'Invalid report interval (must be non negative): %d' %
self._report_interval)
if len(args) != 2:
raise BadArgument('Unexpected number of arguments: %d (must be 2)'
% (len(args)))
if options.empty_zone:
self._empty_zone = True
# Check number of non option arguments: must be 1 with -e; 2 otherwise.
num_args = 1 if self._empty_zone else 2
if len(args) != num_args:
raise BadArgument('Unexpected number of arguments: %d (must be %d)'
% (len(args), num_args))
try:
self._zone_name = Name(args[0])
except Exception as ex: # too broad, but there's no better granurality
raise BadArgument("Invalid zone name '" + args[0] + "': " +
str(ex))
self._zone_file = args[1]
if len(args) > 1:
self._zone_file = args[1]
def _get_datasrc_config(self, datasrc_type):
''''Return the default data source configuration of given type.
......@@ -254,6 +265,34 @@ class LoadZoneRunner:
else:
logger.info(LOADZONE_ZONE_UPDATING, self._zone_name,
self._zone_class)
if self._empty_zone:
self.__make_empty_zone(datasrc_client)
else:
self.__load_from_file(datasrc_client)
except Exception as ex:
if created:
datasrc_client.delete_zone(self._zone_name)
logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
self._zone_class)
raise LoadFailure(str(ex))
def __make_empty_zone(self, datasrc_client):
"""Subroutine of _do_load(), create an empty zone or make it empty."""
try:
updater = datasrc_client.get_updater(self._zone_name, True)
updater.commit()
logger.info(LOADZONE_EMPTY_DONE, self._zone_name,
self._zone_class)
except Exception:
# once updater is created, it's very unlikely that commit() fails,
# but in case it happens, clear updater to release any remaining
# lock.
updater = None
raise
def __load_from_file(self, datasrc_client):
"""Subroutine of _do_load(), load a zone file into data source."""
try:
loader = ZoneLoader(datasrc_client, self._zone_name,
self._zone_file)
self._start_time = time.time()
......@@ -279,14 +318,14 @@ class LoadZoneRunner:
sys.stdout.write('\n')
# record the final count of the loaded RRs for logging
self._loaded_rrs = loader.get_rr_count()
except Exception as ex:
total_elapsed_txt = "%.2f" % (time.time() - self._start_time)
logger.info(LOADZONE_DONE, self._loaded_rrs, self._zone_name,
self._zone_class, total_elapsed_txt)
except Exception:
# release any remaining lock held in the loader
loader = None
if created:
datasrc_client.delete_zone(self._zone_name)
logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
self._zone_class)
raise LoadFailure(str(ex))
raise
def _set_signal_handlers(self):
signal.signal(signal.SIGINT, self._interrupt_handler)
......@@ -302,9 +341,6 @@ class LoadZoneRunner:
self._set_signal_handlers()
self._parse_args()
self._do_load()
total_elapsed_txt = "%.2f" % (time.time() - self._start_time)
logger.info(LOADZONE_DONE, self._loaded_rrs, self._zone_name,
self._zone_class, total_elapsed_txt)
return 0
except BadArgument as ex:
logger.error(LOADZONE_ARGUMENT_ERROR, ex)
......
......@@ -33,6 +33,11 @@ an old version of the zone in the data source, it is now deleted.
It also prints the number of RRs that have been loaded
and the time spent for the loading.
% LOADZONE_EMPTY_DONE Completed emptying zone %1/%2
b10-loadzone has successfully emptied content of the specified zone.
This includes the case where the content didn't previously exist, in which
case it just still reamins empty.
% LOADZONE_LOAD_ERROR Failed to load zone %1/%2: %3
Loading a zone by b10-loadzone fails for some reason in the middle of
the loading. This is most likely due to an error in the specified
......
......@@ -82,6 +82,7 @@ class TestLoadZoneRunner(unittest.TestCase):
self.assertEqual(RRClass.IN, self.__runner._zone_class) # default
self.assertEqual('INFO', self.__runner._log_severity) # default
self.assertEqual(0, self.__runner._log_debuglevel)
self.assertFalse(self.__runner._empty_zone)
def test_set_loglevel(self):
runner = LoadZoneRunner(['-d', '1'] + self.__args)
......@@ -90,13 +91,19 @@ class TestLoadZoneRunner(unittest.TestCase):
self.assertEqual(1, runner._log_debuglevel)
def test_parse_bad_args(self):
# There must be exactly 2 non-option arguments: zone name and zone file
# There must usually be exactly 2 non-option arguments: zone name and
# zone file.
self.assertRaises(BadArgument, LoadZoneRunner([])._parse_args)
self.assertRaises(BadArgument, LoadZoneRunner(['example']).
_parse_args)
self.assertRaises(BadArgument, LoadZoneRunner(self.__args + ['0']).
_parse_args)
# With -e it must be only zone name
self.assertRaises(BadArgument, LoadZoneRunner(
['-e', 'example', 'example.zone'])._parse_args)
self.assertRaises(BadArgument, LoadZoneRunner(['-e'])._parse_args)
# Bad zone name
args = ['example.org', 'example.zone'] # otherwise valid args
self.assertRaises(BadArgument,
......@@ -134,22 +141,24 @@ class TestLoadZoneRunner(unittest.TestCase):
self.assertRaises(BadArgument, self.__runner._get_datasrc_config,
'memory')
def __common_load_setup(self):
def __common_load_setup(self, empty=False):
self.__runner._zone_class = RRClass.IN
self.__runner._zone_name = TEST_ZONE_NAME
self.__runner._zone_file = NEW_ZONE_TXT_FILE
self.__runner._datasrc_type = 'sqlite3'
self.__runner._datasrc_config = DATASRC_CONFIG
self.__runner._report_interval = 1
self.__runner._empty_zone = empty
self.__reports = []
self.__runner._report_progress = lambda x, _: self.__reports.append(x)
def __check_zone_soa(self, soa_txt, zone_name=TEST_ZONE_NAME):
"""Check that the given SOA RR exists and matches the expected string
If soa_txt is None, the zone is expected to be non-existent.
Otherwise, if soa_txt is False, the zone should exist but SOA is
expected to be missing.
If soa_txt is None, the zone is expected to be non-existent;
if it's 'empty', the zone should exist but is expected to be empty;
if soa_txt is False, the zone should exist but SOA is expected to be
missing.
"""
......@@ -160,7 +169,10 @@ class TestLoadZoneRunner(unittest.TestCase):
return
self.assertEqual(client.SUCCESS, result)
result, rrset, _ = finder.find(zone_name, RRType.SOA)
if soa_txt:
if soa_txt == 'empty':
self.assertEqual(finder.NXDOMAIN, result)
self.assertIsNone(rrset)
elif soa_txt:
self.assertEqual(finder.SUCCESS, result)
self.assertEqual(soa_txt, rrset.to_text())
else:
......@@ -269,6 +281,19 @@ class TestLoadZoneRunner(unittest.TestCase):
# _do_load() should have once created the zone but then canceled it.
self.__check_zone_soa(None, zone_name=Name('example.com'))
def test_create_and_empty(self):
self.__common_load_setup(True)
self.__runner._zone_name = Name('example.com')
self.__check_zone_soa(None, zone_name=Name('example.com'))
self.__runner._do_load()
self.__check_zone_soa('empty', zone_name=Name('example.com'))
def test_empty(self):
self.__common_load_setup(True)
self.__check_zone_soa(ORIG_SOA_TXT)
self.__runner._do_load()
self.__check_zone_soa('empty')
def __common_post_load_setup(self, zone_file):
'''Common setup procedure for post load tests which should fail.'''
# replace the LoadZoneRunner's original _post_load_warning() for
......
This diff is collapsed.
This diff is collapsed.
......@@ -60,6 +60,27 @@ error is given in the log message.
There was an error opening a connection to the master. The error is
shown in the log message.
% XFRIN_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 xfrin. The xfrin 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 xfrin
rejects the new configuration then the entire BIND 10 system will have
inconsistent state among different modules. If other modules accept
the update but xfrin produces this error, the xfrin module should
probably be restarted.
% XFRIN_DATASRC_UNKNOWN data source to transfer %1 to is unknown
The xfrin daemon received a command that would trigger a transfer,
but could not find a data source where the specified zone belongs.
There can be several reasons for this error: it may be a simple
misspelling in the xfrin or zonemgr configuration, or in the user
supplied parameter if it is triggered by an external command (such as
from bindctl). Another possibility is that this is the initial transfer
for a newly setup secondary zone. In this case at least an initial empty zone
must be created in one of configured data sources. This can be done by
the -e option of b10-loadzone.
% XFRIN_EXITING exiting
The xfrin daemon is exiting.
......@@ -255,19 +276,6 @@ is recommended to check the primary server configuration.
A connection to the master server has been made, the serial value in
the SOA record has been checked, and a zone transfer has been started.
% XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created
On starting an xfrin session, it is identified that the zone to be
transferred is not found in the data source. This can happen if a
secondary DNS server first tries to perform AXFR from a primary server
without creating the zone image beforehand (e.g. by b10-loadzone). As
of this writing the xfrin process provides backward compatible
behavior to previous versions: creating a new one in the data source
not to surprise existing users too much. This is probably not a good
idea, however, in terms of who should be responsible for managing
zones at a higher level. In future it is more likely that a separate
zone management framework is provided, and the situation where the
given zone isn't found in xfrout will be treated as an error.
% XFRIN_ZONE_INVALID Newly received zone %1/%2 fails validation: %3
The zone was received successfully, but it failed validation. The problem
is severe enough that the new version of zone is discarded and the old version,
......
SUBDIRS = tests
python_PYTHON = __init__.py tsig_keyring.py auth_command.py dns_tcp.py
python_PYTHON += datasrc_clients_mgr.py
python_PYTHON += logger.py
pythondir = $(pyexecdir)/isc/server_common
......
# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import isc.dns
import isc.datasrc
import threading
import json
class ConfigError(Exception):
"""Exception class raised for data source configuration errors."""
pass
class DataSrcClientsMgr:
"""A container of data source client lists.
This class represents a set of isc.datasrc.ConfigurableClientList
objects (currently per RR class), and provides APIs to configure
the lists and access to a specific list in a thread safe manner.
It is intended to be used by applications that refer to the global
'data_sources' module. The reconfigure() method can be called from
a configuration callback for the module of the application. The
get_client_list() method is a simple search method to get the configured
ConfigurableClientList object for a specified RR class (if any),
while still allowing a separate thread to reconfigure the entire lists.
"""
def __init__(self, use_cache=False):
"""Constructor.
In the initial implementation, user applications of this class are
generally expected to NOT use in-memory cache; use_cache would be
set to True only for tests. In future, some applications such as
outbound zone transfer may want to set it to True.
Parameter:
use_cache (bool): If set to True, enable in-memory cache on
(re)configuration.
"""
self.__use_cache = use_cache
# Map from RRClass to ConfigurableClientList. Resetting this map
# is protected by __map_lock. Note that this lock doesn't protect
# "updates" of the map content (currently it's not a problem, but
# if and when we support more operations such as reloading
# particular zones in in-memory cache, remember that there will have
# to be an additional layer of protection).
self.__clients_map = {}
self.__map_lock = threading.Lock()
def get_client_list(self, rrclass):
"""Return the configured ConfigurableClientList for the RR class.
If no client list is configured for the specified RR class, it
returns None.
This method should not raise an exception as long as the parameter
is of valid type.
This method can be safely called from a thread even if a different
thread is calling reconfigure(). Also, it's safe for the caller
to use the returned list even if reconfigure() is called while or
after the call to this thread.
Note that this class does not protect further access to the returned
list from multiple threads; it's the caller's responsbility to make
such access thread safe. In general, the find() method on the list
and the use of ZoneFinder created by a DataSourceClient in the list
cannot be done by multiple threads without explicit synchronization.
On the other hand, multiple threads can create and use ZoneUpdater,
ZoneIterator, or ZoneJournalReader on a DataSourceClient in parallel.
Parameter:
rrclass (isc.dns.RRClass): the RR class of the ConfigurableClientList
to be returned.
"""
with self.__map_lock:
client_list = self.__clients_map.get(rrclass)
return client_list
def reconfigure(self, config):
"""(Re)configure the set of client lists.
This method takes a new set of data source configuration, builds
a new set of ConfigurableClientList objects corresponding to the
configuration, and replaces the internal set with the newly built
one. Its parameter is expected to be the "new configuration"
parameter of a configuration update callback for the global
"data_sources" module. It should match the configuration data
of the module spec (see the datasrc.spec file).
Any error in reconfiguration is converted to a ConfigError
exception and is raised from the method. This method guarantees
strong exception safety: unless building a new set for the new
configuration is fully completed, the old set is intact.
This method can be called from a thread while some other thread
is calling get_client_list() and using the result (see
the description of get_client_list()). In general, however,
only one thread can call this method at one time; while data
integrity will still be preserved, the ordering of the change
will not be guaranteed if multiple threads call this method
at the same time.
Parameter:
config (dict): configuration data for the data_sources module.
"""
try:
new_map = {}
for rrclass_cfg, class_cfg in config.get('classes').items():
rrclass = isc.dns.RRClass(rrclass_cfg)
new_client_list = isc.datasrc.ConfigurableClientList(rrclass)
new_client_list.configure(json.dumps(class_cfg),
self.__use_cache)
new_map[rrclass] = new_client_list
with self.__map_lock:
self.__clients_map = new_map
except Exception as ex:
# Catch all types of exceptions as a whole: there won't be much
# granularity for exceptions raised from the C++ module anyway.
raise ConfigError(ex)
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = tsig_keyring_test.py dns_tcp_test.py
PYTESTS = tsig_keyring_test.py dns_tcp_test.py datasrc_clients_mgr_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
......@@ -9,6 +9,14 @@ if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# We use our own "default" datasrc.spec, tweaking some installation path,
# so we can run the tests with something very close to the actual spec and
# yet independent from installation environment.
BUILT_SOURCES = datasrc.spec
datasrc.spec: $(abs_top_builddir)/src/bin/cfgmgr/plugins/datasrc.spec.pre
$(SED) -e "s|@@STATIC_ZONE_FILE@@|$(abs_top_builddir)/src/lib/datasrc/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(abs_builddir)/zone.sqlite3|" $(abs_top_builddir)/src/bin/cfgmgr/plugins/datasrc.spec.pre >$@
CLEANFILES = datasrc.spec zone.sqlite3
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
......@@ -21,5 +29,6 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import isc.log
from isc.server_common.datasrc_clients_mgr import *
from isc.dns import *
import unittest
import isc.config
import os
# A (slightly tweaked) local copy of the default data source spec
DATASRC_SPECFILE = os.environ["B10_FROM_BUILD"] + \
"/src/lib/python/isc/server_common/tests/datasrc.spec"
DEFAULT_CONFIG = \
isc.config.ConfigData(isc.config.module_spec_from_file(DATASRC_SPECFILE)).\
get_full_config()
class DataSrcClientsMgrTest(unittest.TestCase):
def setUp(self):
# We construct the manager with enabling in-memory cache for easier
# tests. There should be no risk of inter-thread issues in the tests.
self.__mgr = DataSrcClientsMgr(use_cache=True)
def test_init(self):
"""Check some initial state.
Initially there's no client list available, but get_client_list
doesn't cause disruption.
"""
self.assertIsNone(self.__mgr.get_client_list(RRClass.IN))
self.assertIsNone(self.__mgr.get_client_list(RRClass.CH))
def test_reconfigure(self):
"""Check configuration behavior.
First try the default configuration, and replace it with something
else.
"""
# There should be at least in-memory only data for the static
# bind/CH zone. (We don't assume the existence of SQLite3 datasrc,
# so it'll still work if and when we make the default DB-independent).
self.__mgr.reconfigure(DEFAULT_CONFIG)
clist = self.__mgr.get_client_list(RRClass.CH)
self.assertIsNotNone(clist)
self.assertTrue(clist.find(Name('bind'), True, False)[2])
# Reconfigure it with a simple new config: the list for CH will be
# gone, and and an empty list for IN will be installed.