andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone

Blame 0404-Ticket-47619-cannot-reindex-retrochangelog.patch

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