Blame SOURCES/0033-Issue-49300-entryUSN-is-duplicated-after-memberOf-op.patch

5d2be4
From 220dbafa048269105b3f7958a5d5bfd1d988da26 Mon Sep 17 00:00:00 2001
5d2be4
From: Simon Pichugin <spichugi@redhat.com>
5d2be4
Date: Tue, 30 Jun 2020 15:39:30 +0200
5d2be4
Subject: [PATCH 8/8] Issue 49300 - entryUSN is duplicated after memberOf
5d2be4
 operation
5d2be4
5d2be4
Bug Description: When we assign a member to a group we have two
5d2be4
oprations - group modification and user modification.
5d2be4
As a result, they both have the same entryUSN because USN Plugin
5d2be4
assigns entryUSN value in bepreop but increments the counter
5d2be4
in the postop and a lot of things can happen in between.
5d2be4
5d2be4
Fix Description: Increment the counter in bepreop together with
5d2be4
entryUSN assignment. Also, decrement the counter in bepostop if
5d2be4
the failuer has happened.
5d2be4
Add test suite to cover the change.
5d2be4
5d2be4
https://pagure.io/389-ds-base/issue/49300
5d2be4
5d2be4
Reviewed by: tbordaz (Thanks!)
5d2be4
---
5d2be4
 .../tests/suites/plugins/entryusn_test.py     | 240 ++++++++++++++++++
5d2be4
 ldap/servers/plugins/usn/usn.c                | 109 ++++----
5d2be4
 ldap/servers/slapd/pblock.c                   |  14 +-
5d2be4
 ldap/servers/slapd/pblock_v3.h                |   1 +
5d2be4
 ldap/servers/slapd/slapi-plugin.h             |   3 +
5d2be4
 5 files changed, 322 insertions(+), 45 deletions(-)
5d2be4
 create mode 100644 dirsrvtests/tests/suites/plugins/entryusn_test.py
