Commit 23059244 authored by Paul Selkirk's avatar Paul Selkirk
Browse files

[2967] convert to general datasrc config

parent 5c03ce92
# 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"}
......
#!@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):
......
# 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.
......
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