andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone
Blob Blame History Raw
From 183b91f804e1487f459c4ddf358447b4c01e311c Mon Sep 17 00:00:00 2001
From: "Thierry bordaz (tbordaz)" <tbordaz@redhat.com>
Date: Wed, 15 Jan 2014 09:59:13 +0100
Subject: [PATCH 404/404] Ticket 47619: cannot reindex retrochangelog

Bug Description:
        An index task (through db2index.pl) on changelog backend may hang.
        The reason is that the retrocl-plugin start function (retrocl_start) acquires the
        changelog backend in read (slapi_be_Rlock) but does not release it.
        The task will try to acquire it in Write but will wait indefinitely.

Fix Description:
        Make retrocl_start to release the lock acquired in Read in slapi_mapping_tree_select

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

Reviewed by: Rich Megginson

Platforms tested: F17

Flag Day: no

Doc impact: no

(cherry picked from commit 8087f058cd9acc304de351412742fd8ceb3ceb48)
(cherry picked from commit a5499cbf60c6b8277f0acffed8763ffe52ae4aff)
---
 dirsrvtests/tickets/ticket47619_test.py | 299 ++++++++++++++++++++++++++++++++
 ldap/servers/plugins/retrocl/retrocl.c  |   6 +-
 2 files changed, 303 insertions(+), 2 deletions(-)
 create mode 100644 dirsrvtests/tickets/ticket47619_test.py