5d2be4
5d2be4
diff --git a/dirsrvtests/tests/suites/plugins/entryusn_test.py b/dirsrvtests/tests/suites/plugins/entryusn_test.py
5d2be4
new file mode 100644
5d2be4
index 000000000..721315419
5d2be4
--- /dev/null
5d2be4
+++ b/dirsrvtests/tests/suites/plugins/entryusn_test.py
5d2be4
@@ -0,0 +1,240 @@
5d2be4
+# --- BEGIN COPYRIGHT BLOCK ---
5d2be4
+# Copyright (C) 2020 Red Hat, Inc.
5d2be4
+# All rights reserved.
5d2be4
+#
5d2be4
+# License: GPL (version 3 or any later version).
5d2be4
+# See LICENSE for details.
5d2be4
+# --- END COPYRIGHT BLOCK ---
5d2be4
+#
5d2be4
+import ldap
5d2be4
+import logging
5d2be4
+import pytest
5d2be4
+from lib389._constants import DEFAULT_SUFFIX
5d2be4
+from lib389.config import Config
5d2be4
+from lib389.plugins import USNPlugin, MemberOfPlugin
5d2be4
+from lib389.idm.group import Groups
5d2be4
+from lib389.idm.user import UserAccounts
5d2be4
+from lib389.idm.organizationalunit import OrganizationalUnit
5d2be4
+from lib389.tombstone import Tombstones
5d2be4
+from lib389.rootdse import RootDSE
5d2be4
+from lib389.topologies import topology_st, topology_m2
5d2be4
+
5d2be4
+log = logging.getLogger(__name__)
5d2be4
+
5d2be4
+USER_NUM = 10
5d2be4
+GROUP_NUM = 3
5d2be4
+
5d2be4
+
5d2be4
+def check_entryusn_no_duplicates(entryusn_list):
5d2be4
+    """Check that all values in the list are unique"""
5d2be4
+
5d2be4
+    if len(entryusn_list) > len(set(entryusn_list)):
5d2be4
+        raise AssertionError(f"EntryUSN values have duplicates, please, check logs")
5d2be4
+
5d2be4
+
5d2be4
+def check_lastusn_after_restart(inst):
5d2be4
+    """Check that last usn is the same after restart"""
5d2be4
+
5d2be4
+    root_dse = RootDSE(inst)
5d2be4
+    last_usn_before = root_dse.get_attr_val_int("lastusn;userroot")
5d2be4
+    inst.restart()
5d2be4
+    last_usn_after = root_dse.get_attr_val_int("lastusn;userroot")
5d2be4
+    assert last_usn_after == last_usn_before
5d2be4
+
5d2be4
+
5d2be4
+@pytest.fixture(scope="module")
5d2be4
+def setup(topology_st, request):
5d2be4
+    """
5d2be4
+    Enable USN plug-in
5d2be4
+    Enable MEMBEROF plugin
5d2be4
+    Add test entries
5d2be4
+    """
5d2be4
+
5d2be4
+    inst = topology_st.standalone
5d2be4
+
5d2be4
+    log.info("Enable the USN plugin...")
5d2be4
+    plugin = USNPlugin(inst)
5d2be4
+    plugin.enable()
5d2be4
+
5d2be4
+    log.info("Enable the MEMBEROF plugin...")
5d2be4
+    plugin = MemberOfPlugin(inst)
5d2be4
+    plugin.enable()
5d2be4
+
5d2be4
+    inst.restart()
5d2be4
+
5d2be4
+    users_list = []
5d2be4
+    log.info("Adding test entries...")
5d2be4
+    users = UserAccounts(inst, DEFAULT_SUFFIX)
5d2be4
+    for id in range(USER_NUM):
5d2be4
+        user = users.create_test_user(uid=id)
5d2be4
+        users_list.append(user)
5d2be4
+
5d2be4
+    groups_list = []
5d2be4
+    log.info("Adding test groups...")
5d2be4
+    groups = Groups(inst, DEFAULT_SUFFIX)
5d2be4
+    for id in range(GROUP_NUM):
5d2be4
+        group = groups.create(properties={'cn': f'test_group{id}'})
5d2be4
+        groups_list.append(group)
5d2be4
+
5d2be4
+    def fin():
5d2be4
+        for user in users_list:
5d2be4
+            try:
5d2be4
+                user.delete()
5d2be4
+            except ldap.NO_SUCH_OBJECT:
5d2be4
+                pass
5d2be4
+        for group in groups_list:
5d2be4
+            try:
5d2be4
+                group.delete()
5d2be4
+            except ldap.NO_SUCH_OBJECT:
5d2be4
+                pass
5d2be4
+    request.addfinalizer(fin)
5d2be4
+
5d2be4
+    return {"users": users_list,
5d2be4
+            "groups": groups_list}
5d2be4
+
5d2be4
+
5d2be4
+def test_entryusn_no_duplicates(topology_st, setup):
5d2be4
+    """Verify that entryUSN is not duplicated after memberOf operation
5d2be4
+
5d2be4
+    :id: 1a7d382d-1214-4d56-b9c2-9c4ed57d1683
5d2be4
+    :setup: Standalone instance, Groups and Users, USN and memberOf are enabled
5d2be4
+    :steps:
5d2be4
+        1. Add a member to group 1
5d2be4
+        2. Add a member to group 1 and 2
5d2be4
+        3. Check that entryUSNs are different
5d2be4
+        4. Check that lastusn before and after a restart are the same
5d2be4
+    :expectedresults:
5d2be4
+        1. Success
5d2be4
+        2. Success
5d2be4
+        3. Success
5d2be4
+        4. Success
5d2be4
+    """
5d2be4
+
5d2be4
+    inst = topology_st.standalone
5d2be4
+    config = Config(inst)
5d2be4
+    config.replace('nsslapd-accesslog-level', '260')  # Internal op
5d2be4
+    config.replace('nsslapd-errorlog-level', '65536')
5d2be4
+    config.replace('nsslapd-plugin-logging', 'on')
5d2be4
+    entryusn_list = []
5d2be4
+
5d2be4
+    users = setup["users"]
5d2be4
+    groups = setup["groups"]
5d2be4
+
5d2be4
+    groups[0].replace('member', users[0].dn)
5d2be4
+    entryusn_list.append(users[0].get_attr_val_int('entryusn'))
5d2be4
+    log.info(f"{users[0].dn}_1: {entryusn_list[-1:]}")
5d2be4
+    entryusn_list.append(groups[0].get_attr_val_int('entryusn'))
5d2be4
+    log.info(f"{groups[0].dn}_1: {entryusn_list[-1:]}")
5d2be4
+    check_entryusn_no_duplicates(entryusn_list)
5d2be4
+
5d2be4
+    groups[1].replace('member', [users[0].dn, users[1].dn])
5d2be4
+    entryusn_list.append(users[0].get_attr_val_int('entryusn'))
5d2be4
+    log.info(f"{users[0].dn}_2: {entryusn_list[-1:]}")
5d2be4
+    entryusn_list.append(users[1].get_attr_val_int('entryusn'))
5d2be4
+    log.info(f"{users[1].dn}_2: {entryusn_list[-1:]}")
5d2be4
+    entryusn_list.append(groups[1].get_attr_val_int('entryusn'))
5d2be4
+    log.info(f"{groups[1].dn}_2: {entryusn_list[-1:]}")
5d2be4
+    check_entryusn_no_duplicates(entryusn_list)
5d2be4
+
5d2be4
+    check_lastusn_after_restart(inst)
5d2be4
+
5d2be4
+
5d2be4
+def test_entryusn_is_same_after_failure(topology_st, setup):
5d2be4
+    """Verify that entryUSN is the same after failed operation
5d2be4
+
5d2be4
+    :id: 1f227533-370a-48c1-b920-9b3b0bcfc32e
5d2be4
+    :setup: Standalone instance, Groups and Users, USN and memberOf are enabled
5d2be4
+    :steps:
5d2be4
+        1. Get current group's entryUSN value
5d2be4
+        2. Try to modify the group with an invalid syntax
5d2be4
+        3. Get new group's entryUSN value and compare with old
5d2be4
+        4. Check that lastusn before and after a restart are the same
5d2be4
+    :expectedresults:
5d2be4
+        1. Success
5d2be4
+        2. Invalid Syntax error
5d2be4
+        3. Should be the same
5d2be4
+        4. Success
5d2be4
+    """
5d2be4
+
5d2be4
+    inst = topology_st.standalone
5d2be4
+    users = setup["users"]
5d2be4
+
5d2be4
+    # We need this update so we get the latest USN pointed to our entry
5d2be4
+    users[0].replace('description', 'update')
5d2be4
+
5d2be4
+    entryusn_before = users[0].get_attr_val_int('entryusn')
5d2be4
+    users[0].replace('description', 'update')
5d2be4
+    try:
5d2be4
+        users[0].replace('uid', 'invalid update')
5d2be4
+    except ldap.NOT_ALLOWED_ON_RDN:
5d2be4
+        pass
5d2be4
+    users[0].replace('description', 'second update')
5d2be4
+    entryusn_after = users[0].get_attr_val_int('entryusn')
5d2be4
+
5d2be4
+    # entryUSN should be OLD + 2 (only two user updates)
5d2be4
+    assert entryusn_after == (entryusn_before + 2)
5d2be4
+
5d2be4
+    check_lastusn_after_restart(inst)
5d2be4
+
5d2be4
+
5d2be4
+def test_entryusn_after_repl_delete(topology_m2):
5d2be4
+    """Verify that entryUSN is incremented on 1 after delete operation which creates a tombstone
5d2be4
+
5d2be4
+    :id: 1704cf65-41bc-4347-bdaf-20fc2431b218
5d2be4
+    :setup: An instance with replication, Users, USN enabled
5d2be4
+    :steps:
5d2be4
+        1. Try to delete a user
5d2be4
+        2. Check the tombstone has the incremented USN
5d2be4
+        3. Try to delete ou=People with users
5d2be4
+        4. Check the entry has a not incremented entryUSN
5d2be4
+    :expectedresults:
5d2be4
+        1. Success
5d2be4
+        2. Success
5d2be4
+        3. Should fail with Not Allowed On Non-leaf error
5d2be4
+        4. Success
5d2be4
+    """
5d2be4
+
5d2be4
+    inst = topology_m2.ms["master1"]
5d2be4
+    plugin = USNPlugin(inst)
5d2be4
+    plugin.enable()
5d2be4
+    inst.restart()
5d2be4
+    users = UserAccounts(inst, DEFAULT_SUFFIX)
5d2be4
+
5d2be4
+    try:
5d2be4
+        user_1 = users.create_test_user()
5d2be4
+        user_rdn = user_1.rdn
5d2be4
+        tombstones = Tombstones(inst, DEFAULT_SUFFIX)
5d2be4
+
5d2be4
+        user_1.replace('description', 'update_ts')
5d2be4
+        user_usn = user_1.get_attr_val_int('entryusn')
5d2be4
+
5d2be4
+        user_1.delete()
5d2be4
+
5d2be4
+        ts = tombstones.get(user_rdn)
5d2be4
+        ts_usn = ts.get_attr_val_int('entryusn')
5d2be4
+
5d2be4
+        assert (user_usn + 1) == ts_usn
5d2be4
+
5d2be4
+        user_1 = users.create_test_user()
5d2be4
+        org = OrganizationalUnit(inst, f"ou=People,{DEFAULT_SUFFIX}")
5d2be4
+        org.replace('description', 'update_ts')
5d2be4
+        ou_usn_before = org.get_attr_val_int('entryusn')
5d2be4
+        try:
5d2be4
+            org.delete()
5d2be4
+        except ldap.NOT_ALLOWED_ON_NONLEAF:
5d2be4
+            pass
5d2be4
+        ou_usn_after = org.get_attr_val_int('entryusn')
5d2be4
+        assert ou_usn_before == ou_usn_after
5d2be4
+
5d2be4
+    finally:
5d2be4
+        try:
5d2be4
+            user_1.delete()
5d2be4
+        except ldap.NO_SUCH_OBJECT:
5d2be4
+            pass
5d2be4
+
5d2be4
+
5d2be4
+if __name__ == '__main__':
5d2be4
+    # Run isolated
5d2be4
+    # -s for DEBUG mode
5d2be4
+    CURRENT_FILE = os.path.realpath(__file__)
5d2be4
+    pytest.main("-s %s" % CURRENT_FILE)
5d2be4
diff --git a/ldap/servers/plugins/usn/usn.c b/ldap/servers/plugins/usn/usn.c
5d2be4
index 12ba040c6..f2cc8a62c 100644
5d2be4
--- a/ldap/servers/plugins/usn/usn.c
5d2be4
+++ b/ldap/servers/plugins/usn/usn.c
5d2be4
@@ -333,6 +333,12 @@ _usn_add_next_usn(Slapi_Entry *e, Slapi_Backend *be)
5d2be4
     }
5d2be4
     slapi_ch_free_string(&usn_berval.bv_val);
5d2be4
 
5d2be4
+    /*
5d2be4
+     * increment the counter now and decrement in the bepostop
5d2be4
+     * if the operation will fail
5d2be4
+     */
5d2be4
+    slapi_counter_increment(be->be_usn_counter);
5d2be4
+
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "<-- _usn_add_next_usn\n");
5d2be4
 
5d2be4
@@ -370,6 +376,12 @@ _usn_mod_next_usn(LDAPMod ***mods, Slapi_Backend *be)
5d2be4
 
5d2be4
     *mods = slapi_mods_get_ldapmods_passout(&smods);
5d2be4
 
5d2be4
+    /*
5d2be4
+     * increment the counter now and decrement in the bepostop
5d2be4
+     * if the operation will fail
5d2be4
+     */
5d2be4
+    slapi_counter_increment(be->be_usn_counter);
5d2be4
+
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "<-- _usn_mod_next_usn\n");
5d2be4
     return LDAP_SUCCESS;
5d2be4
@@ -420,6 +432,7 @@ usn_betxnpreop_delete(Slapi_PBlock *pb)
5d2be4
 {
5d2be4
     Slapi_Entry *e = NULL;
5d2be4
     Slapi_Backend *be = NULL;
5d2be4
+    int32_t tombstone_incremented = 0;
5d2be4
     int rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
 
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
@@ -441,7 +454,9 @@ usn_betxnpreop_delete(Slapi_PBlock *pb)
5d2be4
         goto bail;
5d2be4
     }
