Commit 43c9101c authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[2964] added -e (empty zone) option to b10-loadzone.

it'll soon be impossible to assume a specific data source for the very
initial transfer, we need to extract the zone creation part from b10-xfrin
(which should be the right course anyway).  the new loadzone option is
my proposed way to do this.  I think this option itself is of some use, too.
parent 159d0dd5
......@@ -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,10 @@
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 empty current content, if any,
of the specified zone from 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 +114,19 @@
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 empties any existing content of the specified
zone from the specified data source while the data source still
recognizes the existence of the zone. If the zone originally
didn't exist in the zone, it effectively creates the zone without
any content.
This mode of operation can be used for setting up a secondary
zone before transferring zone content from a primary server.
</para>
</refsect1>
<refsect1>
......@@ -141,6 +164,17 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term>-e</term>
<listitem><para>
Empty any 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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment