From: Mukund Sivaraman Date: Mon, 18 Jun 2012 09:31:14 +0000 (+0530) Subject: Merge branch 'master' into trac1828 X-Git-Tag: trac2351_base~226^2~42^2~1^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=464682a2180c672f1ed12d8a56fd0a5ab3eb96ed;p=thirdparty%2Fkea.git Merge branch 'master' into trac1828 Conflicts: src/bin/ddns/tests/ddns_test.py src/bin/zonemgr/tests/zonemgr_test.py --- 464682a2180c672f1ed12d8a56fd0a5ab3eb96ed diff --cc src/bin/ddns/tests/ddns_test.py index 75816ac4f3,b0b34ca02b..3343031d6a --- a/src/bin/ddns/tests/ddns_test.py +++ b/src/bin/ddns/tests/ddns_test.py @@@ -146,16 -379,199 +383,202 @@@ 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 + # default should be an empty map. + self.assertEqual({}, self.ddns_server._zone_config) + def test_config_handler(self): - # Config handler does not do anything yet, but should at least - # return 'ok' for now. - new_config = {} + # Update with a simple zone configuration: including an accept-all ACL + new_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } answer = self.ddns_server.config_handler(new_config) self.assertEqual((0, None), isc.config.parse_answer(answer)) + acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)] + self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT)) + + # Slightly more complicated one: containing multiple ACLs + new_config = { 'zones': [ { 'origin': 'example.com', + 'class': 'CH', + 'update_acl': [{'action': 'REJECT', + 'from': '2001:db8::1'}] }, + { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] }, + { 'origin': 'example.org', + 'class': 'CH', + 'update_acl': [{'action': 'DROP'}] } ] } + answer = self.ddns_server.config_handler(new_config) + self.assertEqual((0, None), isc.config.parse_answer(answer)) + self.assertEqual(3, len(self.ddns_server._zone_config)) + acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)] + self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT)) + + # empty zone config + new_config = { 'zones': [] } + answer = self.ddns_server.config_handler(new_config) + self.assertEqual((0, None), isc.config.parse_answer(answer)) + self.assertEqual({}, self.ddns_server._zone_config) + + # bad zone config data: bad name. The previous config shouls be kept. + bad_config = { 'zones': [ { 'origin': 'bad..example', + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # bad zone config data: bad class. + bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': 'badclass', + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # bad zone config data: bad ACL. + bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'badaction'}]}]} + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # the first zone cofig is valid, but not the second. the first one + # shouldn't be installed. + bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] }, + { 'origin': 'bad..example', + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # Half-broken case: 'origin, class' pair is duplicate. For now we + # we accept it (the latter one will win) + dup_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'REJECT'}] }, + { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(dup_config) + self.assertEqual((0, None), isc.config.parse_answer(answer)) + 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 = \ + {'database_file': './notexistentdir/somedb.sqlite3'} + self.__cc_session.add_remote_config_by_name('Auth') + rrclass, datasrc_client = self.ddns_server._datasrc_info + self.assertEqual(RRClass.IN(), rrclass) + self.assertRaises(isc.datasrc.Error, + datasrc_client.find_zone, Name('example.org')) + + # Check the current info isn't changed if the new config doesn't + # update it. + info_orig = self.ddns_server._datasrc_info + self.ddns_server._datasrc_info = 42 # dummy value, should be kept. + self.__cc_session._auth_config = {'other_config': 'value'} + self.__cc_session.add_remote_config_by_name('Auth') + self.assertEqual(42, self.ddns_server._datasrc_info) + self.ddns_server._datasrc_info = info_orig + + def test_secondary_zones_config(self): + # By default it should be an empty list + self.assertEqual(set(), self.ddns_server._secondary_zones) + + # emulating an update. + self.__cc_session._zonemgr_config = {'secondary_zones': [ + {'name': TEST_ZONE_NAME_STR, 'class': TEST_RRCLASS_STR}]} + self.__cc_session.add_remote_config_by_name('Zonemgr') + + # The new set of secondary zones should be stored. + self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)}, + self.ddns_server._secondary_zones) + + # Similar to the above, but 'class' is unspecified. The default value + # should be used. + self.__cc_session._zonemgr_config = {'secondary_zones': [ + {'name': TEST_ZONE_NAME_STR}]} + self.__cc_session.add_remote_config_by_name('Zonemgr') + self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)}, + self.ddns_server._secondary_zones) + + # The given list has a duplicate. The resulting set should unify them. + self.__cc_session._zonemgr_config = {'secondary_zones': [ + {'name': TEST_ZONE_NAME_STR, 'class': TEST_RRCLASS_STR}, + {'name': TEST_ZONE_NAME_STR, 'class': TEST_RRCLASS_STR}]} + self.__cc_session.add_remote_config_by_name('Zonemgr') + self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)}, + self.ddns_server._secondary_zones) + + # Check the 2ndary zones aren't changed if the new config doesn't + # update it. + seczones_orig = self.ddns_server._secondary_zones + self.ddns_server._secondary_zones = 42 # dummy value, should be kept. + self.__cc_session._zonemgr_config = {} + self.__cc_session.add_remote_config_by_name('Zonemgr') + self.assertEqual(42, self.ddns_server._secondary_zones) + self.ddns_server._secondary_zones = seczones_orig + + # If the update config is broken, the existing set should be intact. + self.__cc_session._zonemgr_config = {'secondary_zones': [ + {'name': 'good.example', 'class': TEST_RRCLASS_STR}, + {'name': 'badd..example', 'class': TEST_RRCLASS_STR}]} + self.__cc_session.add_remote_config_by_name('Zonemgr') + self.assertEqual({(TEST_ZONE_NAME, TEST_RRCLASS)}, + self.ddns_server._secondary_zones) + + def __check_remote_config_fail(self, mod_name, num_ex, expected_ex): + '''Subroutine for remote_config_fail test.''' + + # fake pause function for inspection and to avoid having timeouts + added_pause = [] + ddns.add_pause = lambda sec: added_pause.append(sec) + + # In our current implementation, there will be up to 3 tries of + # adding the module, each separated by a 1-sec pause. If all attempts + # fail the exception will be propagated. + exceptions = [expected_ex for i in range(0, num_ex)] + self.__cc_session._raise_mods = {mod_name: exceptions} + if num_ex >= 3: + self.assertRaises(expected_ex, ddns.DDNSServer, self.__cc_session) + else: + ddns.DDNSServer(self.__cc_session) + self.assertEqual([1 for i in range(0, num_ex)], added_pause) + + def test_remote_config_fail(self): + # If getting config of Auth or Zonemgr fails on construction of + # DDNServer, it should result in an exception and a few times + # of retries. We test all possible cases, changing the number of + # raised exceptions and the type of exceptions that can happen, + # which should also cover the fatal error case. + for i in range(0, 4): + self.__check_remote_config_fail('Auth', i, ModuleCCSessionError) + self.__check_remote_config_fail('Auth', i, ModuleSpecError) + self.__check_remote_config_fail('Zonemgr', i, ModuleCCSessionError) + self.__check_remote_config_fail('Zonemgr', i, ModuleSpecError) def test_shutdown_command(self): '''Test whether the shutdown command works''' diff --cc src/lib/python/isc/util/cio/tests/socketsession_test.py index eba9fa2413,f2de9990af..6502aba937 --- a/src/lib/python/isc/util/cio/tests/socketsession_test.py +++ b/src/lib/python/isc/util/cio/tests/socketsession_test.py @@@ -126,67 -125,67 +128,70 @@@ class TestForwarder(unittest.TestCase) def check_push_and_pop(self, family, type, protocol, local, remote, data, new_connection): - sock = self.create_socket(family, type, protocol, local, True) - fwd_fd = sock.fileno() - if protocol == IPPROTO_TCP: - client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \ - else ('127.0.0.1', 0) - client_sock = self.create_socket(family, type, protocol, - client_addr, False) - client_sock.setblocking(False) - try: - client_sock.connect(local) - except socket.error: - pass - server_sock, _ = sock.accept() - fwd_fd = server_sock.fileno() - - # If a new connection is required, start the "server", have the - # internal forwarder connect to it, and then internally accept it. - if new_connection: - self.start_listen() - self.forwarder.connect_to_receiver() - self.accept_sock = self.accept_forwarder() - - # Then push one socket session via the forwarder. - self.forwarder.push(fwd_fd, family, type, protocol, local, remote, - data) - - # Pop the socket session we just pushed from a local receiver, and - # check the content. - receiver = SocketSessionReceiver(self.accept_sock) - signal.alarm(1) - sock_session = receiver.pop() - signal.alarm(0) - passed_sock = sock_session[0] - self.assertNotEqual(fwd_fd, passed_sock.fileno()) - self.assertEqual(family, passed_sock.family) - self.assertEqual(type, passed_sock.type) - self.assertEqual(protocol, passed_sock.proto) - self.assertEqual(local, sock_session[1]) - self.assertEqual(remote, sock_session[2]) - self.assertEqual(data, sock_session[3]) - - # Check if the passed FD is usable by sending some data from it. - passed_sock.setblocking(True) - if protocol == IPPROTO_UDP: - self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA, - local)) - sock.settimeout(10) - self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0]) - else: - server_sock.close() - self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA)) - client_sock.setblocking(True) - client_sock.settimeout(10) - self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA))) + with self.create_socket(family, type, protocol, local, True) as sock: + fwd_fd = sock.fileno() + if protocol == IPPROTO_TCP: + client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \ + else ('127.0.0.1', 0) + client_sock = self.create_socket(family, type, protocol, + client_addr, False) + client_sock.setblocking(False) + try: + client_sock.connect(local) + except socket.error: + pass + server_sock, _ = sock.accept() + fwd_fd = server_sock.fileno() + + # If a new connection is required, start the "server", have the + # internal forwarder connect to it, and then internally accept it. + if new_connection: + self.start_listen() + self.forwarder.connect_to_receiver() + self.accept_sock = self.accept_forwarder() + + # Then push one socket session via the forwarder. + self.forwarder.push(fwd_fd, family, type, protocol, local, remote, + data) + + # Pop the socket session we just pushed from a local receiver, and + # check the content. + receiver = SocketSessionReceiver(self.accept_sock) + signal.alarm(1) + sock_session = receiver.pop() + signal.alarm(0) + passed_sock = sock_session[0] + self.assertNotEqual(fwd_fd, passed_sock.fileno()) + self.assertEqual(family, passed_sock.family) + self.assertEqual(type, passed_sock.type) + self.assertEqual(protocol, passed_sock.proto) + self.assertEqual(local, sock_session[1]) + self.assertEqual(remote, sock_session[2]) + self.assertEqual(data, sock_session[3]) + + # Check if the passed FD is usable by sending some data from it. + passed_sock.setblocking(True) + if protocol == IPPROTO_UDP: + self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA, + local)) + sock.settimeout(10) + self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0]) + else: + self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA)) + client_sock.setblocking(True) + client_sock.settimeout(10) + self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA))) + server_sock.close() + client_sock.close() + + passed_sock.close() def test_push_and_pop(self): - # This is a straightforward port of C++ pushAndPop test. + # This is a straightforward port of C++ pushAndPop test. See the + # C++ version why we use multiple ports for "local". local6 = ('::1', TEST_PORT, 0, 0) + local6_alt = ('::1', TEST_PORT2, 0, 0) + local6_alt2 = ('::1', TEST_PORT3, 0, 0) remote6 = ('2001:db8::1', 5300, 0, 0) self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, local6, remote6, TEST_DATA, True)