5d2be4
     _usn_add_next_usn(e, be);
5d2be4
+    tombstone_incremented = 1;
5d2be4
 bail:
5d2be4
+    slapi_pblock_set(pb, SLAPI_USN_INCREMENT_FOR_TOMBSTONE, &tombstone_incremented);
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "<-- usn_betxnpreop_delete\n");
5d2be4
 
5d2be4
@@ -483,7 +498,7 @@ bail:
5d2be4
     return rc;
5d2be4
 }
5d2be4
 
5d2be4
-/* count up the counter */
5d2be4
+/* count down the counter */
5d2be4
 static int
5d2be4
 usn_bepostop(Slapi_PBlock *pb)
5d2be4
 {
5d2be4
@@ -493,25 +508,24 @@ usn_bepostop(Slapi_PBlock *pb)
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "--> usn_bepostop\n");
5d2be4
 
5d2be4
-    /* if op is not successful, don't increment the counter */
5d2be4
+    /* if op is not successful, decrement the counter, else - do nothing */
5d2be4
     slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
     if (LDAP_SUCCESS != rc) {
5d2be4
-        /* no plugin failure */
5d2be4
-        rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
-        goto bail;
5d2be4
-    }
5d2be4
+        slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5d2be4
+        if (NULL == be) {
5d2be4
+            rc = LDAP_PARAM_ERROR;
5d2be4
+            slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
+            rc = SLAPI_PLUGIN_FAILURE;
5d2be4
+            goto bail;
5d2be4
+        }
5d2be4
 
5d2be4
-    slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5d2be4
-    if (NULL == be) {
5d2be4
-        rc = LDAP_PARAM_ERROR;
5d2be4
-        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
-        rc = SLAPI_PLUGIN_FAILURE;
5d2be4
-        goto bail;
5d2be4
+        if (be->be_usn_counter) {
5d2be4
+            slapi_counter_decrement(be->be_usn_counter);
5d2be4
+        }
5d2be4
     }
5d2be4
 
5d2be4
-    if (be->be_usn_counter) {
5d2be4
-        slapi_counter_increment(be->be_usn_counter);
5d2be4
-    }
5d2be4
+    /* no plugin failure */
5d2be4
+    rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
 bail:
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "<-- usn_bepostop\n");
5d2be4
@@ -519,13 +533,14 @@ bail:
5d2be4
     return rc;
5d2be4
 }
5d2be4
 
5d2be4
-/* count up the counter */
5d2be4
+/* count down the counter on a failure and mod ignore */
5d2be4
 static int
5d2be4
 usn_bepostop_modify(Slapi_PBlock *pb)
5d2be4
 {
5d2be4
     int rc = SLAPI_PLUGIN_FAILURE;
5d2be4
     Slapi_Backend *be = NULL;
5d2be4
     LDAPMod **mods = NULL;
5d2be4
+    int32_t do_decrement = 0;
5d2be4
     int i;
5d2be4
 
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
@@ -534,9 +549,7 @@ usn_bepostop_modify(Slapi_PBlock *pb)
5d2be4
     /* if op is not successful, don't increment the counter */
5d2be4
     slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
     if (LDAP_SUCCESS != rc) {
5d2be4
-        /* no plugin failure */
5d2be4
-        rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
-        goto bail;
5d2be4
+        do_decrement = 1;
5d2be4
     }
5d2be4
 
5d2be4
     slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods;;
5d2be4
@@ -545,25 +558,29 @@ usn_bepostop_modify(Slapi_PBlock *pb)
5d2be4
             if (mods[i]->mod_op & LDAP_MOD_IGNORE) {
5d2be4
                 slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                               "usn_bepostop_modify - MOD_IGNORE detected\n");
5d2be4
-                goto bail; /* conflict occurred.
5d2be4
-                              skip incrementing the counter. */
5d2be4
+                do_decrement = 1; /* conflict occurred.
5d2be4
+                                     decrement he counter. */
5d2be4
             } else {
5d2be4
                 break;
5d2be4
             }
