Merge branch 'master' into trac2036
... | ... | @@ -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 | ||
... | ... | @@ -299,6 +341,10 @@ class TestDDNSServer(unittest.TestCase): |
self.__select_answer = None | ||
self.__select_exception = None | ||
self.__hook_called = False | ||
# Because we overwrite the _listen_socket, close any existing | ||
# socket object. | ||
if self.ddns_server._listen_socket is not None: | ||
self.ddns_server._listen_socket.close() | ||
self.ddns_server._listen_socket = FakeSocket(2) | ||
ddns.select.select = self.__select | ||
... | ... | @@ -306,12 +352,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): | ||
''' | ||
... | ... | @@ -334,6 +383,9 @@ class TestDDNSServer(unittest.TestCase): |
# Now make sure the clear_socket really works | ||
ddns.clear_socket() | ||
self.assertFalse(os.path.exists(ddns.SOCKET_FILE)) | ||
# Let ddns object complete any necessary cleanup (not part of the test, | ||
# but for suppressing any warnings from the Python interpreter) | ||
ddnss.shutdown_cleanup() | ||
def test_initial_config(self): | ||
# right now, the only configuration is the zone configuration, whose | ||
... | ... | @@ -422,6 +474,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 = \ | ||
< |