From 99e6707c66d9740ce134df800695f1f6cd3e7ffc Mon Sep 17 00:00:00 2001 From: tbordaz Date: Mon, 2 Dec 2024 17:18:32 +0100 Subject: [PATCH] Issue 6417 - If an entry RDN is identical to the suffix, then Entryrdn gets broken during a reindex (#6418) Bug description: During a reindex, the entryrdn index is built at the end from each entry in the suffix. If one entry has a RDN that is identical to the suffix DN, then entryrdn_lookup_dn may erroneously return the suffix DN as the DN of the entry. Fix description: When the lookup entry has no parent (because index is under work) the loop lookup the entry using the RDN. If this RDN matches the suffix DN, then it exits from the loop with the suffix DN. Before exiting it checks that the original lookup entryID is equal to suffix entryID. If it does not match the function fails and then the DN from the entry will be built from id2enty fixes: #6417 Reviewed by: Pierre Rogier, Simon Pichugin (Thanks !!!) --- .../tests/suites/indexes/entryrdn_test.py | 108 +++++++++++++++++- .../back-ldbm/db-mdb/mdb_import_threads.c | 2 +- ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 11 +- 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/dirsrvtests/tests/suites/indexes/entryrdn_test.py b/dirsrvtests/tests/suites/indexes/entryrdn_test.py index 345955d4da..7669292ab2 100644 --- a/dirsrvtests/tests/suites/indexes/entryrdn_test.py +++ b/dirsrvtests/tests/suites/indexes/entryrdn_test.py @@ -11,11 +11,16 @@ import pytest import ldap import logging +from lib389 import Entry from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX -from lib389.backend import Backends +from lib389.backend import Backends, Backend +from lib389.mappingTree import MappingTrees +from lib389.configurations.sample import create_base_domain +from lib389.idm.domain import Domain from lib389.idm.user import UserAccounts, UserAccount from lib389.idm.organizationalunit import OrganizationalUnits from lib389.topologies import topology_m2 as topo_m2 +from lib389.topologies import topology_st from lib389.agreement import Agreements from lib389.utils import ds_is_older, ensure_bytes from lib389.tasks import Tasks,ExportTask, ImportTask @@ -283,6 +288,107 @@ def test_long_rdn(topo_m2): ou.delete() assert not ou.exists() +def test_entry_rdn_same_as_suffix(topology_st, request): + """ + Test that a reindex is successful even if an entry + has a RDN that is identical to the suffix + + :id: 7f5a38e9-b979-4664-b132-81df0e60f38a + :setup: standalone + :steps: + 1. Create a new backend with suffix 'dc=dup_rdn' (ID 1) + 2. Create a dummy entry 'ou=my_org,dc=dup_rdn' (ID 2) + 3. Create the problematic entry 'dc=dup_rdn,dc=dup_rdn' (ID 3) + 4. Create a dummy entry 'ou=my_org,dc=dup_rdn,dc=dup_rdn' (ID 4) + 5. Do an offline reindex + 6. Check that entryrdn contains the key P3 (parent of ID 3) + 7. Check that error log does not contain 'entryrdn_insert_key - Same DN' + :expectedresults: + 1. Should succeed + 2. Should succeed + 3. Should succeed + 4. Should succeed + 5. Should succeed + 6. Should succeed + 7. Should succeed + """ + inst = topology_st.standalone + + # Create a suffix 'dc=dup_rdn' + be_name = 'domain' + dc_value = 'dup_rdn' + suffix = 'dc=' + dc_value + rdn = 'my_org' + be1 = Backend(inst) + be1.create(properties={ + 'cn': be_name, + 'nsslapd-suffix': suffix, + }, + create_mapping_tree=False + ) + + mts = MappingTrees(inst) + mt = mts.create(properties={ + 'cn': suffix, + 'nsslapd-state': 'backend', + 'nsslapd-backend': be_name, + }) + + # Create the domain entry 'dc=dup_rdn' + create_base_domain(inst, suffix) + + # Create the org ou=my_org,dc=dup_rdn + ous = OrganizationalUnits(inst, suffix) + ou = ous.create(properties={ 'ou': rdn }) + + # when reindexing entryrdn the following entry + # (dc=dup_rdn,dc=dup_rdn) Triggers + # this message. + # This is because its RDN (dc=dup_rdn) is also + # the suffix DN + info_message = 'entryrdn_insert_key - Same DN (dn: %s) is already in the entryrdn file with different' % (ou.dn) + log.info("In case if the bug still exist this line should be in the error log") + log.info(" --> " + info_message) + + # Create the domain entry 'dc=dup_rdn,dc=dup_rdn' + trigger_entry = suffix + "," + suffix + domain = Domain(inst, dn=trigger_entry) + domain.create(properties={ + 'dc': dc_value, + 'description': 'Entry with RDN identical to suffix' + }) + + # Create the org ou=my_org,dc=dup_rdn,dc=dup_rdn + ous = OrganizationalUnits(inst, trigger_entry) + ou = ous.create(properties={ 'ou': rdn }) + + + # Trigger an offline reindex + log.info('Offline reindex, stopping the server') + topology_st.standalone.stop() + + log.info('Reindex all the suffix') + topology_st.standalone.db2index(bename=be_name) + + # make sure the key 'P3' (parent of 'dc=dup_rdn,dc=dup_rdn') exists + dbscanout = topology_st.standalone.dbscan(bename=be_name, index='entryrdn') + log.info(dbscanout) + assert(ensure_bytes('P3') in ensure_bytes(dbscanout)) + + # make sure there is no failure detected/logged in error logs + if topology_st.standalone.get_db_lib() == "mdb": + pattern_str = ".*Inconsistent id2entry database.*" + else: + pattern_str = ".*entryrdn_insert_key - Same DN.*is already in the entryrdn file with different.*$" + assert not topology_st.standalone.ds_error_log.match(pattern_str) + + + def fin(): + topology_st.standalone.restart() + mt.delete() + be1.delete() + + request.addfinalizer(fin) if __name__ == "__main__": # Run isolated diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c index 2d2a4f711e..409c9b96e2 100644 --- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c +++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c @@ -731,7 +731,7 @@ get_entry_type(WorkerQueueData_t *wqelmt, Slapi_DN *sdn) int len = SLAPI_ATTR_UNIQUEID_LENGTH; const char *ndn = slapi_sdn_get_ndn(sdn); - if (slapi_be_issuffix(be, sdn)) { + if (slapi_be_issuffix(be, sdn) && (wqelmt->wait_id == 1)) { return DNRC_SUFFIX; } if (PL_strncasecmp(ndn, SLAPI_ATTR_UNIQUEID, len) || ndn[len] != '=') { diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c index ef79219469..eed88d7f75 100644 --- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c +++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c @@ -1077,7 +1077,16 @@ entryrdn_lookup_dn(backend *be, _ENTRYRDN_DEBUG_GOTO_BAIL(); goto bail; } - maybesuffix = 1; + if (workid == 1) { + /* The loop (workid) iterates from the starting 'id' + * up to the suffix ID (i.e. '1'). + * A corner case (#6417) is if an entry, on the path + * 'id' -> suffix, has the same RDN than the suffix. + * In order to erroneously believe the loop hits the suffix + * we need to check that 'workid' is '1' (suffix) + */ + maybesuffix = 1; + } } else { _entryrdn_cursor_print_error("entryrdn_lookup_dn", key.data, data.size, data.ulen, rc);