5d2be4
         }
5d2be4
     }
5d2be4
 
5d2be4
-    slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5d2be4
-    if (NULL == be) {
5d2be4
-        rc = LDAP_PARAM_ERROR;
5d2be4
-        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
-        rc = SLAPI_PLUGIN_FAILURE;
5d2be4
-        goto bail;
5d2be4
+    if (do_decrement) {
5d2be4
+        slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5d2be4
+        if (NULL == be) {
5d2be4
+            rc = LDAP_PARAM_ERROR;
5d2be4
+            slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
+            rc = SLAPI_PLUGIN_FAILURE;
5d2be4
+            goto bail;
5d2be4
+        }
5d2be4
+        if (be->be_usn_counter) {
5d2be4
+            slapi_counter_decrement(be->be_usn_counter);
5d2be4
+        }
5d2be4
     }
5d2be4
 
5d2be4
-    if (be->be_usn_counter) {
5d2be4
-        slapi_counter_increment(be->be_usn_counter);
5d2be4
-    }
5d2be4
+    /* no plugin failure */
5d2be4
+    rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
 bail:
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "<-- usn_bepostop_modify\n");
5d2be4
@@ -573,34 +590,38 @@ bail:
5d2be4
 
5d2be4
 /* count up the counter */
5d2be4
 /* if the op is delete and the op was not successful, remove preventryusn */
5d2be4
+/* the function is executed on TXN level */
5d2be4
 static int
5d2be4
 usn_bepostop_delete(Slapi_PBlock *pb)
5d2be4
 {
5d2be4
     int rc = SLAPI_PLUGIN_FAILURE;
5d2be4
     Slapi_Backend *be = NULL;
5d2be4
+    int32_t tombstone_incremented = 0;
5d2be4
 
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "--> usn_bepostop_delete\n");
5d2be4
 
5d2be4
-    /* if op is not successful, don't increment the counter */
5d2be4
+    /* if op is not successful and it is a tombstone entry, decrement the counter */
5d2be4
     slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
     if (LDAP_SUCCESS != rc) {
5d2be4
-        /* no plugin failure */
5d2be4
-        rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
-        goto bail;
5d2be4
-    }
5d2be4
+        slapi_pblock_get(pb, SLAPI_USN_INCREMENT_FOR_TOMBSTONE, &tombstone_incremented);
5d2be4
+        if (tombstone_incremented) {
5d2be4
+            slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5d2be4
+            if (NULL == be) {
5d2be4
+                rc = LDAP_PARAM_ERROR;
5d2be4
+                slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
+                rc = SLAPI_PLUGIN_FAILURE;
5d2be4
+                goto bail;
5d2be4
+            }
5d2be4
 
5d2be4
-    slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5d2be4
-    if (NULL == be) {
5d2be4
-        rc = LDAP_PARAM_ERROR;
5d2be4
-        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
5d2be4
-        rc = SLAPI_PLUGIN_FAILURE;
5d2be4
-        goto bail;
5d2be4
+            if (be->be_usn_counter) {
5d2be4
+                slapi_counter_decrement(be->be_usn_counter);
5d2be4
+            }
5d2be4
+        }
5d2be4
     }
5d2be4
 
5d2be4
-    if (be->be_usn_counter) {
5d2be4
-        slapi_counter_increment(be->be_usn_counter);
5d2be4
-    }
5d2be4
+    /* no plugin failure */
5d2be4
+    rc = SLAPI_PLUGIN_SUCCESS;
5d2be4
 bail:
5d2be4
     slapi_log_err(SLAPI_LOG_TRACE, USN_PLUGIN_SUBSYSTEM,
5d2be4
                   "<-- usn_bepostop_delete\n");
