Skip to content

Commit efb76f7

Browse files
committed
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 2229d83 commit efb76f7

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

@@ -631,7 +635,7 @@ def test105_reconnect_restore(self):
631635
bind_dn = 'cn=user1,'+self.server.suffix
632636
l1.simple_bind_s(bind_dn, 'user1_pw')
633637
self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn)
634-
self.server._proc.terminate()
638+
self.server.terminate()
635639
self.server.wait()
636640
try:
637641
l1.whoami_s()
@@ -640,9 +644,78 @@ def test105_reconnect_restore(self):
640644
else:
641645
self.assertEqual(True, False)
642646
finally:
643-
self.server._start_slapd()
647+
self.server.resume()
644648
self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn)
645649

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

647720
@requires_init_fd()
648721
class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy