mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 22:15:23 +00:00
Merge branch 'trac1414'
This commit is contained in:
@@ -20,6 +20,7 @@ endif
|
|||||||
for pytest in $(PYTESTS) ; do \
|
for pytest in $(PYTESTS) ; do \
|
||||||
echo Running test: $$pytest ; \
|
echo Running test: $$pytest ; \
|
||||||
$(LIBRARY_PATH_PLACEHOLDER) \
|
$(LIBRARY_PATH_PLACEHOLDER) \
|
||||||
|
B10_FROM_BUILD=$(abs_top_builddir) \
|
||||||
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
|
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
|
||||||
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
|
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
|
||||||
done
|
done
|
||||||
|
@@ -48,28 +48,16 @@ class MySession():
|
|||||||
def group_recvmsg(self, nonblock, seq):
|
def group_recvmsg(self, nonblock, seq):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
class FakeConfig:
|
class FakeCCSession(isc.config.ConfigData):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.zone_list = []
|
module_spec = isc.config.module_spec_from_file(SPECFILE_LOCATION)
|
||||||
self.set_zone_list_from_name_classes([ZONE_NAME_CLASS1_IN,
|
ConfigData.__init__(self, module_spec)
|
||||||
ZONE_NAME_CLASS2_CH])
|
|
||||||
def set_zone_list_from_name_classes(self, zones):
|
def get_remote_config_value(self, module_name, identifier):
|
||||||
self.zone_list = map(lambda nc: {"name": nc[0], "class": nc[1]}, zones)
|
if module_name == "Auth" and identifier == "database_file":
|
||||||
def get(self, name):
|
return "initdb.file", False
|
||||||
if name == 'lowerbound_refresh':
|
|
||||||
return LOWERBOUND_REFRESH
|
|
||||||
elif name == 'lowerbound_retry':
|
|
||||||
return LOWERBOUND_RETRY
|
|
||||||
elif name == 'max_transfer_timeout':
|
|
||||||
return MAX_TRANSFER_TIMEOUT
|
|
||||||
elif name == 'refresh_jitter':
|
|
||||||
return REFRESH_JITTER
|
|
||||||
elif name == 'reload_jitter':
|
|
||||||
return RELOAD_JITTER
|
|
||||||
elif name == 'secondary_zones':
|
|
||||||
return self.zone_list
|
|
||||||
else:
|
else:
|
||||||
raise ValueError('Uknown config option')
|
return "unknown", False
|
||||||
|
|
||||||
class MyZonemgrRefresh(ZonemgrRefresh):
|
class MyZonemgrRefresh(ZonemgrRefresh):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -92,7 +80,7 @@ class MyZonemgrRefresh(ZonemgrRefresh):
|
|||||||
sqlite3_ds.get_zone_soa = get_zone_soa
|
sqlite3_ds.get_zone_soa = get_zone_soa
|
||||||
|
|
||||||
ZonemgrRefresh.__init__(self, MySession(), "initdb.file",
|
ZonemgrRefresh.__init__(self, MySession(), "initdb.file",
|
||||||
self._slave_socket, FakeConfig())
|
self._slave_socket, FakeCCSession())
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
self._zonemgr_refresh_info = {
|
self._zonemgr_refresh_info = {
|
||||||
('example.net.', 'IN'): {
|
('example.net.', 'IN'): {
|
||||||
@@ -112,6 +100,7 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
self.stderr_backup = sys.stderr
|
self.stderr_backup = sys.stderr
|
||||||
sys.stderr = open(os.devnull, 'w')
|
sys.stderr = open(os.devnull, 'w')
|
||||||
self.zone_refresh = MyZonemgrRefresh()
|
self.zone_refresh = MyZonemgrRefresh()
|
||||||
|
self.cc_session = FakeCCSession()
|
||||||
|
|
||||||
def test_random_jitter(self):
|
def test_random_jitter(self):
|
||||||
max = 100025.120
|
max = 100025.120
|
||||||
@@ -458,7 +447,23 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
"secondary_zones": [ { "name": "example.net.",
|
"secondary_zones": [ { "name": "example.net.",
|
||||||
"class": "IN" } ]
|
"class": "IN" } ]
|
||||||
}
|
}
|
||||||
self.zone_refresh.update_config_data(config_data)
|
self.zone_refresh.update_config_data(config_data, self.cc_session)
|
||||||
|
self.assertTrue(("example.net.", "IN") in
|
||||||
|
self.zone_refresh._zonemgr_refresh_info)
|
||||||
|
|
||||||
|
# make sure it does fail if we don't provide a name
|
||||||
|
config_data = {
|
||||||
|
"secondary_zones": [ { "class": "IN" } ]
|
||||||
|
}
|
||||||
|
self.assertRaises(ZonemgrException,
|
||||||
|
self.zone_refresh.update_config_data,
|
||||||
|
config_data, self.cc_session)
|
||||||
|
|
||||||
|
# But not if we don't provide a class
|
||||||
|
config_data = {
|
||||||
|
"secondary_zones": [ { "name": "example.net." } ]
|
||||||
|
}
|
||||||
|
self.zone_refresh.update_config_data(config_data, self.cc_session)
|
||||||
self.assertTrue(("example.net.", "IN") in
|
self.assertTrue(("example.net.", "IN") in
|
||||||
self.zone_refresh._zonemgr_refresh_info)
|
self.zone_refresh._zonemgr_refresh_info)
|
||||||
|
|
||||||
@@ -471,7 +476,7 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
"reload_jitter" : 0.75,
|
"reload_jitter" : 0.75,
|
||||||
"secondary_zones": []
|
"secondary_zones": []
|
||||||
}
|
}
|
||||||
self.zone_refresh.update_config_data(config_data)
|
self.zone_refresh.update_config_data(config_data, self.cc_session)
|
||||||
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
|
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
|
||||||
self.assertEqual(30, self.zone_refresh._lowerbound_retry)
|
self.assertEqual(30, self.zone_refresh._lowerbound_retry)
|
||||||
self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
|
self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
|
||||||
@@ -482,7 +487,7 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
config_data = {
|
config_data = {
|
||||||
"reload_jitter" : 0.35,
|
"reload_jitter" : 0.35,
|
||||||
}
|
}
|
||||||
self.zone_refresh.update_config_data(config_data)
|
self.zone_refresh.update_config_data(config_data, self.cc_session)
|
||||||
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
|
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
|
||||||
self.assertEqual(30, self.zone_refresh._lowerbound_retry)
|
self.assertEqual(30, self.zone_refresh._lowerbound_retry)
|
||||||
self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
|
self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
|
||||||
@@ -500,7 +505,7 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
"secondary_zones": [ { "name": "doesnotexist",
|
"secondary_zones": [ { "name": "doesnotexist",
|
||||||
"class": "IN" } ]
|
"class": "IN" } ]
|
||||||
}
|
}
|
||||||
self.zone_refresh.update_config_data(config_data)
|
self.zone_refresh.update_config_data(config_data, self.cc_session)
|
||||||
name_class = ("doesnotexist.", "IN")
|
name_class = ("doesnotexist.", "IN")
|
||||||
self.assertTrue(self.zone_refresh._zonemgr_refresh_info[name_class]["zone_soa_rdata"]
|
self.assertTrue(self.zone_refresh._zonemgr_refresh_info[name_class]["zone_soa_rdata"]
|
||||||
is None)
|
is None)
|
||||||
@@ -520,7 +525,7 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
"reload_jitter" : 0.75,
|
"reload_jitter" : 0.75,
|
||||||
"secondary_zones": []
|
"secondary_zones": []
|
||||||
}
|
}
|
||||||
self.zone_refresh.update_config_data(config_data)
|
self.zone_refresh.update_config_data(config_data, self.cc_session)
|
||||||
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
|
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
|
||||||
self.assertEqual(30, self.zone_refresh._lowerbound_retry)
|
self.assertEqual(30, self.zone_refresh._lowerbound_retry)
|
||||||
self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
|
self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
|
||||||
@@ -536,46 +541,68 @@ class TestZonemgrRefresh(unittest.TestCase):
|
|||||||
self.assertFalse(listener.is_alive())
|
self.assertFalse(listener.is_alive())
|
||||||
|
|
||||||
def test_secondary_zones(self):
|
def test_secondary_zones(self):
|
||||||
|
def zone_list_from_name_classes(zones):
|
||||||
|
return map(lambda nc: {"name": nc[0], "class": nc[1]}, zones)
|
||||||
|
|
||||||
"""Test that we can modify the list of secondary zones"""
|
"""Test that we can modify the list of secondary zones"""
|
||||||
config = FakeConfig()
|
config = self.cc_session.get_full_config()
|
||||||
config.zone_list = []
|
config['secondary_zones'] = []
|
||||||
# First, remove everything
|
# First, remove everything
|
||||||
self.zone_refresh.update_config_data(config)
|
self.zone_refresh.update_config_data(config, self.cc_session)
|
||||||
self.assertEqual(self.zone_refresh._zonemgr_refresh_info, {})
|
self.assertEqual(self.zone_refresh._zonemgr_refresh_info, {})
|
||||||
# Put something in
|
# Put something in
|
||||||
config.set_zone_list_from_name_classes([ZONE_NAME_CLASS1_IN])
|
config['secondary_zones'] = \
|
||||||
self.zone_refresh.update_config_data(config)
|
zone_list_from_name_classes([ZONE_NAME_CLASS1_IN])
|
||||||
|
self.zone_refresh.update_config_data(config, self.cc_session)
|
||||||
self.assertTrue(("example.net.", "IN") in
|
self.assertTrue(("example.net.", "IN") in
|
||||||
self.zone_refresh._zonemgr_refresh_info)
|
self.zone_refresh._zonemgr_refresh_info)
|
||||||
# This one does not exist
|
# Reset the data, set to use a different class, and make sure
|
||||||
config.set_zone_list_from_name_classes(["example.net", "CH"])
|
# it does not get set to IN
|
||||||
self.zone_refresh.update_config_data(config)
|
config['secondary_zones'] = \
|
||||||
self.assertFalse(("example.net.", "CH") in
|
zone_list_from_name_classes([ZONE_NAME_CLASS1_CH])
|
||||||
self.zone_refresh._zonemgr_refresh_info)
|
self.zone_refresh.update_config_data(config, self.cc_session)
|
||||||
# Simply skip loading soa for the zone, the other configs should be updated successful
|
|
||||||
self.assertFalse(("example.net.", "IN") in
|
self.assertFalse(("example.net.", "IN") in
|
||||||
self.zone_refresh._zonemgr_refresh_info)
|
self.zone_refresh._zonemgr_refresh_info)
|
||||||
# Make sure it works even when we "accidentally" forget the final dot
|
# Make sure it works even when we "accidentally" forget the final dot
|
||||||
config.set_zone_list_from_name_classes([("example.net", "IN")])
|
config['secondary_zones'] = \
|
||||||
self.zone_refresh.update_config_data(config)
|
zone_list_from_name_classes([("example.net", "IN")])
|
||||||
|
self.zone_refresh.update_config_data(config, self.cc_session)
|
||||||
self.assertTrue(("example.net.", "IN") in
|
self.assertTrue(("example.net.", "IN") in
|
||||||
self.zone_refresh._zonemgr_refresh_info)
|
self.zone_refresh._zonemgr_refresh_info)
|
||||||
|
|
||||||
|
# and with case-insensitive checking
|
||||||
|
config['secondary_zones'] = \
|
||||||
|
zone_list_from_name_classes([("Example.NeT.", "in")])
|
||||||
|
self.zone_refresh.update_config_data(config, self.cc_session)
|
||||||
|
self.assertTrue(("example.net.", "IN") in
|
||||||
|
self.zone_refresh._zonemgr_refresh_info)
|
||||||
|
|
||||||
|
# Try some bad names
|
||||||
|
config['secondary_zones'] = \
|
||||||
|
zone_list_from_name_classes([("example..net", "IN")])
|
||||||
|
self.assertRaises(ZonemgrException,
|
||||||
|
self.zone_refresh.update_config_data,
|
||||||
|
config, self.cc_session)
|
||||||
|
config['secondary_zones'] = \
|
||||||
|
zone_list_from_name_classes([("", "IN")])
|
||||||
|
self.assertRaises(ZonemgrException,
|
||||||
|
self.zone_refresh.update_config_data,
|
||||||
|
config, self.cc_session)
|
||||||
|
# Try a bad class
|
||||||
|
config['secondary_zones'] = \
|
||||||
|
zone_list_from_name_classes([("example.net", "BADCLASS")])
|
||||||
|
self.assertRaises(ZonemgrException,
|
||||||
|
self.zone_refresh.update_config_data,
|
||||||
|
config, self.cc_session)
|
||||||
|
config['secondary_zones'] = \
|
||||||
|
zone_list_from_name_classes([("example.net", "")])
|
||||||
|
self.assertRaises(ZonemgrException,
|
||||||
|
self.zone_refresh.update_config_data,
|
||||||
|
config, self.cc_session)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
sys.stderr= self.stderr_backup
|
sys.stderr= self.stderr_backup
|
||||||
|
|
||||||
|
|
||||||
class MyCCSession():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_remote_config_value(self, module_name, identifier):
|
|
||||||
if module_name == "Auth" and identifier == "database_file":
|
|
||||||
return "initdb.file", False
|
|
||||||
else:
|
|
||||||
return "unknown", False
|
|
||||||
|
|
||||||
|
|
||||||
class MyZonemgr(Zonemgr):
|
class MyZonemgr(Zonemgr):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -583,7 +610,7 @@ class MyZonemgr(Zonemgr):
|
|||||||
self._zone_refresh = None
|
self._zone_refresh = None
|
||||||
self._shutdown_event = threading.Event()
|
self._shutdown_event = threading.Event()
|
||||||
self._cc = MySession()
|
self._cc = MySession()
|
||||||
self._module_cc = MyCCSession()
|
self._module_cc = FakeCCSession()
|
||||||
self._config_data = {
|
self._config_data = {
|
||||||
"lowerbound_refresh" : 10,
|
"lowerbound_refresh" : 10,
|
||||||
"lowerbound_retry" : 5,
|
"lowerbound_retry" : 5,
|
||||||
@@ -622,7 +649,7 @@ class TestZonemgr(unittest.TestCase):
|
|||||||
self.assertEqual(0.5, self.zonemgr._config_data.get("refresh_jitter"))
|
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
|
# The zone doesn't exist in database, simply skip loading soa for it and log an warning
|
||||||
self.zonemgr._zone_refresh = ZonemgrRefresh(None, "initdb.file", None,
|
self.zonemgr._zone_refresh = ZonemgrRefresh(None, "initdb.file", None,
|
||||||
config_data1)
|
FakeCCSession())
|
||||||
config_data1["secondary_zones"] = [{"name": "nonexistent.example",
|
config_data1["secondary_zones"] = [{"name": "nonexistent.example",
|
||||||
"class": "IN"}]
|
"class": "IN"}]
|
||||||
self.assertEqual(self.zonemgr.config_handler(config_data1),
|
self.assertEqual(self.zonemgr.config_handler(config_data1),
|
||||||
@@ -660,4 +687,5 @@ class TestZonemgr(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if __name__== "__main__":
|
if __name__== "__main__":
|
||||||
|
isc.log.resetUnitTestRootLogger()
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -28,6 +28,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
import isc
|
import isc
|
||||||
|
import isc.dns
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import select
|
import select
|
||||||
@@ -98,7 +99,7 @@ class ZonemgrRefresh:
|
|||||||
can be stopped by calling shutdown() in another thread.
|
can be stopped by calling shutdown() in another thread.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cc, db_file, slave_socket, config_data):
|
def __init__(self, cc, db_file, slave_socket, module_cc_session):
|
||||||
self._cc = cc
|
self._cc = cc
|
||||||
self._check_sock = slave_socket
|
self._check_sock = slave_socket
|
||||||
self._db_file = db_file
|
self._db_file = db_file
|
||||||
@@ -108,7 +109,8 @@ class ZonemgrRefresh:
|
|||||||
self._max_transfer_timeout = None
|
self._max_transfer_timeout = None
|
||||||
self._refresh_jitter = None
|
self._refresh_jitter = None
|
||||||
self._reload_jitter = None
|
self._reload_jitter = None
|
||||||
self.update_config_data(config_data)
|
self.update_config_data(module_cc_session.get_full_config(),
|
||||||
|
module_cc_session)
|
||||||
self._running = False
|
self._running = False
|
||||||
|
|
||||||
def _random_jitter(self, max, jitter):
|
def _random_jitter(self, max, jitter):
|
||||||
@@ -424,7 +426,7 @@ class ZonemgrRefresh:
|
|||||||
self._read_sock = None
|
self._read_sock = None
|
||||||
self._write_sock = None
|
self._write_sock = None
|
||||||
|
|
||||||
def update_config_data(self, new_config):
|
def update_config_data(self, new_config, module_cc_session):
|
||||||
""" update ZonemgrRefresh config """
|
""" update ZonemgrRefresh config """
|
||||||
# Get a new value, but only if it is defined (commonly used below)
|
# 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
|
# We don't use "value or default", because if value would be
|
||||||
@@ -456,11 +458,42 @@ class ZonemgrRefresh:
|
|||||||
if secondary_zones is not None:
|
if secondary_zones is not None:
|
||||||
# Add new zones
|
# Add new zones
|
||||||
for secondary_zone in new_config.get('secondary_zones'):
|
for secondary_zone in new_config.get('secondary_zones'):
|
||||||
|
if 'name' not in secondary_zone:
|
||||||
|
raise ZonemgrException("Secondary zone specified "
|
||||||
|
"without a name")
|
||||||
name = secondary_zone['name']
|
name = secondary_zone['name']
|
||||||
# Be tolerant to sclerotic users who forget the final dot
|
|
||||||
if name[-1] != '.':
|
# Convert to Name and back (both to check and to normalize)
|
||||||
name = name + '.'
|
try:
|
||||||
name_class = (name, secondary_zone['class'])
|
name = isc.dns.Name(name, True).to_text()
|
||||||
|
# Name() can raise a number of different exceptions, just
|
||||||
|
# catch 'em all.
|
||||||
|
except Exception as isce:
|
||||||
|
raise ZonemgrException("Bad zone name '" + name +
|
||||||
|
"': " + str(isce))
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
# 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
|
||||||
|
# that contains default values itself
|
||||||
|
# (then this entire method can be simplified a lot, and we
|
||||||
|
# wouldn't need direct access to the ccsession object)
|
||||||
|
if 'class' in secondary_zone:
|
||||||
|
rr_class = secondary_zone['class']
|
||||||
|
else:
|
||||||
|
rr_class = module_cc_session.get_default_value(
|
||||||
|
'secondary_zones/class')
|
||||||
|
# Convert rr_class to and from RRClass to check its value
|
||||||
|
try:
|
||||||
|
name_class = (name, isc.dns.RRClass(rr_class).to_text())
|
||||||
|
except isc.dns.InvalidRRClass:
|
||||||
|
raise ZonemgrException("Bad RR class '" +
|
||||||
|
rr_class +
|
||||||
|
"' for zone " + name)
|
||||||
required[name_class] = True
|
required[name_class] = True
|
||||||
# Add it only if it isn't there already
|
# Add it only if it isn't there already
|
||||||
if not name_class in self._zonemgr_refresh_info:
|
if not name_class in self._zonemgr_refresh_info:
|
||||||
@@ -485,7 +518,7 @@ class Zonemgr:
|
|||||||
self._db_file = self.get_db_file()
|
self._db_file = self.get_db_file()
|
||||||
# Create socket pair for communicating between main thread and zonemgr timer thread
|
# 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._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._config_data)
|
self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._module_cc)
|
||||||
self._zone_refresh.run_timer()
|
self._zone_refresh.run_timer()
|
||||||
|
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
@@ -540,7 +573,7 @@ class Zonemgr:
|
|||||||
self._config_data_check(complete)
|
self._config_data_check(complete)
|
||||||
if self._zone_refresh is not None:
|
if self._zone_refresh is not None:
|
||||||
try:
|
try:
|
||||||
self._zone_refresh.update_config_data(complete)
|
self._zone_refresh.update_config_data(complete, self._module_cc)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
answer = create_answer(1, str(e))
|
answer = create_answer(1, str(e))
|
||||||
ok = False
|
ok = False
|
||||||
|
Reference in New Issue
Block a user