dirsrvtests/tests/tickets/ticket48759_test.py | 285 ++++++++++++++++++++++++++
ldap/servers/slapd/back-ldbm/ldbm_delete.c | 44 ++--
2 files changed, 309 insertions(+), 20 deletions(-)
New commits:
commit 5d975ea1a14b9eaf31eea8032e911c68c047965b
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
Date: Tue Mar 15 12:18:22 2016 +0100
add testcase for ticket 48759
Fixes for ticket 47859 reviewed by Noriko, thanks
diff --git a/dirsrvtests/tests/tickets/ticket48759_test.py
b/dirsrvtests/tests/tickets/ticket48759_test.py
new file mode 100644
index 0000000..93c3223
--- /dev/null
+++ b/dirsrvtests/tests/tickets/ticket48759_test.py
@@ -0,0 +1,285 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2015 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+import os
+import sys
+import time
+import ldap
+import logging
+import pytest
+from lib389 import DirSrv, Entry, tools, tasks
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+
+log = logging.getLogger(__name__)
+
+installation_prefix = None
+
+MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF +
',cn=plugins,cn=config')
+GROUP_DN = ("cn=group," + DEFAULT_SUFFIX)
+MEMBER_DN_COMP = "uid=member"
+
+class TopologyStandalone(object):
+ def __init__(self, standalone):
+ standalone.open()
+ self.standalone = standalone
+
+
+(a)pytest.fixture(scope="module")
+def topology(request):
+ '''
+ This fixture is used to standalone topology for the 'module'.
+ '''
+ global installation_prefix
+
+ if installation_prefix:
+ args_instance[SER_DEPLOYED_DIR] = installation_prefix
+
+ standalone = DirSrv(verbose=False)
+
+ # Args for the standalone instance
+ args_instance[SER_HOST] = HOST_STANDALONE
+ args_instance[SER_PORT] = PORT_STANDALONE
+ args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+ args_standalone = args_instance.copy()
+ standalone.allocate(args_standalone)
+
+ # Get the status of the instance and restart it if it exists
+ instance_standalone = standalone.exists()
+
+ # Remove the instance
+ if instance_standalone:
+ standalone.delete()
+
+ # Create the instance
+ standalone.create()
+
+ # Used to retrieve configuration information (dbdir, confdir...)
+ standalone.open()
+
+ # clear the tmp directory
+ standalone.clearTmpDir(__file__)
+
+ # Here we have standalone instance up and running
+ return TopologyStandalone(standalone)
+
+def _add_group_with_members(topology):
+ # Create group
+ try:
+ topology.standalone.add_s(Entry((GROUP_DN,
+ {'objectclass': 'top
groupofnames'.split(),
+ 'cn': 'group'})))
+ except ldap.LDAPError as e:
+ log.fatal('Failed to add group: error ' + e.message['desc'])
+ assert False
+
+ # Add members to the group - set timeout
+ log.info('Adding members to the group...')
+ for idx in range(1, 5):
+ try:
+ MEMBER_VAL = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
+ topology.standalone.modify_s(GROUP_DN,
+ [(ldap.MOD_ADD,
+ 'member',
+ MEMBER_VAL)])
+ except ldap.LDAPError as e:
+ log.fatal('Failed to update group: member (%s) - error: %s' %
+ (MEMBER_VAL, e.message['desc']))
+ assert False
+
+def _find_retrocl_changes(topology, user_dn=None):
+ ents = topology.standalone.search_s('cn=changelog', ldap.SCOPE_SUBTREE,
'(targetDn=%s)' %user_dn)
+ return len(ents)
+
+def _find_memberof(topology, user_dn=None, group_dn=None, find_result=True):
+ ent = topology.standalone.getEntry(user_dn, ldap.SCOPE_BASE,
"(objectclass=*)", ['memberof'])
+ found = False
+ if ent.hasAttr('memberof'):
+
+ for val in ent.getValues('memberof'):
+ topology.standalone.log.info("!!!!!!! %s: memberof->%s" %
(user_dn, val))
+ if val == group_dn:
+ found = True
+ break
+
+ if find_result:
+ assert(found)
+ else:
+ assert(not found)
+
+def test_ticket48759(topology):
+ """
+ The fix for ticket 48759 has to prevent plugin calls for tombstone purging
+
+ The test uses the memberof and retrocl plugins to verify this.
+ In tombstone purging without the fix the mmeberof plugin is called,
+ if the tombstone entry is a group,
+ it modifies the user entries for the group
+ and if retrocl is enabled this mod is written to the retrocl
+
+ The test sequence is:
+ - enable replication
+ - enable memberof and retro cl plugin
+ - add user entries
+ - add a group and add the users as members
+ - verify memberof is set to users
+ - delete the group
+ - verify memberof is removed from users
+ - add group again
+ - verify memberof is set to users
+ - get number of changes in retro cl for one user
+ - configure tombstone purging
+ - wait for purge interval to pass
+ - add a dummy entry to increase maxcsn
+ - wait for purge interval to pass two times
+ - get number of changes in retro cl for user again
+ - assert there was no additional change
+ """
+
+ log.info('Testing Ticket 48759 - no plugin calls for tombstone purging')
+
+ #
+ # Setup Replication
+ #
+ log.info('Setting up replication...')
+ topology.standalone.replica.enableReplication(suffix=DEFAULT_SUFFIX,
role=REPLICAROLE_MASTER,
+ replicaId=REPLICAID_MASTER_1)
+
+ #
+ # enable dynamic plugins, memberof and retro cl plugin
+ #
+ log.info('Enable plugins...')
+ 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
+
+ topology.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
+ topology.standalone.plugins.enable(name=PLUGIN_RETRO_CHANGELOG)
+ # Configure memberOf group attribute
+ try:
+ topology.standalone.modify_s(MEMBEROF_PLUGIN_DN,
+ [(ldap.MOD_REPLACE,
+ 'memberofgroupattr',
+ 'member')])
+ except ldap.LDAPError as e:
+ log.fatal('Failed to configure memberOf plugin: error ' +
e.message['desc'])
+ assert False
+
+
+ #
+ # create some users and a group
+ #
+ log.info('create users and group...')
+ for idx in range(1, 5):
+ try:
+ USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
+ topology.standalone.add_s(Entry((USER_DN,
+ {'objectclass': 'top
extensibleObject'.split(),
+ 'uid': 'member%d' %
(idx)})))
+ except ldap.LDAPError as e:
+ log.fatal('Failed to add user (%s): error %s' % (USER_DN,
e.message['desc']))
+ assert False
+
+ _add_group_with_members(topology)
+
+ MEMBER_VAL = ("uid=member2,%s" % DEFAULT_SUFFIX)
+ time.sleep(1)
+ _find_memberof(topology, MEMBER_VAL, GROUP_DN, True)
+
+ # delete group
+ log.info('delete group...')
+ try:
+ topology.standalone.delete_s(GROUP_DN)
+ except ldap.LDAPError as e:
+ log.error('Failed to delete entry: ' + e.message['desc'])
+ assert False
+
+ time.sleep(1)
+ _find_memberof(topology, MEMBER_VAL, GROUP_DN, False)
+
+ # add group again
+ log.info('add group again')
+ _add_group_with_members(topology)
+ time.sleep(1)
+ _find_memberof(topology, MEMBER_VAL, GROUP_DN, True)
+
+ #
+ # get number of changelog records for one user entry
+ log.info('get number of changes for %s before tombstone purging' %
MEMBER_VAL)
+ changes_pre = _find_retrocl_changes(topology, MEMBER_VAL)
+
+ # configure tombstone purging
+ args = {REPLICA_PRECISE_PURGING: 'on',
+ REPLICA_PURGE_DELAY: '5',
+ REPLICA_PURGE_INTERVAL: '5'}
+ try:
+ topology.standalone.replica.setProperties(DEFAULT_SUFFIX, None, None, args)
+ except:
+ log.fatal('Failed to configure replica')
+ assert False
+
+ # Wait for the interval to pass
+ log.info('Wait for tombstone purge interval to pass ...')
+ time.sleep(6)
+
+ # Add an entry to trigger replication
+ log.info('add dummy entry')
+ try:
+ topology.standalone.add_s(Entry(('cn=test_entry,dc=example,dc=com', {
+ 'objectclass': 'top person'.split(),
+ 'sn': 'user',
+ 'cn': 'entry1'})))
+ except ldap.LDAPError as e:
+ log.error('Failed to add entry: ' + e.message['desc'])
+ assert False
+
+ # check memberof is still correct
+ time.sleep(1)
+ _find_memberof(topology, MEMBER_VAL, GROUP_DN, True)
+
+ # Wait for the interval to pass again
+ log.info('Wait for tombstone purge interval to pass again...')
+ time.sleep(10)
+
+ #
+ # get number of changelog records for one user entry
+ log.info('get number of changes for %s before tombstone purging' %
MEMBER_VAL)
+ changes_post = _find_retrocl_changes(topology, MEMBER_VAL)
+
+ assert (changes_pre == changes_post)
+
+
+def test_ticket48759_final(topology):
+ topology.standalone.delete()
+ log.info('Testcase PASSED')
+
+
+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 '(a)pytest.fixture' line before
'topology' function.
+ - set the installation prefix
+ - run this program
+ '''
+ global installation_prefix
+ installation_prefix = None
+
+ topo = topology(True)
+ test_ticket48759(topo)
+ test_ticket48759_final(topo)
+
+if __name__ == '__main__':
+ run_isolated()
commit 93f7d8222b52e671ea91ca4e46c8a35a2acabb65
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
Date: Tue Mar 15 12:10:21 2016 +0100
Ticket 48759 - no plugin calls in tombstone purging
Bug Description: BE_TXN plugins are called for tombstone purging
Fix Description: check in delete operation if the deleted enntry is
a tombstone, don't call plugins for tombstones
https://fedorahosted.org/389/ticket/48759
Reviewed by: ?
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
index 100a71d..5b24af2 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
@@ -346,24 +346,26 @@ ldbm_back_delete( Slapi_PBlock *pb )
if for some reason they ever do, do not use e->ep_entry since
it could be in the cache and referenced by other search threads -
instead, have them modify a copy of the entry */
- retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN);
- if (retval) {
- LDAPDebug1Arg( LDAP_DEBUG_TRACE,
- "SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN plugin "
- "returned error code %d\n", retval );
- if (!ldap_result_code) {
- slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
- }
- if (!opreturn) {
- slapi_pblock_get( pb, SLAPI_PLUGIN_OPRETURN, &opreturn );
- }
- if (!opreturn) {
- slapi_pblock_set( pb, SLAPI_PLUGIN_OPRETURN,
- ldap_result_code ?
- &ldap_result_code : &retval );
+ if (!delete_tombstone_entry) {
+ retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN);
+ if (retval) {
+ LDAPDebug1Arg( LDAP_DEBUG_TRACE,
+ "SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN plugin "
+ "returned error code %d\n", retval );
+ if (!ldap_result_code) {
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+ }
+ if (!opreturn) {
+ slapi_pblock_get( pb, SLAPI_PLUGIN_OPRETURN, &opreturn );
+ }
+ if (!opreturn) {
+ slapi_pblock_set( pb, SLAPI_PLUGIN_OPRETURN,
+ ldap_result_code ?
+ &ldap_result_code : &retval );
+ }
+ slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message);
+ goto error_return;
}
- slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message);
- goto error_return;
}
/*
@@ -640,7 +642,7 @@ ldbm_back_delete( Slapi_PBlock *pb )
}
}
} /* if (0 == retry_count) just once */
- else {
+ else if (!delete_tombstone_entry) {
/* call the transaction pre delete plugins not just once
* but every time transaction is restarted. */
/* these should not need to modify the entry to be deleted -
@@ -1209,7 +1211,8 @@ ldbm_back_delete( Slapi_PBlock *pb )
}
/* call the transaction post delete plugins just before the commit */
- if (plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN)) {
+ if (!delete_tombstone_entry &&
+ plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN)) {
LDAPDebug0Args( LDAP_DEBUG_TRACE, "SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN plugin
"
"returned error code\n" );
if (!ldap_result_code) {
@@ -1361,7 +1364,8 @@ error_return:
}
/* call the transaction post delete plugins just before the abort */
- if (plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN)) {
+ if (!delete_tombstone_entry &&
+ plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN)) {
LDAPDebug1Arg( LDAP_DEBUG_TRACE, "SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN plugin
"
"returned error code %d\n", retval );
if (!ldap_result_code) {