5d2be4
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
5d2be4
index cb562e938..454ea9cc3 100644
5d2be4
--- a/ldap/servers/slapd/pblock.c
5d2be4
+++ b/ldap/servers/slapd/pblock.c
5d2be4
@@ -2436,7 +2436,7 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
5d2be4
             (*(char **)value) = NULL;
5d2be4
         }
5d2be4
 	break;
5d2be4
-		
5d2be4
+
5d2be4
     case SLAPI_SEARCH_CTRLS:
5d2be4
         if (pblock->pb_intop != NULL) {
5d2be4
             (*(LDAPControl ***)value) = pblock->pb_intop->pb_search_ctrls;
5d2be4
@@ -2479,6 +2479,14 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
5d2be4
         }
5d2be4
         break;
5d2be4
 
5d2be4
+    case SLAPI_USN_INCREMENT_FOR_TOMBSTONE:
5d2be4
+        if (pblock->pb_intop != NULL) {
5d2be4
+            (*(int32_t *)value) = pblock->pb_intop->pb_usn_tombstone_incremented;
5d2be4
+        } else {
5d2be4
+            (*(int32_t *)value) = 0;
5d2be4
+        }
5d2be4
+        break;
5d2be4
+
5d2be4
     /* ACI Target Check */
5d2be4
     case SLAPI_ACI_TARGET_CHECK:
5d2be4
         if (pblock->pb_misc != NULL) {
5d2be4
@@ -4156,6 +4164,10 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
5d2be4
         pblock->pb_intop->pb_paged_results_cookie = *(int *)value;
5d2be4
         break;
5d2be4
 
5d2be4
+    case SLAPI_USN_INCREMENT_FOR_TOMBSTONE:
5d2be4
+        pblock->pb_intop->pb_usn_tombstone_incremented = *((int32_t *)value);
5d2be4
+        break;
5d2be4
+
5d2be4
     /* ACI Target Check */
5d2be4
     case SLAPI_ACI_TARGET_CHECK:
5d2be4
         _pblock_assert_pb_misc(pblock);
5d2be4
diff --git a/ldap/servers/slapd/pblock_v3.h b/ldap/servers/slapd/pblock_v3.h
5d2be4
index 7ec2f37d6..90498c0b0 100644
5d2be4
--- a/ldap/servers/slapd/pblock_v3.h
5d2be4
+++ b/ldap/servers/slapd/pblock_v3.h
5d2be4
@@ -161,6 +161,7 @@ typedef struct _slapi_pblock_intop
5d2be4
 
5d2be4
     int pb_paged_results_index;  /* stash SLAPI_PAGED_RESULTS_INDEX */
5d2be4
     int pb_paged_results_cookie; /* stash SLAPI_PAGED_RESULTS_COOKIE */
5d2be4
+    int32_t pb_usn_tombstone_incremented; /* stash SLAPI_PAGED_RESULTS_COOKIE */
5d2be4
 } slapi_pblock_intop;
5d2be4
 
5d2be4
 /* Stuff that is rarely used, but still present */
5d2be4
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
5d2be4
index 04c02cf7c..589830bb4 100644
5d2be4
--- a/ldap/servers/slapd/slapi-plugin.h
5d2be4
+++ b/ldap/servers/slapd/slapi-plugin.h
5d2be4
@@ -7483,6 +7483,9 @@ typedef enum _slapi_op_note_t {
5d2be4
 #define SLAPI_PAGED_RESULTS_INDEX  1945
5d2be4
 #define SLAPI_PAGED_RESULTS_COOKIE 1949
5d2be4
 
5d2be4
+/* USN Plugin flag for tombstone entries */
5d2be4
+#define SLAPI_USN_INCREMENT_FOR_TOMBSTONE 1950
5d2be4
+
5d2be4
 /* ACI Target Check */
5d2be4
 #define SLAPI_ACI_TARGET_CHECK 1946
5d2be4
 
5d2be4
-- 
5d2be4
2.26.2
5d2be4