Skip to content

Commit 898e3c0

Browse files
spaceonedroideck
authored andcommitted
test: Implement test cases for reconnection handling
test_106_reconnect_restore() handles a SERVER_DOWN exception manually and tries to re-use the connection afterwards again. This established the connection again but did not bind(), so it now raises ldap.INSUFFICIENT_ACCESS. test_107_reconnect_restore() restarts the LDAP server during searches, which causes a UNAVAILABLE exception.
1 parent 1d978c6 commit 898e3c0

File tree

2 files changed

+84
-3
lines changed

2 files changed

+84
-3
lines changed

Lib/slapdtest/_slapdtest.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,16 @@ def restart(self):
467467
"""
468468
Restarts the slapd server with same data
469469
"""
470-
self._proc.terminate()
470+
self.terminate()
471471
self.wait()
472+
self.resume()
473+
474+
def terminate(self):
475+
"""Terminate slapd server"""
476+
self._proc.terminate()
477+
478+
def resume(self):
479+
"""Start slapd server"""
472480
self._start_slapd()
473481

474482
def wait(self):

Tests/t_ldapobject.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
import os
1010
import re
1111
import socket
12+
import threading
13+
import time
14+
import traceback
1215
import unittest
1316
import pickle
1417

18+
1519
# Switch off processing .ldaprc or ldap.conf before importing _ldap
1620
os.environ['LDAPNOINIT'] = '1'
1721

@@ -639,7 +643,7 @@ def test105_reconnect_restore(self):
639643
bind_dn = 'cn=user1,'+self.server.suffix
640644
l1.simple_bind_s(bind_dn, 'user1_pw')
641645
self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn)
642-
self.server._proc.terminate()
646+
self.server.terminate()
643647
self.server.wait()
644648
try:
645649
l1.whoami_s()
@@ -648,9 +652,78 @@ def test105_reconnect_restore(self):
648652
else:
649653
self.assertEqual(True, False)
650654
finally:
651-
self.server._start_slapd()
655+
self.server.resume()
652656
self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn)
653657

658+
def test106_reconnect_restore(self):
659+
"""
660+
The idea of this test is to stop the LDAP server, make a search and ignore the `SERVER_DOWN` exception which happens after the reconnect timeout
661+
and then re-use the same connection when the LDAP server is available again.
662+
After starting the server the LDAP connection can be re-used again as it will reconnect on the next operation.
663+
Prior to fixing PR !267 the connection was reestablished but no `bind()` was done resulting in a anonymous search which caused `INSUFFICIENT_ACCESS` when anonymous seach is disallowed.
664+
"""
665+
lo = self.ldap_object_class(self.server.ldap_uri, retry_max=2, retry_delay=1)
666+
bind_dn = 'cn=user1,' + self.server.suffix
667+
lo.simple_bind_s(bind_dn, 'user1_pw')
668+
669+
dn = lo.whoami_s()[3:]
670+
671+
self.server.terminate()
672+
self.server.wait()
673+
674+
# do a search, wait for the timeout, ignore SERVER_DOWN
675+
with self.assertRaises(ldap.SERVER_DOWN):
676+
lo.search_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')
677+
678+
self.server.resume()
679+
680+
# try to use the connection again
681+
lo.search_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')
682+
683+
def test107_reconnect_restore(self):
684+
"""
685+
The idea of this test is to restart the LDAP-Server while there are ongoing searches.
686+
This causes :class:`ldap.UNAVAILABLE` to be raised (with |OpenLDAP|) for a short time.
687+
To increase the chance of triggering this bug we are starting multiple threads
688+
with a large number of retry attempts in a short amount of time.
689+
"""
690+
excs = []
691+
thread_count = 10
692+
run_time = 10.0
693+
start_barrier = threading.Barrier(thread_count + 1) # +1 for the main thread
694+
695+
def _reconnect_search_thread():
696+
lo = self.ldap_object_class(self.server.ldap_uri)
697+
bind_dn = 'cn=user1,' + self.server.suffix
698+
lo.simple_bind_s(bind_dn, 'user1_pw')
699+
lo._retry_max = 10E4
700+
lo._retry_delay = 0.001
701+
lo.search_ext_s(self.server.suffix, ldap.SCOPE_SUBTREE, "cn=user1", attrlist=["cn"])
702+
start_barrier.wait()
703+
end_time = time.time() + run_time
704+
while time.time() < end_time:
705+
lo.search_ext_s(self.server.suffix, ldap.SCOPE_SUBTREE, filterstr="cn=user1", attrlist=["cn"])
706+
707+
def reconnect_search_thread():
708+
try:
709+
_reconnect_search_thread()
710+
except Exception as exc:
711+
excs.append((str(exc), traceback.format_exc()))
712+
713+
threads = [threading.Thread(target=reconnect_search_thread) for _ in range(thread_count)]
714+
for t in threads:
715+
t.start()
716+
717+
start_barrier.wait() # wait until all threads are ready to start
718+
self.server.restart() # restart after all threads have started their search loop
719+
720+
for t in threads:
721+
t.join()
722+
723+
for exc, tb in excs[:5]:
724+
print('Exception occurred', exc, tb)
725+
self.assertEqual(excs, [])
726+
654727

655728
@requires_init_fd()
656729
class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject):

0 commit comments

Comments
 (0)