From 5af8dfb53572604eac62c720405e417cf572532f Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 28 Aug 2015 15:01:58 -0400
Subject: [PATCH] Ticket 47931 - lib389 test - memberof and retrocl deadlock

Description:  lib389 test to verify deadlock is resolved by setting
              the retrocl scope to a single backend.

https://fedorahosted.org/389/ticket/47931

Reviewed by: ?
---
 dirsrvtests/tickets/ticket47931_test.py | 207 ++++++++++++++++++++++++++++++++
 1 file changed, 207 insertions(+)
 create mode 100644 dirsrvtests/tickets/ticket47931_test.py

diff --git a/dirsrvtests/tickets/ticket47931_test.py b/dirsrvtests/tickets/ticket47931_test.py
new file mode 100644
index 0000000..9aa54fc
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47931_test.py
@@ -0,0 +1,207 @@
+import os
+import sys
+import time
+import ldap
+import logging
+import pytest
+import threading
+from lib389 import DirSrv, Entry, tools, tasks
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+from lib389.utils import *
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+installation1_prefix = None
+SECOND_SUFFIX = "dc=deadlock"
+SECOND_BACKEND = "deadlock"
+RETROCL_PLUGIN_DN = ('cn=' + PLUGIN_RETRO_CHANGELOG + ',cn=plugins,cn=config')
+MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
+GROUP_DN = ("cn=group," + DEFAULT_SUFFIX)
+MEMBER_DN_COMP = "uid=member"
+TIME_OUT = 5
+
+
+class TopologyStandalone(object):
+    def __init__(self, standalone):
+        standalone.open()
+        self.standalone = standalone
+
+
+class modifySecondBackendThread(threading.Thread):
+    def __init__(self, inst, timeout):
+        threading.Thread.__init__(self)
+        self.daemon = True
+        self.inst = inst
+        self.timeout = timeout
+
+    def run(self):
+        conn = self.inst.openConnection()
+        conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
+        log.info('Modify second suffix...')
+        for x in range(0, 5000):
+            try:
+                conn.modify_s(SECOND_SUFFIX,
+                              [(ldap.MOD_REPLACE,
+                                'description',
+                                'new description')])
+            except ldap.LDAPError as e:
+                log.fatal('Failed to modify second suffix - error: %s' %
+                          (e.message['desc']))
+                assert False
+
+        conn.close()
+        log.info('Finished modifying second suffix')
+
+
+@pytest.fixture(scope="module")
+def topology(request):
+    global installation1_prefix
+    if installation1_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation1_prefix
+
+    # Creating standalone instance ...
+    standalone = DirSrv(verbose=False)
+    args_instance[SER_HOST] = HOST_STANDALONE
+    args_instance[SER_PORT] = PORT_STANDALONE
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+    args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
+    args_standalone = args_instance.copy()
+    standalone.allocate(args_standalone)
+    instance_standalone = standalone.exists()
+    if instance_standalone:
+        standalone.delete()
+    standalone.create()
+    standalone.open()
+
+    # Delete each instance in the end
+    def fin():
+        standalone.delete()
+    request.addfinalizer(fin)
+
+    # Clear out the tmp dir
+    standalone.clearTmpDir(__file__)
+
+    return TopologyStandalone(standalone)
+
+
+def test_ticket47931(topology):
+    """Test Retro Changelog and MemberOf deadlock fix.
+       Verification steps:
+           - Enable retro cl and memberOf.
+           - Create two backends: A & B.
+           - Configure retrocl scoping for backend A.
+           - Configure memberOf plugin for uniquemember
+           - Create group in backend A.
+           - In parallel, add members to the group on A, and make modifications
+             to entries in backend B.
+           - Make sure the server does not hang during the updates to both
+             backends.
+
+    """
+
+    # Enable dynamic plugins to make plugin configuration easier
+    try:
+        topology.standalone.modify_s(DN_CONFIG,
+                                     [(ldap.MOD_REPLACE,
+                                       'nsslapd-dynamic-plugins',
+                                       'on')])
+    except ldap.LDAPError as e:
+        ldap.error('Failed to enable dynamic plugins! ' + e.message['desc'])
+        assert False
+
+    # Enable the plugins
+    topology.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
+    topology.standalone.plugins.enable(name=PLUGIN_RETRO_CHANGELOG)
+
+    # Create second backend
+    topology.standalone.backend.create(SECOND_SUFFIX, {BACKEND_NAME: SECOND_BACKEND})
+    topology.standalone.mappingtree.create(SECOND_SUFFIX, bename=SECOND_BACKEND)
+
+    # Create the root node of the second backend
+    try:
+        topology.standalone.add_s(Entry((SECOND_SUFFIX,
+                                  {'objectclass': 'top domain'.split(),
+                                   'dc': 'deadlock'})))
+    except ldap.LDAPError as e:
+        log.fatal('Failed to create suffix entry: error ' + e.message['desc'])
+        assert False
+
+    # Configure retrocl scope
+    try:
+        topology.standalone.modify_s(RETROCL_PLUGIN_DN,
+                                     [(ldap.MOD_REPLACE,
+                                       'nsslapd-include-suffix',
+                                       DEFAULT_SUFFIX)])
+    except ldap.LDAPError as e:
+        ldap.error('Failed to configure retrocl plugin: ' + e.message['desc'])
+        assert False
+
+    # Configure memberOf group attribute
+    try:
+        topology.standalone.modify_s(MEMBEROF_PLUGIN_DN,
+                                     [(ldap.MOD_REPLACE,
+                                       'memberofgroupattr',
+                                       'uniquemember')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to configure memberOf plugin: error ' + e.message['desc'])
+        assert False
+
+    # Create group
+    try:
+        topology.standalone.add_s(Entry((GROUP_DN,
+                                         {'objectclass': 'top extensibleObject'.split(),
+                                          'cn': 'group'})))
+    except ldap.LDAPError as e:
+        log.fatal('Failed to add grouo: error ' + e.message['desc'])
+        assert False
+
+    # Create 1500 entries (future members of the group)
+    for idx in range(1, 1500):
+        try:
+            USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
+            topology.standalone.add_s(Entry((USER_DN,
+                                             {'objectclass': 'top extensibleObject'.split(),
+                                              'uid': 'member%d' % (x)})))
+        except ldap.LDAPError as e:
+            log.fatal('Failed to add user (%s): error %s' % (USER_DN, e.message['desc']))
+            assert False
+
+    # Modify second backend (separate thread)
+    mod_backend_thrd = modifySecondBackendThread(topology.standalone, TIME_OUT)
+    mod_backend_thrd.start()
+
+    # Add members to the group - set timeout
+    log.info('Adding members to the group...')
+    topology.standalone.set_option(ldap.OPT_TIMEOUT, TIME_OUT)
+    for idx in range(1, 1500):
+        try:
+            MEMBER_VAL = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
+            topology.standalone.modify_s(GROUP_DN,
+                                         [(ldap.MOD_ADD,
+                                           'uniquemember',
+                                           MEMBER_VAL)])
+        except ldap.TIMEOUT:
+            log.fatal('Deadlock!  Bug verification failed.')
+            assert False
+        except ldap.LDAPError as e:
+            log.fatal('Failed to update group(not a deadlock) member (%s) - error: %s' %
+                      (MEMBER_VAL, e.message['desc']))
+            assert False
+    log.info('Finished adding members to the group.')
+
+    # Wait for the thread to finish
+    mod_backend_thrd.join()
+
+    # No timeout, test passed!
+    log.info('Test complete\n')
+
+
+if __name__ == '__main__':
+    # Run isolated
+    # -s for DEBUG mode
+    CURRENT_FILE = os.path.realpath(__file__)
+    pytest.main("-s %s" % CURRENT_FILE)
\ No newline at end of file
-- 
1.9.3

