import OL 389-ds-base-1.4.3.39-22.module+el8.10.0+90818+58a94ceb
This commit is contained in:
parent
c56f2c1841
commit
f72d3a883e
@ -0,0 +1,91 @@
|
||||
From c160ef5a53f0ae9725790ebcd2667dcda2089f14 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 28 Oct 2025 10:49:18 -0400
|
||||
Subject: [PATCH 1/2] Issue 7071 - search filter (&(cn:dn:=groups)) no longer
|
||||
returns results
|
||||
|
||||
Description:
|
||||
|
||||
When processing an "and" filter and it only contains one filter component then
|
||||
the logic in the code breaks down and the filter is seen as not matching.
|
||||
|
||||
The logic breaks down because we are not setting "nomatch" after the access
|
||||
check is successful. If there are two components then it works fine
|
||||
because we do the access check on the first filter component and set that
|
||||
the access check was done(access_check_done), but "nomatch" is not set yet.
|
||||
So when the next filter component is checked for access we see that the access
|
||||
check was done and then we set "nomatch".
|
||||
|
||||
To recap we always need to set "nomatch" when the access check is successful
|
||||
in order to handle the case where an "and" fitler only has one component.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7071
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
.../tests/suites/filter/complex_filters_test.py | 17 ++++++++++++++---
|
||||
ldap/servers/slapd/filterentry.c | 7 ++++++-
|
||||
2 files changed, 20 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/filter/complex_filters_test.py b/dirsrvtests/tests/suites/filter/complex_filters_test.py
|
||||
index 62be27381..3180ab229 100644
|
||||
--- a/dirsrvtests/tests/suites/filter/complex_filters_test.py
|
||||
+++ b/dirsrvtests/tests/suites/filter/complex_filters_test.py
|
||||
@@ -25,8 +25,15 @@ AND_FILTERS = [("(&(uid=uid1)(sn=last1)(givenname=first1))", 1),
|
||||
("(&(uid=*)(&(sn=last3)(givenname=*)))", 1),
|
||||
("(&(uid=uid5)(&(&(sn=*))(&(givenname=*))))", 1),
|
||||
("(&(objectclass=*)(uid=*)(sn=last*))", 5),
|
||||
- ("(&(objectclass=*)(uid=*)(sn=last1))", 1)]
|
||||
-
|
||||
+ ("(&(objectclass=*)(uid=*)(sn=last1))", 1),
|
||||
+ ("(&(sn:dn:=last1))", 1),
|
||||
+ ("(&(sn:dn:=last1)(givenname:dn:=first1))", 1),
|
||||
+ ("(&(sn:dn:=last1)(givenname=first1))", 1),
|
||||
+ ("(&(sn=last1)(givenname=first1)(uid:dn:=uid1))", 1),
|
||||
+ ("(&(uid:dn:=uid1))", 1),
|
||||
+ ("(&(uid:dn:=uid1)(cn:dn:=full1))", 1),
|
||||
+ ("(&(uid:dn:=uid1)(givenname:dn:=first1))", 1),
|
||||
+ ("(&(uid:dn:=uid1)(givenname:dn:=first1)(cn:dn:=full1))", 1)]
|
||||
OR_FILTERS = [("(|(uid=uid1)(sn=last1)(givenname=first1))", 1),
|
||||
("(|(uid=uid1)(|(sn=last1)(givenname=first1)))", 1),
|
||||
("(|(uid=uid1)(|(|(sn=last1))(|(givenname=first1))))", 1),
|
||||
@@ -51,7 +58,11 @@ ZERO_AND_FILTERS = [("(&(uid=uid1)(sn=last1)(givenname=NULL))", 0),
|
||||
("(&(uid=uid1)(&(sn=last1)(givenname=NULL)))", 0),
|
||||
("(&(uid=uid1)(&(&(sn=last1))(&(givenname=NULL))))", 0),
|
||||
("(&(uid=uid1)(&(&(sn=last1))(&(givenname=NULL)(sn=*)))(|(sn=NULL)))", 0),
|
||||
- ("(&(uid=uid1)(&(&(sn=last*))(&(givenname=first*)))(&(sn=NULL)))", 0)]
|
||||
+ ("(&(uid=uid1)(&(&(sn=last*))(&(givenname=first*)))(&(sn=NULL)))", 0),
|
||||
+ ("(&(uid:dn:=not_uid))", 0),
|
||||
+ ("(&(uid:dn:=not_uid)(cn:dn:=full1))", 0),
|
||||
+ ("(&(uid:dn:=uid1)(givenname:dn:=not_first1))", 0),
|
||||
+ ("(&(uid:dn:=uid1)(givenname:dn:=first1)(cn:dn:=not_full1))", 0)]
|
||||
|
||||
ZERO_OR_FILTERS = [("(|(uid=NULL)(sn=NULL)(givenname=NULL))", 0),
|
||||
("(|(uid=NULL)(|(sn=NULL)(givenname=NULL)))", 0),
|
||||
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
|
||||
index cae5c7edc..1c513fe62 100644
|
||||
--- a/ldap/servers/slapd/filterentry.c
|
||||
+++ b/ldap/servers/slapd/filterentry.c
|
||||
@@ -1011,13 +1011,18 @@ vattr_test_filter_list_and(
|
||||
nomatch = -1;
|
||||
break;
|
||||
} else {
|
||||
+ /* We have a match, but we need to check access */
|
||||
if (!verify_access || (*access_check_done)) {
|
||||
nomatch = 0;
|
||||
} else {
|
||||
/* check access */
|
||||
rc = slapi_vattr_filter_test_ext_internal(pb, e, f, verify_access, 1, access_check_done);
|
||||
- if (rc)
|
||||
+ if (rc) {
|
||||
undefined = rc;
|
||||
+ } else {
|
||||
+ /* Access is good so mark this as a match */
|
||||
+ nomatch = 0;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,235 @@
|
||||
From 05ce84ae76e0e71381bcc7b8491a74e7edcd888f Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 20 Jan 2026 09:52:47 +0100
|
||||
Subject: [PATCH 2/2] Issue 7189 - DSBLE0007 generates incorrect remediation
|
||||
commands for scan limits
|
||||
|
||||
Bug Description:
|
||||
|
||||
The generated dsconf commands for fixing missing system indexes had two issues:
|
||||
|
||||
1. The --add-scanlimit value was not quoted, causing the shell to interpret
|
||||
"limit=5000 type=eq flags=AND" as multiple arguments instead of a single
|
||||
value, resulting in "unrecognized arguments: type=eq flags=AND" error.
|
||||
|
||||
2. When both matching rule and scanlimit were missing, two separate commands
|
||||
were generated where the second would fail because the matching rule was
|
||||
already added by the first command.
|
||||
|
||||
Fix Description:
|
||||
|
||||
1. Quote the scanlimit value in all remediation commands
|
||||
|
||||
2. Combine matching rule and scanlimit fixes into a single command when
|
||||
both are missing for the same index instead of expected_scanlimit)
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7189
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 126 ++++++++++++++++++
|
||||
src/lib389/lib389/backend.py | 39 +++---
|
||||
2 files changed, 147 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 1700ba207..f25de4214 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -406,6 +406,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
+def test_missing_scanlimit(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
|
||||
+
|
||||
+ :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove nsIndexIDListScanLimit from parentId index
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Verify the remediation command has properly quoted scanlimit
|
||||
+ 6. Re-add the scanlimit
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 code and related details
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. The scanlimit value is quoted in the remediation command
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove nsIndexIDListScanLimit from parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+
|
||||
+ # Verify the remediation command has properly quoted scanlimit
|
||||
+ args = FakeArgs()
|
||||
+ args.instance = standalone.serverid
|
||||
+ args.verbose = standalone.verbose
|
||||
+ args.list_errors = False
|
||||
+ args.list_checks = False
|
||||
+ args.exclude_check = []
|
||||
+ args.check = ["backends"]
|
||||
+ args.dry_run = False
|
||||
+ args.json = False
|
||||
+ health_check_run(standalone, topology_st.logcap.log, args)
|
||||
+ # Check that the scanlimit is quoted in the output
|
||||
+ assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
+ log.info("Verified scanlimit is properly quoted in remediation command")
|
||||
+ topology_st.logcap.flush()
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the nsIndexIDListScanLimit")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
|
||||
+
|
||||
+ :id: af8214ad-5e4c-422a-8f74-3e99227551df
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
|
||||
+ 3. Use healthcheck and verify a single combined command is generated
|
||||
+ 4. Re-add the matching rule and scanlimit
|
||||
+ 5. Use healthcheck without --json option
|
||||
+ 6. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
|
||||
+ 4. Success
|
||||
+ 5. healthcheck reports no issues found
|
||||
+ 6. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
+ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ # Run healthcheck and verify combined command
|
||||
+ args = FakeArgs()
|
||||
+ args.instance = standalone.serverid
|
||||
+ args.verbose = standalone.verbose
|
||||
+ args.list_errors = False
|
||||
+ args.list_checks = False
|
||||
+ args.exclude_check = []
|
||||
+ args.check = ["backends"]
|
||||
+ args.dry_run = False
|
||||
+ args.json = False
|
||||
+ health_check_run(standalone, topology_st.logcap.log, args)
|
||||
+
|
||||
+ # Verify DSBLE0007 is reported
|
||||
+ assert topology_st.logcap.contains(RET_CODE)
|
||||
+ log.info("healthcheck returned code: %s" % RET_CODE)
|
||||
+
|
||||
+ # Verify a single combined command is generated with both --add-mr and --add-scanlimit
|
||||
+ assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
+ log.info("Verified combined command with both --add-mr and --add-scanlimit")
|
||||
+
|
||||
+ topology_st.logcap.flush()
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 14b64d1d3..3cea0df36 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -602,7 +602,7 @@ class Backend(DSLdapObject):
|
||||
if expected_config.get('matching_rule'):
|
||||
cmd += f" --matching-rule {expected_config['matching_rule']}"
|
||||
if expected_config.get('scanlimit'):
|
||||
- cmd += f" --add-scanlimit {expected_config['scanlimit']}"
|
||||
+ cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
else:
|
||||
@@ -624,28 +624,31 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
- # Check matching rules
|
||||
+ # Check matching rules and scanlimit together to generate a single combined command
|
||||
expected_mr = expected_config.get('matching_rule')
|
||||
+ expected_scanlimit = expected_config.get('scanlimit')
|
||||
+
|
||||
+ missing_mr = False
|
||||
if expected_mr:
|
||||
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
|
||||
if expected_mr.lower() not in actual_mrs_lower:
|
||||
discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
|
||||
- # Add the missing matching rule
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
|
||||
- remediation_commands.append(cmd)
|
||||
- reindex_attrs.add(attr_name)
|
||||
-
|
||||
- # Check fine grain definitions for parentid ONLY
|
||||
- expected_scanlimit = expected_config.get('scanlimit')
|
||||
- if (attr_name.lower() == "parentid") and expected_scanlimit and (len(actual_scanlimit) == 0):
|
||||
- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_mr}")
|
||||
- # Add the missing scanlimit
|
||||
- if expected_mr:
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr} --add-scanlimit {expected_scanlimit}"
|
||||
- else:
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-scanlimit {expected_scanlimit}"
|
||||
- remediation_commands.append(cmd)
|
||||
- reindex_attrs.add(attr_name)
|
||||
+ missing_mr = True
|
||||
+
|
||||
+ missing_scanlimit = False
|
||||
+ if expected_scanlimit and (len(actual_scanlimit) == 0):
|
||||
+ discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
|
||||
+ missing_scanlimit = True
|
||||
+
|
||||
+ # Generate a single combined command for all missing items
|
||||
+ if missing_mr or missing_scanlimit:
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
|
||||
+ if missing_mr:
|
||||
+ cmd += f" --add-mr {expected_mr}"
|
||||
+ if missing_scanlimit:
|
||||
+ cmd += f" --add-scanlimit \"{expected_scanlimit}\""
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
|
||||
except Exception as e:
|
||||
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,789 @@
|
||||
From 850cb2ae142b1dc31081387e8e997699668e70f4 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Revert index scan limits for system indexes
|
||||
|
||||
This reverts changes introduced by the following commits:
|
||||
c6f458b42 Issue 7189 - DSBLE0007 generates incorrect remediation commands for scan limits
|
||||
8b6b3a9f9 Issue 6966 - On large DB, unlimited IDL scan limit reduce the SRCH performance
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
.../tests/suites/config/config_test.py | 27 +---
|
||||
.../healthcheck/health_system_indexes_test.py | 135 +-----------------
|
||||
.../paged_results/paged_results_test.py | 25 +---
|
||||
ldap/servers/slapd/back-ldbm/back-ldbm.h | 1 -
|
||||
ldap/servers/slapd/back-ldbm/index.c | 2 -
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 108 ++------------
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.c | 30 ----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.h | 1 -
|
||||
.../slapd/back-ldbm/ldbm_index_config.c | 8 --
|
||||
src/lib389/lib389/backend.py | 50 ++-----
|
||||
src/lib389/lib389/cli_conf/backend.py | 20 ---
|
||||
11 files changed, 39 insertions(+), 368 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
|
||||
index 430176602..bbe13d248 100644
|
||||
--- a/dirsrvtests/tests/suites/config/config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/config_test.py
|
||||
@@ -514,19 +514,17 @@ def test_ndn_cache_enabled(topo):
|
||||
topo.standalone.config.set('nsslapd-ndn-cache-max-size', 'invalid_value')
|
||||
|
||||
|
||||
-def test_require_index(topo, request):
|
||||
+def test_require_index(topo):
|
||||
"""Validate that unindexed searches are rejected
|
||||
|
||||
:id: fb6e31f2-acc2-4e75-a195-5c356faeb803
|
||||
:setup: Standalone instance
|
||||
:steps:
|
||||
1. Set "nsslapd-require-index" to "on"
|
||||
- 2. ancestorid/idlscanlimit to 100
|
||||
- 3. Test an unindexed search is rejected
|
||||
+ 2. Test an unindexed search is rejected
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
- 3. Success
|
||||
"""
|
||||
|
||||
# Set the config
|
||||
@@ -537,10 +535,6 @@ def test_require_index(topo, request):
|
||||
|
||||
db_cfg = DatabaseConfig(topo.standalone)
|
||||
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
|
||||
- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
|
||||
- ancestorid_index = backend.get_index('ancestorid')
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
|
||||
- topo.standalone.restart()
|
||||
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
for i in range(101):
|
||||
@@ -551,15 +545,10 @@ def test_require_index(topo, request):
|
||||
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
raw_objects.filter("(description=test*)")
|
||||
|
||||
- def fin():
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
|
||||
-
|
||||
- request.addfinalizer(fin)
|
||||
-
|
||||
|
||||
|
||||
@pytest.mark.skipif(ds_is_older('1.4.2'), reason="The config setting only exists in 1.4.2 and higher")
|
||||
-def test_require_internal_index(topo, request):
|
||||
+def test_require_internal_index(topo):
|
||||
"""Ensure internal operations require indexed attributes
|
||||
|
||||
:id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f
|
||||
@@ -591,10 +580,6 @@ def test_require_internal_index(topo, request):
|
||||
# Create a bunch of users
|
||||
db_cfg = DatabaseConfig(topo.standalone)
|
||||
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
|
||||
- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
|
||||
- ancestorid_index = backend.get_index('ancestorid')
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
|
||||
- topo.standalone.restart()
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
for i in range(102, 202):
|
||||
users.create_test_user(uid=i)
|
||||
@@ -619,12 +604,6 @@ def test_require_internal_index(topo, request):
|
||||
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
user.delete()
|
||||
|
||||
- def fin():
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
|
||||
-
|
||||
- request.addfinalizer(fin)
|
||||
-
|
||||
-
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index f25de4214..842f7e8dd 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -172,8 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
|
||||
log.info("Re-add the parentId index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -261,8 +260,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer
|
||||
|
||||
log.info("Re-add the entryusn index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
- backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
+ backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -406,132 +404,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
-def test_missing_scanlimit(topology_st, log_buffering_enabled):
|
||||
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
|
||||
-
|
||||
- :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
|
||||
- :setup: Standalone instance
|
||||
- :steps:
|
||||
- 1. Create DS instance
|
||||
- 2. Remove nsIndexIDListScanLimit from parentId index
|
||||
- 3. Use healthcheck without --json option
|
||||
- 4. Use healthcheck with --json option
|
||||
- 5. Verify the remediation command has properly quoted scanlimit
|
||||
- 6. Re-add the scanlimit
|
||||
- 7. Use healthcheck without --json option
|
||||
- 8. Use healthcheck with --json option
|
||||
- :expectedresults:
|
||||
- 1. Success
|
||||
- 2. Success
|
||||
- 3. healthcheck reports DSBLE0007 code and related details
|
||||
- 4. healthcheck reports DSBLE0007 code and related details
|
||||
- 5. The scanlimit value is quoted in the remediation command
|
||||
- 6. Success
|
||||
- 7. healthcheck reports no issues found
|
||||
- 8. healthcheck reports no issues found
|
||||
- """
|
||||
-
|
||||
- RET_CODE = "DSBLE0007"
|
||||
- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
-
|
||||
- standalone = topology_st.standalone
|
||||
-
|
||||
- log.info("Remove nsIndexIDListScanLimit from parentId index")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
-
|
||||
- # Verify the remediation command has properly quoted scanlimit
|
||||
- args = FakeArgs()
|
||||
- args.instance = standalone.serverid
|
||||
- args.verbose = standalone.verbose
|
||||
- args.list_errors = False
|
||||
- args.list_checks = False
|
||||
- args.exclude_check = []
|
||||
- args.check = ["backends"]
|
||||
- args.dry_run = False
|
||||
- args.json = False
|
||||
- health_check_run(standalone, topology_st.logcap.log, args)
|
||||
- # Check that the scanlimit is quoted in the output
|
||||
- assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
- log.info("Verified scanlimit is properly quoted in remediation command")
|
||||
- topology_st.logcap.flush()
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
-
|
||||
- log.info("Re-add the nsIndexIDListScanLimit")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
-
|
||||
-
|
||||
-def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
|
||||
- """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
|
||||
-
|
||||
- :id: af8214ad-5e4c-422a-8f74-3e99227551df
|
||||
- :setup: Standalone instance
|
||||
- :steps:
|
||||
- 1. Create DS instance
|
||||
- 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
|
||||
- 3. Use healthcheck and verify a single combined command is generated
|
||||
- 4. Re-add the matching rule and scanlimit
|
||||
- 5. Use healthcheck without --json option
|
||||
- 6. Use healthcheck with --json option
|
||||
- :expectedresults:
|
||||
- 1. Success
|
||||
- 2. Success
|
||||
- 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
|
||||
- 4. Success
|
||||
- 5. healthcheck reports no issues found
|
||||
- 6. healthcheck reports no issues found
|
||||
- """
|
||||
-
|
||||
- RET_CODE = "DSBLE0007"
|
||||
- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
-
|
||||
- standalone = topology_st.standalone
|
||||
-
|
||||
- log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- # Run healthcheck and verify combined command
|
||||
- args = FakeArgs()
|
||||
- args.instance = standalone.serverid
|
||||
- args.verbose = standalone.verbose
|
||||
- args.list_errors = False
|
||||
- args.list_checks = False
|
||||
- args.exclude_check = []
|
||||
- args.check = ["backends"]
|
||||
- args.dry_run = False
|
||||
- args.json = False
|
||||
- health_check_run(standalone, topology_st.logcap.log, args)
|
||||
-
|
||||
- # Verify DSBLE0007 is reported
|
||||
- assert topology_st.logcap.contains(RET_CODE)
|
||||
- log.info("healthcheck returned code: %s" % RET_CODE)
|
||||
-
|
||||
- # Verify a single combined command is generated with both --add-mr and --add-scanlimit
|
||||
- assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
- log.info("Verified combined command with both --add-mr and --add-scanlimit")
|
||||
-
|
||||
- topology_st.logcap.flush()
|
||||
-
|
||||
- log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
-
|
||||
-
|
||||
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
|
||||
@@ -572,8 +444,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
|
||||
log.info("Re-add the missing system indexes")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
backend.add_index("nsuniqueid", ["eq"])
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
index 8835be8fa..1ed11c891 100644
|
||||
--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
@@ -317,19 +317,19 @@ def test_search_success(topology_st, create_user, page_size, users_num):
|
||||
del_users(users_list)
|
||||
|
||||
|
||||
-@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err, restart", [
|
||||
+@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err", [
|
||||
(50, 200, 'cn=config,%s' % DN_LDBM, 'nsslapd-idlistscanlimit', '100',
|
||||
- ldap.UNWILLING_TO_PERFORM, True),
|
||||
+ ldap.UNWILLING_TO_PERFORM),
|
||||
(5, 15, DN_CONFIG, 'nsslapd-timelimit', '20',
|
||||
- ldap.UNAVAILABLE_CRITICAL_EXTENSION, False),
|
||||
+ ldap.UNAVAILABLE_CRITICAL_EXTENSION),
|
||||
(21, 50, DN_CONFIG, 'nsslapd-sizelimit', '20',
|
||||
- ldap.SIZELIMIT_EXCEEDED, False),
|
||||
+ ldap.SIZELIMIT_EXCEEDED),
|
||||
(21, 50, DN_CONFIG, 'nsslapd-pagedsizelimit', '5',
|
||||
- ldap.SIZELIMIT_EXCEEDED, False),
|
||||
+ ldap.SIZELIMIT_EXCEEDED),
|
||||
(5, 50, 'cn=config,%s' % DN_LDBM, 'nsslapd-lookthroughlimit', '20',
|
||||
- ldap.ADMINLIMIT_EXCEEDED, False)])
|
||||
+ ldap.ADMINLIMIT_EXCEEDED)])
|
||||
def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
- suffix, attr_name, attr_value, expected_err, restart):
|
||||
+ suffix, attr_name, attr_value, expected_err):
|
||||
"""Verify that search with a simple paged results control
|
||||
throws expected exceptoins when corresponding limits are
|
||||
exceeded.
|
||||
@@ -351,15 +351,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
|
||||
users_list = add_users(topology_st, users_num, DEFAULT_SUFFIX)
|
||||
attr_value_bck = change_conf_attr(topology_st, suffix, attr_name, attr_value)
|
||||
- ancestorid_index = None
|
||||
- if attr_name == 'nsslapd-idlistscanlimit':
|
||||
- backend = Backends(topology_st.standalone).get_backend(DEFAULT_SUFFIX)
|
||||
- ancestorid_index = backend.get_index('ancestorid')
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
|
||||
-
|
||||
- if (restart):
|
||||
- log.info('Instance restarted')
|
||||
- topology_st.standalone.restart()
|
||||
conf_param_dict = {attr_name: attr_value}
|
||||
search_flt = r'(uid=test*)'
|
||||
searchreq_attrlist = ['dn', 'sn']
|
||||
@@ -412,8 +403,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
- if ancestorid_index:
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
|
||||
del_users(users_list)
|
||||
change_conf_attr(topology_st, suffix, attr_name, attr_value_bck)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
index cde30cedd..d17ec644b 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
@@ -554,7 +554,6 @@ struct ldbminfo
|
||||
int li_mode;
|
||||
int li_lookthroughlimit;
|
||||
int li_allidsthreshold;
|
||||
- int li_system_allidsthreshold;
|
||||
char *li_directory;
|
||||
int li_reslimit_lookthrough_handle;
|
||||
uint64_t li_dbcachesize;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
|
||||
index 63f0196c1..30fa09ebb 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -999,8 +999,6 @@ index_read_ext_allids(
|
||||
}
|
||||
if (pb) {
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and);
|
||||
- } else if (strcasecmp(type, LDBM_ANCESTORID_STR) == 0) {
|
||||
- is_and = 1;
|
||||
}
|
||||
ai_flags = is_and ? INDEX_ALLIDS_FLAG_AND : 0;
|
||||
/* the caller can pass in a value of 0 - just ignore those - but if the index
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 29643f7e4..6098e04fc 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
/* Forward declarations */
|
||||
static void ldbm_instance_destructor(void **arg);
|
||||
-Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr, char *scanlimit);
|
||||
+Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr);
|
||||
|
||||
|
||||
/* Creates and initializes a new ldbm_instance structure.
|
||||
@@ -127,7 +127,7 @@ done:
|
||||
* Take a bunch of strings, and create a index config entry
|
||||
*/
|
||||
Slapi_Entry *
|
||||
-ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr, char *scanlimit)
|
||||
+ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr)
|
||||
{
|
||||
Slapi_Entry *e = slapi_entry_alloc();
|
||||
struct berval *vals[2];
|
||||
@@ -168,11 +168,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
|
||||
slapi_entry_add_values(e, "nsMatchingRule", vals);
|
||||
}
|
||||
|
||||
- if (scanlimit) {
|
||||
- val.bv_val = scanlimit;
|
||||
- val.bv_len = strlen(scanlimit);
|
||||
- slapi_entry_add_values(e, "nsIndexIDListScanLimit", vals);
|
||||
- }
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -185,60 +180,8 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
{
|
||||
Slapi_Entry *e;
|
||||
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
/* write the dse file only on the final index */
|
||||
int flags = LDBM_INSTANCE_CONFIG_DONT_WRITE;
|
||||
- char *ancestorid_indexes_limit = NULL;
|
||||
- char *parentid_indexes_limit = NULL;
|
||||
- struct attrinfo *ai = NULL;
|
||||
- int index_already_configured = 0;
|
||||
- struct index_idlistsizeinfo *iter;
|
||||
- int cookie;
|
||||
- int limit;
|
||||
-
|
||||
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
- if (ai && ai->ai_idlistinfo) {
|
||||
- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
|
||||
- if (iter) {
|
||||
- limit = iter->ai_idlistsizelimit;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set ancestorid limit to %d from attribute index\n",
|
||||
- limit);
|
||||
- } else {
|
||||
- limit = li->li_system_allidsthreshold;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set ancestorid limit to %d from default (fail to read limit)\n",
|
||||
- limit);
|
||||
- }
|
||||
- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
|
||||
- } else {
|
||||
- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set ancestorid limit to %d from default (no attribute or limit)\n",
|
||||
- li->li_system_allidsthreshold);
|
||||
- }
|
||||
-
|
||||
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
- if (ai && ai->ai_idlistinfo) {
|
||||
- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
|
||||
- if (iter) {
|
||||
- limit = iter->ai_idlistsizelimit;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set parentid limit to %d from attribute index\n",
|
||||
- limit);
|
||||
- } else {
|
||||
- limit = li->li_system_allidsthreshold;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set parentid limit to %d from default (fail to read limit)\n",
|
||||
- limit);
|
||||
- }
|
||||
- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
|
||||
- } else {
|
||||
- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set parentid limit to %d from default (no attribute or limit)\n",
|
||||
- li->li_system_allidsthreshold);
|
||||
- }
|
||||
|
||||
/*
|
||||
* Always index (entrydn or entryrdn), parentid, objectclass,
|
||||
@@ -247,59 +190,47 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ACL routines.
|
||||
*/
|
||||
if (entryrdn_get_switch()) { /* subtree-rename: on */
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
} else {
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
|
||||
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
- /* Check if the attrinfo is actually for parentid, not a fallback to .default */
|
||||
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0);
|
||||
- if (!index_already_configured) {
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
|
||||
- ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
- }
|
||||
-
|
||||
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
-#if 0 /* don't need copiedfrom */
|
||||
- e = ldbm_instance_init_config_entry("copiedfrom","pres",0 ,0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
-#endif
|
||||
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* For MMR, we need this attribute (to replace use of dncomp in delete). */
|
||||
- e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* write the dse file only on the final index */
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* ldbm_instance_config_add_index_entry(inst, 2, argv); */
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0);
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
|
||||
@@ -308,20 +239,11 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
- /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
|
||||
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
|
||||
- if (!index_already_configured) {
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
|
||||
- ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
- }
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
+ slapi_entry_free(e);
|
||||
}
|
||||
|
||||
- slapi_ch_free_string(&ancestorid_indexes_limit);
|
||||
- slapi_ch_free_string(&parentid_indexes_limit);
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
index f8d8f7474..b7bceabf2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
@@ -366,35 +366,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
|
||||
return retval;
|
||||
}
|
||||
|
||||
-static void *
|
||||
-ldbm_config_system_allidsthreshold_get(void *arg)
|
||||
-{
|
||||
- struct ldbminfo *li = (struct ldbminfo *)arg;
|
||||
-
|
||||
- return (void *)((uintptr_t)(li->li_system_allidsthreshold));
|
||||
-}
|
||||
-
|
||||
-static int
|
||||
-ldbm_config_system_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply)
|
||||
-{
|
||||
- struct ldbminfo *li = (struct ldbminfo *)arg;
|
||||
- int retval = LDAP_SUCCESS;
|
||||
- int val = (int)((uintptr_t)value);
|
||||
-
|
||||
- /* Do whatever we can to make sure the data is ok. */
|
||||
-
|
||||
- /* Catch attempts to configure a stupidly low ancestorid allidsthreshold */
|
||||
- if ((val > -1) && (val < 5000)) {
|
||||
- val = 5000;
|
||||
- }
|
||||
-
|
||||
- if (apply) {
|
||||
- li->li_system_allidsthreshold = val;
|
||||
- }
|
||||
-
|
||||
- return retval;
|
||||
-}
|
||||
-
|
||||
static void *
|
||||
ldbm_config_pagedallidsthreshold_get(void *arg)
|
||||
{
|
||||
@@ -974,7 +945,6 @@ static config_info ldbm_config[] = {
|
||||
{CONFIG_LOOKTHROUGHLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_lookthroughlimit_get, &ldbm_config_lookthroughlimit_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_MODE, CONFIG_TYPE_INT_OCTAL, "0600", &ldbm_config_mode_get, &ldbm_config_mode_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_IDLISTSCANLIMIT, CONFIG_TYPE_INT, "2147483646", &ldbm_config_allidsthreshold_get, &ldbm_config_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
- {CONFIG_SYSTEMIDLISTSCANLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_system_allidsthreshold_get, &ldbm_config_system_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_DIRECTORY, CONFIG_TYPE_STRING, "", &ldbm_config_directory_get, &ldbm_config_directory_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE | CONFIG_FLAG_SKIP_DEFAULT_SETTING},
|
||||
{CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &ldbm_config_maxpassbeforemerge_get, &ldbm_config_maxpassbeforemerge_set, 0},
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
index 004e5ea7e..48446193e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
@@ -60,7 +60,6 @@ struct config_info
|
||||
#define CONFIG_RANGELOOKTHROUGHLIMIT "nsslapd-rangelookthroughlimit"
|
||||
#define CONFIG_PAGEDLOOKTHROUGHLIMIT "nsslapd-pagedlookthroughlimit"
|
||||
#define CONFIG_IDLISTSCANLIMIT "nsslapd-idlistscanlimit"
|
||||
-#define CONFIG_SYSTEMIDLISTSCANLIMIT "nsslapd-systemidlistscanlimit"
|
||||
#define CONFIG_PAGEDIDLISTSCANLIMIT "nsslapd-pagedidlistscanlimit"
|
||||
#define CONFIG_DIRECTORY "nsslapd-directory"
|
||||
#define CONFIG_MODE "nsslapd-mode"
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
index bae2a64b9..38e7368e1 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
@@ -384,14 +384,6 @@ ldbm_instance_config_add_index_entry(
|
||||
}
|
||||
}
|
||||
|
||||
- /* get nsIndexIDListScanLimit and its values, and add them */
|
||||
- if (0 == slapi_entry_attr_find(e, "nsIndexIDListScanLimit", &attr)) {
|
||||
- for (j = slapi_attr_first_value(attr, &sval); j != -1; j = slapi_attr_next_value(attr, j, &sval)) {
|
||||
- attrValue = slapi_value_get_berval(sval);
|
||||
- eBuf = PR_sprintf_append(eBuf, "nsIndexIDListScanLimit: %s\n", attrValue->bv_val);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
ldbm_config_add_dse_entry(li, eBuf, flags);
|
||||
if (eBuf) {
|
||||
PR_smprintf_free(eBuf);
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 3cea0df36..4babf6850 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -539,10 +539,11 @@ class Backend(DSLdapObject):
|
||||
indexes = self.get_indexes()
|
||||
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
+ # Note: entryrdn and ancestorid are internal system indexes that are not
|
||||
+ # exposed in cn=config - they are managed internally by the server.
|
||||
+ # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
|
||||
expected_system_indexes = {
|
||||
- 'entryrdn': {'types': ['subtree'], 'matching_rule': None},
|
||||
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
|
||||
- 'ancestorid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
|
||||
+ 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
@@ -599,17 +600,14 @@ class Backend(DSLdapObject):
|
||||
# Generate remediation command
|
||||
index_types = ' '.join([f"--index-type {t}" for t in expected_config['types']])
|
||||
cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
|
||||
- if expected_config.get('matching_rule'):
|
||||
+ if expected_config['matching_rule']:
|
||||
cmd += f" --matching-rule {expected_config['matching_rule']}"
|
||||
- if expected_config.get('scanlimit'):
|
||||
- cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
else:
|
||||
# Index exists, check configuration
|
||||
actual_types = index.get_attr_vals_utf8('nsIndexType') or []
|
||||
actual_mrs = index.get_attr_vals_utf8('nsMatchingRule') or []
|
||||
- actual_scanlimit = index.get_attr_vals_utf8('nsIndexIDListScanLimit') or []
|
||||
|
||||
# Normalize to lowercase for comparison
|
||||
actual_types = [t.lower() for t in actual_types]
|
||||
@@ -624,31 +622,16 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
- # Check matching rules and scanlimit together to generate a single combined command
|
||||
+ # Check matching rules
|
||||
expected_mr = expected_config.get('matching_rule')
|
||||
- expected_scanlimit = expected_config.get('scanlimit')
|
||||
-
|
||||
- missing_mr = False
|
||||
if expected_mr:
|
||||
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
|
||||
if expected_mr.lower() not in actual_mrs_lower:
|
||||
discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
|
||||
- missing_mr = True
|
||||
-
|
||||
- missing_scanlimit = False
|
||||
- if expected_scanlimit and (len(actual_scanlimit) == 0):
|
||||
- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
|
||||
- missing_scanlimit = True
|
||||
-
|
||||
- # Generate a single combined command for all missing items
|
||||
- if missing_mr or missing_scanlimit:
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
|
||||
- if missing_mr:
|
||||
- cmd += f" --add-mr {expected_mr}"
|
||||
- if missing_scanlimit:
|
||||
- cmd += f" --add-scanlimit \"{expected_scanlimit}\""
|
||||
- remediation_commands.append(cmd)
|
||||
- reindex_attrs.add(attr_name)
|
||||
+ # Add the missing matching rule
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
|
||||
except Exception as e:
|
||||
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
@@ -879,13 +862,12 @@ class Backend(DSLdapObject):
|
||||
return
|
||||
raise ValueError("Can not delete index because it does not exist")
|
||||
|
||||
- def add_index(self, attr_name, types, matching_rules=None, idlistscanlimit=None, reindex=False):
|
||||
+ def add_index(self, attr_name, types, matching_rules=None, reindex=False):
|
||||
""" Add an index.
|
||||
|
||||
:param attr_name - name of the attribute to index
|
||||
:param types - a List of index types(eq, pres, sub, approx)
|
||||
:param matching_rules - a List of matching rules for the index
|
||||
- :param idlistscanlimit - a List of fine grain definitions for scanning limit
|
||||
:param reindex - If set to True then index the attribute after creating it.
|
||||
"""
|
||||
|
||||
@@ -915,15 +897,6 @@ class Backend(DSLdapObject):
|
||||
# Only add if there are actually rules present in the list.
|
||||
if len(mrs) > 0:
|
||||
props['nsMatchingRule'] = mrs
|
||||
-
|
||||
- if idlistscanlimit is not None:
|
||||
- scanlimits = []
|
||||
- for scanlimit in idlistscanlimit:
|
||||
- scanlimits.append(scanlimit)
|
||||
- # Only add if there are actually limits in the list.
|
||||
- if len(scanlimits) > 0:
|
||||
- props['nsIndexIDListScanLimit'] = scanlimits
|
||||
-
|
||||
new_index.create(properties=props, basedn="cn=index," + self._dn)
|
||||
|
||||
if reindex:
|
||||
@@ -1230,7 +1203,6 @@ class DatabaseConfig(DSLdapObject):
|
||||
'nsslapd-lookthroughlimit',
|
||||
'nsslapd-mode',
|
||||
'nsslapd-idlistscanlimit',
|
||||
- 'nsslapd-systemidlistscanlimit',
|
||||
'nsslapd-directory',
|
||||
'nsslapd-import-cachesize',
|
||||
'nsslapd-idl-switch',
|
||||
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
|
||||
index d57cb9433..4dc67d563 100644
|
||||
--- a/src/lib389/lib389/cli_conf/backend.py
|
||||
+++ b/src/lib389/lib389/cli_conf/backend.py
|
||||
@@ -39,7 +39,6 @@ arg_to_attr = {
|
||||
'mode': 'nsslapd-mode',
|
||||
'state': 'nsslapd-state',
|
||||
'idlistscanlimit': 'nsslapd-idlistscanlimit',
|
||||
- 'systemidlistscanlimit': 'nsslapd-systemidlistscanlimit',
|
||||
'directory': 'nsslapd-directory',
|
||||
'dbcachesize': 'nsslapd-dbcachesize',
|
||||
'logdirectory': 'nsslapd-db-logdirectory',
|
||||
@@ -588,21 +587,6 @@ def backend_set_index(inst, basedn, log, args):
|
||||
except ldap.NO_SUCH_ATTRIBUTE:
|
||||
raise ValueError('Can not delete matching rule type because it does not exist')
|
||||
|
||||
- if args.replace_scanlimit is not None:
|
||||
- for replace_scanlimit in args.replace_scanlimit:
|
||||
- index.replace('nsIndexIDListScanLimit', replace_scanlimit)
|
||||
-
|
||||
- if args.add_scanlimit is not None:
|
||||
- for add_scanlimit in args.add_scanlimit:
|
||||
- index.add('nsIndexIDListScanLimit', add_scanlimit)
|
||||
-
|
||||
- if args.del_scanlimit is not None:
|
||||
- for del_scanlimit in args.del_scanlimit:
|
||||
- try:
|
||||
- index.remove('nsIndexIDListScanLimit', del_scanlimit)
|
||||
- except ldap.NO_SUCH_ATTRIBUTE:
|
||||
- raise ValueError('Can not delete a fine grain limit definition because it does not exist')
|
||||
-
|
||||
if args.reindex:
|
||||
be.reindex(attrs=[args.attr])
|
||||
log.info("Index successfully updated")
|
||||
@@ -924,9 +908,6 @@ def create_parser(subparsers):
|
||||
edit_index_parser.add_argument('--del-type', action='append', help='Removes an index type from the index: (eq, sub, pres, or approx)')
|
||||
edit_index_parser.add_argument('--add-mr', action='append', help='Adds a matching-rule to the index')
|
||||
edit_index_parser.add_argument('--del-mr', action='append', help='Removes a matching-rule from the index')
|
||||
- edit_index_parser.add_argument('--add-scanlimit', action='append', help='Adds a fine grain limit definiton to the index')
|
||||
- edit_index_parser.add_argument('--replace-scanlimit', action='append', help='Replaces a fine grain limit definiton to the index')
|
||||
- edit_index_parser.add_argument('--del-scanlimit', action='append', help='Removes a fine grain limit definiton to the index')
|
||||
edit_index_parser.add_argument('--reindex', action='store_true', help='Re-indexes the database after editing the index')
|
||||
edit_index_parser.add_argument('be_name', help='The backend name or suffix')
|
||||
|
||||
@@ -1053,7 +1034,6 @@ def create_parser(subparsers):
|
||||
'will check when examining candidate entries in response to a search request')
|
||||
set_db_config_parser.add_argument('--mode', help='Specifies the permissions used for newly created index files')
|
||||
set_db_config_parser.add_argument('--idlistscanlimit', help='Specifies the number of entry IDs that are searched during a search operation')
|
||||
- set_db_config_parser.add_argument('--systemidlistscanlimit', help='Specifies the number of entry IDs that are fetch from ancestorid/parentid indexes')
|
||||
set_db_config_parser.add_argument('--directory', help='Specifies absolute path to database instance')
|
||||
set_db_config_parser.add_argument('--dbcachesize', help='Specifies the database index cache size in bytes')
|
||||
set_db_config_parser.add_argument('--logdirectory', help='Specifies the path to the directory that contains the database transaction logs')
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
From e9f5082218c06f7dbcb1e7f70337841178f18271 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 2 Feb 2026 09:30:13 +0100
|
||||
Subject: [PATCH] Issue 7223 - Backport upgrade infrastructure from main branch
|
||||
|
||||
Backport the upgrade.c infrastructure from main/1.4.4 branches to provide
|
||||
a mechanism for automatic configuration fixes during server startup.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
Makefile.am | 1 +
|
||||
ldap/servers/slapd/main.c | 16 ++++++++++++++++
|
||||
ldap/servers/slapd/slap.h | 10 ++++++++++
|
||||
ldap/servers/slapd/upgrade.c | 29 +++++++++++++++++++++++++++++
|
||||
4 files changed, 56 insertions(+)
|
||||
create mode 100644 ldap/servers/slapd/upgrade.c
|
||||
|
||||
diff --git a/Makefile.am b/Makefile.am
|
||||
index 245d7cf19..41eab712b 100644
|
||||
--- a/Makefile.am
|
||||
+++ b/Makefile.am
|
||||
@@ -2346,6 +2346,7 @@ ns_slapd_SOURCES = ldap/servers/slapd/abandon.c \
|
||||
ldap/servers/slapd/stubs.c \
|
||||
ldap/servers/slapd/tempnam.c \
|
||||
ldap/servers/slapd/unbind.c \
|
||||
+ ldap/servers/slapd/upgrade.c \
|
||||
$(GETSOCKETPEER)
|
||||
|
||||
ns_slapd_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS) $(SASL_CFLAGS) $(SVRCORE_INCLUDES)
|
||||
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
|
||||
index 9b5b845cb..043dc54bc 100644
|
||||
--- a/ldap/servers/slapd/main.c
|
||||
+++ b/ldap/servers/slapd/main.c
|
||||
@@ -738,6 +738,22 @@ main(int argc, char **argv)
|
||||
|
||||
mcfg.n_port = config_get_port();
|
||||
mcfg.s_port = config_get_secureport();
|
||||
+
|
||||
+ /*
|
||||
+ * This step checks for any updates and changes on upgrade
|
||||
+ * specifically, it manages assumptions about what plugins should exist,
|
||||
+ * and their configurations, and potentially even the state of
|
||||
+ * configurations on the server and their removal and deprecation.
|
||||
+ *
|
||||
+ * Has to be after dse to change config, but before plugins start
|
||||
+ * so we can adjust these configurations.
|
||||
+ */
|
||||
+ if (upgrade_server() != UPGRADE_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_EMERG, "main",
|
||||
+ "Server upgrade check failed. Please check the error log for more information.\n");
|
||||
+ return_value = 1;
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
}
|
||||
|
||||
raise_process_limits(); /* should be done ASAP once config file read */
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 36d26bf4a..e6b49d366 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -183,6 +183,16 @@ typedef void (*VFPV)(); /* takes undefined arguments */
|
||||
#include "slapi-private.h"
|
||||
#include "pw.h"
|
||||
|
||||
+/*
|
||||
+ * SERVER UPGRADE INTERNALS
|
||||
+ */
|
||||
+typedef enum _upgrade_status {
|
||||
+ UPGRADE_SUCCESS = 0,
|
||||
+ UPGRADE_FAILURE = 1,
|
||||
+} upgrade_status;
|
||||
+
|
||||
+upgrade_status upgrade_server(void);
|
||||
+
|
||||
/*
|
||||
* call the appropriate signal() function.
|
||||
*/
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
new file mode 100644
|
||||
index 000000000..2f124afaf
|
||||
--- /dev/null
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -0,0 +1,29 @@
|
||||
+/* BEGIN COPYRIGHT BLOCK
|
||||
+ * Copyright (C) 2017 Red Hat, Inc.
|
||||
+ * Copyright (C) 2020 William Brown <william@blackhats.net.au>
|
||||
+ * All rights reserved.
|
||||
+ *
|
||||
+ * License: GPL (version 3 or any later version).
|
||||
+ * See LICENSE for details.
|
||||
+ * END COPYRIGHT BLOCK */
|
||||
+
|
||||
+#include <slap.h>
|
||||
+#include <slapi-private.h>
|
||||
+
|
||||
+/*
|
||||
+ * This is called on server startup *before* plugins start
|
||||
+ * but after config dse is read for operations. This allows
|
||||
+ * us to make internal assertions about the state of the configuration
|
||||
+ * at start up, enable plugins, and more.
|
||||
+ *
|
||||
+ * The functions in this file are named as:
|
||||
+ * upgrade_xxx_yyy, where xxx is the minimum version of the project
|
||||
+ * and yyy is the feature that is having it's configuration upgrade
|
||||
+ * or altered.
|
||||
+ */
|
||||
+
|
||||
+upgrade_status
|
||||
+upgrade_server(void)
|
||||
+{
|
||||
+ return UPGRADE_SUCCESS;
|
||||
+}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,312 @@
|
||||
From c6e2911dca08aac40e98999b48019eac72cc74a6 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add upgrade function to remove
|
||||
nsIndexIDListScanLimit from parentid
|
||||
|
||||
Description:
|
||||
Add `upgrade_remove_index_scanlimit()` function that removes the
|
||||
nsIndexIDListScanLimit attribute from parentid index configuration
|
||||
if present.
|
||||
|
||||
This attribute was incorrectly added by a previous version and can
|
||||
cause issues with index configuration. The upgrade function runs
|
||||
automatically on server startup and removes the attribute if found.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 52 +++++
|
||||
ldap/servers/slapd/upgrade.c | 210 ++++++++++++++++++
|
||||
2 files changed, 262 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 842f7e8dd..72a04fdab 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -451,6 +451,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
+def test_upgrade_removes_parentid_scanlimit(topology_st):
|
||||
+ """Check if upgrade function removes nsIndexIDListScanLimit from parentid index
|
||||
+
|
||||
+ :id: 2808886e-c1c1-441d-b3a3-299c4ef1ab4a
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Stop the server
|
||||
+ 3. Use DSEldif to add nsIndexIDListScanLimit to parentid index
|
||||
+ 4. Start the server (triggers upgrade)
|
||||
+ 5. Verify nsIndexIDListScanLimit is removed from parentid index
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. nsIndexIDListScanLimit is no longer present
|
||||
+ """
|
||||
+ from lib389.dseldif import DSEldif
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
+
|
||||
+ log.info("Stop the server")
|
||||
+ standalone.stop()
|
||||
+
|
||||
+ log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif")
|
||||
+ dse_ldif = DSEldif(standalone)
|
||||
+ dse_ldif.add(PARENTID_DN, "nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ # Verify it was added
|
||||
+ scanlimit = dse_ldif.get(PARENTID_DN, "nsIndexIDListScanLimit")
|
||||
+ assert scanlimit is not None, "Failed to add nsIndexIDListScanLimit"
|
||||
+ log.info(f"Added nsIndexIDListScanLimit: {scanlimit}")
|
||||
+
|
||||
+ log.info("Start the server (triggers upgrade)")
|
||||
+ standalone.start()
|
||||
+
|
||||
+ log.info("Verify nsIndexIDListScanLimit was removed by upgrade")
|
||||
+ # Check via LDAP - the upgrade should have removed it
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ scanlimit_after = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
|
||||
+ log.info(f"nsIndexIDListScanLimit after upgrade: {scanlimit_after}")
|
||||
+
|
||||
+ # The upgrade function should have removed nsIndexIDListScanLimit
|
||||
+ assert not scanlimit_after, \
|
||||
+ f"nsIndexIDListScanLimit should have been removed but found: {scanlimit_after}"
|
||||
+
|
||||
+ log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index 2f124afaf..074c15e3c 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -22,8 +22,218 @@
|
||||
* or altered.
|
||||
*/
|
||||
|
||||
+/*
|
||||
+ * Remove nsIndexIDListScanLimit from parentid index configuration.
|
||||
+ *
|
||||
+ * This attribute was incorrectly added by a previous version and can
|
||||
+ * cause issues with index configuration. Remove it if present.
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_remove_index_scanlimit(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ const char *attrs_to_check[] = {"parentid", NULL};
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_dn || !be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
+ const char *attr_name = attrs_to_check[attr_idx];
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
|
||||
+ attr_name, be_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* Check if nsIndexIDListScanLimit is present */
|
||||
+ if (slapi_entry_attr_get_ref(idx_entries[0], "nsIndexIDListScanLimit") != NULL) {
|
||||
+ /* Remove nsIndexIDListScanLimit */
|
||||
+ Slapi_PBlock *mod_pb = slapi_pblock_new();
|
||||
+ Slapi_Mods smods;
|
||||
+ int rc;
|
||||
+
|
||||
+ slapi_mods_init(&smods, 1);
|
||||
+ slapi_mods_add(&smods, LDAP_MOD_DELETE, "nsIndexIDListScanLimit", 0, NULL);
|
||||
+
|
||||
+ slapi_modify_internal_set_pb(
|
||||
+ mod_pb, idx_dn,
|
||||
+ slapi_mods_get_ldapmods_byref(&smods),
|
||||
+ NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_modify_internal_pb(mod_pb);
|
||||
+ slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_index_scanlimit",
|
||||
+ "Removed 'nsIndexIDListScanLimit' from index '%s' in backend '%s'\n",
|
||||
+ attr_name, be_name);
|
||||
+ } else if (rc != LDAP_NO_SUCH_ATTRIBUTE) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_index_scanlimit",
|
||||
+ "Failed to remove 'nsIndexIDListScanLimit' from index '%s' in backend '%s': error %d\n",
|
||||
+ attr_name, be_name, rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_mods_done(&smods);
|
||||
+ slapi_pblock_destroy(mod_pb);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check if parentid indexes are missing the integerOrderingMatch
|
||||
+ * matching rule.
|
||||
+ *
|
||||
+ * This function logs a warning if we detect this condition, advising
|
||||
+ * the administrator to reindex the affected attributes.
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_check_id_index_matching_rule(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Check each attribute that should have integerOrderingMatch */
|
||||
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
+ const char *attr_name = attrs_to_check[attr_idx];
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
|
||||
+ attr_name, be_name, be_base_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+ PRBool has_matching_rule = PR_FALSE;
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* Index exists, check if it has integerOrderingMatch */
|
||||
+ Slapi_Attr *mr_attr = NULL;
|
||||
+ if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ int idx;
|
||||
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
+ idx != -1;
|
||||
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
+ const struct berval *bval = slapi_value_get_berval(sval);
|
||||
+ if (bval && bval->bv_val &&
|
||||
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
+ has_matching_rule = PR_TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!has_matching_rule) {
|
||||
+ /* Index exists but doesn't have integerOrderingMatch, log a warning */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
|
||||
+ "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
|
||||
+ "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
|
||||
+ "To fix this, add the matching rule and reindex: "
|
||||
+ "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s. "
|
||||
+ "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
|
||||
+ "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
|
||||
+ attr_name, be_name, attr_name, be_name, attr_name, be_name);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
upgrade_status
|
||||
upgrade_server(void)
|
||||
{
|
||||
+ if (upgrade_remove_index_scanlimit() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
+ if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,313 @@
|
||||
From 1669e65b36d543fb69ef5503e5072ea37e9a0e19 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 9 Feb 2026 14:12:18 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add upgrade function to remove ancestorid index
|
||||
config entry
|
||||
|
||||
Description:
|
||||
Add `upgrade_remove_ancestorid_index_config()` function that removes:
|
||||
* ancestorid from `cn=default indexes`
|
||||
* ancestorid index config entries from each backend's `cn=index`
|
||||
|
||||
Also remove ancestorid index configuration from template-dse.ldif.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 85 +++++++++++
|
||||
ldap/ldif/template-dse.ldif.in | 8 --
|
||||
ldap/servers/slapd/upgrade.c | 133 +++++++++++++++++-
|
||||
3 files changed, 214 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 72a04fdab..db5d13bf8 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -502,6 +502,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st):
|
||||
|
||||
log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
|
||||
|
||||
+ # Verify idempotency - restart again and ensure no errors
|
||||
+ log.info("Restart server again to verify idempotency (no errors on second run)")
|
||||
+ standalone.restart()
|
||||
+ # Verify the attribute is still absent
|
||||
+ scanlimit_after_second = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
|
||||
+ assert not scanlimit_after_second, \
|
||||
+ f"nsIndexIDListScanLimit should still be absent after second restart but found: {scanlimit_after_second}"
|
||||
+ log.info("Idempotency verified - no issues on second restart")
|
||||
+
|
||||
+
|
||||
+def test_upgrade_removes_ancestorid_index_config(topology_st):
|
||||
+ """Check if upgrade function removes ancestorid index config entry
|
||||
+
|
||||
+ :id: 3f3d6e9b-75ac-4f0d-b2ce-7204e6eacd0a
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Stop the server
|
||||
+ 3. Use DSEldif to add an ancestorid index config entry
|
||||
+ 4. Start the server (triggers upgrade)
|
||||
+ 5. Verify ancestorid index config entry is removed
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. ancestorid index config entry is no longer present
|
||||
+ """
|
||||
+ from lib389.dseldif import DSEldif
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ ANCESTORID_DN = "cn=ancestorid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ log.info("Stop the server")
|
||||
+ standalone.stop()
|
||||
+
|
||||
+ log.info("Add ancestorid index config entry using DSEldif")
|
||||
+ dse_ldif = DSEldif(standalone)
|
||||
+
|
||||
+ # Create a fake ancestorid index entry
|
||||
+ ancestorid_entry = [
|
||||
+ "dn: {}\n".format(ANCESTORID_DN),
|
||||
+ "objectClass: top\n",
|
||||
+ "objectClass: nsIndex\n",
|
||||
+ "cn: ancestorid\n",
|
||||
+ "nsSystemIndex: true\n",
|
||||
+ "nsIndexType: eq\n",
|
||||
+ "nsMatchingRule: integerOrderingMatch\n",
|
||||
+ "\n"
|
||||
+ ]
|
||||
+ dse_ldif.add_entry(ancestorid_entry)
|
||||
+
|
||||
+ # Verify it was added by re-reading dse.ldif
|
||||
+ dse_ldif2 = DSEldif(standalone)
|
||||
+ cn_value = dse_ldif2.get(ANCESTORID_DN, "cn")
|
||||
+ assert cn_value is not None, "Failed to add ancestorid index config entry"
|
||||
+ log.info(f"Added ancestorid index entry with cn: {cn_value}")
|
||||
+
|
||||
+ log.info("Start the server (triggers upgrade)")
|
||||
+ standalone.start()
|
||||
+
|
||||
+ log.info("Verify ancestorid index config entry was removed by upgrade")
|
||||
+ # Check via LDAP - the upgrade should have removed the entry
|
||||
+ try:
|
||||
+ ancestorid_index = Index(standalone, ANCESTORID_DN)
|
||||
+ # If we can get the entry, it wasn't removed - this is a failure
|
||||
+ cn_after = ancestorid_index.get_attr_vals_utf8("cn")
|
||||
+ assert False, f"ancestorid index config entry should have been removed but still exists: {cn_after}"
|
||||
+ except Exception as e:
|
||||
+ # Entry should not exist - this is expected
|
||||
+ log.info(f"ancestorid index config entry correctly removed (got exception: {e})")
|
||||
+
|
||||
+ log.info("Upgrade successfully removed ancestorid index config entry")
|
||||
+
|
||||
+ # Verify idempotency - restart again and ensure no errors
|
||||
+ log.info("Restart server again to verify idempotency (no errors on second run)")
|
||||
+ standalone.restart()
|
||||
+ # Verify the entry is still absent
|
||||
+ try:
|
||||
+ ancestorid_index = Index(standalone, ANCESTORID_DN)
|
||||
+ cn_after_second = ancestorid_index.get_attr_vals_utf8("cn")
|
||||
+ assert False, f"ancestorid index config entry should still be absent after second restart but found: {cn_after_second}"
|
||||
+ except Exception as e:
|
||||
+ log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})")
|
||||
+
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
|
||||
index c2754adf8..2ddaf5fb3 100644
|
||||
--- a/ldap/ldif/template-dse.ldif.in
|
||||
+++ b/ldap/ldif/template-dse.ldif.in
|
||||
@@ -973,14 +973,6 @@ cn: aci
|
||||
nssystemindex: true
|
||||
nsindextype: pres
|
||||
|
||||
-dn: cn=ancestorid,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
-objectclass: top
|
||||
-objectclass: nsIndex
|
||||
-cn: ancestorid
|
||||
-nssystemindex: true
|
||||
-nsindextype: eq
|
||||
-nsmatchingrule: integerOrderingMatch
|
||||
-
|
||||
dn: cn=cn,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
objectclass: top
|
||||
objectclass: nsIndex
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index 074c15e3c..aa51e72d2 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -123,6 +123,126 @@ upgrade_remove_index_scanlimit(void)
|
||||
return uresult;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Remove ancestorid index configuration entry if present.
|
||||
+ *
|
||||
+ * The ancestorid index is special - it has no corresponding attribute type
|
||||
+ * and should not have a DSE config entry. If an entry exists, remove it.
|
||||
+ *
|
||||
+ * This function removes:
|
||||
+ * 1. The ancestorid entry from cn=default indexes (to prevent re-creation on startup)
|
||||
+ * 2. The ancestorid entry from each backend's cn=index (if it exists)
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_remove_ancestorid_index_config(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+ int rc;
|
||||
+
|
||||
+ /*
|
||||
+ * First, remove ancestorid from cn=default indexes to prevent
|
||||
+ * ldbm_instance_create_default_user_indexes() from re-creating it.
|
||||
+ */
|
||||
+ {
|
||||
+ Slapi_PBlock *def_pb = slapi_pblock_new();
|
||||
+ char *def_idx_dn = slapi_create_dn_string(
|
||||
+ "cn=ancestorid,cn=default indexes,cn=config,%s", be_base_dn);
|
||||
+
|
||||
+ if (def_idx_dn) {
|
||||
+ slapi_delete_internal_set_pb(
|
||||
+ def_pb, def_idx_dn, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_delete_internal_pb(def_pb);
|
||||
+ slapi_pblock_get(def_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Removed 'ancestorid' from default indexes.\n");
|
||||
+ } else if (rc != LDAP_NO_SUCH_OBJECT) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Failed to remove 'ancestorid' from default indexes: error %d\n", rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&def_idx_dn);
|
||||
+ }
|
||||
+ slapi_pblock_destroy(def_pb);
|
||||
+ }
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_dn || !be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=ancestorid,cn=index,%s",
|
||||
+ be_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* ancestorid index entry exists - delete it */
|
||||
+ Slapi_PBlock *del_pb = slapi_pblock_new();
|
||||
+
|
||||
+ slapi_delete_internal_set_pb(
|
||||
+ del_pb, idx_dn, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_delete_internal_pb(del_pb);
|
||||
+ slapi_pblock_get(del_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Removed 'ancestorid' index config entry in backend '%s'.\n",
|
||||
+ be_name);
|
||||
+ } else if (rc != LDAP_NO_SUCH_OBJECT) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Failed to remove 'ancestorid' index config entry in backend '%s': error %d\n",
|
||||
+ be_name, rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_pblock_destroy(del_pb);
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Check if parentid indexes are missing the integerOrderingMatch
|
||||
* matching rule.
|
||||
@@ -137,7 +257,7 @@ upgrade_check_id_index_matching_rule(void)
|
||||
Slapi_Entry **backends = NULL;
|
||||
const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
- const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
|
||||
+ const char *attrs_to_check[] = {"parentid", NULL};
|
||||
upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
|
||||
/* Search for all backend instances */
|
||||
@@ -151,8 +271,9 @@ upgrade_check_id_index_matching_rule(void)
|
||||
|
||||
if (backends) {
|
||||
for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
- if (!be_name) {
|
||||
+ if (!be_dn || !be_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -161,8 +282,8 @@ upgrade_check_id_index_matching_rule(void)
|
||||
const char *attr_name = attrs_to_check[attr_idx];
|
||||
struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
Slapi_Entry **idx_entries = NULL;
|
||||
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
|
||||
- attr_name, be_name, be_base_dn);
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
|
||||
+ attr_name, be_dn);
|
||||
char *idx_filter = "(objectclass=nsIndex)";
|
||||
PRBool has_matching_rule = PR_FALSE;
|
||||
|
||||
@@ -231,6 +352,10 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
+ if (upgrade_remove_ancestorid_index_config() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,307 @@
|
||||
From f6dd2d6dc94290c85d221951b34792443bb08d80 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Detect and log index ordering mismatch during
|
||||
backend startup
|
||||
|
||||
Description:
|
||||
Add `ldbm_instance_check_index_config()` function that checks on-disk
|
||||
index data and logs a message in case of a mismatch with DSE config entry.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 269 ++++++++++++++++++++++++
|
||||
1 file changed, 269 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 6098e04fc..388dd6efb 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -248,6 +248,273 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
}
|
||||
|
||||
|
||||
+/*
|
||||
+ * Check if an index has integerOrderingMatch configured in DSE.
|
||||
+ *
|
||||
+ * This function performs an internal LDAP search to check if the index
|
||||
+ * configuration entry has nsMatchingRule: integerOrderingMatch.
|
||||
+ *
|
||||
+ * Parameters:
|
||||
+ * inst_name - backend instance name (e.g., "userRoot")
|
||||
+ * index_name - name of the index to check (e.g., "parentid", "ancestorid")
|
||||
+ *
|
||||
+ * Returns:
|
||||
+ * PR_TRUE if integerOrderingMatch is configured
|
||||
+ * PR_FALSE if not configured or index entry doesn't exist
|
||||
+ */
|
||||
+static PRBool
|
||||
+ldbm_instance_index_has_int_order_in_dse(const char *inst_name, const char *index_name)
|
||||
+{
|
||||
+ Slapi_PBlock *pb = NULL;
|
||||
+ Slapi_Entry **entries = NULL;
|
||||
+ char *idx_dn = NULL;
|
||||
+ PRBool has_int_order = PR_FALSE;
|
||||
+
|
||||
+ idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,cn=ldbm database,cn=plugins,cn=config",
|
||||
+ index_name, inst_name);
|
||||
+ if (idx_dn == NULL) {
|
||||
+ return PR_FALSE;
|
||||
+ }
|
||||
+
|
||||
+ pb = slapi_pblock_new();
|
||||
+ slapi_search_internal_set_pb(pb, idx_dn, LDAP_SCOPE_BASE,
|
||||
+ "(objectclass=nsIndex)", NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
|
||||
+
|
||||
+ if (entries && entries[0]) {
|
||||
+ Slapi_Attr *mr_attr = NULL;
|
||||
+ if (slapi_entry_attr_find(entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ int idx;
|
||||
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
+ idx != -1;
|
||||
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
+ const struct berval *bval = slapi_value_get_berval(sval);
|
||||
+ if (bval && bval->bv_val &&
|
||||
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
+ has_int_order = PR_TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return has_int_order;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check a system index for ordering mismatch between config and on-disk data.
|
||||
+ *
|
||||
+ * This function compares what's configured in DSE (nsMatchingRule) with
|
||||
+ * what's actually on disk. A mismatch can occur in two scenarios:
|
||||
+ * 1. Ordering rule is configured but disk has lexicographic order
|
||||
+ * (rule was added after index was created)
|
||||
+ * 2. No ordering rule configured but disk has integer order
|
||||
+ * (rule was removed after index was created with it)
|
||||
+ *
|
||||
+ * This function reads the first keys from the specified index and checks
|
||||
+ * if they are stored in lexicographic order (string: "1" < "10" < "2") or
|
||||
+ * integer order (numeric: "1" < "2" < "10").
|
||||
+ *
|
||||
+ * Parameters:
|
||||
+ * be - backend
|
||||
+ * index_name - name of the index to check (e.g., "parentid", "ancestorid")
|
||||
+ *
|
||||
+ */
|
||||
+static void
|
||||
+ldbm_instance_check_index_config(backend *be, const char *index_name)
|
||||
+{
|
||||
+ ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
+ struct attrinfo *ai = NULL;
|
||||
+ DB *db = NULL;
|
||||
+ DBC *dbc = NULL;
|
||||
+ DBT key;
|
||||
+ DBT data;
|
||||
+ int ret = 0;
|
||||
+ PRBool config_has_int_order = PR_FALSE;
|
||||
+ PRBool disk_has_int_order = PR_TRUE; /* Assume integer order until proven otherwise */
|
||||
+ ID prev_id = 0;
|
||||
+ int key_count = 0;
|
||||
+ PRBool first_key = PR_TRUE;
|
||||
+ PRBool found_ordering_evidence = PR_FALSE;
|
||||
+
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': checking %s index ordering...\n",
|
||||
+ inst->inst_name, index_name);
|
||||
+
|
||||
+ /* Check if integerOrderingMatch is configured in DSE */
|
||||
+ config_has_int_order = ldbm_instance_index_has_int_order_in_dse(inst->inst_name, index_name);
|
||||
+
|
||||
+ /* Get attrinfo for the index */
|
||||
+ ainfo_get(be, (char *)index_name, &ai);
|
||||
+ if (ai == NULL || strcmp(ai->ai_type, index_name) != 0) {
|
||||
+ /* No index config found */
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': no %s attrinfo found, skipping check\n",
|
||||
+ inst->inst_name, index_name);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* Open the index file */
|
||||
+ ret = dblayer_get_index_file(be, ai, &db, 0);
|
||||
+ if (ret != 0 || db == NULL) {
|
||||
+ /* Index file doesn't exist or can't be opened - this is fine for new instances */
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': could not open %s index file (ret=%d), skipping order check\n",
|
||||
+ inst->inst_name, index_name, ret);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* Create a cursor to read keys */
|
||||
+ ret = db->cursor(db, NULL, &dbc, 0);
|
||||
+ if (ret != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': could not create cursor on %s index (ret=%d)\n",
|
||||
+ inst->inst_name, index_name, ret);
|
||||
+ dblayer_release_index_file(be, ai, db);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ memset(&key, 0, sizeof(key));
|
||||
+ memset(&data, 0, sizeof(data));
|
||||
+ key.flags = DB_DBT_MALLOC;
|
||||
+ data.flags = DB_DBT_MALLOC;
|
||||
+
|
||||
+ /*
|
||||
+ * Read up to 100 unique keys and check their ordering.
|
||||
+ * With lexicographic ordering: "1" < "10" < "100" < "2" < "20" < "3"
|
||||
+ * With integer ordering: "1" < "2" < "3" < "10" < "20" < "100"
|
||||
+ *
|
||||
+ * If we find a case where prev_id > current_id (numerically), but the
|
||||
+ * keys are still in order (lexicographically), then the index uses
|
||||
+ * lexicographic ordering.
|
||||
+ */
|
||||
+ while (key_count < 100) {
|
||||
+ ID current_id;
|
||||
+
|
||||
+ slapi_ch_free(&(key.data));
|
||||
+ slapi_ch_free(&(data.data));
|
||||
+ key.size = 0;
|
||||
+ data.size = 0;
|
||||
+
|
||||
+ ret = dbc->c_get(dbc, &key, &data, first_key ? DB_FIRST : DB_NEXT_NODUP);
|
||||
+ first_key = PR_FALSE; /* Always advance cursor on next iteration */
|
||||
+ if (ret != 0) {
|
||||
+ break; /* No more keys or error */
|
||||
+ }
|
||||
+
|
||||
+ /* Skip non-equality keys */
|
||||
+ if (key.size < 2 || *(char *)key.data != EQ_PREFIX) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Parse the ID from the key (format: "=<id>") */
|
||||
+ current_id = (ID)strtoul((char *)key.data + 1, NULL, 10);
|
||||
+ if (current_id == 0) {
|
||||
+ continue; /* Invalid ID, skip */
|
||||
+ }
|
||||
+
|
||||
+ key_count++;
|
||||
+
|
||||
+ if (prev_id != 0) {
|
||||
+ /*
|
||||
+ * Check ordering: if prev_id > current_id numerically,
|
||||
+ * but we got this key after prev in DB order, then
|
||||
+ * the index is using lexicographic ordering.
|
||||
+ *
|
||||
+ * Example: if we see "10" followed by "2", that's lexicographic
|
||||
+ * because "10" < "2" as strings, but 10 > 2 as integers.
|
||||
+ */
|
||||
+ if (prev_id > current_id) {
|
||||
+ /* Found evidence of lexicographic ordering */
|
||||
+ disk_has_int_order = PR_FALSE;
|
||||
+ found_ordering_evidence = PR_TRUE;
|
||||
+ break;
|
||||
+ } else if (prev_id < current_id) {
|
||||
+ /*
|
||||
+ * This is consistent with integer ordering, but we need
|
||||
+ * to find a case that proves lexicographic ordering.
|
||||
+ * For example, seeing "1" followed by "2" is ambiguous,
|
||||
+ * but seeing "1" followed by "10" (not "2") proves lexicographic.
|
||||
+ *
|
||||
+ * A definitive test: if we see an ID followed by a smaller
|
||||
+ * ID, that's lexicographic. If all IDs are strictly increasing,
|
||||
+ * it could be either (or the index only has sequential IDs).
|
||||
+ */
|
||||
+ found_ordering_evidence = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
+ prev_id = current_id;
|
||||
+ }
|
||||
+
|
||||
+ /* Close the cursor and free values */
|
||||
+ slapi_ch_free(&(key.data));
|
||||
+ slapi_ch_free(&(data.data));
|
||||
+ dbc->c_close(dbc);
|
||||
+
|
||||
+ /* Release the index file */
|
||||
+ dblayer_release_index_file(be, ai, db);
|
||||
+
|
||||
+ /*
|
||||
+ * Report findings and check for config/disk mismatch.
|
||||
+ * Log an error if there's a discrepancy between what's configured
|
||||
+ * in DSE and what's actually on disk.
|
||||
+ */
|
||||
+ if (!found_ordering_evidence) {
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': %s index ordering check - "
|
||||
+ "could not determine on-disk ordering (index may be empty or have sequential IDs only). "
|
||||
+ "Config has integerOrderingMatch: %s\n",
|
||||
+ inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
|
||||
+ } else if (config_has_int_order && !disk_has_int_order) {
|
||||
+ /* Config expects integer ordering, but disk has lexicographic - MISMATCH */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': MISMATCH - %s index has integerOrderingMatch configured, "
|
||||
+ "but on-disk data uses lexicographic ordering. "
|
||||
+ "This will cause searches to return incorrect or incomplete results. "
|
||||
+ "Please reindex the %s attribute: "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s\n",
|
||||
+ inst->inst_name, index_name, index_name, index_name, inst->inst_name);
|
||||
+ } else if (!config_has_int_order && disk_has_int_order) {
|
||||
+ /* Config expects lexicographic ordering, but disk has integer - MISMATCH */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': MISMATCH - %s index does not have integerOrderingMatch configured, "
|
||||
+ "but on-disk data uses integer ordering. "
|
||||
+ "This will cause searches to return incorrect or incomplete results. "
|
||||
+ "Please reindex the %s attribute: "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s\n",
|
||||
+ inst->inst_name, index_name, index_name, index_name, inst->inst_name);
|
||||
+ } else {
|
||||
+ /* Config and disk ordering match - no action needed */
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': %s index ordering check passed - "
|
||||
+ "config has integerOrderingMatch: %s, on-disk data matches.\n",
|
||||
+ inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check system indexes for ordering mismatches.
|
||||
+ * If a mismatch is detected, log an error advising the administrator
|
||||
+ * to reindex the affected attribute.
|
||||
+ *
|
||||
+ * Note: We only check parentid here. The ancestorid index is a special
|
||||
+ * system index that has no DSE config entry - its ordering is hardcoded
|
||||
+ * in ldbm_instance_init_config_entry() and cannot be changed by users.
|
||||
+ */
|
||||
+static void
|
||||
+ldbm_instance_check_indexes(backend *be)
|
||||
+{
|
||||
+ /* Check parentid index */
|
||||
+ ldbm_instance_check_index_config(be, LDBM_PARENTID_STR);
|
||||
+}
|
||||
+
|
||||
/* Starts a backend instance */
|
||||
int
|
||||
ldbm_instance_start(backend *be)
|
||||
@@ -316,6 +583,8 @@ ldbm_instance_startall(struct ldbminfo *li)
|
||||
ldbm_instance_register_modify_callback(inst);
|
||||
vlv_init(inst);
|
||||
slapi_mtn_be_started(inst->inst_be);
|
||||
+ /* Check index configuration for potential issues */
|
||||
+ ldbm_instance_check_indexes(inst->inst_be);
|
||||
}
|
||||
if (slapi_exist_referral(inst->inst_be)) {
|
||||
slapi_be_set_flag(inst->inst_be, SLAPI_BE_FLAG_CONTAINS_REFERRAL);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@
|
||||
From 384dbcaba418afe9e4de798d16a68d0468632a57 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 13:08:40 +0100
|
||||
Subject: [PATCH] Issue 7223 - Use lexicographical order for ancestorid
|
||||
|
||||
Description:
|
||||
`ldbm_instance_create_default_indexes()` configured ancestorid with
|
||||
integerOrderingMatch in the in-memory attrinfo, but ancestorid on disk
|
||||
might be using lexicographic ordering (data before the upgrade or after
|
||||
ldif2db import).
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 388dd6efb..f5342b99a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -239,7 +239,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0);
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,537 @@
|
||||
From 67c3183380888f4af093b546e717f3e7451a41d6 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 18 Feb 2026 09:26:57 +0100
|
||||
Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for
|
||||
parentid (#7264)
|
||||
|
||||
Description:
|
||||
integerOrderingMatch was introduced as a requirement for parentid and
|
||||
ancestorid indexes for performance reasons. But after #7096 the order
|
||||
for parentid doesn't make a lot of difference.
|
||||
|
||||
Fix Description:
|
||||
* Remove integerOrderingMatch requirement for parentid.
|
||||
* Read only first 100 keys from dbscan in index ordering check
|
||||
* Do not run dsctl index-check during RPM upgrade
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/pull/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 83 ++++----------
|
||||
ldap/servers/slapd/upgrade.c | 106 ------------------
|
||||
rpm/389-ds-base.spec.in | 3 -
|
||||
src/lib389/lib389/backend.py | 5 +-
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++--------
|
||||
5 files changed, 73 insertions(+), 223 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index ce86239e5..088b48587 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -179,7 +179,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
|
||||
|
||||
def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
|
||||
+ """Check that healthcheck does NOT report DSBLE0007 when parentId index is missing integerOrderingMatch.
|
||||
+ Both lexicographic and integer orderings are valid for parentid.
|
||||
|
||||
:id: 7ffa71db-8995-430a-bed8-59bce944221c
|
||||
:setup: Standalone instance
|
||||
@@ -189,19 +190,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
3. Use healthcheck without --json option
|
||||
4. Use healthcheck with --json option
|
||||
5. Re-add the matching rule
|
||||
- 6. Use healthcheck without --json option
|
||||
- 7. Use healthcheck with --json option
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
- 3. healthcheck reports DSBLE0007 code and related details
|
||||
- 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 3. healthcheck reports no issues found
|
||||
+ 4. healthcheck reports no issues found
|
||||
5. Success
|
||||
- 6. healthcheck reports no issues found
|
||||
- 7. healthcheck reports no issues found
|
||||
"""
|
||||
|
||||
- RET_CODE = "DSBLE0007"
|
||||
PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
|
||||
standalone = topology_st.standalone
|
||||
@@ -210,16 +206,13 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
log.info("Re-add the integerOrderingMatch matching rule")
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
-
|
||||
|
||||
def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
|
||||
@@ -908,7 +901,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
|
||||
|
||||
|
||||
def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
- """Check if dsctl index-check --fix adds missing integerOrderingMatch
|
||||
+ """Check that removing integerOrderingMatch from parentid config is not
|
||||
+ flagged as an issue when disk ordering cannot be determined.
|
||||
+ Both lexicographic and integer orderings are valid for parentid.
|
||||
|
||||
:id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
|
||||
:setup: Standalone instance
|
||||
@@ -916,18 +911,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
1. Create DS instance
|
||||
2. Stop the server
|
||||
3. Remove integerOrderingMatch from parentid index using DSEldif
|
||||
- 4. Run dsctl index-check (should detect issue)
|
||||
- 5. Run dsctl index-check --fix
|
||||
- 6. Verify integerOrderingMatch was added back
|
||||
- 7. Start the server
|
||||
+ 4. Run dsctl index-check (should NOT detect issue since disk ordering is unknown)
|
||||
+ 5. Start the server
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
3. Success
|
||||
- 4. index-check returns False and detects missing matching rule
|
||||
- 5. index-check returns True after fix
|
||||
- 6. integerOrderingMatch is present
|
||||
- 7. Success
|
||||
+ 4. index-check returns True (no issues, disk ordering unknown)
|
||||
+ 5. Success
|
||||
"""
|
||||
from lib389.cli_ctl.dbtasks import dbtasks_index_check
|
||||
from lib389.dseldif import DSEldif
|
||||
@@ -961,34 +952,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
f"integerOrderingMatch should be removed, but found: {mr}"
|
||||
log.info("integerOrderingMatch removed from parentid index")
|
||||
|
||||
- log.info("Run index-check without --fix (should detect issue)")
|
||||
+ log.info("Run index-check (should NOT detect issue - disk ordering unknown)")
|
||||
args = FakeArgs()
|
||||
args.backend = "userRoot"
|
||||
args.fix = False
|
||||
|
||||
result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
|
||||
- assert result is False, "index-check should detect missing matching rule"
|
||||
- assert topology_st.logcap.contains("missing integerOrderingMatch")
|
||||
+ assert result is True, \
|
||||
+ "index-check should not flag missing integerOrderingMatch when disk ordering is unknown"
|
||||
+ assert topology_st.logcap.contains("could not determine disk ordering")
|
||||
topology_st.logcap.flush()
|
||||
|
||||
- log.info("Run index-check with --fix")
|
||||
- args.fix = True
|
||||
- result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
|
||||
- assert result is True, "index-check --fix should succeed"
|
||||
- assert topology_st.logcap.contains("integerOrderingMatch")
|
||||
- topology_st.logcap.flush()
|
||||
-
|
||||
- log.info("Verify integerOrderingMatch was added back")
|
||||
- dse_ldif = DSEldif(standalone) # Reload to get fresh data
|
||||
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
|
||||
- assert matching_rules is not None, "nsMatchingRule should be present"
|
||||
- found_int_order = False
|
||||
- for mr in matching_rules:
|
||||
- if "integerorderingmatch" in mr.lower():
|
||||
- found_int_order = True
|
||||
- break
|
||||
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
|
||||
- log.info("integerOrderingMatch successfully added back")
|
||||
+ log.info("Restore integerOrderingMatch and start the server")
|
||||
+ dse_ldif = DSEldif(standalone)
|
||||
+ dse_ldif.add(parentid_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
|
||||
log.info("Start the server")
|
||||
standalone.start()
|
||||
@@ -1078,7 +1055,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
:steps:
|
||||
1. Create DS instance
|
||||
2. Stop the server
|
||||
- 3. Add multiple issues: scanlimit, ancestorid config, missing matching rule
|
||||
+ 3. Add multiple issues: scanlimit and ancestorid config
|
||||
4. Run dsctl index-check (should detect all issues)
|
||||
5. Run dsctl index-check --fix
|
||||
6. Verify all issues were fixed
|
||||
@@ -1120,14 +1097,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
]
|
||||
dse_ldif.add_entry(ancestorid_entry)
|
||||
|
||||
- log.info("Add issue 3: Remove integerOrderingMatch from parentid")
|
||||
- dse_ldif = DSEldif(standalone) # Reload
|
||||
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
|
||||
- if matching_rules:
|
||||
- for mr in matching_rules:
|
||||
- if "integerorderingmatch" in mr.lower():
|
||||
- dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
|
||||
-
|
||||
log.info("Run index-check without --fix (should detect all issues)")
|
||||
args = FakeArgs()
|
||||
args.backend = "userRoot"
|
||||
@@ -1158,16 +1127,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
|
||||
assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
|
||||
|
||||
- # Check matching rule added back
|
||||
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
|
||||
- found_int_order = False
|
||||
- if matching_rules:
|
||||
- for mr in matching_rules:
|
||||
- if "integerorderingmatch" in mr.lower():
|
||||
- found_int_order = True
|
||||
- break
|
||||
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
|
||||
-
|
||||
log.info("All issues verified as fixed")
|
||||
|
||||
log.info("Run index-check again to confirm all clear")
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index aa51e72d2..4eb09ca38 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -243,108 +243,6 @@ upgrade_remove_ancestorid_index_config(void)
|
||||
return uresult;
|
||||
}
|
||||
|
||||
-/*
|
||||
- * Check if parentid indexes are missing the integerOrderingMatch
|
||||
- * matching rule.
|
||||
- *
|
||||
- * This function logs a warning if we detect this condition, advising
|
||||
- * the administrator to reindex the affected attributes.
|
||||
- */
|
||||
-static upgrade_status
|
||||
-upgrade_check_id_index_matching_rule(void)
|
||||
-{
|
||||
- struct slapi_pblock *pb = slapi_pblock_new();
|
||||
- Slapi_Entry **backends = NULL;
|
||||
- const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
- const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
- const char *attrs_to_check[] = {"parentid", NULL};
|
||||
- upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
-
|
||||
- /* Search for all backend instances */
|
||||
- slapi_search_internal_set_pb(
|
||||
- pb, be_base_dn,
|
||||
- LDAP_SCOPE_ONELEVEL,
|
||||
- be_filter, NULL, 0, NULL, NULL,
|
||||
- plugin_get_default_component_id(), 0);
|
||||
- slapi_search_internal_pb(pb);
|
||||
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
-
|
||||
- if (backends) {
|
||||
- for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
- const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
- const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
- if (!be_dn || !be_name) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- /* Check each attribute that should have integerOrderingMatch */
|
||||
- for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
- const char *attr_name = attrs_to_check[attr_idx];
|
||||
- struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
- Slapi_Entry **idx_entries = NULL;
|
||||
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
|
||||
- attr_name, be_dn);
|
||||
- char *idx_filter = "(objectclass=nsIndex)";
|
||||
- PRBool has_matching_rule = PR_FALSE;
|
||||
-
|
||||
- if (!idx_dn) {
|
||||
- slapi_pblock_destroy(idx_pb);
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- slapi_search_internal_set_pb(
|
||||
- idx_pb, idx_dn,
|
||||
- LDAP_SCOPE_BASE,
|
||||
- idx_filter, NULL, 0, NULL, NULL,
|
||||
- plugin_get_default_component_id(), 0);
|
||||
- slapi_search_internal_pb(idx_pb);
|
||||
- slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
-
|
||||
- if (idx_entries && idx_entries[0]) {
|
||||
- /* Index exists, check if it has integerOrderingMatch */
|
||||
- Slapi_Attr *mr_attr = NULL;
|
||||
- if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
- Slapi_Value *sval = NULL;
|
||||
- int idx;
|
||||
- for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
- idx != -1;
|
||||
- idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
- const struct berval *bval = slapi_value_get_berval(sval);
|
||||
- if (bval && bval->bv_val &&
|
||||
- strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
- has_matching_rule = PR_TRUE;
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- if (!has_matching_rule) {
|
||||
- /* Index exists but doesn't have integerOrderingMatch, log a warning */
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
|
||||
- "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
|
||||
- "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
|
||||
- "To fix this, add the matching rule and reindex: "
|
||||
- "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
|
||||
- "dsconf <instance> backend index reindex --attr %s %s. "
|
||||
- "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
|
||||
- "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
|
||||
- attr_name, be_name, attr_name, be_name, attr_name, be_name);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- slapi_ch_free_string(&idx_dn);
|
||||
- slapi_free_search_results_internal(idx_pb);
|
||||
- slapi_pblock_destroy(idx_pb);
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- slapi_free_search_results_internal(pb);
|
||||
- slapi_pblock_destroy(pb);
|
||||
-
|
||||
- return uresult;
|
||||
-}
|
||||
-
|
||||
upgrade_status
|
||||
upgrade_server(void)
|
||||
{
|
||||
@@ -356,9 +254,5 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
- if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
- return UPGRADE_FAILURE;
|
||||
- }
|
||||
-
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index 16b8d14c5..59e3a748f 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -529,9 +529,6 @@ for dir in "$instbase"/slapd-* ; do
|
||||
else
|
||||
echo "instance $inst is not running" >> "$output" 2>&1 || :
|
||||
fi
|
||||
- # Run index-check on all instances (running or not)
|
||||
- # This fixes index ordering mismatches from older versions
|
||||
- dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
|
||||
ninst=$((ninst + 1))
|
||||
done
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 4babf6850..376596cd6 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -541,9 +541,10 @@ class Backend(DSLdapObject):
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
# Note: entryrdn and ancestorid are internal system indexes that are not
|
||||
# exposed in cn=config - they are managed internally by the server.
|
||||
- # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
|
||||
+ # parentid works correctly with both lexicographic and integer ordering,
|
||||
+ # so integerOrderingMatch is not required.
|
||||
expected_system_indexes = {
|
||||
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ 'parentid': {'types': ['eq'], 'matching_rule': None},
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
index 16da966d1..ea8a00cc3 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
@@ -10,6 +10,7 @@
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
+import signal
|
||||
import subprocess
|
||||
from enum import Enum
|
||||
from lib389._constants import TaskWarning
|
||||
@@ -271,45 +272,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
|
||||
if not index_file:
|
||||
return IndexOrdering.UNKNOWN
|
||||
|
||||
+ # Only read the first 100 lines from dbscan to avoid scanning the
|
||||
+ # entire index (which can take hours on large databases).
|
||||
try:
|
||||
- result = subprocess.run(
|
||||
+ proc = subprocess.Popen(
|
||||
[dbscan_path, "-f", index_file],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
- timeout=60,
|
||||
)
|
||||
|
||||
- if result.returncode != 0:
|
||||
- log.warning(" dbscan returned non-zero exit code for %s", index_file)
|
||||
- return IndexOrdering.UNKNOWN
|
||||
-
|
||||
- # Parse keys from dbscan output
|
||||
keys = []
|
||||
- for line in result.stdout.split("\n"):
|
||||
+ line_count = 0
|
||||
+ assert proc.stdout is not None
|
||||
+ for line in proc.stdout:
|
||||
+ line_count += 1
|
||||
+ if line_count > 100:
|
||||
+ break
|
||||
line = line.strip()
|
||||
if line.startswith("="):
|
||||
match = re.match(r"^=(\d+)", line)
|
||||
if match:
|
||||
keys.append(int(match.group(1)))
|
||||
|
||||
+ proc.terminate()
|
||||
+ try:
|
||||
+ proc.wait(timeout=5)
|
||||
+ except subprocess.TimeoutExpired:
|
||||
+ proc.kill()
|
||||
+ proc.wait()
|
||||
+
|
||||
+ if proc.returncode not in (0, -signal.SIGTERM):
|
||||
+ log.warning(" dbscan returned non-zero exit code for %s", index_file)
|
||||
+ return IndexOrdering.UNKNOWN
|
||||
+
|
||||
if len(keys) < 2:
|
||||
return IndexOrdering.UNKNOWN
|
||||
|
||||
# Check if keys are in integer order by looking for decreasing numeric values
|
||||
# (which would indicate lexicographic ordering, e.g., "3" < "30" < "4")
|
||||
prev_id = keys[0]
|
||||
- for i in range(1, min(len(keys), 100)):
|
||||
- current_id = keys[i]
|
||||
+ for current_id in keys[1:]:
|
||||
if prev_id > current_id:
|
||||
return IndexOrdering.LEXICOGRAPHIC
|
||||
prev_id = current_id
|
||||
|
||||
return IndexOrdering.INTEGER
|
||||
|
||||
- except subprocess.TimeoutExpired:
|
||||
- log.warning(" dbscan timed out for %s", index_file)
|
||||
- return IndexOrdering.UNKNOWN
|
||||
except OSError as e:
|
||||
log.warning(" Error running dbscan: %s", e)
|
||||
return IndexOrdering.UNKNOWN
|
||||
@@ -383,8 +392,7 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
# Track all issues found
|
||||
all_ok = True
|
||||
- mismatches = [] # (backend, index_name) tuples needing reindex
|
||||
- missing_matching_rules = [] # (backend, index_name) tuples missing integerOrderingMatch
|
||||
+ config_fixes = [] # (backend, index_name, action) tuples: action is "add_mr" or "remove_mr"
|
||||
scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit
|
||||
ancestorid_configs_to_remove = [] # backend names with ancestorid config entries
|
||||
remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes
|
||||
@@ -417,13 +425,6 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
if disk_ordering == IndexOrdering.UNKNOWN:
|
||||
log.info(" %s - could not determine disk ordering, skipping", index_name)
|
||||
- # For parentid, still check if matching rule is missing
|
||||
- if index_name == "parentid":
|
||||
- config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
|
||||
- if not config_has_int_order:
|
||||
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
|
||||
- missing_matching_rules.append((backend, index_name))
|
||||
- all_ok = False
|
||||
continue
|
||||
|
||||
config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
|
||||
@@ -431,18 +432,15 @@ def dbtasks_index_check(inst, log, args):
|
||||
log.info(" %s - config: %s, disk: %s",
|
||||
index_name, config_desc, disk_ordering.value)
|
||||
|
||||
- # For parentid, the desired state is always integer ordering
|
||||
+ # Both orderings are valid for parentid, but config must match disk.
|
||||
if index_name == "parentid":
|
||||
- if not config_has_int_order:
|
||||
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
|
||||
- if (backend, index_name) not in missing_matching_rules:
|
||||
- missing_matching_rules.append((backend, index_name))
|
||||
+ if config_has_int_order and disk_ordering == IndexOrdering.LEXICOGRAPHIC:
|
||||
+ log.warning(" %s - MISMATCH: config has integerOrderingMatch but disk is lexicographic", index_name)
|
||||
+ config_fixes.append((backend, index_name, "remove_mr"))
|
||||
all_ok = False
|
||||
-
|
||||
- if disk_ordering == IndexOrdering.LEXICOGRAPHIC:
|
||||
- log.warning(" %s - disk ordering is lexicographic, needs reindex", index_name)
|
||||
- if (backend, index_name) not in mismatches:
|
||||
- mismatches.append((backend, index_name))
|
||||
+ elif not config_has_int_order and disk_ordering == IndexOrdering.INTEGER:
|
||||
+ log.warning(" %s - MISMATCH: config is lexicographic but disk has integer ordering", index_name)
|
||||
+ config_fixes.append((backend, index_name, "add_mr"))
|
||||
all_ok = False
|
||||
|
||||
# Handle issues
|
||||
@@ -488,26 +486,27 @@ def dbtasks_index_check(inst, log, args):
|
||||
log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e)
|
||||
return False
|
||||
|
||||
- # Add missing matching rules to dse.ldif
|
||||
- for backend, index_name in missing_matching_rules:
|
||||
+ # Fix config-vs-disk ordering mismatches by adjusting config to match disk
|
||||
+ for backend, index_name, action in config_fixes:
|
||||
index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
|
||||
index_name, backend
|
||||
)
|
||||
- log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
|
||||
- try:
|
||||
- dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
- log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
|
||||
- except Exception as e:
|
||||
- log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
|
||||
- return False
|
||||
-
|
||||
- # Reindex indexes with disk ordering issues
|
||||
- for backend, index_name in mismatches:
|
||||
- log.info(" Reindexing %s in backend %s...", index_name, backend)
|
||||
- if not inst.db2index(bename=backend, attrs=[index_name]):
|
||||
- log.error(" Failed to reindex %s", index_name)
|
||||
- return False
|
||||
- log.info(" Reindex of %s completed successfully", index_name)
|
||||
+ if action == "add_mr":
|
||||
+ log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
|
||||
+ try:
|
||||
+ dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
+ log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
|
||||
+ except Exception as e:
|
||||
+ log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
|
||||
+ return False
|
||||
+ elif action == "remove_mr":
|
||||
+ log.info(" Removing integerOrderingMatch from %s in backend %s...", index_name, backend)
|
||||
+ try:
|
||||
+ dse_ldif.delete(index_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
+ log.info(" Removed integerOrderingMatch from %s", index_name)
|
||||
+ except Exception as e:
|
||||
+ log.error(" Failed to remove integerOrderingMatch from %s: %s", index_name, e)
|
||||
+ return False
|
||||
|
||||
log.info("All issues fixed")
|
||||
return True
|
||||
@@ -572,5 +571,5 @@ def create_parser(subcommands):
|
||||
index_check_parser.add_argument('backend', nargs='?', default=None,
|
||||
help="Backend to check. If not specified, all backends are checked.")
|
||||
index_check_parser.add_argument('--fix', action='store_true', default=False,
|
||||
- help="Fix mismatches by reindexing affected indexes")
|
||||
+ help="Fix mismatches by adjusting config to match on-disk data")
|
||||
index_check_parser.set_defaults(func=dbtasks_index_check)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -52,7 +52,7 @@ ExcludeArch: i686
|
||||
Summary: 389 Directory Server (base)
|
||||
Name: 389-ds-base
|
||||
Version: 1.4.3.39
|
||||
Release: %{?relprefix}20%{?prerel}%{?dist}
|
||||
Release: %{?relprefix}22%{?prerel}%{?dist}
|
||||
License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (0BSD OR Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR BSD-2-Clause OR MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT OR Zlib) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND BSD-3-Clause AND MIT AND MPL-2.0
|
||||
URL: https://www.port389.org
|
||||
Group: System Environment/Daemons
|
||||
@ -373,6 +373,16 @@ Patch72: 0072-Issue-6966-2nd-On-large-DB-unlimited-IDL-scan-limit-.patc
|
||||
Patch73: 0073-Issue-7056-DSBLE0007-doesn-t-generate-remediation-st.patch
|
||||
Patch74: 0074-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch
|
||||
Patch75: 0075-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch
|
||||
Patch76: 0076-Issue-7071-search-filter-cn-dn-groups-no-longer-retu.patch
|
||||
Patch77: 0077-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
|
||||
Patch78: 0078-Issue-7223-Revert-index-scan-limits-for-system-index.patch
|
||||
Patch79: 0079-Issue-7223-Backport-upgrade-infrastructure-from-main.patch
|
||||
Patch80: 0080-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
|
||||
Patch81: 0081-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
|
||||
Patch82: 0082-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
|
||||
Patch83: 0083-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
|
||||
Patch84: 0084-Issue-7223-Use-lexicographical-order-for-ancestorid.patch
|
||||
Patch85: 0085-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
|
||||
|
||||
|
||||
#Patch100: cargo.patch
|
||||
@ -681,7 +691,42 @@ if ! getent passwd $USERNAME >/dev/null ; then
|
||||
fi
|
||||
|
||||
# Reload our sysctl before we restart (if we can)
|
||||
sysctl --system &> $output; true
|
||||
sysctl --system &> "$output"; true
|
||||
|
||||
# Gather running instances, stop them, run index-check, then restart
|
||||
instbase="%{_sysconfdir}/%{pkgname}"
|
||||
instances=""
|
||||
ninst=0
|
||||
|
||||
for dir in "$instbase"/slapd-* ; do
|
||||
echo "dir = $dir" >> "$output" 2>&1 || :
|
||||
if [ ! -d "$dir" ] ; then continue ; fi
|
||||
case "$dir" in *.removed) continue ;; esac
|
||||
basename=$(basename "$dir")
|
||||
inst="%{pkgname}@${basename#slapd-}"
|
||||
inst_name="${basename#slapd-}"
|
||||
echo "found instance $inst - getting status" >> "$output" 2>&1 || :
|
||||
if /bin/systemctl -q is-active "$inst" ; then
|
||||
echo "instance $inst is running - stopping for upgrade" >> "$output" 2>&1 || :
|
||||
instances="$instances $inst"
|
||||
/bin/systemctl stop "$inst" >> "$output" 2>&1 || :
|
||||
else
|
||||
echo "instance $inst is not running" >> "$output" 2>&1 || :
|
||||
fi
|
||||
ninst=$((ninst + 1))
|
||||
done
|
||||
|
||||
if [ $ninst -eq 0 ] ; then
|
||||
echo "no instances to upgrade" >> "$output" 2>&1 || :
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Restart previously running instances
|
||||
for inst in $instances ; do
|
||||
echo "starting instance $inst" >> "$output" 2>&1 || :
|
||||
/bin/systemctl start "$inst" >> "$output" 2>&1 || :
|
||||
done
|
||||
|
||||
|
||||
%preun
|
||||
if [ $1 -eq 0 ]; then # Final removal
|
||||
@ -998,6 +1043,14 @@ exit 0
|
||||
%doc README.md
|
||||
|
||||
%changelog
|
||||
* Wed Feb 18 2026 Viktor Ashirov <vashirov@redhat.com> - 1.4.3.39-22
|
||||
- Resolves: RHEL-148485 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication [rhel-8.10.z]
|
||||
|
||||
* Fri Jan 23 2026 Arun Bansal <arbansal@redhat.com> - 1.4.3.39-21
|
||||
- Resolves: RHEL-141419 - (&(cn:dn:=groups)) no longer returns results [rhel-8.10.z]
|
||||
- Resolves: RHEL-140272 - ipa-healthcheck is complaining about missing or
|
||||
incorrectly configured system indexes. [rhel-8.10.z]
|
||||
|
||||
* Tue Jan 13 2026 Arun Bansal <arbansal@redhat.com> - 1.4.3.39-20
|
||||
- Resolves: RHEL-140086 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication [rhel-8.10.z]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user