diff --git a/dirsrvtests/tickets/ticket47619_test.py b/dirsrvtests/tickets/ticket47619_test.py
new file mode 100644
index 0000000..c3eae8d
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47619_test.py
@@ -0,0 +1,299 @@
+'''
+Created on Nov 7, 2013
+
+@author: tbordaz
+'''
+import os
+import sys
+import time
+import ldap
+import logging
+import socket
+import time
+import logging
+import pytest
+import re
+from lib389 import DirSrv, Entry, tools
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from constants import *
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+installation_prefix = None
+
+TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
+ENTRY_DN = "cn=test_entry, %s" % SUFFIX
+
+OTHER_NAME = 'other_entry'
+MAX_OTHERS = 100
+
+ATTRIBUTES = [ 'street', 'countryName', 'description', 'postalAddress', 'postalCode', 'title', 'l', 'roomNumber' ]
+
+class TopologyMasterConsumer(object):
+    def __init__(self, master, consumer):
+        master.open()
+        self.master = master
+        
+        consumer.open()
+        self.consumer = consumer
+
+    def __repr__(self):
+            return "Master[%s] -> Consumer[%s" % (self.master, self.consumer)
+
+
+@pytest.fixture(scope="module")
+def topology(request):
+    '''
+        This fixture is used to create a replicated topology for the 'module'.
+        The replicated topology is MASTER -> Consumer.
+        At the beginning, It may exists a master instance and/or a consumer instance.
+        It may also exists a backup for the master and/or the consumer.
+    
+        Principle:
+            If master instance exists:
+                restart it
+            If consumer instance exists:
+                restart it
+            If backup of master AND backup of consumer exists:
+                create or rebind to consumer
+                create or rebind to master
+
+                restore master   from backup
+                restore consumer from backup
+            else:
+                Cleanup everything
+                    remove instances
+                    remove backups
+                Create instances
+                Initialize replication
+                Create backups
+    '''
+    global installation_prefix
+
+    if installation_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation_prefix
+        
+    master   = DirSrv(verbose=False)
+    consumer = DirSrv(verbose=False)
+    
+    # Args for the master instance
+    args_instance[SER_HOST] = HOST_MASTER
+    args_instance[SER_PORT] = PORT_MASTER
+    args_instance[SER_SERVERID_PROP] = SERVERID_MASTER
+    args_master = args_instance.copy()
+    master.allocate(args_master)
+    
+    # Args for the consumer instance
+    args_instance[SER_HOST] = HOST_CONSUMER
+    args_instance[SER_PORT] = PORT_CONSUMER
+    args_instance[SER_SERVERID_PROP] = SERVERID_CONSUMER
+    args_consumer = args_instance.copy()
+    consumer.allocate(args_consumer)
+
+    
+    # Get the status of the backups
+    backup_master   = master.checkBackupFS()
+    backup_consumer = consumer.checkBackupFS()
+    
+    # Get the status of the instance and restart it if it exists
+    instance_master   = master.exists()
+    if instance_master:
+        master.stop(timeout=10)
+        master.start(timeout=10)
+        
+    instance_consumer = consumer.exists()
+    if instance_consumer:
+        consumer.stop(timeout=10)
+        consumer.start(timeout=10)
+    
+    if backup_master and backup_consumer:
+        # The backups exist, assuming they are correct 
+        # we just re-init the instances with them
+        if not instance_master:
+            master.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            master.open()
+        
+        if not instance_consumer:
+            consumer.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            consumer.open()
+        
+        # restore master from backup
+        master.stop(timeout=10)
+        master.restoreFS(backup_master)
+        master.start(timeout=10)
+        
+        # restore consumer from backup
+        consumer.stop(timeout=10)
+        consumer.restoreFS(backup_consumer)
+        consumer.start(timeout=10)
+    else:
+        # We should be here only in two conditions
+        #      - This is the first time a test involve master-consumer
+        #        so we need to create everything
+        #      - Something weird happened (instance/backup destroyed)
+        #        so we discard everything and recreate all
+        
+        # Remove all the backups. So even if we have a specific backup file
+        # (e.g backup_master) we clear all backups that an instance my have created
+        if backup_master:
+            master.clearBackupFS()
+        if backup_consumer:
+            consumer.clearBackupFS()
+        
+        # Remove all the instances
+        if instance_master:
+            master.delete()
+        if instance_consumer:
+            consumer.delete()
+                        
+        # Create the instances
+        master.create()
+        master.open()
+        consumer.create()
+        consumer.open()
+    
+        # 
+        # Now prepare the Master-Consumer topology
+        #
+        # First Enable replication
+        master.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER)
+        consumer.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_CONSUMER)
+        
+        # Initialize the supplier->consumer
+        
+        properties = {RA_NAME:      r'meTo_$host:$port',
+                      RA_BINDDN:    defaultProperties[REPLICATION_BIND_DN],
+                      RA_BINDPW:    defaultProperties[REPLICATION_BIND_PW],
+                      RA_METHOD:    defaultProperties[REPLICATION_BIND_METHOD],
+                      RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
+        repl_agreement = master.agreement.create(suffix=SUFFIX, host=consumer.host, port=consumer.port, properties=properties)
+    
+        if not repl_agreement:
+            log.fatal("Fail to create a replica agreement")
+            sys.exit(1)
+            
+        log.debug("%s created" % repl_agreement)
+        master.agreement.init(SUFFIX, HOST_CONSUMER, PORT_CONSUMER)
+        master.waitForReplInit(repl_agreement)
+        
+        # Check replication is working fine
+        master.add_s(Entry((TEST_REPL_DN, {
+                                                'objectclass': "top person".split(),
+                                                'sn': 'test_repl',
+                                                'cn': 'test_repl'})))
+        loop = 0
+        while loop <= 10:
+            try:
+                ent = consumer.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+                break
+            except ldap.NO_SUCH_OBJECT:
+                time.sleep(1)
+                loop += 1
+                
+        # Time to create the backups
+        master.stop(timeout=10)
+        master.backupfile = master.backupFS()
+        master.start(timeout=10)
+        
+        consumer.stop(timeout=10)
+        consumer.backupfile = consumer.backupFS()
+        consumer.start(timeout=10)
+    
+    # 
+    # Here we have two instances master and consumer
+    # with replication working. Either coming from a backup recovery
+    # or from a fresh (re)init
+    # Time to return the topology
+    return TopologyMasterConsumer(master, consumer)
+
+
+def test_ticket47619_init(topology):
+    """ 
+        Initialize the test environment
+    """
+    topology.master.plugins.enable(name=PLUGIN_RETRO_CHANGELOG)
+    #topology.master.plugins.enable(name=PLUGIN_MEMBER_OF)
+    #topology.master.plugins.enable(name=PLUGIN_REFER_INTEGRITY)
+    topology.master.stop(timeout=10)
+    topology.master.start(timeout=10)
+    
+    topology.master.log.info("test_ticket47619_init topology %r" % (topology))
+    # the test case will check if a warning message is logged in the 
+    # error log of the supplier
+    topology.master.errorlog_file = open(topology.master.errlog, "r")
+    
+    
+    # add dummy entries
+    for cpt in range(MAX_OTHERS):
+        name = "%s%d" % (OTHER_NAME, cpt)
+        topology.master.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
+                                            'objectclass': "top person".split(),
+                                            'sn': name,
+                                            'cn': name})))
+        
+    topology.master.log.info("test_ticket47619_init: %d entries ADDed %s[0..%d]" % (MAX_OTHERS, OTHER_NAME, MAX_OTHERS-1))
+    
+    # Check the number of entries in the retro changelog
+    time.sleep(2)
+    ents = topology.master.search_s(RETROCL_SUFFIX, ldap.SCOPE_ONELEVEL, "(objectclass=*)")
+    assert len(ents) == MAX_OTHERS
+
+def test_ticket47619_create_index(topology):
+    
+    args = {INDEX_TYPE: 'eq'}
+    for attr in ATTRIBUTES:
+        topology.master.index.create(suffix=RETROCL_SUFFIX, attr=attr, args=args)
+
+def test_ticket47619_reindex(topology):
+    '''
+    Reindex all the attributes in ATTRIBUTES
+    '''
+    args = {TASK_WAIT: True,
+            TASK_TIMEOUT: 10}
+    for attr in ATTRIBUTES:
+        rc = topology.master.tasks.reindex(suffix=RETROCL_SUFFIX, attrname=attr, args=args)
+        assert rc == 0
+
+def test_ticket47619_check_indexed_search(topology):
+    for attr in ATTRIBUTES:
+        ents = topology.master.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, "(%s=hello)" % attr)
+        assert len(ents) == 0
+    
+def test_ticket47619_final(topology):
+    topology.master.stop(timeout=10)
+    topology.consumer.stop(timeout=10)
+
+def run_isolated():
+    '''
+        run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
+        To run isolated without py.test, you need to 
+            - edit this file and comment '@pytest.fixture' line before 'topology' function.
+            - set the installation prefix
+            - run this program
+    '''
+    global installation_prefix
+    installation_prefix =  None
+        
+    topo = topology(True)
+    test_ticket47619_init(topo)
+    
+    test_ticket47619_create_index(topo)
+    
+    # important restart that trigger the hang
+    # at restart, finding the new 'changelog' backend, the backend is acquired in Read
+    # preventing the reindex task to complete
+    topo.master.restart(timeout=10)
+    test_ticket47619_reindex(topo)
+    test_ticket47619_check_indexed_search(topo)
+
+    test_ticket47619_final(topo)
+
+
+if __name__ == '__main__':
+    run_isolated()
+
diff --git a/ldap/servers/plugins/retrocl/retrocl.c b/ldap/servers/plugins/retrocl/retrocl.c
index 08484c7..6ed9fe9 100644
--- a/ldap/servers/plugins/retrocl/retrocl.c
+++ b/ldap/servers/plugins/retrocl/retrocl.c
@@ -239,8 +239,6 @@ static int retrocl_select_backend(void)
     err = slapi_mapping_tree_select(pb,&be,&referral,errbuf);
 	slapi_entry_free(referral);
 
-    operation_free(&op,NULL);
-
     if (err != LDAP_SUCCESS || be == NULL || be == defbackend_get_backend()) {
         LDAPDebug2Args(LDAP_DEBUG_TRACE,"Mapping tree select failed (%d) %s.\n",
 		  err,errbuf);
@@ -257,6 +255,10 @@ static int retrocl_select_backend(void)
     }
 
     retrocl_create_cle();
+    slapi_pblock_destroy(pb);
+    
+    if (be)
+            slapi_be_Unlock(be);
 
     return retrocl_get_changenumbers();
 }
-- 
2.4.11