Compare commits
No commits in common. "c8-stream-1.4" and "c9-beta" have entirely different histories.
c8-stream-
...
c9-beta
@ -1,3 +1,3 @@
|
||||
bd9aab32d9cbf9231058d585479813f3420dc872 SOURCES/389-ds-base-1.4.3.39.tar.bz2
|
||||
4c73bf0e828d8ae0b1ba2f2a4bbaa7f450af9f3d SOURCES/389-ds-base-2.9.0.tar.bz2
|
||||
1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
8d3275209f2f8e1a69053340930ad1fb037d61fb SOURCES/vendor-1.4.3.39-3.tar.gz
|
||||
005e09ff6e01c2a5907b3ebf9c167ce2f1c2053c SOURCES/vendor-2.9.0-1.tar.gz
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
SOURCES/389-ds-base-1.4.3.39.tar.bz2
|
||||
SOURCES/389-ds-base-2.9.0.tar.bz2
|
||||
SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
SOURCES/vendor-1.4.3.39-3.tar.gz
|
||||
SOURCES/vendor-2.9.0-1.tar.gz
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
From 527934c27234e923cbf38157e935e0606da21b26 Mon Sep 17 00:00:00 2001
|
||||
From 71cda087462c7cef744555676603c7c5fde3ca55 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 3 Jun 2026 17:52:09 -0400
|
||||
Subject: [PATCH 2/3] Issue 7554 - deref plugin null pointer dereference if
|
||||
Subject: [PATCH 1/8] Issue 7554 - deref plugin null pointer dereference if
|
||||
ber_init fails
|
||||
|
||||
Description:
|
||||
@ -41,5 +41,5 @@ index fc1c10f71..fc157f6d6 100644
|
||||
(tag != LBER_ERROR) && (tag != LBER_END_OF_SEQORSET);
|
||||
tag = ber_next_element(ber, &len, last)) {
|
||||
--
|
||||
2.55.0
|
||||
2.54.0
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
From dddb14210b402f317e566b6387c76a8e659bf7fa Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 14 Feb 2023 13:34:10 +0100
|
||||
Subject: [PATCH 1/2] issue 5647 - covscan: memory leak in audit log when
|
||||
adding entries (#5650)
|
||||
|
||||
covscan reported an issue about "vals" variable in auditlog.c:231 and indeed a charray_free is missing.
|
||||
Issue: 5647
|
||||
Reviewed by: @mreynolds389, @droideck
|
||||
---
|
||||
ldap/servers/slapd/auditlog.c | 71 +++++++++++++++++++----------------
|
||||
1 file changed, 38 insertions(+), 33 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 68cbc674d..3128e0497 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -177,6 +177,40 @@ write_auditfail_log_entry(Slapi_PBlock *pb)
|
||||
slapi_ch_free_string(&audit_config);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Write the attribute values to the audit log as "comments"
|
||||
+ *
|
||||
+ * Slapi_Attr *entry - the attribute begin logged.
|
||||
+ * char *attrname - the attribute name.
|
||||
+ * lenstr *l - the audit log buffer
|
||||
+ *
|
||||
+ * Resulting output in the log:
|
||||
+ *
|
||||
+ * #ATTR: VALUE
|
||||
+ * #ATTR: VALUE
|
||||
+ */
|
||||
+static void
|
||||
+log_entry_attr(Slapi_Attr *entry_attr, char *attrname, lenstr *l)
|
||||
+{
|
||||
+ Slapi_Value **vals = attr_get_present_values(entry_attr);
|
||||
+ for(size_t i = 0; vals && vals[i]; i++) {
|
||||
+ char log_val[256] = "";
|
||||
+ const struct berval *bv = slapi_value_get_berval(vals[i]);
|
||||
+ if (bv->bv_len >= 256) {
|
||||
+ strncpy(log_val, bv->bv_val, 252);
|
||||
+ strcpy(log_val+252, "...");
|
||||
+ } else {
|
||||
+ strncpy(log_val, bv->bv_val, bv->bv_len);
|
||||
+ log_val[bv->bv_len] = 0;
|
||||
+ }
|
||||
+ addlenstr(l, "#");
|
||||
+ addlenstr(l, attrname);
|
||||
+ addlenstr(l, ": ");
|
||||
+ addlenstr(l, log_val);
|
||||
+ addlenstr(l, "\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Write "requested" attributes from the entry to the audit log as "comments"
|
||||
*
|
||||
@@ -212,21 +246,9 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
for (req_attr = ldap_utf8strtok_r(display_attrs, ", ", &last); req_attr;
|
||||
req_attr = ldap_utf8strtok_r(NULL, ", ", &last))
|
||||
{
|
||||
- char **vals = slapi_entry_attr_get_charray(entry, req_attr);
|
||||
- for(size_t i = 0; vals && vals[i]; i++) {
|
||||
- char log_val[256] = {0};
|
||||
-
|
||||
- if (strlen(vals[i]) > 256) {
|
||||
- strncpy(log_val, vals[i], 252);
|
||||
- strcat(log_val, "...");
|
||||
- } else {
|
||||
- strcpy(log_val, vals[i]);
|
||||
- }
|
||||
- addlenstr(l, "#");
|
||||
- addlenstr(l, req_attr);
|
||||
- addlenstr(l, ": ");
|
||||
- addlenstr(l, log_val);
|
||||
- addlenstr(l, "\n");
|
||||
+ slapi_entry_attr_find(entry, req_attr, &entry_attr);
|
||||
+ if (entry_attr) {
|
||||
+ log_entry_attr(entry_attr, req_attr, l);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -234,7 +256,6 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
for (; entry_attr; entry_attr = entry_attr->a_next) {
|
||||
Slapi_Value **vals = attr_get_present_values(entry_attr);
|
||||
char *attr = NULL;
|
||||
- const char *val = NULL;
|
||||
|
||||
slapi_attr_get_type(entry_attr, &attr);
|
||||
if (strcmp(attr, PSEUDO_ATTR_UNHASHEDUSERPASSWORD) == 0) {
|
||||
@@ -251,23 +272,7 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
addlenstr(l, ": ****************************\n");
|
||||
continue;
|
||||
}
|
||||
-
|
||||
- for(size_t i = 0; vals && vals[i]; i++) {
|
||||
- char log_val[256] = {0};
|
||||
-
|
||||
- val = slapi_value_get_string(vals[i]);
|
||||
- if (strlen(val) > 256) {
|
||||
- strncpy(log_val, val, 252);
|
||||
- strcat(log_val, "...");
|
||||
- } else {
|
||||
- strcpy(log_val, val);
|
||||
- }
|
||||
- addlenstr(l, "#");
|
||||
- addlenstr(l, attr);
|
||||
- addlenstr(l, ": ");
|
||||
- addlenstr(l, log_val);
|
||||
- addlenstr(l, "\n");
|
||||
- }
|
||||
+ log_entry_attr(entry_attr, attr, l);
|
||||
}
|
||||
}
|
||||
slapi_ch_free_string(&display_attrs);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
From d42d7eefa937d4f12e21929210786274f9c8e849 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Thu, 4 Jun 2026 19:21:30 -0700
|
||||
Subject: [PATCH 2/8] Issue 3555 - UI - Fix audit issue with npm -
|
||||
brace-expansion (#7556)
|
||||
|
||||
Description: Run npm audit fix to address the vulnerability
|
||||
in brace-expansion.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/3555
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7527
|
||||
|
||||
Reviewed by: jchapma (Thanks!)
|
||||
---
|
||||
src/cockpit/389-console/package-lock.json | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/cockpit/389-console/package-lock.json b/src/cockpit/389-console/package-lock.json
|
||||
index 6c8ccdbd8..1efed1c1d 100644
|
||||
--- a/src/cockpit/389-console/package-lock.json
|
||||
+++ b/src/cockpit/389-console/package-lock.json
|
||||
@@ -2377,9 +2377,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
- "version": "5.0.5",
|
||||
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
+ "version": "5.0.6",
|
||||
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
From be7c2b82958e91ce08775bf6b5da3c311d3b00e5 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 20 Feb 2023 16:14:05 +0100
|
||||
Subject: [PATCH 2/2] Issue 5647 - Fix unused variable warning from previous
|
||||
commit (#5670)
|
||||
|
||||
* issue 5647 - memory leak in audit log when adding entries
|
||||
* Issue 5647 - Fix unused variable warning from previous commit
|
||||
---
|
||||
ldap/servers/slapd/auditlog.c | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 3128e0497..0597ecc6f 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -254,7 +254,6 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
} else {
|
||||
/* Return all attributes */
|
||||
for (; entry_attr; entry_attr = entry_attr->a_next) {
|
||||
- Slapi_Value **vals = attr_get_present_values(entry_attr);
|
||||
char *attr = NULL;
|
||||
|
||||
slapi_attr_get_type(entry_attr, &attr);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -1,147 +0,0 @@
|
||||
From 692c4cec6cc5c0086cf58f83bcfa690c766c9887 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 2 Feb 2024 14:14:28 +0100
|
||||
Subject: [PATCH] Issue 5407 - sync_repl crashes if enabled while dynamic
|
||||
plugin is enabled (#5411)
|
||||
|
||||
Bug description:
|
||||
When dynamic plugin is enabled, if a MOD enables sync_repl plugin
|
||||
then sync_repl init function registers the postop callback
|
||||
that will be called for the MOD itself while the preop
|
||||
has not been called.
|
||||
postop expects preop to be called and so primary operation
|
||||
to be set. When it is not set it crashes
|
||||
|
||||
Fix description:
|
||||
If the primary operation is not set, just return
|
||||
|
||||
relates: #5407
|
||||
---
|
||||
.../suites/syncrepl_plugin/basic_test.py | 68 +++++++++++++++++++
|
||||
ldap/servers/plugins/sync/sync_persist.c | 23 ++++++-
|
||||
2 files changed, 90 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
index eb3770b78..cdf35eeaa 100644
|
||||
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
@@ -592,6 +592,74 @@ def test_sync_repl_cenotaph(topo_m2, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+def test_sync_repl_dynamic_plugin(topology, request):
|
||||
+ """Test sync_repl with dynamic plugin
|
||||
+
|
||||
+ :id: d4f84913-c18a-459f-8525-110f610ca9e6
|
||||
+ :setup: install a standalone instance
|
||||
+ :steps:
|
||||
+ 1. reset instance to standard (no retroCL, no sync_repl, no dynamic plugin)
|
||||
+ 2. Enable dynamic plugin
|
||||
+ 3. Enable retroCL/content_sync
|
||||
+ 4. Establish a sync_repl req
|
||||
+ :expectedresults:
|
||||
+ 1. Should succeeds
|
||||
+ 2. Should succeeds
|
||||
+ 3. Should succeeds
|
||||
+ 4. Should succeeds
|
||||
+ """
|
||||
+
|
||||
+ # Reset the instance in a default config
|
||||
+ # Disable content sync plugin
|
||||
+ topology.standalone.plugins.disable(name=PLUGIN_REPL_SYNC)
|
||||
+
|
||||
+ # Disable retro changelog
|
||||
+ topology.standalone.plugins.disable(name=PLUGIN_RETRO_CHANGELOG)
|
||||
+
|
||||
+ # Disable dynamic plugins
|
||||
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-dynamic-plugins', b'off')])
|
||||
+ topology.standalone.restart()
|
||||
+
|
||||
+ # Now start the test
|
||||
+ # Enable dynamic plugins
|
||||
+ try:
|
||||
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-dynamic-plugins', b'on')])
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.error('Failed to enable dynamic plugin! {}'.format(e.args[0]['desc']))
|
||||
+ assert False
|
||||
+
|
||||
+ # Enable retro changelog
|
||||
+ topology.standalone.plugins.enable(name=PLUGIN_RETRO_CHANGELOG)
|
||||
+
|
||||
+ # Enbale content sync plugin
|
||||
+ topology.standalone.plugins.enable(name=PLUGIN_REPL_SYNC)
|
||||
+
|
||||
+ # create a sync repl client and wait 5 seconds to be sure it is running
|
||||
+ sync_repl = Sync_persist(topology.standalone)
|
||||
+ sync_repl.start()
|
||||
+ time.sleep(5)
|
||||
+
|
||||
+ # create users
|
||||
+ users = UserAccounts(topology.standalone, DEFAULT_SUFFIX)
|
||||
+ users_set = []
|
||||
+ for i in range(10001, 10004):
|
||||
+ users_set.append(users.create_test_user(uid=i))
|
||||
+
|
||||
+ time.sleep(10)
|
||||
+ # delete users, that automember/memberof will generate nested updates
|
||||
+ for user in users_set:
|
||||
+ user.delete()
|
||||
+ # stop the server to get the sync_repl result set (exit from while loop).
|
||||
+ # Only way I found to acheive that.
|
||||
+ # and wait a bit to let sync_repl thread time to set its result before fetching it.
|
||||
+ topology.standalone.stop()
|
||||
+ sync_repl.get_result()
|
||||
+ sync_repl.join()
|
||||
+ log.info('test_sync_repl_dynamic_plugin: PASS\n')
|
||||
+
|
||||
+ # Success
|
||||
+ log.info('Test complete')
|
||||
+
|
||||
def test_sync_repl_invalid_cookie(topology, request):
|
||||
"""Test sync_repl with invalid cookie
|
||||
|
||||
diff --git a/ldap/servers/plugins/sync/sync_persist.c b/ldap/servers/plugins/sync/sync_persist.c
|
||||
index d2210b64c..283607361 100644
|
||||
--- a/ldap/servers/plugins/sync/sync_persist.c
|
||||
+++ b/ldap/servers/plugins/sync/sync_persist.c
|
||||
@@ -156,6 +156,17 @@ ignore_op_pl(Slapi_PBlock *pb)
|
||||
* This is the same for ident
|
||||
*/
|
||||
prim_op = get_thread_primary_op();
|
||||
+ if (prim_op == NULL) {
|
||||
+ /* This can happen if the PRE_OP (sync_update_persist_betxn_pre_op) was not called.
|
||||
+ * The only known case it happens is with dynamic plugin enabled and an
|
||||
+ * update that enable the sync_repl plugin. In such case sync_repl registers
|
||||
+ * the postop (sync_update_persist_op) that is called while the preop was not called
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, SYNC_PLUGIN_SUBSYSTEM,
|
||||
+ "ignore_op_pl - Operation without primary op set (0x%lx)\n",
|
||||
+ (ulong) op);
|
||||
+ return;
|
||||
+ }
|
||||
ident = sync_persist_get_operation_extension(pb);
|
||||
|
||||
if (ident) {
|
||||
@@ -232,8 +243,18 @@ sync_update_persist_op(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eprev, ber
|
||||
|
||||
|
||||
prim_op = get_thread_primary_op();
|
||||
+ if (prim_op == NULL) {
|
||||
+ /* This can happen if the PRE_OP (sync_update_persist_betxn_pre_op) was not called.
|
||||
+ * The only known case it happens is with dynamic plugin enabled and an
|
||||
+ * update that enable the sync_repl plugin. In such case sync_repl registers
|
||||
+ * the postop (sync_update_persist_op) that is called while the preop was not called
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, SYNC_PLUGIN_SUBSYSTEM,
|
||||
+ "sync_update_persist_op - Operation without primary op set (0x%lx)\n",
|
||||
+ (ulong) pb_op);
|
||||
+ return;
|
||||
+ }
|
||||
ident = sync_persist_get_operation_extension(pb);
|
||||
- PR_ASSERT(prim_op);
|
||||
|
||||
if ((ident == NULL) && operation_is_flag_set(pb_op, OP_FLAG_NOOP)) {
|
||||
/* This happens for URP (add cenotaph, fixup rename, tombstone resurrect)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,687 @@
|
||||
From 46808f90d9026454b77a65bbab7fccdde75253a4 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 11 Jul 2025 11:43:28 +0200
|
||||
Subject: [PATCH 3/8] Issue 6922 - AddressSanitizer: leaks found by acl test
|
||||
suite
|
||||
|
||||
Description:
|
||||
Fix multiple memory leaks in ACL plugin:
|
||||
|
||||
* aclgroup.c: Fix memory leak in `aclgroup_free()` by properly cleaning
|
||||
up all user groups before destroying the rwlock and freeing the main
|
||||
structure
|
||||
* aclscan.l & acltext.y: use `PERM_FREE` macro instead of `free()`
|
||||
* Add cleanups in aclparse.c, aclinit.c, acllist.c, acllas.c
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6922
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
ldap/servers/plugins/acl/aclgroup.c | 16 ++++-
|
||||
ldap/servers/plugins/acl/aclinit.c | 4 ++
|
||||
ldap/servers/plugins/acl/acllas.c | 7 ++
|
||||
ldap/servers/plugins/acl/acllist.c | 2 +
|
||||
ldap/servers/plugins/acl/aclparse.c | 7 ++
|
||||
lib/libaccess/aclscan.l | 104 ++++++++++++++++++++++++++--
|
||||
lib/libaccess/acltext.y | 54 +++++++--------
|
||||
7 files changed, 160 insertions(+), 34 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/acl/aclgroup.c b/ldap/servers/plugins/acl/aclgroup.c
|
||||
index a5b0426f9..43faeae58 100644
|
||||
--- a/ldap/servers/plugins/acl/aclgroup.c
|
||||
+++ b/ldap/servers/plugins/acl/aclgroup.c
|
||||
@@ -50,8 +50,20 @@ aclgroup_init()
|
||||
void
|
||||
aclgroup_free()
|
||||
{
|
||||
- slapi_destroy_rwlock(aclUserGroups->aclg_rwlock);
|
||||
- slapi_ch_free((void **)&aclUserGroups);
|
||||
+ aclUserGroup *u_group, *next_group;
|
||||
+
|
||||
+ if (aclUserGroups) {
|
||||
+ /* Clean up all user groups */
|
||||
+ u_group = aclUserGroups->aclg_first;
|
||||
+ while (u_group) {
|
||||
+ next_group = u_group->aclug_next;
|
||||
+ __aclg__delete_userGroup(u_group);
|
||||
+ u_group = next_group;
|
||||
+ }
|
||||
+
|
||||
+ slapi_destroy_rwlock(aclUserGroups->aclg_rwlock);
|
||||
+ slapi_ch_free((void **)&aclUserGroups);
|
||||
+ }
|
||||
}
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/plugins/acl/aclinit.c b/ldap/servers/plugins/acl/aclinit.c
|
||||
index e2d5eba95..925fbbe53 100644
|
||||
--- a/ldap/servers/plugins/acl/aclinit.c
|
||||
+++ b/ldap/servers/plugins/acl/aclinit.c
|
||||
@@ -409,6 +409,7 @@ __aclinit__RegisterAttributes(void)
|
||||
rv = ACL_MethodRegister(&errp, DS_METHOD, &methodinfo);
|
||||
if (rv < 0) {
|
||||
acl_print_acllib_err(&errp, NULL);
|
||||
+ nserrDispose(&errp);
|
||||
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
|
||||
"__aclinit__RegisterAttributes - Unable to Register the methods\n");
|
||||
return ACL_ERR;
|
||||
@@ -416,6 +417,7 @@ __aclinit__RegisterAttributes(void)
|
||||
rv = ACL_MethodSetDefault(&errp, methodinfo);
|
||||
if (rv < 0) {
|
||||
acl_print_acllib_err(&errp, NULL);
|
||||
+ nserrDispose(&errp);
|
||||
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
|
||||
"__aclinit__RegisterAttributes - Unable to Set the default method\n");
|
||||
return ACL_ERR;
|
||||
@@ -424,6 +426,7 @@ __aclinit__RegisterAttributes(void)
|
||||
methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL);
|
||||
if (rv < 0) {
|
||||
acl_print_acllib_err(&errp, NULL);
|
||||
+ nserrDispose(&errp);
|
||||
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
|
||||
"__aclinit__RegisterAttributes - Unable to Register Attr ip\n");
|
||||
return ACL_ERR;
|
||||
@@ -432,6 +435,7 @@ __aclinit__RegisterAttributes(void)
|
||||
methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL);
|
||||
if (rv < 0) {
|
||||
acl_print_acllib_err(&errp, NULL);
|
||||
+ nserrDispose(&errp);
|
||||
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
|
||||
"__aclinit__RegisterAttributes - Unable to Register Attr dns\n");
|
||||
return ACL_ERR;
|
||||
diff --git a/ldap/servers/plugins/acl/acllas.c b/ldap/servers/plugins/acl/acllas.c
|
||||
index 792216cca..282359ae1 100644
|
||||
--- a/ldap/servers/plugins/acl/acllas.c
|
||||
+++ b/ldap/servers/plugins/acl/acllas.c
|
||||
@@ -258,6 +258,7 @@ DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t auth_in
|
||||
rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb, subject, resource, auth_info, global_auth);
|
||||
if (rv != LAS_EVAL_TRUE || (NULL == aclpb)) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"DS_LASIpGetter: Unable to get the ACLPB(%d)\n", rv);
|
||||
return LAS_EVAL_FAIL;
|
||||
@@ -334,6 +335,7 @@ DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t auth_i
|
||||
subject, resource, auth_info, global_auth);
|
||||
if (rv != LAS_EVAL_TRUE || (NULL == aclpb)) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"DS_LASDnsGetter - Unable to get the ACLPB(%d)\n", rv);
|
||||
return LAS_EVAL_FAIL;
|
||||
@@ -3743,6 +3745,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
|
||||
|
||||
if (rc != LAS_EVAL_TRUE) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"__acllas_setup - %s:Unable to get the clientdn attribute(%d)\n", lasName, rc);
|
||||
return LAS_EVAL_FAIL;
|
||||
@@ -3762,6 +3765,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
|
||||
if ((rc = PListFindValue(subject, DS_ATTR_ENTRY,
|
||||
(void **)&linfo->resourceEntry, NULL)) < 0) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"__acllas_setup - %s:Unable to get the Slapi_Entry attr(%d)\n", lasName, rc);
|
||||
return LAS_EVAL_FAIL;
|
||||
@@ -3772,6 +3776,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
|
||||
subject, resource, auth_info, global_auth);
|
||||
if (rc != LAS_EVAL_TRUE) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"__acllas_setup - %s:Unable to get the ACLPB(%d)\n", lasName, rc);
|
||||
return LAS_EVAL_FAIL;
|
||||
@@ -3795,6 +3800,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
|
||||
if ((rc = PListFindValue(subject, DS_ATTR_AUTHTYPE,
|
||||
(void **)&linfo->authType, NULL)) < 0) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"__acllas_setup - %s:Unable to get the auth type(%d)\n", lasName, rc);
|
||||
return LAS_EVAL_FAIL;
|
||||
@@ -3804,6 +3810,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
|
||||
if ((rc = PListFindValue(subject, DS_ATTR_SSF,
|
||||
(void **)&linfo->ssf, NULL)) < 0) {
|
||||
acl_print_acllib_err(errp, NULL);
|
||||
+ nserrDispose(errp);
|
||||
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
|
||||
"__acllas_setup - %s:Unable to get the ssf(%d)\n", lasName, rc);
|
||||
}
|
||||
diff --git a/ldap/servers/plugins/acl/acllist.c b/ldap/servers/plugins/acl/acllist.c
|
||||
index bf1168691..6d80a5835 100644
|
||||
--- a/ldap/servers/plugins/acl/acllist.c
|
||||
+++ b/ldap/servers/plugins/acl/acllist.c
|
||||
@@ -526,6 +526,8 @@ acllist_free_aci(aci_t *item)
|
||||
|
||||
slapi_sdn_free(&item->aci_sdn);
|
||||
slapi_filter_free(item->target, 1);
|
||||
+ slapi_filter_free(item->target_to, 1);
|
||||
+ slapi_filter_free(item->target_from, 1);
|
||||
|
||||
/* slapi_filter_free(item->targetAttr, 1); */
|
||||
attrArray = item->targetAttr;
|
||||
diff --git a/ldap/servers/plugins/acl/aclparse.c b/ldap/servers/plugins/acl/aclparse.c
|
||||
index 261cb69ec..882f4ef28 100644
|
||||
--- a/ldap/servers/plugins/acl/aclparse.c
|
||||
+++ b/ldap/servers/plugins/acl/aclparse.c
|
||||
@@ -423,6 +423,9 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
|
||||
}
|
||||
f = slapi_str2filter(tstr);
|
||||
slapi_ch_free_string(&tstr);
|
||||
+ if (rv > 0) {
|
||||
+ slapi_ch_free_string(&tmpstr);
|
||||
+ }
|
||||
} else if (strncmp(str, aci_targetdn, targetdnlen) == 0) {
|
||||
char *tstr = NULL;
|
||||
size_t LDAP_URL_prefix_len = 0;
|
||||
@@ -706,6 +709,7 @@ __aclp__sanity_check_acltxt(aci_t *aci_item, char *str)
|
||||
/* check for acl syntax error */
|
||||
if ((handle = (ACLListHandle_t *)ACL_ParseString(&errp, newstr)) == NULL) {
|
||||
acl_print_acllib_err(&errp, str);
|
||||
+ nserrDispose(&errp);
|
||||
slapi_ch_free_string(&newstr);
|
||||
return ACL_SYNTAX_ERR;
|
||||
} else {
|
||||
@@ -1144,13 +1148,16 @@ normalize_nextACERule:
|
||||
if (nextACE && *nextACE != '\0')
|
||||
aclutil_str_append(&ret_str, nextACE);
|
||||
slapi_ch_free_string(&s_aclstr);
|
||||
+ slapi_ch_free_string(&s_acestr);
|
||||
return (ret_str);
|
||||
}
|
||||
acestr = nextACE;
|
||||
+ slapi_ch_free_string(&s_acestr);
|
||||
goto normalize_nextACERule;
|
||||
}
|
||||
|
||||
slapi_ch_free_string(&s_aclstr);
|
||||
+ slapi_ch_free_string(&s_acestr);
|
||||
return (ret_str);
|
||||
|
||||
error:
|
||||
diff --git a/lib/libaccess/aclscan.l b/lib/libaccess/aclscan.l
|
||||
index 7067096c0..9c836decc 100644
|
||||
--- a/lib/libaccess/aclscan.l
|
||||
+++ b/lib/libaccess/aclscan.l
|
||||
@@ -64,11 +64,18 @@ variable [\*a-zA-Z0-9\.\-\_][\*a-zA-Z0-9\.\-\_]*
|
||||
|
||||
<<EOF>> {
|
||||
yylval.string = NULL;
|
||||
- last_string = yylval.string;
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ last_string = NULL;
|
||||
+ }
|
||||
return(0);
|
||||
}
|
||||
|
||||
{qstring} {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ last_string = NULL;
|
||||
+ }
|
||||
yylval.string = PERM_STRDUP( yytext+1 );
|
||||
last_string = yylval.string;
|
||||
if ( yylval.string[yyleng-2] != '"' )
|
||||
@@ -81,96 +88,144 @@ variable [\*a-zA-Z0-9\.\-\_][\*a-zA-Z0-9\.\-\_]*
|
||||
|
||||
|
||||
absolute {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_ABSOLUTE_TOK;
|
||||
}
|
||||
|
||||
acl {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_ACL_TOK;
|
||||
}
|
||||
|
||||
allow {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_ALLOW_TOK;
|
||||
}
|
||||
|
||||
always {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_ALWAYS_TOK;
|
||||
}
|
||||
|
||||
at {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_AT_TOK;
|
||||
}
|
||||
|
||||
authenticate {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_AUTHENTICATE_TOK;
|
||||
}
|
||||
|
||||
content {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_CONTENT_TOK;
|
||||
}
|
||||
|
||||
default {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_DEFAULT_TOK;
|
||||
}
|
||||
|
||||
deny {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_DENY_TOK;
|
||||
}
|
||||
|
||||
in {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_IN_TOK;
|
||||
}
|
||||
|
||||
inherit {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_INHERIT_TOK;
|
||||
}
|
||||
|
||||
terminal {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_TERMINAL_TOK;
|
||||
}
|
||||
|
||||
version {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_VERSION_TOK;
|
||||
}
|
||||
|
||||
with {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_WITH_TOK;
|
||||
}
|
||||
|
||||
not {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return ACL_NOT_TOK;
|
||||
}
|
||||
|
||||
and {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = ACL_EXPR_OP_AND;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -178,6 +233,9 @@ and {
|
||||
}
|
||||
|
||||
or {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = ACL_EXPR_OP_OR;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -185,6 +243,9 @@ or {
|
||||
}
|
||||
|
||||
"=" {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = CMP_OP_EQ;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -192,6 +253,9 @@ or {
|
||||
}
|
||||
|
||||
">=" {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = CMP_OP_GE;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -199,6 +263,9 @@ or {
|
||||
}
|
||||
|
||||
">" {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = CMP_OP_GT;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -206,6 +273,9 @@ or {
|
||||
}
|
||||
|
||||
"<" {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = CMP_OP_LT;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -213,6 +283,9 @@ or {
|
||||
}
|
||||
|
||||
"<=" {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = CMP_OP_LE;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -220,6 +293,9 @@ or {
|
||||
}
|
||||
|
||||
"!=" {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
yylval.ival = CMP_OP_NE;
|
||||
acl_tokenpos += yyleng;
|
||||
@@ -227,6 +303,9 @@ or {
|
||||
}
|
||||
|
||||
[(){},;] {
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ }
|
||||
last_string = NULL;
|
||||
acl_tokenpos += yyleng;
|
||||
return yytext[0];
|
||||
@@ -234,6 +313,10 @@ or {
|
||||
|
||||
{variable} {
|
||||
acl_tokenpos += yyleng;
|
||||
+ if (last_string) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ last_string = NULL;
|
||||
+ }
|
||||
yylval.string = PERM_STRDUP( yytext );
|
||||
last_string = yylval.string;
|
||||
return ACL_VARIABLE_TOK;
|
||||
@@ -272,8 +355,10 @@ char errorStr[256];
|
||||
#if defined(UTEST) || defined(ACL_COMPILER)
|
||||
printf("ACL file: %s\n", acl_filename);
|
||||
printf("Syntax error at line: %d, token: %s\n", acl_lineno, yytext);
|
||||
- if ( last_string )
|
||||
- free(last_string);
|
||||
+ if ( last_string ) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ last_string = NULL;
|
||||
+ }
|
||||
#else
|
||||
sprintf(errorStr, "%d", acl_lineno);
|
||||
if (yytext) {
|
||||
@@ -283,8 +368,10 @@ char errorStr[256];
|
||||
nserrGenerate(acl_errp, ACLERRPARSE, ACLERR1780, ACL_Program,
|
||||
2, acl_filename, errorStr);
|
||||
}
|
||||
- if ( last_string )
|
||||
- free(last_string);
|
||||
+ if ( last_string ) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ last_string = NULL;
|
||||
+ }
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -294,6 +381,7 @@ acl_InitScanner(NSErr_t *errp, char *filename, char *buffer)
|
||||
{
|
||||
acl_errp = errp;
|
||||
acl_lineno = 1;
|
||||
+ last_string = NULL;
|
||||
acl_use_buffer = (filename == NULL) ? 1 : 0 ;
|
||||
if ( filename != NULL ) {
|
||||
PL_strncpyz(acl_filename, filename, sizeof(acl_filename));
|
||||
@@ -342,6 +430,12 @@ acl_EndScanner()
|
||||
#endif
|
||||
yyin = NULL ;
|
||||
}
|
||||
+
|
||||
+ if ( last_string ) {
|
||||
+ PERM_FREE(last_string);
|
||||
+ last_string = NULL;
|
||||
+ }
|
||||
+
|
||||
return(0);
|
||||
}
|
||||
|
||||
diff --git a/lib/libaccess/acltext.y b/lib/libaccess/acltext.y
|
||||
index 16eea355b..ed2c6109b 100644
|
||||
--- a/lib/libaccess/acltext.y
|
||||
+++ b/lib/libaccess/acltext.y
|
||||
@@ -80,7 +80,7 @@ acl_free_args(char **args_list)
|
||||
|
||||
for (ii = 0; ii < MAX_LIST_SIZE; ii++) {
|
||||
if ( args_list[ii] )
|
||||
- free(args_list[ii]);
|
||||
+ PERM_FREE(args_list[ii]);
|
||||
else
|
||||
break;
|
||||
}
|
||||
@@ -240,7 +240,7 @@ acl_set_ip_dns(ACLExprHandle_t *expr, char **ip_dns)
|
||||
start: | start_acl_v2
|
||||
| ACL_VERSION_TOK ACL_VARIABLE_TOK
|
||||
{
|
||||
- free($<string>2);
|
||||
+ PERM_FREE($<string>2);
|
||||
}
|
||||
';' start_acl_v3
|
||||
;
|
||||
@@ -267,7 +267,7 @@ acl_v2: ACL_ACL_TOK acl_name_v2
|
||||
acl_name_v2: ACL_VARIABLE_TOK
|
||||
{
|
||||
curr_acl = ACL_AclNew(NULL, $<string>1);
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
|
||||
yyerror("Couldn't add ACL to list.");
|
||||
return(-1);
|
||||
@@ -289,7 +289,7 @@ acl_name_v2: ACL_VARIABLE_TOK
|
||||
| ACL_QSTRING_TOK
|
||||
{
|
||||
curr_acl = ACL_AclNew(NULL, $<string>1);
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
|
||||
yyerror("Couldn't add ACL to list.");
|
||||
return(-1);
|
||||
@@ -503,8 +503,8 @@ ip_spec_v2: ACL_VARIABLE_TOK ACL_VARIABLE_TOK
|
||||
char tmp_str[255];
|
||||
|
||||
util_sprintf(tmp_str, "%s+%s", $<string>1, $<string>2);
|
||||
- free($<string>1);
|
||||
- free($<string>2);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>2);
|
||||
acl_add_arg(curr_ip_dns_list, PERM_STRDUP(tmp_str));
|
||||
}
|
||||
;
|
||||
@@ -538,26 +538,26 @@ method_v2: ACL_VARIABLE_TOK ACL_VARIABLE_TOK ';'
|
||||
{
|
||||
acl_string_lower($<string>1);
|
||||
if (strcmp($<string>1, "database") == 0) {
|
||||
- free($<string>1);
|
||||
- free($<string>2);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>2);
|
||||
} else {
|
||||
if ( PListInitProp(curr_auth_info,
|
||||
ACL_Attr2Index($<string>1), $<string>1, $<string>2, NULL) < 0 ) {
|
||||
}
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
}
|
||||
}
|
||||
| ACL_VARIABLE_TOK ACL_QSTRING_TOK ';'
|
||||
{
|
||||
acl_string_lower($<string>1);
|
||||
if (strcmp($<string>1, "database") == 0) {
|
||||
- free($<string>1);
|
||||
- free($<string>2);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>2);
|
||||
} else {
|
||||
if ( PListInitProp(curr_auth_info,
|
||||
ACL_Attr2Index($<string>1), $<string>1, $<string>2, NULL) < 0 ) {
|
||||
}
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -586,7 +586,7 @@ acl: named_acl ';' body_list
|
||||
named_acl: ACL_ACL_TOK ACL_VARIABLE_TOK
|
||||
{
|
||||
curr_acl = ACL_AclNew(NULL, $<string>2);
|
||||
- free($<string>2);
|
||||
+ PERM_FREE($<string>2);
|
||||
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
|
||||
yyerror("Couldn't add ACL to list.");
|
||||
return(-1);
|
||||
@@ -595,7 +595,7 @@ named_acl: ACL_ACL_TOK ACL_VARIABLE_TOK
|
||||
| ACL_ACL_TOK ACL_QSTRING_TOK
|
||||
{
|
||||
curr_acl = ACL_AclNew(NULL, $<string>2);
|
||||
- free($<string>2);
|
||||
+ PERM_FREE($<string>2);
|
||||
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
|
||||
yyerror("Couldn't add ACL to list.");
|
||||
return(-1);
|
||||
@@ -654,8 +654,8 @@ deny_common: ACL_VARIABLE_TOK ACL_EQ_TOK ACL_QSTRING_TOK
|
||||
yyerror("ACL_ExprSetDenyWith() failed");
|
||||
return(-1);
|
||||
}
|
||||
- free($<string>1);
|
||||
- free($<string>3);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>3);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -692,7 +692,7 @@ attribute: ACL_VARIABLE_TOK
|
||||
yyerror("ACL_ExprAddArg() failed");
|
||||
return(-1);
|
||||
}
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -706,7 +706,7 @@ parameter: ACL_VARIABLE_TOK ACL_EQ_TOK ACL_QSTRING_TOK
|
||||
if ( PListInitProp(curr_auth_info,
|
||||
ACL_Attr2Index($<string>1), $<string>1, $<string>3, NULL) < 0 ) {
|
||||
}
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
}
|
||||
| ACL_VARIABLE_TOK ACL_EQ_TOK ACL_VARIABLE_TOK
|
||||
{
|
||||
@@ -714,7 +714,7 @@ parameter: ACL_VARIABLE_TOK ACL_EQ_TOK ACL_QSTRING_TOK
|
||||
if ( PListInitProp(curr_auth_info,
|
||||
ACL_Attr2Index($<string>1), $<string>1, $<string>3, NULL) < 0 ) {
|
||||
}
|
||||
- free($<string>1);
|
||||
+ PERM_FREE($<string>1);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -896,12 +896,12 @@ base_expr: ACL_VARIABLE_TOK relop ACL_QSTRING_TOK
|
||||
if ( ACL_ExprTerm(NULL, curr_expr,
|
||||
$<string>1, (CmpOp_t) $<ival>2, $<string>3) < 0 ) {
|
||||
yyerror("ACL_ExprTerm() failed");
|
||||
- free($<string>1);
|
||||
- free($<string>3);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>3);
|
||||
return(-1);
|
||||
}
|
||||
- free($<string>1);
|
||||
- free($<string>3);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>3);
|
||||
}
|
||||
| ACL_VARIABLE_TOK relop ACL_VARIABLE_TOK
|
||||
{
|
||||
@@ -909,12 +909,12 @@ base_expr: ACL_VARIABLE_TOK relop ACL_QSTRING_TOK
|
||||
if ( ACL_ExprTerm(NULL, curr_expr,
|
||||
$<string>1, (CmpOp_t) $<ival>2, $<string>3) < 0 ) {
|
||||
yyerror("ACL_ExprTerm() failed");
|
||||
- free($<string>1);
|
||||
- free($<string>3);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>3);
|
||||
return(-1);
|
||||
}
|
||||
- free($<string>1);
|
||||
- free($<string>3);
|
||||
+ PERM_FREE($<string>1);
|
||||
+ PERM_FREE($<string>3);
|
||||
}
|
||||
;
|
||||
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,840 +0,0 @@
|
||||
From 8dc61a176323f0d41df730abd715ccff3034c2be Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Sun, 27 Nov 2022 09:37:19 -0500
|
||||
Subject: [PATCH] Issue 5547 - automember plugin improvements
|
||||
|
||||
Description:
|
||||
|
||||
Rebuild task has the following improvements:
|
||||
|
||||
- Only one task allowed at a time
|
||||
- Do not cleanup previous members by default. Add new CLI option to intentionally
|
||||
cleanup memberships before rebuilding from scratch.
|
||||
- Add better task logging to show fixup progress
|
||||
|
||||
To prevent automember from being called in a nested be_txn loop thread storage is
|
||||
used to check and skip these loops.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/5547
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
.../automember_plugin/automember_mod_test.py | 43 +++-
|
||||
ldap/servers/plugins/automember/automember.c | 232 ++++++++++++++----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_add.c | 11 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_delete.c | 10 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modify.c | 11 +-
|
||||
.../lib389/cli_conf/plugins/automember.py | 10 +-
|
||||
src/lib389/lib389/plugins.py | 7 +-
|
||||
src/lib389/lib389/tasks.py | 9 +-
|
||||
8 files changed, 250 insertions(+), 83 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py b/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py
|
||||
index 8d25384bf..7a0ed3275 100644
|
||||
--- a/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py
|
||||
+++ b/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py
|
||||
@@ -5,12 +5,13 @@
|
||||
# License: GPL (version 3 or any later version).
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
+import ldap
|
||||
import logging
|
||||
import pytest
|
||||
import os
|
||||
+import time
|
||||
from lib389.utils import ds_is_older
|
||||
-from lib389._constants import *
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.idm.group import Groups
|
||||
@@ -41,6 +42,11 @@ def automember_fixture(topo, request):
|
||||
user_accts = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
user = user_accts.create_test_user()
|
||||
|
||||
+ # Create extra users
|
||||
+ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
+ for i in range(0, 100):
|
||||
+ users.create_test_user(uid=i)
|
||||
+
|
||||
# Create automember definitions and regex rules
|
||||
automember_prop = {
|
||||
'cn': 'testgroup_definition',
|
||||
@@ -59,7 +65,7 @@ def automember_fixture(topo, request):
|
||||
automemberplugin.enable()
|
||||
topo.standalone.restart()
|
||||
|
||||
- return (user, groups)
|
||||
+ return user, groups
|
||||
|
||||
|
||||
def test_mods(automember_fixture, topo):
|
||||
@@ -72,19 +78,21 @@ def test_mods(automember_fixture, topo):
|
||||
2. Update user that should add it to group[1]
|
||||
3. Update user that should add it to group[2]
|
||||
4. Update user that should add it to group[0]
|
||||
- 5. Test rebuild task correctly moves user to group[1]
|
||||
+ 5. Test rebuild task adds user to group[1]
|
||||
+ 6. Test rebuild task cleanups groups and only adds it to group[1]
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
3. Success
|
||||
4. Success
|
||||
5. Success
|
||||
+ 6. Success
|
||||
"""
|
||||
(user, groups) = automember_fixture
|
||||
|
||||
# Update user which should go into group[0]
|
||||
user.replace('cn', 'whatever')
|
||||
- groups[0].is_member(user.dn)
|
||||
+ assert groups[0].is_member(user.dn)
|
||||
if groups[1].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -92,7 +100,7 @@ def test_mods(automember_fixture, topo):
|
||||
|
||||
# Update user0 which should go into group[1]
|
||||
user.replace('cn', 'mark')
|
||||
- groups[1].is_member(user.dn)
|
||||
+ assert groups[1].is_member(user.dn)
|
||||
if groups[0].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -100,7 +108,7 @@ def test_mods(automember_fixture, topo):
|
||||
|
||||
# Update user which should go into group[2]
|
||||
user.replace('cn', 'simon')
|
||||
- groups[2].is_member(user.dn)
|
||||
+ assert groups[2].is_member(user.dn)
|
||||
if groups[0].is_member(user.dn):
|
||||
assert False
|
||||
if groups[1].is_member(user.dn):
|
||||
@@ -108,7 +116,7 @@ def test_mods(automember_fixture, topo):
|
||||
|
||||
# Update user which should go back into group[0] (full circle)
|
||||
user.replace('cn', 'whatever')
|
||||
- groups[0].is_member(user.dn)
|
||||
+ assert groups[0].is_member(user.dn)
|
||||
if groups[1].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -128,12 +136,24 @@ def test_mods(automember_fixture, topo):
|
||||
automemberplugin.enable()
|
||||
topo.standalone.restart()
|
||||
|
||||
- # Run rebuild task
|
||||
+ # Run rebuild task (no cleanup)
|
||||
task = automemberplugin.fixup(DEFAULT_SUFFIX, "objectclass=posixaccount")
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ # test only one fixup task is allowed at a time
|
||||
+ automemberplugin.fixup(DEFAULT_SUFFIX, "objectclass=top")
|
||||
task.wait()
|
||||
|
||||
- # Test membership
|
||||
- groups[1].is_member(user.dn)
|
||||
+ # Test membership (user should still be in groups[0])
|
||||
+ assert groups[1].is_member(user.dn)
|
||||
+ if not groups[0].is_member(user.dn):
|
||||
+ assert False
|
||||
+
|
||||
+ # Run rebuild task with cleanup
|
||||
+ task = automemberplugin.fixup(DEFAULT_SUFFIX, "objectclass=posixaccount", cleanup=True)
|
||||
+ task.wait()
|
||||
+
|
||||
+ # Test membership (user should only be in groups[1])
|
||||
+ assert groups[1].is_member(user.dn)
|
||||
if groups[0].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -148,4 +168,3 @@ if __name__ == '__main__':
|
||||
# -s for DEBUG mode
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main(["-s", CURRENT_FILE])
|
||||
-
|
||||
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
|
||||
index 3494d0343..419adb052 100644
|
||||
--- a/ldap/servers/plugins/automember/automember.c
|
||||
+++ b/ldap/servers/plugins/automember/automember.c
|
||||
@@ -1,5 +1,5 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
- * Copyright (C) 2011 Red Hat, Inc.
|
||||
+ * Copyright (C) 2022 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* License: GPL (version 3 or any later version).
|
||||
@@ -14,7 +14,7 @@
|
||||
* Auto Membership Plug-in
|
||||
*/
|
||||
#include "automember.h"
|
||||
-
|
||||
+#include <pthread.h>
|
||||
|
||||
/*
|
||||
* Plug-in globals
|
||||
@@ -22,7 +22,9 @@
|
||||
static PRCList *g_automember_config = NULL;
|
||||
static Slapi_RWLock *g_automember_config_lock = NULL;
|
||||
static uint64_t abort_rebuild_task = 0;
|
||||
-
|
||||
+static pthread_key_t td_automem_block_nested;
|
||||
+static PRBool fixup_running = PR_FALSE;
|
||||
+static PRLock *fixup_lock = NULL;
|
||||
static void *_PluginID = NULL;
|
||||
static Slapi_DN *_PluginDN = NULL;
|
||||
static Slapi_DN *_ConfigAreaDN = NULL;
|
||||
@@ -93,9 +95,43 @@ static void automember_task_export_destructor(Slapi_Task *task);
|
||||
static void automember_task_map_destructor(Slapi_Task *task);
|
||||
|
||||
#define DEFAULT_FILE_MODE PR_IRUSR | PR_IWUSR
|
||||
+#define FIXUP_PROGRESS_LIMIT 1000
|
||||
static uint64_t plugin_do_modify = 0;
|
||||
static uint64_t plugin_is_betxn = 0;
|
||||
|
||||
+/* automember_plugin fixup task and add operations should block other be_txn
|
||||
+ * plugins from calling automember_post_op_mod() */
|
||||
+static int32_t
|
||||
+slapi_td_block_nested_post_op(void)
|
||||
+{
|
||||
+ int32_t val = 12345;
|
||||
+
|
||||
+ if (pthread_setspecific(td_automem_block_nested, (void *)&val) != 0) {
|
||||
+ return PR_FAILURE;
|
||||
+ }
|
||||
+ return PR_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+static int32_t
|
||||
+slapi_td_unblock_nested_post_op(void)
|
||||
+{
|
||||
+ if (pthread_setspecific(td_automem_block_nested, NULL) != 0) {
|
||||
+ return PR_FAILURE;
|
||||
+ }
|
||||
+ return PR_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+static int32_t
|
||||
+slapi_td_is_post_op_nested(void)
|
||||
+{
|
||||
+ int32_t *value = pthread_getspecific(td_automem_block_nested);
|
||||
+
|
||||
+ if (value == NULL) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Config cache locking functions
|
||||
*/
|
||||
@@ -317,6 +353,14 @@ automember_start(Slapi_PBlock *pb)
|
||||
return -1;
|
||||
}
|
||||
|
||||
+ if (fixup_lock == NULL) {
|
||||
+ if ((fixup_lock = PR_NewLock()) == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_start - Failed to create fixup lock.\n");
|
||||
+ return -1;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* Get the plug-in target dn from the system
|
||||
* and store it for future use. */
|
||||
@@ -360,6 +404,11 @@ automember_start(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
|
||||
+ if (pthread_key_create(&td_automem_block_nested, NULL) != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_start - pthread_key_create failed\n");
|
||||
+ }
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_start - ready for service\n");
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
@@ -394,6 +443,8 @@ automember_close(Slapi_PBlock *pb __attribute__((unused)))
|
||||
slapi_sdn_free(&_ConfigAreaDN);
|
||||
slapi_destroy_rwlock(g_automember_config_lock);
|
||||
g_automember_config_lock = NULL;
|
||||
+ PR_DestroyLock(fixup_lock);
|
||||
+ fixup_lock = NULL;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"<-- automember_close\n");
|
||||
@@ -1619,7 +1670,6 @@ out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
-
|
||||
/*
|
||||
* automember_update_member_value()
|
||||
*
|
||||
@@ -1634,7 +1684,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
LDAPMod *mods[2];
|
||||
char *vals[2];
|
||||
char *member_value = NULL;
|
||||
- int rc = 0;
|
||||
+ int rc = LDAP_SUCCESS;
|
||||
Slapi_DN *group_sdn;
|
||||
|
||||
/* First thing check that the group still exists */
|
||||
@@ -1653,7 +1703,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
"automember_update_member_value - group (default or target) can not be retrieved (%s) err=%d\n",
|
||||
group_dn, rc);
|
||||
}
|
||||
- return rc;
|
||||
+ goto out;
|
||||
}
|
||||
|
||||
/* If grouping_value is dn, we need to fetch the dn instead. */
|
||||
@@ -1879,6 +1929,13 @@ automember_mod_post_op(Slapi_PBlock *pb)
|
||||
PRCList *list = NULL;
|
||||
int rc = SLAPI_PLUGIN_SUCCESS;
|
||||
|
||||
+ if (slapi_td_is_post_op_nested()) {
|
||||
+ /* don't process op twice in the same thread */
|
||||
+ return rc;
|
||||
+ } else {
|
||||
+ slapi_td_block_nested_post_op();
|
||||
+ }
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"--> automember_mod_post_op\n");
|
||||
|
||||
@@ -2005,6 +2062,7 @@ automember_mod_post_op(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
+ slapi_td_unblock_nested_post_op();
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"<-- automember_mod_post_op (%d)\n", rc);
|
||||
@@ -2024,6 +2082,13 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"--> automember_add_post_op\n");
|
||||
|
||||
+ if (slapi_td_is_post_op_nested()) {
|
||||
+ /* don't process op twice in the same thread */
|
||||
+ return rc;
|
||||
+ } else {
|
||||
+ slapi_td_block_nested_post_op();
|
||||
+ }
|
||||
+
|
||||
/* Reload config if a config entry was added. */
|
||||
if ((sdn = automember_get_sdn(pb))) {
|
||||
if (automember_dn_is_config(sdn)) {
|
||||
@@ -2039,7 +2104,7 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
|
||||
/* If replication, just bail. */
|
||||
if (automember_isrepl(pb)) {
|
||||
- return SLAPI_PLUGIN_SUCCESS;
|
||||
+ goto bail;
|
||||
}
|
||||
|
||||
/* Get the newly added entry. */
|
||||
@@ -2052,7 +2117,7 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
tombstone);
|
||||
slapi_value_free(&tombstone);
|
||||
if (is_tombstone) {
|
||||
- return SLAPI_PLUGIN_SUCCESS;
|
||||
+ goto bail;
|
||||
}
|
||||
|
||||
/* Check if a config entry applies
|
||||
@@ -2063,21 +2128,19 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
list = PR_LIST_HEAD(g_automember_config);
|
||||
while (list != g_automember_config) {
|
||||
config = (struct configEntry *)list;
|
||||
-
|
||||
/* Does the entry meet scope and filter requirements? */
|
||||
if (slapi_dn_issuffix(slapi_sdn_get_dn(sdn), config->scope) &&
|
||||
- (slapi_filter_test_simple(e, config->filter) == 0)) {
|
||||
+ (slapi_filter_test_simple(e, config->filter) == 0))
|
||||
+ {
|
||||
/* Find out what membership changes are needed and make them. */
|
||||
if (automember_update_membership(config, e, NULL) == SLAPI_PLUGIN_FAILURE) {
|
||||
rc = SLAPI_PLUGIN_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
-
|
||||
list = PR_NEXT_LINK(list);
|
||||
}
|
||||
}
|
||||
-
|
||||
automember_config_unlock();
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
@@ -2098,6 +2161,7 @@ bail:
|
||||
slapi_pblock_set(pb, SLAPI_RESULT_CODE, &result);
|
||||
slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, &errtxt);
|
||||
}
|
||||
+ slapi_td_unblock_nested_post_op();
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -2138,6 +2202,7 @@ typedef struct _task_data
|
||||
Slapi_DN *base_dn;
|
||||
char *bind_dn;
|
||||
int scope;
|
||||
+ PRBool cleanup;
|
||||
} task_data;
|
||||
|
||||
static void
|
||||
@@ -2270,6 +2335,7 @@ automember_task_abort_thread(void *arg)
|
||||
* basedn: dc=example,dc=com
|
||||
* filter: (uid=*)
|
||||
* scope: sub
|
||||
+ * cleanup: yes/on (default is off)
|
||||
*
|
||||
* basedn and filter are required. If scope is omitted, the default is sub
|
||||
*/
|
||||
@@ -2284,9 +2350,22 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
const char *base_dn;
|
||||
const char *filter;
|
||||
const char *scope;
|
||||
+ const char *cleanup_str;
|
||||
+ PRBool cleanup = PR_FALSE;
|
||||
|
||||
*returncode = LDAP_SUCCESS;
|
||||
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ if (fixup_running) {
|
||||
+ PR_Unlock(fixup_lock);
|
||||
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_task_add - there is already a fixup task running\n");
|
||||
+ rv = SLAPI_DSE_CALLBACK_ERROR;
|
||||
+ goto out;
|
||||
+ }
|
||||
+ PR_Unlock(fixup_lock);
|
||||
+
|
||||
/*
|
||||
* Grab the task params
|
||||
*/
|
||||
@@ -2300,6 +2379,12 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
rv = SLAPI_DSE_CALLBACK_ERROR;
|
||||
goto out;
|
||||
}
|
||||
+ if ((cleanup_str = slapi_entry_attr_get_ref(e, "cleanup"))) {
|
||||
+ if (strcasecmp(cleanup_str, "yes") == 0 || strcasecmp(cleanup_str, "on")) {
|
||||
+ cleanup = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
scope = slapi_fetch_attr(e, "scope", "sub");
|
||||
/*
|
||||
* setup our task data
|
||||
@@ -2315,6 +2400,7 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
mytaskdata->bind_dn = slapi_ch_strdup(bind_dn);
|
||||
mytaskdata->base_dn = slapi_sdn_new_dn_byval(base_dn);
|
||||
mytaskdata->filter_str = slapi_ch_strdup(filter);
|
||||
+ mytaskdata->cleanup = cleanup;
|
||||
|
||||
if (scope) {
|
||||
if (strcasecmp(scope, "sub") == 0) {
|
||||
@@ -2334,6 +2420,9 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
task = slapi_plugin_new_task(slapi_entry_get_ndn(e), arg);
|
||||
slapi_task_set_destructor_fn(task, automember_task_destructor);
|
||||
slapi_task_set_data(task, mytaskdata);
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ fixup_running = PR_TRUE;
|
||||
+ PR_Unlock(fixup_lock);
|
||||
/*
|
||||
* Start the task as a separate thread
|
||||
*/
|
||||
@@ -2345,6 +2434,9 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
"automember_task_add - Unable to create task thread!\n");
|
||||
*returncode = LDAP_OPERATIONS_ERROR;
|
||||
slapi_task_finish(task, *returncode);
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ fixup_running = PR_FALSE;
|
||||
+ PR_Unlock(fixup_lock);
|
||||
rv = SLAPI_DSE_CALLBACK_ERROR;
|
||||
} else {
|
||||
rv = SLAPI_DSE_CALLBACK_OK;
|
||||
@@ -2372,6 +2464,9 @@ automember_rebuild_task_thread(void *arg)
|
||||
PRCList *list = NULL;
|
||||
PRCList *include_list = NULL;
|
||||
int result = 0;
|
||||
+ int64_t fixup_progress_count = 0;
|
||||
+ int64_t fixup_progress_elapsed = 0;
|
||||
+ int64_t fixup_start_time = 0;
|
||||
size_t i = 0;
|
||||
|
||||
/* Reset abort flag */
|
||||
@@ -2380,6 +2475,7 @@ automember_rebuild_task_thread(void *arg)
|
||||
if (!task) {
|
||||
return; /* no task */
|
||||
}
|
||||
+
|
||||
slapi_task_inc_refcount(task);
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_rebuild_task_thread - Refcount incremented.\n");
|
||||
@@ -2393,9 +2489,11 @@ automember_rebuild_task_thread(void *arg)
|
||||
slapi_task_log_status(task, "Automember rebuild task starting (base dn: (%s) filter (%s)...",
|
||||
slapi_sdn_get_dn(td->base_dn), td->filter_str);
|
||||
/*
|
||||
- * Set the bind dn in the local thread data
|
||||
+ * Set the bind dn in the local thread data, and block post op mods
|
||||
*/
|
||||
slapi_td_set_dn(slapi_ch_strdup(td->bind_dn));
|
||||
+ slapi_td_block_nested_post_op();
|
||||
+ fixup_start_time = slapi_current_rel_time_t();
|
||||
/*
|
||||
* Take the config lock now and search the database
|
||||
*/
|
||||
@@ -2426,6 +2524,21 @@ automember_rebuild_task_thread(void *arg)
|
||||
* Loop over the entries
|
||||
*/
|
||||
for (i = 0; entries && (entries[i] != NULL); i++) {
|
||||
+ fixup_progress_count++;
|
||||
+ if (fixup_progress_count % FIXUP_PROGRESS_LIMIT == 0 ) {
|
||||
+ slapi_task_log_notice(task,
|
||||
+ "Processed %ld entries in %ld seconds (+%ld seconds)",
|
||||
+ fixup_progress_count,
|
||||
+ slapi_current_rel_time_t() - fixup_start_time,
|
||||
+ slapi_current_rel_time_t() - fixup_progress_elapsed);
|
||||
+ slapi_task_log_status(task,
|
||||
+ "Processed %ld entries in %ld seconds (+%ld seconds)",
|
||||
+ fixup_progress_count,
|
||||
+ slapi_current_rel_time_t() - fixup_start_time,
|
||||
+ slapi_current_rel_time_t() - fixup_progress_elapsed);
|
||||
+ slapi_task_inc_progress(task);
|
||||
+ fixup_progress_elapsed = slapi_current_rel_time_t();
|
||||
+ }
|
||||
if (slapi_atomic_load_64(&abort_rebuild_task, __ATOMIC_ACQUIRE) == 1) {
|
||||
/* The task was aborted */
|
||||
slapi_task_log_notice(task, "Automember rebuild task was intentionally aborted");
|
||||
@@ -2443,48 +2556,66 @@ automember_rebuild_task_thread(void *arg)
|
||||
if (slapi_dn_issuffix(slapi_entry_get_dn(entries[i]), config->scope) &&
|
||||
(slapi_filter_test_simple(entries[i], config->filter) == 0))
|
||||
{
|
||||
- /* First clear out all the defaults groups */
|
||||
- for (size_t ii = 0; config->default_groups && config->default_groups[ii]; ii++) {
|
||||
- if ((result = automember_update_member_value(entries[i], config->default_groups[ii],
|
||||
- config->grouping_attr, config->grouping_value, NULL, DEL_MEMBER)))
|
||||
- {
|
||||
- slapi_task_log_notice(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from default group (%s) error (%d)",
|
||||
- config->default_groups[ii], result);
|
||||
- slapi_task_log_status(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from default group (%s) error (%d)",
|
||||
- config->default_groups[ii], result);
|
||||
- slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
- "automember_rebuild_task_thread - Unable to unable to delete from (%s) error (%d)\n",
|
||||
- config->default_groups[ii], result);
|
||||
- goto out;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- /* Then clear out the non-default group */
|
||||
- if (config->inclusive_rules && !PR_CLIST_IS_EMPTY((PRCList *)config->inclusive_rules)) {
|
||||
- include_list = PR_LIST_HEAD((PRCList *)config->inclusive_rules);
|
||||
- while (include_list != (PRCList *)config->inclusive_rules) {
|
||||
- struct automemberRegexRule *curr_rule = (struct automemberRegexRule *)include_list;
|
||||
- if ((result = automember_update_member_value(entries[i], slapi_sdn_get_dn(curr_rule->target_group_dn),
|
||||
- config->grouping_attr, config->grouping_value, NULL, DEL_MEMBER)))
|
||||
+ if (td->cleanup) {
|
||||
+
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Cleaning up groups (config %s)\n",
|
||||
+ config->dn);
|
||||
+ /* First clear out all the defaults groups */
|
||||
+ for (size_t ii = 0; config->default_groups && config->default_groups[ii]; ii++) {
|
||||
+ if ((result = automember_update_member_value(entries[i],
|
||||
+ config->default_groups[ii],
|
||||
+ config->grouping_attr,
|
||||
+ config->grouping_value,
|
||||
+ NULL, DEL_MEMBER)))
|
||||
{
|
||||
slapi_task_log_notice(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from group (%s) error (%d)",
|
||||
- slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ "member from default group (%s) error (%d)",
|
||||
+ config->default_groups[ii], result);
|
||||
slapi_task_log_status(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from group (%s) error (%d)",
|
||||
- slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ "member from default group (%s) error (%d)",
|
||||
+ config->default_groups[ii], result);
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_rebuild_task_thread - Unable to unable to delete from (%s) error (%d)\n",
|
||||
- slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ config->default_groups[ii], result);
|
||||
goto out;
|
||||
}
|
||||
- include_list = PR_NEXT_LINK(include_list);
|
||||
}
|
||||
+
|
||||
+ /* Then clear out the non-default group */
|
||||
+ if (config->inclusive_rules && !PR_CLIST_IS_EMPTY((PRCList *)config->inclusive_rules)) {
|
||||
+ include_list = PR_LIST_HEAD((PRCList *)config->inclusive_rules);
|
||||
+ while (include_list != (PRCList *)config->inclusive_rules) {
|
||||
+ struct automemberRegexRule *curr_rule = (struct automemberRegexRule *)include_list;
|
||||
+ if ((result = automember_update_member_value(entries[i],
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn),
|
||||
+ config->grouping_attr,
|
||||
+ config->grouping_value,
|
||||
+ NULL, DEL_MEMBER)))
|
||||
+ {
|
||||
+ slapi_task_log_notice(task, "Automember rebuild membership task unable to delete "
|
||||
+ "member from group (%s) error (%d)",
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ slapi_task_log_status(task, "Automember rebuild membership task unable to delete "
|
||||
+ "member from group (%s) error (%d)",
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Unable to unable to delete from (%s) error (%d)\n",
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ goto out;
|
||||
+ }
|
||||
+ include_list = PR_NEXT_LINK(include_list);
|
||||
+ }
|
||||
+ }
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Finished cleaning up groups (config %s)\n",
|
||||
+ config->dn);
|
||||
}
|
||||
|
||||
/* Update the memberships for this entries */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Updating membership (config %s)\n",
|
||||
+ config->dn);
|
||||
if (slapi_is_shutting_down() ||
|
||||
automember_update_membership(config, entries[i], NULL) == SLAPI_PLUGIN_FAILURE)
|
||||
{
|
||||
@@ -2508,15 +2639,22 @@ out:
|
||||
slapi_task_log_notice(task, "Automember rebuild task aborted. Error (%d)", result);
|
||||
slapi_task_log_status(task, "Automember rebuild task aborted. Error (%d)", result);
|
||||
} else {
|
||||
- slapi_task_log_notice(task, "Automember rebuild task finished. Processed (%d) entries.", (int32_t)i);
|
||||
- slapi_task_log_status(task, "Automember rebuild task finished. Processed (%d) entries.", (int32_t)i);
|
||||
+ slapi_task_log_notice(task, "Automember rebuild task finished. Processed (%ld) entries in %ld seconds",
|
||||
+ (int64_t)i, slapi_current_rel_time_t() - fixup_start_time);
|
||||
+ slapi_task_log_status(task, "Automember rebuild task finished. Processed (%ld) entries in %ld seconds",
|
||||
+ (int64_t)i, slapi_current_rel_time_t() - fixup_start_time);
|
||||
}
|
||||
slapi_task_inc_progress(task);
|
||||
slapi_task_finish(task, result);
|
||||
slapi_task_dec_refcount(task);
|
||||
slapi_atomic_store_64(&abort_rebuild_task, 0, __ATOMIC_RELEASE);
|
||||
+ slapi_td_unblock_nested_post_op();
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ fixup_running = PR_FALSE;
|
||||
+ PR_Unlock(fixup_lock);
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
- "automember_rebuild_task_thread - Refcount decremented.\n");
|
||||
+ "automember_rebuild_task_thread - task finished, refcount decremented.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
index ba2d73a84..ce4c314a1 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
@@ -1,6 +1,6 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2005 Red Hat, Inc.
|
||||
+ * Copyright (C) 2022 Red Hat, Inc.
|
||||
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -1264,10 +1264,6 @@ ldbm_back_add(Slapi_PBlock *pb)
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
if (addingentry_id_assigned) {
|
||||
next_id_return(be, addingentry->ep_id);
|
||||
}
|
||||
@@ -1376,6 +1372,11 @@ diskfull_return:
|
||||
if (!not_an_error) {
|
||||
rc = SLAPI_FAIL_GENERAL;
|
||||
}
|
||||
+
|
||||
+ /* Revert the caches if this is the parent operation */
|
||||
+ if (parent_op && betxn_callback_fails) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
}
|
||||
|
||||
common_return:
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
index de23190c3..27f0ac58a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
@@ -1407,11 +1407,6 @@ commit_return:
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
-
|
||||
if (tombstone) {
|
||||
if (cache_is_in_cache(&inst->inst_cache, tombstone)) {
|
||||
tomb_ep_id = tombstone->ep_id; /* Otherwise, tombstone might have been freed. */
|
||||
@@ -1496,6 +1491,11 @@ error_return:
|
||||
conn_id, op_id, parent_modify_c.old_entry, parent_modify_c.new_entry, myrc);
|
||||
}
|
||||
|
||||
+ /* Revert the caches if this is the parent operation */
|
||||
+ if (parent_op && betxn_callback_fails) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
+
|
||||
common_return:
|
||||
if (orig_entry) {
|
||||
/* NOTE: #define SLAPI_DELETE_BEPREOP_ENTRY SLAPI_ENTRY_PRE_OP */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
index 537369055..64b293001 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
@@ -1,6 +1,6 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2005 Red Hat, Inc.
|
||||
+ * Copyright (C) 2022 Red Hat, Inc.
|
||||
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -1043,11 +1043,6 @@ ldbm_back_modify(Slapi_PBlock *pb)
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
-
|
||||
if (postentry != NULL) {
|
||||
slapi_entry_free(postentry);
|
||||
postentry = NULL;
|
||||
@@ -1103,6 +1098,10 @@ error_return:
|
||||
if (!not_an_error) {
|
||||
rc = SLAPI_FAIL_GENERAL;
|
||||
}
|
||||
+ /* Revert the caches if this is the parent operation */
|
||||
+ if (parent_op && betxn_callback_fails) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* if ec is in cache, remove it, then add back e if we still have it */
|
||||
diff --git a/src/lib389/lib389/cli_conf/plugins/automember.py b/src/lib389/lib389/cli_conf/plugins/automember.py
|
||||
index 15b00c633..568586ad8 100644
|
||||
--- a/src/lib389/lib389/cli_conf/plugins/automember.py
|
||||
+++ b/src/lib389/lib389/cli_conf/plugins/automember.py
|
||||
@@ -155,7 +155,7 @@ def fixup(inst, basedn, log, args):
|
||||
log.info('Attempting to add task entry... This will fail if Automembership plug-in is not enabled.')
|
||||
if not plugin.status():
|
||||
log.error("'%s' is disabled. Rebuild membership task can't be executed" % plugin.rdn)
|
||||
- fixup_task = plugin.fixup(args.DN, args.filter)
|
||||
+ fixup_task = plugin.fixup(args.DN, args.filter, args.cleanup)
|
||||
if args.wait:
|
||||
log.info(f'Waiting for fixup task "{fixup_task.dn}" to complete. You can safely exit by pressing Control C ...')
|
||||
fixup_task.wait(timeout=args.timeout)
|
||||
@@ -225,8 +225,8 @@ def create_parser(subparsers):
|
||||
subcommands = automember.add_subparsers(help='action')
|
||||
add_generic_plugin_parsers(subcommands, AutoMembershipPlugin)
|
||||
|
||||
- list = subcommands.add_parser('list', help='List Automembership definitions or regex rules.')
|
||||
- subcommands_list = list.add_subparsers(help='action')
|
||||
+ automember_list = subcommands.add_parser('list', help='List Automembership definitions or regex rules.')
|
||||
+ subcommands_list = automember_list.add_subparsers(help='action')
|
||||
list_definitions = subcommands_list.add_parser('definitions', help='Lists Automembership definitions.')
|
||||
list_definitions.set_defaults(func=definition_list)
|
||||
list_regexes = subcommands_list.add_parser('regexes', help='List Automembership regex rules.')
|
||||
@@ -269,6 +269,8 @@ def create_parser(subparsers):
|
||||
fixup_task.add_argument('-f', '--filter', required=True, help='Sets the LDAP filter for entries to fix up')
|
||||
fixup_task.add_argument('-s', '--scope', required=True, choices=['sub', 'base', 'one'], type=str.lower,
|
||||
help='Sets the LDAP search scope for entries to fix up')
|
||||
+ fixup_task.add_argument('--cleanup', action='store_true',
|
||||
+ help="Clean up previous group memberships before rebuilding")
|
||||
fixup_task.add_argument('--wait', action='store_true',
|
||||
help="Wait for the task to finish, this could take a long time")
|
||||
fixup_task.add_argument('--timeout', default=0, type=int,
|
||||
@@ -279,7 +281,7 @@ def create_parser(subparsers):
|
||||
fixup_status.add_argument('--dn', help="The task entry's DN")
|
||||
fixup_status.add_argument('--show-log', action='store_true', help="Display the task log")
|
||||
fixup_status.add_argument('--watch', action='store_true',
|
||||
- help="Watch the task's status and wait for it to finish")
|
||||
+ help="Watch the task's status and wait for it to finish")
|
||||
|
||||
abort_fixup = subcommands.add_parser('abort-fixup', help='Abort the rebuild membership task.')
|
||||
abort_fixup.set_defaults(func=abort)
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 52691a44c..a1ad0a45b 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -1141,13 +1141,15 @@ class AutoMembershipPlugin(Plugin):
|
||||
def __init__(self, instance, dn="cn=Auto Membership Plugin,cn=plugins,cn=config"):
|
||||
super(AutoMembershipPlugin, self).__init__(instance, dn)
|
||||
|
||||
- def fixup(self, basedn, _filter=None):
|
||||
+ def fixup(self, basedn, _filter=None, cleanup=False):
|
||||
"""Create an automember rebuild membership task
|
||||
|
||||
:param basedn: Basedn to fix up
|
||||
:type basedn: str
|
||||
:param _filter: a filter for entries to fix up
|
||||
:type _filter: str
|
||||
+ :param cleanup: cleanup old group memberships
|
||||
+ :type cleanup: boolean
|
||||
|
||||
:returns: an instance of Task(DSLdapObject)
|
||||
"""
|
||||
@@ -1156,6 +1158,9 @@ class AutoMembershipPlugin(Plugin):
|
||||
task_properties = {'basedn': basedn}
|
||||
if _filter is not None:
|
||||
task_properties['filter'] = _filter
|
||||
+ if cleanup:
|
||||
+ task_properties['cleanup'] = "yes"
|
||||
+
|
||||
task.create(properties=task_properties)
|
||||
|
||||
return task
|
||||
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
|
||||
index 1a16bbb83..193805780 100644
|
||||
--- a/src/lib389/lib389/tasks.py
|
||||
+++ b/src/lib389/lib389/tasks.py
|
||||
@@ -1006,12 +1006,13 @@ class Tasks(object):
|
||||
return exitCode
|
||||
|
||||
def automemberRebuild(self, suffix=DEFAULT_SUFFIX, scope='sub',
|
||||
- filterstr='objectclass=top', args=None):
|
||||
+ filterstr='objectclass=top', cleanup=False, args=None):
|
||||
'''
|
||||
- @param suffix - The suffix the task should examine - defualt is
|
||||
+ @param suffix - The suffix the task should examine - default is
|
||||
"dc=example,dc=com"
|
||||
@param scope - The scope of the search to find entries
|
||||
- @param fitlerstr - THe search filter to find entries
|
||||
+ @param fitlerstr - The search filter to find entries
|
||||
+ @param cleanup - reset/clear the old group mmeberships prior to rebuilding
|
||||
@param args - is a dictionary that contains modifier of the task
|
||||
wait: True/[False] - If True, waits for the completion of
|
||||
the task before to return
|
||||
@@ -1027,6 +1028,8 @@ class Tasks(object):
|
||||
entry.setValues('basedn', suffix)
|
||||
entry.setValues('filter', filterstr)
|
||||
entry.setValues('scope', scope)
|
||||
+ if cleanup:
|
||||
+ entry.setValues('cleanup', 'yes')
|
||||
|
||||
# start the task and possibly wait for task completion
|
||||
try:
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
From 56dde3de7c90d9d81f4588a5afda2cfa05bdb20f Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 24 Apr 2026 11:19:04 +0200
|
||||
Subject: [PATCH 4/8] Issue 7437 - LeakSanitizer: memory leaks in CoS cache
|
||||
error paths (#7438)
|
||||
|
||||
Description:
|
||||
Fix memory leaks in CoS plugin when a CoS definition fails validation
|
||||
or is incomplete.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7437
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
ldap/servers/plugins/cos/cos_cache.c | 16 ++++++++++++++++
|
||||
1 file changed, 16 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/plugins/cos/cos_cache.c b/ldap/servers/plugins/cos/cos_cache.c
|
||||
index d8ebbbe3d..7074cb60a 100644
|
||||
--- a/ldap/servers/plugins/cos/cos_cache.c
|
||||
+++ b/ldap/servers/plugins/cos/cos_cache.c
|
||||
@@ -1007,6 +1007,14 @@ cos_dn_defs_cb(Slapi_Entry *e, void *callback_data)
|
||||
cos_cache_del_attrval_list(&pCosSpecifier);
|
||||
if (pCosAttribute)
|
||||
cos_cache_del_attrval_list(&pCosAttribute);
|
||||
+ if (pCosOverrides)
|
||||
+ cos_cache_del_attrval_list(&pCosOverrides);
|
||||
+ if (pCosOperational)
|
||||
+ cos_cache_del_attrval_list(&pCosOperational);
|
||||
+ if (pCosMerge)
|
||||
+ cos_cache_del_attrval_list(&pCosMerge);
|
||||
+ if (pCosOpDefault)
|
||||
+ cos_cache_del_attrval_list(&pCosOpDefault);
|
||||
if (pDn)
|
||||
cos_cache_del_attrval_list(&pDn);
|
||||
}
|
||||
@@ -1430,6 +1438,14 @@ out:
|
||||
cos_cache_del_attrval_list(spec);
|
||||
if (pAttrs)
|
||||
cos_cache_del_attrval_list(pAttrs);
|
||||
+ if (pOverrides)
|
||||
+ cos_cache_del_attrval_list(pOverrides);
|
||||
+ if (pOperational)
|
||||
+ cos_cache_del_attrval_list(pOperational);
|
||||
+ if (pCosMerge)
|
||||
+ cos_cache_del_attrval_list(pCosMerge);
|
||||
+ if (pCosOpDefault)
|
||||
+ cos_cache_del_attrval_list(pCosOpDefault);
|
||||
}
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "<-- cos_cache_add_defn\n");
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
From 9319d5b022918f14cacb00e3faef85a6ab730a26 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 27 Feb 2024 16:30:47 -0800
|
||||
Subject: [PATCH] Issue 3527 - Support HAProxy and Instance on the same machine
|
||||
configuration (#6107)
|
||||
|
||||
Description: Improve how we handle HAProxy connections to work better when
|
||||
the DS and HAProxy are on the same machine.
|
||||
Ensure the client and header destination IPs are checked against the trusted IP list.
|
||||
|
||||
Additionally, this change will also allow configuration having
|
||||
HAProxy is listening on a different subnet than the one used to forward the request.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/3527
|
||||
|
||||
Reviewed by: @progier389, @jchapma (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/connection.c | 35 +++++++++++++++++++++++++--------
|
||||
1 file changed, 27 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
|
||||
index d28a39bf7..10a8cc577 100644
|
||||
--- a/ldap/servers/slapd/connection.c
|
||||
+++ b/ldap/servers/slapd/connection.c
|
||||
@@ -1187,6 +1187,8 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
|
||||
char str_ip[INET6_ADDRSTRLEN + 1] = {0};
|
||||
char str_haproxy_ip[INET6_ADDRSTRLEN + 1] = {0};
|
||||
char str_haproxy_destip[INET6_ADDRSTRLEN + 1] = {0};
|
||||
+ int trusted_matches_ip_found = 0;
|
||||
+ int trusted_matches_destip_found = 0;
|
||||
struct berval **bvals = NULL;
|
||||
int proxy_connection = 0;
|
||||
|
||||
@@ -1245,21 +1247,38 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
|
||||
normalize_IPv4(conn->cin_addr, buf_ip, sizeof(buf_ip), str_ip, sizeof(str_ip));
|
||||
normalize_IPv4(&pr_netaddr_dest, buf_haproxy_destip, sizeof(buf_haproxy_destip),
|
||||
str_haproxy_destip, sizeof(str_haproxy_destip));
|
||||
+ size_t ip_len = strlen(buf_ip);
|
||||
+ size_t destip_len = strlen(buf_haproxy_destip);
|
||||
|
||||
/* Now, reset RC and set it to 0 only if a match is found */
|
||||
haproxy_rc = -1;
|
||||
|
||||
- /* Allow only:
|
||||
- * Trusted IP == Original Client IP == HAProxy Header Destination IP */
|
||||
+ /*
|
||||
+ * We need to allow a configuration where DS instance and HAProxy are on the same machine.
|
||||
+ * In this case, we need to check if
|
||||
+ * the HAProxy client IP (which will be a loopback address) matches one of the the trusted IP addresses,
|
||||
+ * while still checking that
|
||||
+ * the HAProxy header destination IP address matches one of the trusted IP addresses.
|
||||
+ * Additionally, this change will also allow configuration having
|
||||
+ * HAProxy listening on a different subnet than one used to forward the request.
|
||||
+ */
|
||||
for (size_t i = 0; bvals[i] != NULL; ++i) {
|
||||
- if ((strlen(bvals[i]->bv_val) == strlen(buf_ip)) &&
|
||||
- (strlen(bvals[i]->bv_val) == strlen(buf_haproxy_destip)) &&
|
||||
- (strncasecmp(bvals[i]->bv_val, buf_ip, strlen(buf_ip)) == 0) &&
|
||||
- (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, strlen(buf_haproxy_destip)) == 0)) {
|
||||
- haproxy_rc = 0;
|
||||
- break;
|
||||
+ size_t bval_len = strlen(bvals[i]->bv_val);
|
||||
+
|
||||
+ /* Check if the Client IP (HAProxy's machine IP) address matches the trusted IP address */
|
||||
+ if (!trusted_matches_ip_found) {
|
||||
+ trusted_matches_ip_found = (bval_len == ip_len) && (strncasecmp(bvals[i]->bv_val, buf_ip, ip_len) == 0);
|
||||
+ }
|
||||
+ /* Check if the HAProxy header destination IP address matches the trusted IP address */
|
||||
+ if (!trusted_matches_destip_found) {
|
||||
+ trusted_matches_destip_found = (bval_len == destip_len) && (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, destip_len) == 0);
|
||||
}
|
||||
}
|
||||
+
|
||||
+ if (trusted_matches_ip_found && trusted_matches_destip_found) {
|
||||
+ haproxy_rc = 0;
|
||||
+ }
|
||||
+
|
||||
if (haproxy_rc == -1) {
|
||||
slapi_log_err(SLAPI_LOG_CONNS, "connection_read_operation", "HAProxy header received from unknown source.\n");
|
||||
disconnect_server_nomutex(conn, conn->c_connid, -1, SLAPD_DISCONNECT_PROXY_UNKNOWN, EPROTO);
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,169 @@
|
||||
From f34eff84824f64470a8c181a0f7a8492695eaab3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 5 May 2026 11:13:47 +0200
|
||||
Subject: [PATCH 5/8] Issue 7372 - Reindex adds tombstones to ancestorid
|
||||
causing export failures (#7373)
|
||||
|
||||
Bug Description:
|
||||
During import/reindex, tombstone entries are added to the ancestorid
|
||||
index. When those tombstones are later purged, the purge thread skips
|
||||
updates to ancestorid index. This leaves stale entry IDs in the
|
||||
ancestorid index referencing entries in id2entry that are no longer
|
||||
there.
|
||||
|
||||
Fix Description:
|
||||
Skip tombstone entries when building the ancestorid index during
|
||||
import/reindex.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7372
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../export/export_reindex_tombstone_test.py | 107 ++++++++++++++++++
|
||||
.../slapd/back-ldbm/db-bdb/bdb_ldif2db.c | 2 +-
|
||||
.../back-ldbm/db-mdb/mdb_import_threads.c | 2 +-
|
||||
3 files changed, 109 insertions(+), 2 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py b/dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py
|
||||
new file mode 100644
|
||||
index 000000000..3cb6e6178
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py
|
||||
@@ -0,0 +1,107 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2026 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+
|
||||
+import os
|
||||
+import time
|
||||
+import pytest
|
||||
+import ldap
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.domain import Domain
|
||||
+from lib389.tombstone import Tombstones
|
||||
+from lib389.topologies import topology_m2 as topo_m2
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DEFAULT_BENAME, ErrorLog
|
||||
+from lib389.utils import *
|
||||
+from lib389.backend import Backends
|
||||
+from lib389.replica import Replicas, ReplicationManager
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+
|
||||
+def test_export_after_reindex_and_tombstone_purge(topo_m2):
|
||||
+ """Test that export with -s works after reindex and tombstone purge.
|
||||
+
|
||||
+ :id: 8c5cb603-1f43-46ad-9935-5f518d6d7fe0
|
||||
+ :setup: Two supplier replication topology
|
||||
+ :steps:
|
||||
+ 1. Add entries under ou=people on S1, wait for replication to S2
|
||||
+ 2. Delete the entries on S1, wait for replication
|
||||
+ 3. Perform additional modifications to advance the RUV
|
||||
+ 4. Reindex S1
|
||||
+ 5. Configure aggressive tombstone purging and wait for tombstones to be purged on S1
|
||||
+ 6. Export ou=people,dc=example,dc=com with -s
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Export completes successfully
|
||||
+ """
|
||||
+ S1 = topo_m2.ms["supplier1"]
|
||||
+ S2 = topo_m2.ms["supplier2"]
|
||||
+ PEOPLE = f"ou=people,{DEFAULT_SUFFIX}"
|
||||
+
|
||||
+ users = UserAccounts(S1, DEFAULT_SUFFIX, rdn="ou=people")
|
||||
+ test_users = []
|
||||
+ for i in range(5):
|
||||
+ user = users.create_test_user(uid=1234 + i)
|
||||
+ test_users.append(user)
|
||||
+ log.info("Added 5 test entries under ou=people")
|
||||
+
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+ repl.wait_for_replication(S1, S2)
|
||||
+
|
||||
+ for user in test_users:
|
||||
+ user.delete()
|
||||
+ log.info("Deleted 5 test entries")
|
||||
+
|
||||
+ repl.wait_for_replication(S1, S2)
|
||||
+
|
||||
+ domain = Domain(S1, DEFAULT_SUFFIX)
|
||||
+ for i in range(10):
|
||||
+ domain.replace("description", f"advancing RUV {i}")
|
||||
+ repl.wait_for_replication(S1, S2)
|
||||
+
|
||||
+ domain2 = Domain(S2, DEFAULT_SUFFIX)
|
||||
+ for i in range(10):
|
||||
+ domain2.replace("description", f"advancing RUV from S2 {i}")
|
||||
+ repl.wait_for_replication(S2, S1)
|
||||
+ log.info("RUV advanced on both suppliers")
|
||||
+
|
||||
+ S1.stop()
|
||||
+ S1.db2index(DEFAULT_BENAME)
|
||||
+ log.info("Reindex completed on S1")
|
||||
+ S1.start()
|
||||
+
|
||||
+ replica = Replicas(S1).get(DEFAULT_SUFFIX)
|
||||
+ replica.replace("nsDS5ReplicaPurgeDelay", "1")
|
||||
+ replica.replace("nsDS5ReplicaTombstonePurgeInterval", "1")
|
||||
+
|
||||
+ S1.config.loglevel((ErrorLog.REPLICA,), "error")
|
||||
+
|
||||
+ log.info("Waiting for tombstone purge on S1...")
|
||||
+ tombstones = Tombstones(S1, PEOPLE)
|
||||
+ for attempt in range(60):
|
||||
+ time.sleep(2)
|
||||
+ ts_list = tombstones.list()
|
||||
+ if len(ts_list) == 0:
|
||||
+ log.info(f"All tombstones purged after {(attempt + 1) * 2}s")
|
||||
+ break
|
||||
+ log.info(f"Attempt {attempt + 1}: {len(ts_list)} tombstones remaining")
|
||||
+ else:
|
||||
+ pytest.fail("Tombstones not purged after 120s")
|
||||
+
|
||||
+ S1.deleteErrorLogs()
|
||||
+
|
||||
+ backends = Backends(S1)
|
||||
+ task = backends.export_ldif(be_names=[DEFAULT_BENAME], include_suffixes=[PEOPLE])
|
||||
+ task.wait()
|
||||
+ assert task.is_complete()
|
||||
+ assert task.get_exit_code() == 0
|
||||
+
|
||||
+ log.info("Export after reindex + tombstone purge succeeded")
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
|
||||
index f5127cc66..5138646f6 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
|
||||
@@ -2053,7 +2053,7 @@ bdb_db2index(Slapi_PBlock *pb)
|
||||
/*
|
||||
* Update the ancestorid and entryrdn index
|
||||
*/
|
||||
- if (!entryrdn_get_noancestorid() && (index_ext & DB2INDEX_ANCESTORID)) {
|
||||
+ if (!entryrdn_get_noancestorid() && (index_ext & DB2INDEX_ANCESTORID) && !istombstone) {
|
||||
rc = ldbm_ancestorid_index_entry(be, ep, BE_INDEX_ADD, NULL);
|
||||
if (rc != 0) {
|
||||
slapi_log_err(SLAPI_LOG_ERR,
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
index d37f223fd..35965df85 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
@@ -3087,7 +3087,7 @@ process_entryrdn(backentry *ep, WorkerQueueData_t *wqelmnt)
|
||||
add_update_entry_operational_attributes(ep, 0);
|
||||
}
|
||||
|
||||
- if (ctx->ancestorid && wqelmnt->entry_info) {
|
||||
+ if (ctx->ancestorid && wqelmnt->entry_info && wqelmnt->dnrc != DNRC_TOMBSTONE) {
|
||||
/* Update ancestorids */
|
||||
wqd.dbi = ctx->ancestorid->dbi;
|
||||
for (n=0; n<wqelmnt->entry_info[INFO_IDX_NB_ANCESTORS]; n++) {
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
From 016a2b6bd3e27cbff36609824a75b020dfd24823 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 1 May 2024 15:01:33 +0100
|
||||
Subject: [PATCH] CVE-2024-2199
|
||||
|
||||
---
|
||||
.../tests/suites/password/password_test.py | 56 +++++++++++++++++++
|
||||
ldap/servers/slapd/modify.c | 8 ++-
|
||||
2 files changed, 62 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/password_test.py b/dirsrvtests/tests/suites/password/password_test.py
|
||||
index 38079476a..b3ff08904 100644
|
||||
--- a/dirsrvtests/tests/suites/password/password_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/password_test.py
|
||||
@@ -65,6 +65,62 @@ def test_password_delete_specific_password(topology_st):
|
||||
log.info('test_password_delete_specific_password: PASSED')
|
||||
|
||||
|
||||
+def test_password_modify_non_utf8(topology_st):
|
||||
+ """Attempt a modify of the userPassword attribute with
|
||||
+ an invalid non utf8 value
|
||||
+
|
||||
+ :id: a31af9d5-d665-42b9-8d6e-fea3d0837d36
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Add a user if it doesnt exist and set its password
|
||||
+ 2. Verify password with a bind
|
||||
+ 3. Modify userPassword attr with invalid value
|
||||
+ 4. Attempt a bind with invalid password value
|
||||
+ 5. Verify original password with a bind
|
||||
+ :expectedresults:
|
||||
+ 1. The user with userPassword should be added successfully
|
||||
+ 2. Operation should be successful
|
||||
+ 3. Server returns ldap.UNWILLING_TO_PERFORM
|
||||
+ 4. Server returns ldap.INVALID_CREDENTIALS
|
||||
+ 5. Operation should be successful
|
||||
+ """
|
||||
+
|
||||
+ log.info('Running test_password_modify_non_utf8...')
|
||||
+
|
||||
+ # Create user and set password
|
||||
+ standalone = topology_st.standalone
|
||||
+ users = UserAccounts(standalone, DEFAULT_SUFFIX)
|
||||
+ if not users.exists(TEST_USER_PROPERTIES['uid'][0]):
|
||||
+ user = users.create(properties=TEST_USER_PROPERTIES)
|
||||
+ else:
|
||||
+ user = users.get(TEST_USER_PROPERTIES['uid'][0])
|
||||
+ user.set('userpassword', PASSWORD)
|
||||
+
|
||||
+ # Verify password
|
||||
+ try:
|
||||
+ user.bind(PASSWORD)
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('Failed to bind as {}, error: '.format(user.dn) + e.args[0]['desc'])
|
||||
+ assert False
|
||||
+
|
||||
+ # Modify userPassword with an invalid value
|
||||
+ password = b'tes\x82t-password' # A non UTF-8 encoded password
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ user.replace('userpassword', password)
|
||||
+
|
||||
+ # Verify a bind fails with invalid pasword
|
||||
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
||||
+ user.bind(password)
|
||||
+
|
||||
+ # Verify we can still bind with original password
|
||||
+ try:
|
||||
+ user.bind(PASSWORD)
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('Failed to bind as {}, error: '.format(user.dn) + e.args[0]['desc'])
|
||||
+ assert False
|
||||
+
|
||||
+ log.info('test_password_modify_non_utf8: PASSED')
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index 5ca78539c..669bb104c 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -765,8 +765,10 @@ op_shared_modify(Slapi_PBlock *pb, int pw_change, char *old_pw)
|
||||
* flagged - leave mod attributes alone */
|
||||
if (!repl_op && !skip_modified_attrs && lastmod) {
|
||||
modify_update_last_modified_attr(pb, &smods);
|
||||
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, slapi_mods_get_ldapmods_byref(&smods));
|
||||
}
|
||||
|
||||
+
|
||||
if (0 == slapi_mods_get_num_mods(&smods)) {
|
||||
/* nothing to do - no mods - this is not an error - just
|
||||
send back LDAP_SUCCESS */
|
||||
@@ -933,8 +935,10 @@ op_shared_modify(Slapi_PBlock *pb, int pw_change, char *old_pw)
|
||||
|
||||
/* encode password */
|
||||
if (pw_encodevals_ext(pb, sdn, va)) {
|
||||
- slapi_log_err(SLAPI_LOG_CRIT, "op_shared_modify", "Unable to hash userPassword attribute for %s.\n", slapi_entry_get_dn_const(e));
|
||||
- send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Unable to store attribute \"userPassword\" correctly\n", 0, NULL);
|
||||
+ slapi_log_err(SLAPI_LOG_CRIT, "op_shared_modify", "Unable to hash userPassword attribute for %s, "
|
||||
+ "check value is utf8 string.\n", slapi_entry_get_dn_const(e));
|
||||
+ send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Unable to hash \"userPassword\" attribute, "
|
||||
+ "check value is utf8 string.\n", 0, NULL);
|
||||
valuearray_free(&va);
|
||||
goto free_and_return;
|
||||
}
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,199 @@
|
||||
From af0216be875e3580f5c916c7ca9ab63db4df32f7 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 8 Apr 2026 23:56:15 +0100
|
||||
Subject: [PATCH 6/8] Issue 7327 - dsctl healthcheck DSMOLE0001 inaccurate
|
||||
recommendations with multiple backends (#7328)
|
||||
|
||||
Description:
|
||||
The dsctl healthcheck tool generates incorrect recommendations for the MO plugin when
|
||||
multiple backends are present. This commonly occurs in IPA environments where both
|
||||
userroot and ipaca backends exist. Healthcheck incorrectly suggests indexing attributes
|
||||
for backends that are not within the MemberOf plugin scope.
|
||||
|
||||
Fix:
|
||||
Determine the MO plugin scope while iterating over backends and only generate
|
||||
recommendations for backends that fall within that scope.
|
||||
|
||||
Removed references to nsslapd-plugincontainerscope as we dont use it.
|
||||
|
||||
Fixes:
|
||||
https://github.com/389ds/389-ds-base/issues/7327
|
||||
|
||||
Reviewed by: @droideck (Thank you)
|
||||
---
|
||||
.../memberof_include_scopes_test.py | 86 +++++++++++++++++++
|
||||
src/lib389/lib389/plugins.py | 30 ++++---
|
||||
2 files changed, 106 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
|
||||
index 347eb880f..8cdf94f3c 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
|
||||
@@ -10,6 +10,7 @@ import pytest
|
||||
import os
|
||||
import ldap
|
||||
import time
|
||||
+from lib389.backend import Backends
|
||||
from lib389.utils import ensure_str
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389._constants import *
|
||||
@@ -17,6 +18,8 @@ from lib389.plugins import MemberOfPlugin, ReferentialIntegrityPlugin
|
||||
from lib389.idm.user import UserAccount, UserAccounts
|
||||
from lib389.idm.group import Group, Groups
|
||||
from lib389.idm.nscontainer import nsContainers
|
||||
+from lib389.idm.domain import Domain
|
||||
+from lib389.idm.organizationalunit import OrganizationalUnit
|
||||
|
||||
SUBTREE_1 = 'cn=sub1,%s' % SUFFIX
|
||||
SUBTREE_2 = 'cn=sub2,%s' % SUFFIX
|
||||
@@ -131,6 +134,89 @@ def test_multiple_scopes(topo):
|
||||
assert not group.present("member", INCLUDED_USER)
|
||||
assert not group.present("member", EXCLUDED_USER)
|
||||
|
||||
+def test_memberof_scope_multiple_backends(topology_st):
|
||||
+ """Test memberOf plugin correctly handles multiple backends with different scopes
|
||||
+
|
||||
+ :id: 96419128-70a4-4a81-943e-8d1e9c92f241
|
||||
+ :setup: Instance with multiple backends
|
||||
+ :steps:
|
||||
+ 1. Create out of scope backend
|
||||
+ 2. Create domain and OU's for out of scope backend
|
||||
+ 3. Create groups and users in both backends
|
||||
+ 4. Enable MO plugin and set scope to backend 1
|
||||
+ 5. Add users to groups in both backends
|
||||
+ 6. Verify in scope user has memberOf attr value of in scope group dn
|
||||
+ 7. Verify out scope user has an empty memberOf attr value
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success, trigger MO update
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ outscope_suffix = 'dc=other,dc=org'
|
||||
+
|
||||
+ # Create a backend 2
|
||||
+ backends = Backends(inst)
|
||||
+ outscope_be = backends.create(properties={
|
||||
+ 'cn': 'testBackend',
|
||||
+ 'nsslapd-suffix': outscope_suffix
|
||||
+ })
|
||||
+
|
||||
+ # Create domain entry for backend 2
|
||||
+ domain = Domain(inst, outscope_suffix)
|
||||
+ domain.create(properties={
|
||||
+ 'dc': 'other'
|
||||
+ })
|
||||
+
|
||||
+ # Create OUs for backend 2
|
||||
+ people_ou = OrganizationalUnit(inst, f'ou=People,{outscope_suffix}')
|
||||
+ people_ou.create(properties={'ou': 'People'})
|
||||
+ groups_ou = OrganizationalUnit(inst, f'ou=Groups,{outscope_suffix}')
|
||||
+ groups_ou.create(properties={'ou': 'Groups'})
|
||||
+
|
||||
+ # Create test groups and users
|
||||
+ inscope_users = UserAccounts(inst, 'dc=example,dc=com')
|
||||
+ inscope_groups = Groups(inst, 'dc=example,dc=com')
|
||||
+ inscope_user = inscope_users.create_test_user(uid=1000)
|
||||
+ inscope_group = inscope_groups.create(properties={'cn': 'testgroup1'}) # NO MEMBERS initially
|
||||
+
|
||||
+ outscope_users = UserAccounts(inst, outscope_suffix)
|
||||
+ outscope_groups = Groups(inst, outscope_suffix)
|
||||
+ outscope_user = outscope_users.create_test_user(uid=2000)
|
||||
+ outscope_group = outscope_groups.create(properties={'cn': 'testgroup2'}) # NO MEMBERS initially
|
||||
+
|
||||
+ # Configure memberof plugin with backend 1 scope
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.enable()
|
||||
+ memberof.replace('memberOfEntryScope', 'dc=example,dc=com')
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Trigger memberof processing
|
||||
+ inscope_group.add('member', inscope_user.dn)
|
||||
+ outscope_group.add('member', outscope_user.dn)
|
||||
+
|
||||
+ # Sleep for a bit
|
||||
+ time.sleep(2)
|
||||
+
|
||||
+ # In scope user should have memberOf attribute
|
||||
+ inscope_user_entry = inscope_user.get_attrs_vals_utf8(['memberOf'])
|
||||
+ assert inscope_user_entry['memberOf'] == [inscope_group.dn]
|
||||
+
|
||||
+ # Out of scope user should not have memberOf attribute
|
||||
+ outscope_user_entry = outscope_user.get_attrs_vals_utf8(['memberOf'])
|
||||
+ assert outscope_user_entry['memberOf'] == []
|
||||
+
|
||||
+ # Cleanup
|
||||
+ inscope_group.delete()
|
||||
+ inscope_user.delete()
|
||||
+ outscope_group.delete()
|
||||
+ outscope_user.delete()
|
||||
+ outscope_be.delete()
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 483d86659..0c0148119 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -794,16 +794,21 @@ class MemberOfPlugin(Plugin):
|
||||
from lib389.backend import Backends
|
||||
backends = Backends(self._instance).list()
|
||||
attrs = self.get_attr_vals_utf8_l("memberofgroupattr")
|
||||
- container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
|
||||
+ scopes = self.get_attr_vals_utf8_l("memberofentryscope")
|
||||
for backend in backends:
|
||||
suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
if suffix == "cn=changelog":
|
||||
# Always skip retro changelog
|
||||
continue
|
||||
- if container is not None:
|
||||
- # Check if this backend is in the scope
|
||||
- if not container.endswith(suffix):
|
||||
- # skip this backend that is not in the scope
|
||||
+ if scopes:
|
||||
+ # Is this backend suffix in scope
|
||||
+ in_scope = False
|
||||
+ for scope in scopes:
|
||||
+ if scope.endswith(suffix) or suffix.endswith(scope):
|
||||
+ in_scope = True
|
||||
+ break
|
||||
+
|
||||
+ if not in_scope:
|
||||
continue
|
||||
indexes = backend.get_indexes()
|
||||
for attr in attrs:
|
||||
@@ -842,16 +847,21 @@ class MemberOfPlugin(Plugin):
|
||||
from lib389.backend import Backends
|
||||
backends = Backends(self._instance).list()
|
||||
membership_attrs = ['member', 'uniquemember']
|
||||
- container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
|
||||
+ scopes = self.get_attr_vals_utf8_l("memberofentryscope")
|
||||
for backend in backends:
|
||||
suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
if suffix == "cn=changelog":
|
||||
# Always skip retro changelog
|
||||
continue
|
||||
- if container is not None:
|
||||
- # Check if this backend is in the scope
|
||||
- if not container.endswith(suffix):
|
||||
- # skip this backend that is not in the scope
|
||||
+ if scopes:
|
||||
+ # Is this backend suffix in scope
|
||||
+ in_scope = False
|
||||
+ for scope in scopes:
|
||||
+ if scope.endswith(suffix) or suffix.endswith(scope):
|
||||
+ in_scope = True
|
||||
+ break
|
||||
+
|
||||
+ if not in_scope:
|
||||
continue
|
||||
indexes = backend.get_indexes()
|
||||
for attr in membership_attrs:
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,213 +0,0 @@
|
||||
From d5bbe52fbe84a7d3b5938bf82d5c4af15061a8e2 Mon Sep 17 00:00:00 2001
|
||||
From: Pierre Rogier <progier@redhat.com>
|
||||
Date: Wed, 17 Apr 2024 18:18:04 +0200
|
||||
Subject: [PATCH] CVE-2024-3657
|
||||
|
||||
---
|
||||
.../tests/suites/filter/large_filter_test.py | 34 +++++-
|
||||
ldap/servers/slapd/back-ldbm/index.c | 111 ++++++++++--------
|
||||
2 files changed, 92 insertions(+), 53 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/filter/large_filter_test.py b/dirsrvtests/tests/suites/filter/large_filter_test.py
|
||||
index ecc7bf979..40526bb16 100644
|
||||
--- a/dirsrvtests/tests/suites/filter/large_filter_test.py
|
||||
+++ b/dirsrvtests/tests/suites/filter/large_filter_test.py
|
||||
@@ -13,19 +13,29 @@ verify and testing Filter from a search
|
||||
|
||||
import os
|
||||
import pytest
|
||||
+import ldap
|
||||
|
||||
-from lib389._constants import PW_DM
|
||||
+from lib389._constants import PW_DM, DEFAULT_SUFFIX, ErrorLog
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
from lib389.idm.account import Accounts
|
||||
from lib389.backend import Backends
|
||||
from lib389.idm.domain import Domain
|
||||
+from lib389.utils import get_ldapurl_from_serverid
|
||||
|
||||
SUFFIX = 'dc=anuj,dc=com'
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
|
||||
+def open_new_ldapi_conn(dsinstance):
|
||||
+ ldapurl, certdir = get_ldapurl_from_serverid(dsinstance)
|
||||
+ assert 'ldapi://' in ldapurl
|
||||
+ conn = ldap.initialize(ldapurl)
|
||||
+ conn.sasl_interactive_bind_s("", ldap.sasl.external())
|
||||
+ return conn
|
||||
+
|
||||
+
|
||||
@pytest.fixture(scope="module")
|
||||
def _create_entries(request, topo):
|
||||
"""
|
||||
@@ -160,6 +170,28 @@ def test_large_filter(topo, _create_entries, real_value):
|
||||
assert len(Accounts(conn, SUFFIX).filter(real_value)) == 3
|
||||
|
||||
|
||||
+def test_long_filter_value(topo):
|
||||
+ """Exercise large eq filter with dn syntax attributes
|
||||
+
|
||||
+ :id: b069ef72-fcc3-11ee-981c-482ae39447e5
|
||||
+ :setup: Standalone
|
||||
+ :steps:
|
||||
+ 1. Try to pass filter rules as per the condition.
|
||||
+ :expectedresults:
|
||||
+ 1. Pass
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ inst.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE,ErrorLog.SEARCH_FILTER))
|
||||
+ filter_value = "a\x1Edmin" * 1025
|
||||
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
|
||||
+ filter_value = "aAdmin" * 1025
|
||||
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
|
||||
+ filter_value = "*"
|
||||
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
|
||||
+ inst.config.loglevel(vals=(ErrorLog.DEFAULT,))
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s -v %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
|
||||
index 410db23d1..30fa09ebb 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -71,6 +71,32 @@ typedef struct _index_buffer_handle index_buffer_handle;
|
||||
#define INDEX_BUFFER_FLAG_SERIALIZE 1
|
||||
#define INDEX_BUFFER_FLAG_STATS 2
|
||||
|
||||
+/*
|
||||
+ * space needed to encode a byte:
|
||||
+ * 0x00-0x31 and 0x7f-0xff requires 3 bytes: \xx
|
||||
+ * 0x22 and 0x5C requires 2 bytes: \" and \\
|
||||
+ * other requires 1 byte: c
|
||||
+ */
|
||||
+static char encode_size[] = {
|
||||
+ /* 0x00 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0x10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0x20 */ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
|
||||
+ /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
|
||||
+ /* 0x80 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0x90 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xA0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xB0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xC0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xD0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xE0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xF0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+};
|
||||
+
|
||||
+
|
||||
/* Index buffering functions */
|
||||
|
||||
static int
|
||||
@@ -799,65 +825,46 @@ index_add_mods(
|
||||
|
||||
/*
|
||||
* Convert a 'struct berval' into a displayable ASCII string
|
||||
+ * returns the printable string
|
||||
*/
|
||||
-
|
||||
-#define SPECIAL(c) (c < 32 || c > 126 || c == '\\' || c == '"')
|
||||
-
|
||||
const char *
|
||||
encode(const struct berval *data, char buf[BUFSIZ])
|
||||
{
|
||||
- char *s;
|
||||
- char *last;
|
||||
- if (data == NULL || data->bv_len == 0)
|
||||
- return "";
|
||||
- last = data->bv_val + data->bv_len - 1;
|
||||
- for (s = data->bv_val; s < last; ++s) {
|
||||
- if (SPECIAL(*s)) {
|
||||
- char *first = data->bv_val;
|
||||
- char *bufNext = buf;
|
||||
- size_t bufSpace = BUFSIZ - 4;
|
||||
- while (1) {
|
||||
- /* printf ("%lu bytes ASCII\n", (unsigned long)(s - first)); */
|
||||
- if (bufSpace < (size_t)(s - first))
|
||||
- s = first + bufSpace - 1;
|
||||
- if (s != first) {
|
||||
- memcpy(bufNext, first, s - first);
|
||||
- bufNext += (s - first);
|
||||
- bufSpace -= (s - first);
|
||||
- }
|
||||
- do {
|
||||
- if (bufSpace) {
|
||||
- *bufNext++ = '\\';
|
||||
- --bufSpace;
|
||||
- }
|
||||
- if (bufSpace < 2) {
|
||||
- memcpy(bufNext, "..", 2);
|
||||
- bufNext += 2;
|
||||
- goto bail;
|
||||
- }
|
||||
- if (*s == '\\' || *s == '"') {
|
||||
- *bufNext++ = *s;
|
||||
- --bufSpace;
|
||||
- } else {
|
||||
- sprintf(bufNext, "%02x", (unsigned)*(unsigned char *)s);
|
||||
- bufNext += 2;
|
||||
- bufSpace -= 2;
|
||||
- }
|
||||
- } while (++s <= last && SPECIAL(*s));
|
||||
- if (s > last)
|
||||
- break;
|
||||
- first = s;
|
||||
- while (!SPECIAL(*s) && s <= last)
|
||||
- ++s;
|
||||
- }
|
||||
- bail:
|
||||
- *bufNext = '\0';
|
||||
- /* printf ("%lu chars in buffer\n", (unsigned long)(bufNext - buf)); */
|
||||
+ if (!data || !data->bv_val) {
|
||||
+ strcpy(buf, "<NULL>");
|
||||
+ return buf;
|
||||
+ }
|
||||
+ char *endbuff = &buf[BUFSIZ-4]; /* Reserve space to append "...\0" */
|
||||
+ char *ptout = buf;
|
||||
+ unsigned char *ptin = (unsigned char*) data->bv_val;
|
||||
+ unsigned char *endptin = ptin+data->bv_len;
|
||||
+
|
||||
+ while (ptin < endptin) {
|
||||
+ if (ptout >= endbuff) {
|
||||
+ /*
|
||||
+ * BUFSIZ(8K) > SLAPI_LOG_BUFSIZ(2K) so the error log message will be
|
||||
+ * truncated anyway. So there is no real interrest to test if the original
|
||||
+ * data contains no special characters and return it as is.
|
||||
+ */
|
||||
+ strcpy(endbuff, "...");
|
||||
return buf;
|
||||
}
|
||||
+ switch (encode_size[*ptin]) {
|
||||
+ case 1:
|
||||
+ *ptout++ = *ptin++;
|
||||
+ break;
|
||||
+ case 2:
|
||||
+ *ptout++ = '\\';
|
||||
+ *ptout++ = *ptin++;
|
||||
+ break;
|
||||
+ case 3:
|
||||
+ sprintf(ptout, "\\%02x", *ptin++);
|
||||
+ ptout += 3;
|
||||
+ break;
|
||||
+ }
|
||||
}
|
||||
- /* printf ("%lu bytes, all ASCII\n", (unsigned long)(s - data->bv_val)); */
|
||||
- return data->bv_val;
|
||||
+ *ptout = 0;
|
||||
+ return buf;
|
||||
}
|
||||
|
||||
static const char *
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,651 @@
|
||||
From 50ee439bb1d2e2344ea8cef076d54585204d9fbb Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Wed, 25 Feb 2026 18:00:24 +0100
|
||||
Subject: [PATCH 7/8] Issue 7267 - MDB_BAD_VALSIZE error when updating index
|
||||
(#7268)
|
||||
|
||||
* Issue 7267 - MDB_BAD_VALSIZE error when updating index
|
||||
* Improve import log when writer fails
|
||||
* Fix Sourcery AI comments
|
||||
* Fix INDEX_KEY_LENGTH typo
|
||||
|
||||
Problem with the key prefix handling when key is too long and must be hashed.
|
||||
The issue is that the # that is prepended is not reset when iterating over the valueset values (Ending up with very long prefix)
|
||||
|
||||
Also refactored the code to avoid duplicate the code that prepare the key from the attribute value (used when updating the index or retrieving a value from an index)
|
||||
|
||||
Issue: #7267
|
||||
|
||||
Reviewed by: @tbordaz , @vashirov (Thanks!)
|
||||
|
||||
Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
|
||||
---
|
||||
.../tests/suites/indexes/regression_test.py | 58 +++++++
|
||||
ldap/servers/slapd/back-ldbm/attrcrypt.h | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/back-ldbm.h | 2 +
|
||||
.../slapd/back-ldbm/db-bdb/bdb_import.c | 39 +----
|
||||
.../back-ldbm/db-mdb/mdb_import_threads.c | 46 ++++-
|
||||
ldap/servers/slapd/back-ldbm/index.c | 161 +++++++-----------
|
||||
ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c | 13 +-
|
||||
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 2 +-
|
||||
ldap/servers/slapd/log.c | 40 +++++
|
||||
ldap/servers/slapd/slapi-private.h | 2 +
|
||||
10 files changed, 221 insertions(+), 144 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
index e2c2f5c35..53d340cdd 100644
|
||||
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
@@ -699,6 +699,64 @@ def test_idl_range_limit(topo, add_some_entries):
|
||||
assert len(entries) == 3
|
||||
|
||||
|
||||
+def test_large_multivalued_sn_attribute(topo):
|
||||
+ """Test adding a user entry with 512 values for sn attribute, each 512 bytes
|
||||
+
|
||||
+ :id: 8f2a9b3c-e8d7-11ef-9a5f-482ae39447e5
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Create a user with 512 sn values, each 512 bytes long
|
||||
+ 2. Verify the user was created successfully
|
||||
+ 3. Search for the user and verify all sn values are present
|
||||
+ 4. Clean up the user entry
|
||||
+ :expectedresults:
|
||||
+ 1. User is created successfully
|
||||
+ 2. User entry exists
|
||||
+ 3. All 512 sn values are present and have correct length
|
||||
+ 4. User is deleted successfully
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ log.info("Creating user with 512 sn values, each 512 bytes")
|
||||
+
|
||||
+ # Generate 512 unique sn values, each 512 bytes long
|
||||
+ # Use a pattern that makes each value unique but predictable
|
||||
+ sn_values = []
|
||||
+ for i in range(512):
|
||||
+ # Create a 512-byte value with unique identifier at the start
|
||||
+ value = f'sn_value_{i:04d}_' + 'x' * (512 - len(f'sn_value_{i:04d}_'))
|
||||
+ sn_values.append(value)
|
||||
+
|
||||
+ # Create the user with first sn value
|
||||
+ user_name = 'test_user_large_sn'
|
||||
+ user = users.create(properties={
|
||||
+ 'uid': user_name,
|
||||
+ 'cn': user_name,
|
||||
+ 'sn': sn_values,
|
||||
+ 'uidNumber': '99999',
|
||||
+ 'gidNumber': '99999',
|
||||
+ 'homeDirectory': f'/home/{user_name}'
|
||||
+ })
|
||||
+
|
||||
+ # Verify the entry was created and has all sn values
|
||||
+ log.info("Verifying all sn values are present")
|
||||
+ sn_attr_values = user.get_attr_vals_utf8('sn')
|
||||
+
|
||||
+ assert len(sn_attr_values) == 512, f"Expected 512 sn values, got {len(sn_attr_values)}"
|
||||
+
|
||||
+ # Verify each value has the correct length
|
||||
+ for idx, value in enumerate(sn_attr_values):
|
||||
+ assert len(value) == 512, f"sn value {idx} has length {len(value)}, expected 512"
|
||||
+
|
||||
+ log.info("Successfully created and verified user with 512 sn values of 512 bytes each")
|
||||
+
|
||||
+ # Clean up
|
||||
+ user.delete()
|
||||
+ log.info("User entry deleted successfully")
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/attrcrypt.h b/ldap/servers/slapd/back-ldbm/attrcrypt.h
|
||||
index d653ba951..dcbea80fe 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/attrcrypt.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/attrcrypt.h
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
-/* Private tructures and #defines used in the attribute encryption code. */
|
||||
+/* Private structures and #defines used in the attribute encryption code. */
|
||||
|
||||
#ifndef _ATTRCRYPT_H_
|
||||
#define _ATTRCRYPT_H_
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
index 70f532538..f77924f1a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
@@ -104,6 +104,8 @@ typedef unsigned short u_int16_t;
|
||||
*/
|
||||
#define BE_CHANGELOG_FILE "replication_changelog"
|
||||
|
||||
+#define INDEX_KEY_LENGTH(lenval,lenprefix) (lenval+lenprefix+2)
|
||||
+
|
||||
#define BDB_IMPL "bdb"
|
||||
#define BDB_BACKEND "libback-ldbm" /* This backend plugin */
|
||||
#define BDB_NEWIDL "newidl" /* new idl format */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
|
||||
index 59a472f58..bdc4ee4f6 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
|
||||
@@ -75,9 +75,9 @@ static IDList *bdb_idl_union_allids(backend *be, struct attrinfo *ai, IDList *a,
|
||||
#define DEBUG_SUBCOUNT_MSG(msg, ...) { debug_subcount(__FUNCTION__, __LINE__, (msg), __VA_ARGS__); }
|
||||
#define DUMP_SUBCOUNT_KEY(msg, key, ret) { debug_subcount(__FUNCTION__, __LINE__, "ret=%d size=%u ulen=%u doff=%u dlen=%u", \
|
||||
ret, (key).size, (key).ulen, (key).doff, (key).dlen); \
|
||||
- if (ret == 0) hexadump(msg, (key).data, 0, (key).size); \
|
||||
+ if (ret == 0) slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).size); \
|
||||
else if (ret == DB_BUFFER_SMALL) \
|
||||
- hexadump(msg, (key).data, 0, (key).ulen); }
|
||||
+ slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).ulen); }
|
||||
|
||||
static void
|
||||
debug_subcount(const char *funcname, int line, char *msg, ...)
|
||||
@@ -90,41 +90,6 @@ debug_subcount(const char *funcname, int line, char *msg, ...)
|
||||
slapi_log_err(SLAPI_LOG_INFO, (char*)funcname, "DEBUG SUBCOUNT [%d] %s\n", line, buff);
|
||||
}
|
||||
|
||||
-/*
|
||||
- * Dump a memory buffer in hexa and ascii in error log
|
||||
- *
|
||||
- * addr - The memory buffer address.
|
||||
- * len - The memory buffer lenght.
|
||||
- */
|
||||
-static void
|
||||
-hexadump(char *msg, const void *addr, size_t offset, size_t len)
|
||||
-{
|
||||
-#define HEXADUMP_TAB 4
|
||||
-/* 4 characters per bytes: 2 hexa digits, 1 space and the ascii */
|
||||
-#define HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
|
||||
- char hexdigit[] = "0123456789ABCDEF";
|
||||
-
|
||||
- const unsigned char *pt = addr;
|
||||
- char buff[HEXADUMP_BUF_SIZE+1];
|
||||
- memset (buff, ' ', HEXADUMP_BUF_SIZE);
|
||||
- buff[HEXADUMP_BUF_SIZE] = '\0';
|
||||
- while (len > 0) {
|
||||
- int dpl;
|
||||
- for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
|
||||
- buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
|
||||
- buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
|
||||
- buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
|
||||
- pt++;
|
||||
- }
|
||||
- for (;dpl < 16; dpl++) {
|
||||
- buff[3*dpl] = ' ';
|
||||
- buff[3*dpl+1] = ' ';
|
||||
- buff[3*16+HEXADUMP_TAB+dpl] = ' ';
|
||||
- }
|
||||
- slapi_log_err(SLAPI_LOG_INFO, msg, "[0x%08lx] %s\n", offset, buff);
|
||||
- offset += 16;
|
||||
- }
|
||||
-}
|
||||
#else
|
||||
#define DEBUG_SUBCOUNT_MSG(msg, ...)
|
||||
#define DUMP_SUBCOUNT_KEY(msg, key, ret)
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
index 35965df85..c49412a70 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
@@ -1122,6 +1122,21 @@ dbmdb_import_entry_info_by_backentry(mdb_privdb_t *db, BulkQueueData_t *bqdata,
|
||||
return dnrc;
|
||||
}
|
||||
|
||||
+/* Log wqelmt details */
|
||||
+void
|
||||
+log_wqelmt(int loglvl, char *fname, WorkerQueueData_t *wqelmt)
|
||||
+{
|
||||
+ if (wqelmt->dn) {
|
||||
+ slapi_log_err(loglvl, fname, "log_wqelmt: dn=%s\n", wqelmt->dn);
|
||||
+ }
|
||||
+ if (wqelmt->filename && wqelmt->lineno) {
|
||||
+ slapi_log_err(loglvl, fname, "log_wqelmt: ldif=%s[%d]\n", wqelmt->filename, wqelmt->lineno);
|
||||
+ }
|
||||
+ if (wqelmt->data) {
|
||||
+ size_t len = wqelmt->datalen ? wqelmt->datalen : strlen(wqelmt->data);
|
||||
+ slapi_log_hexadump(loglvl, "log_wqelmt:data", wqelmt->data, len);
|
||||
+ }
|
||||
+}
|
||||
|
||||
/* producer thread for ldif import case:
|
||||
* read through the given file list, parsing entries (str2entry), assigning
|
||||
@@ -1255,6 +1270,7 @@ dbmdb_import_producer(void *param)
|
||||
import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_import_producer",
|
||||
"ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
|
||||
wqelmt.dnrc);
|
||||
+ log_wqelmt(SLAPI_LOG_ERR, "dbmdb_import_producer", &wqelmt);
|
||||
abort();
|
||||
case DNRC_OK:
|
||||
case DNRC_SUFFIX:
|
||||
@@ -1761,6 +1777,7 @@ dbmdb_index_producer(void *param)
|
||||
import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_index_producer",
|
||||
"ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
|
||||
tmpslot.dnrc);
|
||||
+ log_wqelmt(SLAPI_LOG_ERR, "dbmdb_index_producer", &tmpslot);
|
||||
abort();
|
||||
case DNRC_OK:
|
||||
case DNRC_SUFFIX:
|
||||
@@ -3951,10 +3968,24 @@ dbmdb_import_writer(void*param)
|
||||
if (!txn) {
|
||||
MDB_STAT_STEP(stats, MDB_STAT_TXNSTART);
|
||||
rc = TXN_BEGIN(ctx->ctx->env, NULL, 0, &txn);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
+ "Failed to begin a txn. Error is 0x%x: %s.\n",
|
||||
+ rc, mdb_strerror(rc));
|
||||
+ }
|
||||
}
|
||||
if (!rc) {
|
||||
MDB_STAT_STEP(stats, MDB_STAT_WRITE);
|
||||
rc = MDB_PUT(txn, slot->dbi->dbi, &slot->key, &slot->data, 0);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
+ "Failed to write record in dbi %s. Error is 0x%x: %s.\n",
|
||||
+ slot->dbi->dbname, rc, mdb_strerror(rc));
|
||||
+ slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:key",
|
||||
+ slot->key.mv_data, slot->key.mv_size);
|
||||
+ slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:data",
|
||||
+ slot->data.mv_data, slot->data.mv_size);
|
||||
+ }
|
||||
}
|
||||
MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
nextslot = slot->next;
|
||||
@@ -3968,6 +3999,9 @@ dbmdb_import_writer(void*param)
|
||||
rc = TXN_COMMIT(txn);
|
||||
MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
+ "Failed to commit the txn. Error is 0x%x: %s.\n",
|
||||
+ rc, mdb_strerror(rc));
|
||||
break;
|
||||
}
|
||||
count = 0;
|
||||
@@ -3980,6 +4014,10 @@ dbmdb_import_writer(void*param)
|
||||
MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
if (!rc) {
|
||||
txn = NULL;
|
||||
+ } else {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
+ "Failed to commit the txn. Error is 0x%x: %s.\n",
|
||||
+ rc, mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
if (txn) {
|
||||
@@ -3992,13 +4030,17 @@ dbmdb_import_writer(void*param)
|
||||
if (!rc) {
|
||||
/* Ensure that all data are written on disk */
|
||||
rc = mdb_env_sync(ctx->ctx->env, 1);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
+ "mdb_env_sync failed. Error is 0x%x: %s.\n",
|
||||
+ rc, mdb_strerror(rc));
|
||||
+ }
|
||||
}
|
||||
MDB_STAT_END(stats);
|
||||
|
||||
if (rc) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
- "Failed to write in the database. Error is 0x%x: %s.\n",
|
||||
- rc, mdb_strerror(rc));
|
||||
+ "Aborting import after failure.\n");
|
||||
thread_abort(info);
|
||||
} else {
|
||||
char buf[200];
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
|
||||
index ca3a866bb..3bdf8926b 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -891,6 +891,67 @@ index_read(
|
||||
return index_read_ext(be, (char *)type, indextype, val, txn, err, NULL);
|
||||
}
|
||||
|
||||
+/* Prepare an index key (hashed if too long, encrypted if needed from attribute value */
|
||||
+int
|
||||
+prepare_key(backend *be, struct attrinfo *a, char **buf, size_t *buflen,
|
||||
+ int flags, const char *prefix, const struct berval *bvp, dbi_val_t *key)
|
||||
+{
|
||||
+ /* Key format is [Hash?] [prefix] [val] [\0] */
|
||||
+ struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
+ size_t plen = strlen(prefix);
|
||||
+ struct berval *hashed_bvp = NULL;
|
||||
+ struct berval *encrypted_bvp = NULL;
|
||||
+ int rc = 0;
|
||||
+
|
||||
+ /* Hash large index key if necessary */
|
||||
+ if (INDEX_KEY_LENGTH(bvp->bv_len,plen) >= li->li_max_key_len) {
|
||||
+ rc = attrcrypt_hash_large_index_key(be, prefix, a, bvp, &hashed_bvp);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
|
||||
+ "Failed to hash large index key for %s\n", a->ai_type);
|
||||
+ return rc;
|
||||
+ } else {
|
||||
+ bvp = hashed_bvp;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Encrypt the index key if necessary */
|
||||
+ if (rc == 0 && a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
|
||||
+ rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
|
||||
+ "Failed to encrypt index key for %s\n", a->ai_type);
|
||||
+ } else {
|
||||
+ bvp = encrypted_bvp;
|
||||
+ }
|
||||
+ }
|
||||
+ if (hashed_bvp) {
|
||||
+ prefix = slapi_ch_smprintf("%c%s",HASH_PREFIX, prefix);
|
||||
+ plen++;
|
||||
+ }
|
||||
+ if (buf && buflen) {
|
||||
+ if (plen+bvp->bv_len+1 > *buflen) {
|
||||
+ *buflen = plen+bvp->bv_len+1;
|
||||
+ *buf = slapi_ch_realloc(*buf, *buflen);
|
||||
+ }
|
||||
+ dblayer_value_concat(be, key, *buf, *buflen, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
|
||||
+ } else {
|
||||
+ dblayer_value_concat(be, key, NULL, 0, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
|
||||
+ }
|
||||
+
|
||||
+ if (hashed_bvp) {
|
||||
+ ber_bvfree(hashed_bvp);
|
||||
+ hashed_bvp = NULL;
|
||||
+ slapi_ch_free_string((char**)&prefix);
|
||||
+ }
|
||||
+ if (encrypted_bvp) {
|
||||
+ ber_bvfree(encrypted_bvp);
|
||||
+ encrypted_bvp = NULL;
|
||||
+ }
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+
|
||||
/*
|
||||
* Extended version of index_read.
|
||||
* The unindexed flag can be used to distinguish between a
|
||||
@@ -927,7 +988,6 @@ index_read_ext_allids(
|
||||
struct berval *hashed_val = NULL;
|
||||
int is_and = 0;
|
||||
unsigned int ai_flags = 0;
|
||||
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
|
||||
*err = 0;
|
||||
|
||||
@@ -1038,36 +1098,7 @@ index_read_ext_allids(
|
||||
}
|
||||
|
||||
if (val != NULL) {
|
||||
- size_t vlen;
|
||||
- int ret = 0;
|
||||
-
|
||||
- /* If necessary, hash this index key */
|
||||
- if (val->bv_len >= li->li_max_key_len) {
|
||||
- ret = attrcrypt_hash_large_index_key(be, &prefix, ai, val, &hashed_val);
|
||||
- if (ret) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
|
||||
- "Failed to hash large index key for %s\n", basetype);
|
||||
- *err = DBI_RC_OTHER;
|
||||
- index_free_prefix(prefix);
|
||||
- slapi_ch_free_string(&basetmp);
|
||||
- return (NULL);
|
||||
- }
|
||||
- if (hashed_val) {
|
||||
- val = hashed_val;
|
||||
- }
|
||||
- }
|
||||
- /* If necessary, encrypt this index key */
|
||||
- ret = attrcrypt_encrypt_index_key(be, ai, val, &encrypted_val);
|
||||
- if (ret) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
|
||||
- "Failed to encrypt index key for %s\n", basetype);
|
||||
- }
|
||||
- if (encrypted_val) {
|
||||
- val = encrypted_val;
|
||||
- }
|
||||
- vlen = val->bv_len;
|
||||
- dblayer_value_concat(be, &key, buf, sizeof(buf),
|
||||
- prefix, strlen(prefix), val->bv_val, vlen, "", 1);
|
||||
+ (void) prepare_key(be, ai, NULL, 0, 0, prefix, val, &key);
|
||||
} else {
|
||||
dblayer_value_concat(be, &key, buf, sizeof(buf), prefix, strlen(prefix),
|
||||
"", 1, NULL, 0);
|
||||
@@ -1867,6 +1898,7 @@ index_range_read(
|
||||
return index_range_read_ext(pb, be, type, indextype, operator, val, nextval, range, txn, err, 0);
|
||||
}
|
||||
|
||||
+
|
||||
static int
|
||||
addordel_values_sv(
|
||||
backend *be,
|
||||
@@ -1885,15 +1917,10 @@ addordel_values_sv(
|
||||
int i = 0;
|
||||
dbi_val_t key = {0};
|
||||
dbi_txn_t *db_txn = NULL;
|
||||
- size_t plen, vlen, len;
|
||||
char *tmpbuf = NULL;
|
||||
size_t tmpbuflen = 0;
|
||||
- char *realbuf;
|
||||
char *prefix = NULL;
|
||||
const struct berval *bvp;
|
||||
- struct berval *hashed_bvp = NULL;
|
||||
- struct berval *encrypted_bvp = NULL;
|
||||
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
char *index_id = get_index_name(be, db, a);
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "addordel_values_sv", "%s_values\n",
|
||||
@@ -1932,66 +1959,14 @@ addordel_values_sv(
|
||||
return (rc);
|
||||
}
|
||||
|
||||
- plen = strlen(prefix);
|
||||
for (i = 0; vals[i] != NULL; i++) {
|
||||
bvp = slapi_value_get_berval(vals[i]);
|
||||
|
||||
- /* Hash large index key if necessary */
|
||||
- if (bvp->bv_len >= li->li_max_key_len) {
|
||||
- rc = attrcrypt_hash_large_index_key(be, &prefix, a, bvp, &hashed_bvp);
|
||||
- if (rc) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
|
||||
- "Failed to hash large index key for %s\n", a->ai_type);
|
||||
- break;
|
||||
- } else {
|
||||
- bvp = hashed_bvp;
|
||||
- plen = strlen(prefix);
|
||||
- }
|
||||
- }
|
||||
- /* Encrypt the index key if necessary */
|
||||
- {
|
||||
- if (a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
|
||||
- rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
|
||||
- if (rc) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
|
||||
- "Failed to encrypt index key for %s\n", a->ai_type);
|
||||
- } else {
|
||||
- bvp = encrypted_bvp;
|
||||
- }
|
||||
- }
|
||||
+ rc = prepare_key(be, a, &tmpbuf, &tmpbuflen, flags, prefix, bvp, &key);
|
||||
+ if (rc) {
|
||||
+ break;
|
||||
}
|
||||
|
||||
- vlen = bvp->bv_len;
|
||||
- len = plen + vlen;
|
||||
-
|
||||
- if (len < tmpbuflen) {
|
||||
- realbuf = tmpbuf;
|
||||
- } else {
|
||||
- tmpbuf = slapi_ch_realloc(tmpbuf, len + 1);
|
||||
- tmpbuflen = len + 1;
|
||||
- realbuf = tmpbuf;
|
||||
- }
|
||||
-
|
||||
- assert(realbuf); /* For coverity */
|
||||
- memcpy(realbuf, prefix, plen);
|
||||
- memcpy(realbuf + plen, bvp->bv_val, vlen);
|
||||
- realbuf[len] = '\0';
|
||||
- /* Free the encrypted berval if necessary */
|
||||
- if (hashed_bvp) {
|
||||
- ber_bvfree(hashed_bvp);
|
||||
- hashed_bvp = NULL;
|
||||
- }
|
||||
- if (encrypted_bvp) {
|
||||
- ber_bvfree(encrypted_bvp);
|
||||
- encrypted_bvp = NULL;
|
||||
- }
|
||||
- /* should be okay to use USERMEM here because we know what
|
||||
- * the key is and it should never return a different value
|
||||
- * than the one we pass in.
|
||||
- */
|
||||
- dblayer_value_set_buffer(be, &key, realbuf, plen + vlen + 1);
|
||||
- key.ulen = tmpbuflen;
|
||||
-
|
||||
if (slapi_is_loglevel_set(LDAP_DEBUG_TRACE)) {
|
||||
char encbuf[BUFSIZ];
|
||||
|
||||
@@ -2024,10 +1999,6 @@ addordel_values_sv(
|
||||
ldbm_nasty(NASTY_MSG("addordel_values_sv"), index_id, 1130, rc);
|
||||
break;
|
||||
}
|
||||
- if (NULL != key.dptr && realbuf != key.dptr) { /* realloc'ed */
|
||||
- tmpbuf = key.dptr;
|
||||
- tmpbuflen = key.size;
|
||||
- }
|
||||
}
|
||||
index_free_prefix(prefix);
|
||||
if (tmpbuf != NULL) {
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
|
||||
index 3b86a5dd7..14b3b9ce2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
|
||||
@@ -1065,15 +1065,15 @@ attrcrypt_decrypt_index_key(backend *be,
|
||||
* : NULL - no hash or failure
|
||||
*/
|
||||
int
|
||||
-attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
|
||||
+attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
|
||||
{
|
||||
int ret = 0;
|
||||
struct berval *out_berval = NULL;
|
||||
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
- char *new_prefix;
|
||||
+ size_t final_key_len = INDEX_KEY_LENGTH(in->bv_len, strlen(prefix));
|
||||
|
||||
/* If the index key is too long (i.e mdb case) we must hash it */
|
||||
- if (in->bv_len >= li->li_max_key_len) {
|
||||
+ if (final_key_len >= li->li_max_key_len) {
|
||||
PK11Context *c = PK11_CreateDigestContext(SEC_OID_MD5);
|
||||
if (c != NULL) {
|
||||
unsigned char hash[32];
|
||||
@@ -1087,16 +1087,13 @@ attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai,
|
||||
return ENOMEM;
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "attrcrypt_hash_large_index_key",
|
||||
- "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", in->bv_len, li->li_max_key_len);
|
||||
+ "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", final_key_len, li->li_max_key_len);
|
||||
slapi_be_set_flag(be, SLAPI_BE_FLAG_DONT_BYPASS_FILTERTEST);
|
||||
PK11_DigestBegin(c);
|
||||
/* Compute hash for the key without the prefix */
|
||||
PK11_DigestOp(c, (unsigned char *)in->bv_val, in->bv_len);
|
||||
PK11_DigestFinal(c, hash, &hashLen, sizeof hash);
|
||||
- /* Add HASH_PREFIX before the prefix */
|
||||
- new_prefix = slapi_ch_smprintf("%c%s", HASH_PREFIX, *prefix);
|
||||
- index_free_prefix(*prefix);
|
||||
- *prefix = new_prefix;
|
||||
+
|
||||
/* Build the key: hash value in hexa */
|
||||
hkey = slapi_ch_malloc(1+2*sizeof hash);
|
||||
out_berval->bv_val = hkey;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
index 8754a4847..1884da603 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
@@ -620,7 +620,7 @@ int attrcrypt_encrypt_entry_inplace(backend *be, const struct backentry *inout);
|
||||
int attrcrypt_encrypt_entry(backend *be, const struct backentry *in, struct backentry **out);
|
||||
int attrcrypt_encrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
|
||||
int attrcrypt_decrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
|
||||
-int attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
|
||||
+int attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
|
||||
int attrcrypt_init(ldbm_instance *li);
|
||||
int attrcrypt_cleanup_private(ldbm_instance *li);
|
||||
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index ca8d481e5..17c7bbb7d 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -92,6 +92,10 @@ static int slapi_log_map[] = {
|
||||
#define SLAPI_LOG_MAX SLAPI_LOG_DEBUG /* from slapi-plugin.h */
|
||||
#define LOG_CHUNK 16384 /* zlib compression */
|
||||
|
||||
+#define HEXADUMP_TAB 4
|
||||
+/* 4 characters per bytes: 2 hexa digits, 1 space and the ascii */
|
||||
+#define HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
|
||||
+
|
||||
/**************************************************************************
|
||||
* PROTOTYPES
|
||||
*************************************************************************/
|
||||
@@ -3065,6 +3069,42 @@ slapi_log_backtrace(int loglevel)
|
||||
}
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Dump a memory buffer in hexa and ascii in error log
|
||||
+ *
|
||||
+ * addr - The memory buffer address.
|
||||
+ * len - The memory buffer lenght.
|
||||
+ */
|
||||
+void
|
||||
+slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len)
|
||||
+{
|
||||
+ char hexdigit[] = "0123456789ABCDEF";
|
||||
+ const unsigned char *pt = addr;
|
||||
+ char buff[HEXADUMP_BUF_SIZE+1];
|
||||
+ size_t offset = 0;
|
||||
+
|
||||
+ if (!slapi_is_loglevel_set(loglevel)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ memset (buff, ' ', HEXADUMP_BUF_SIZE);
|
||||
+ buff[HEXADUMP_BUF_SIZE] = '\0';
|
||||
+ while (len > 0) {
|
||||
+ int dpl;
|
||||
+ for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
|
||||
+ buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
|
||||
+ buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
|
||||
+ buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
|
||||
+ pt++;
|
||||
+ }
|
||||
+ for (;dpl < 16; dpl++) {
|
||||
+ buff[3*dpl] = ' ';
|
||||
+ buff[3*dpl+1] = ' ';
|
||||
+ buff[3*16+HEXADUMP_TAB+dpl] = ' ';
|
||||
+ }
|
||||
+ slapi_log_err(loglevel, fname, "[0x%08lx] %s\n", offset, buff);
|
||||
+ offset += 16;
|
||||
+ }
|
||||
+}
|
||||
|
||||
/******************************************************************************
|
||||
* write in the access log
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index a55ea3ee1..97eed5a9b 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -1527,6 +1527,8 @@ void slapi_pblock_set_task_warning(Slapi_PBlock *pb, task_warning warning);
|
||||
int slapi_exists_or_add_internal(Slapi_DN *dn, const char *filter, const char *entry, const char *modifier_name);
|
||||
|
||||
void slapi_log_backtrace(int loglevel);
|
||||
+void slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len);
|
||||
+
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
--
|
||||
2.54.0
|
||||
|
||||
39
SOURCES/0008-Fix-test389-imports-on-older-branches.patch
Normal file
39
SOURCES/0008-Fix-test389-imports-on-older-branches.patch
Normal file
@ -0,0 +1,39 @@
|
||||
From 853d11cd6abc69a8e93172771b19b52950293e6f Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 5 Jun 2026 11:17:35 +0200
|
||||
Subject: [PATCH 8/8] Fix test389 imports on older branches
|
||||
|
||||
---
|
||||
dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py | 2 +-
|
||||
dirsrvtests/tests/suites/features/ldap_controls_test.py | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py b/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py
|
||||
index 77f839c40..adbe2861a 100644
|
||||
--- a/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py
|
||||
+++ b/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py
|
||||
@@ -13,7 +13,7 @@ import pytest
|
||||
from lib389._constants import DEFAULT_SUFFIX
|
||||
from lib389.idm.organizationalunit import OrganizationalUnit, OrganizationalUnits
|
||||
from lib389.idm.user import UserAccount, UserAccounts
|
||||
-from test389.topologies import topology_st as topo
|
||||
+from lib389.topologies import topology_st as topo
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/features/ldap_controls_test.py b/dirsrvtests/tests/suites/features/ldap_controls_test.py
|
||||
index 59a58b21d..7d8b21c8f 100644
|
||||
--- a/dirsrvtests/tests/suites/features/ldap_controls_test.py
|
||||
+++ b/dirsrvtests/tests/suites/features/ldap_controls_test.py
|
||||
@@ -12,7 +12,7 @@ import ldap
|
||||
from ldap.controls import RequestControl
|
||||
from ldap.controls.readentry import PostReadControl
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
-from test389.topologies import topology_st
|
||||
+from lib389.topologies import topology_st
|
||||
from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,143 +0,0 @@
|
||||
From 6e5f03d5872129963106024f53765234a282406c Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 16 Feb 2024 11:13:16 +0000
|
||||
Subject: [PATCH] Issue 6096 - Improve connection timeout error logging (#6097)
|
||||
|
||||
Bug description: When a paged result search is run with a time limit,
|
||||
if the time limit is exceed the server closes the connection with
|
||||
closed IO timeout (nsslapd-ioblocktimeout) - T2. This error message
|
||||
is incorrect as the reason the connection has been closed was because
|
||||
the specified time limit on a paged result search has been exceeded.
|
||||
|
||||
Fix description: Correct error message
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6096
|
||||
|
||||
Reviewed by: @tbordaz (Thank you)
|
||||
---
|
||||
ldap/admin/src/logconv.pl | 24 ++++++++++++++++++-
|
||||
ldap/servers/slapd/daemon.c | 4 ++--
|
||||
ldap/servers/slapd/disconnect_error_strings.h | 1 +
|
||||
ldap/servers/slapd/disconnect_errors.h | 2 +-
|
||||
4 files changed, 27 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/admin/src/logconv.pl b/ldap/admin/src/logconv.pl
|
||||
index 7698c383a..2a933c4a3 100755
|
||||
--- a/ldap/admin/src/logconv.pl
|
||||
+++ b/ldap/admin/src/logconv.pl
|
||||
@@ -267,7 +267,7 @@ my $optimeAvg = 0;
|
||||
my %cipher = ();
|
||||
my @removefiles = ();
|
||||
|
||||
-my @conncodes = qw(A1 B1 B4 T1 T2 B2 B3 R1 P1 P2 U1);
|
||||
+my @conncodes = qw(A1 B1 B4 T1 T2 T3 B2 B3 R1 P1 P2 U1);
|
||||
my %conn = ();
|
||||
map {$conn{$_} = $_} @conncodes;
|
||||
|
||||
@@ -355,6 +355,7 @@ $connmsg{"B1"} = "Bad Ber Tag Encountered";
|
||||
$connmsg{"B4"} = "Server failed to flush data (response) back to Client";
|
||||
$connmsg{"T1"} = "Idle Timeout Exceeded";
|
||||
$connmsg{"T2"} = "IO Block Timeout Exceeded or NTSSL Timeout";
|
||||
+$connmsg{"T3"} = "Paged Search Time Limit Exceeded";
|
||||
$connmsg{"B2"} = "Ber Too Big";
|
||||
$connmsg{"B3"} = "Ber Peek";
|
||||
$connmsg{"R1"} = "Revents";
|
||||
@@ -1723,6 +1724,10 @@ if ($usage =~ /j/i || $verb eq "yes"){
|
||||
print "\n $recCount. You have some coonections that are being closed by the ioblocktimeout setting. You may want to increase the ioblocktimeout.\n";
|
||||
$recCount++;
|
||||
}
|
||||
+ if (defined($conncount->{"T3"}) and $conncount->{"T3"} > 0){
|
||||
+ print "\n $recCount. You have some connections that are being closed because a paged result search limit has been exceeded. You may want to increase the search time limit.\n";
|
||||
+ $recCount++;
|
||||
+ }
|
||||
# compare binds to unbinds, if the difference is more than 30% of the binds, then report a issue
|
||||
if (($bindCount - $unbindCount) > ($bindCount*.3)){
|
||||
print "\n $recCount. You have a significant difference between binds and unbinds. You may want to investigate this difference.\n";
|
||||
@@ -2366,6 +2371,7 @@ sub parseLineNormal
|
||||
$brokenPipeCount++;
|
||||
if (m/- T1/){ $hashes->{rc}->{"T1"}++; }
|
||||
elsif (m/- T2/){ $hashes->{rc}->{"T2"}++; }
|
||||
+ elsif (m/- T3/){ $hashes->{rc}->{"T3"}++; }
|
||||
elsif (m/- A1/){ $hashes->{rc}->{"A1"}++; }
|
||||
elsif (m/- B1/){ $hashes->{rc}->{"B1"}++; }
|
||||
elsif (m/- B4/){ $hashes->{rc}->{"B4"}++; }
|
||||
@@ -2381,6 +2387,7 @@ sub parseLineNormal
|
||||
$connResetByPeerCount++;
|
||||
if (m/- T1/){ $hashes->{src}->{"T1"}++; }
|
||||
elsif (m/- T2/){ $hashes->{src}->{"T2"}++; }
|
||||
+ elsif (m/- T3/){ $hashes->{src}->{"T3"}++; }
|
||||
elsif (m/- A1/){ $hashes->{src}->{"A1"}++; }
|
||||
elsif (m/- B1/){ $hashes->{src}->{"B1"}++; }
|
||||
elsif (m/- B4/){ $hashes->{src}->{"B4"}++; }
|
||||
@@ -2396,6 +2403,7 @@ sub parseLineNormal
|
||||
$resourceUnavailCount++;
|
||||
if (m/- T1/){ $hashes->{rsrc}->{"T1"}++; }
|
||||
elsif (m/- T2/){ $hashes->{rsrc}->{"T2"}++; }
|
||||
+ elsif (m/- T3/){ $hashes->{rsrc}->{"T3"}++; }
|
||||
elsif (m/- A1/){ $hashes->{rsrc}->{"A1"}++; }
|
||||
elsif (m/- B1/){ $hashes->{rsrc}->{"B1"}++; }
|
||||
elsif (m/- B4/){ $hashes->{rsrc}->{"B4"}++; }
|
||||
@@ -2494,6 +2502,20 @@ sub parseLineNormal
|
||||
}
|
||||
}
|
||||
}
|
||||
+ if (m/- T3/){
|
||||
+ if ($_ =~ /conn= *([0-9A-Z]+)/i) {
|
||||
+ $exc = "no";
|
||||
+ $ip = getIPfromConn($1, $serverRestartCount);
|
||||
+ for (my $xxx = 0; $xxx < $#excludeIP; $xxx++){
|
||||
+ if ($ip eq $excludeIP[$xxx]){$exc = "yes";}
|
||||
+ }
|
||||
+ if ($exc ne "yes"){
|
||||
+ $hashes->{T3}->{$ip}++;
|
||||
+ $hashes->{conncount}->{"T3"}++;
|
||||
+ $connCodeCount++;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
if (m/- B2/){
|
||||
if ($_ =~ /conn= *([0-9A-Z]+)/i) {
|
||||
$exc = "no";
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 5a48aa66f..bb80dae36 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1599,9 +1599,9 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
int add_fd = 1;
|
||||
/* check timeout for PAGED RESULTS */
|
||||
if (pagedresults_is_timedout_nolock(c)) {
|
||||
- /* Exceeded the timelimit; disconnect the client */
|
||||
+ /* Exceeded the paged search timelimit; disconnect the client */
|
||||
disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
- SLAPD_DISCONNECT_IO_TIMEOUT,
|
||||
+ SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT,
|
||||
0);
|
||||
connection_table_move_connection_out_of_active_list(ct,
|
||||
c);
|
||||
diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
index f7a31d728..c2d9e283b 100644
|
||||
--- a/ldap/servers/slapd/disconnect_error_strings.h
|
||||
+++ b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
@@ -27,6 +27,7 @@ ER2(SLAPD_DISCONNECT_BER_FLUSH, "B4")
|
||||
ER2(SLAPD_DISCONNECT_IDLE_TIMEOUT, "T1")
|
||||
ER2(SLAPD_DISCONNECT_REVENTS, "R1")
|
||||
ER2(SLAPD_DISCONNECT_IO_TIMEOUT, "T2")
|
||||
+ER2(SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT, "T3")
|
||||
ER2(SLAPD_DISCONNECT_PLUGIN, "P1")
|
||||
ER2(SLAPD_DISCONNECT_UNBIND, "U1")
|
||||
ER2(SLAPD_DISCONNECT_POLL, "P2")
|
||||
diff --git a/ldap/servers/slapd/disconnect_errors.h b/ldap/servers/slapd/disconnect_errors.h
|
||||
index a0484f1c2..e118f674c 100644
|
||||
--- a/ldap/servers/slapd/disconnect_errors.h
|
||||
+++ b/ldap/servers/slapd/disconnect_errors.h
|
||||
@@ -35,6 +35,6 @@
|
||||
#define SLAPD_DISCONNECT_SASL_FAIL SLAPD_DISCONNECT_ERROR_BASE + 12
|
||||
#define SLAPD_DISCONNECT_PROXY_INVALID_HEADER SLAPD_DISCONNECT_ERROR_BASE + 13
|
||||
#define SLAPD_DISCONNECT_PROXY_UNKNOWN SLAPD_DISCONNECT_ERROR_BASE + 14
|
||||
-
|
||||
+#define SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT SLAPD_DISCONNECT_ERROR_BASE + 15
|
||||
|
||||
#endif /* __DISCONNECT_ERRORS_H_ */
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
From a112394af3a20787755029804684d57a9c3ffa9a Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 21 Feb 2024 12:43:03 +0000
|
||||
Subject: [PATCH] Issue 6103 - New connection timeout error breaks errormap
|
||||
(#6104)
|
||||
|
||||
Bug description: A recent addition to the connection disconnect error
|
||||
messaging, conflicts with how errormap.c maps error codes/strings.
|
||||
|
||||
Fix description: errormap expects error codes/strings to be in ascending
|
||||
order. Moved the new error code to the bottom of the list.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6103
|
||||
|
||||
Reviewed by: @droideck. @progier389 (Thank you)
|
||||
---
|
||||
ldap/servers/slapd/disconnect_error_strings.h | 5 +++--
|
||||
1 file changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
index c2d9e283b..f603a08ce 100644
|
||||
--- a/ldap/servers/slapd/disconnect_error_strings.h
|
||||
+++ b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
@@ -14,7 +14,8 @@
|
||||
/* disconnect_error_strings.h
|
||||
*
|
||||
* Strings describing the errors used in logging the reason a connection
|
||||
- * was closed.
|
||||
+ * was closed. Ensure definitions are in the same order as the error codes
|
||||
+ * defined in disconnect_errors.h
|
||||
*/
|
||||
#ifndef __DISCONNECT_ERROR_STRINGS_H_
|
||||
#define __DISCONNECT_ERROR_STRINGS_H_
|
||||
@@ -35,6 +36,6 @@ ER2(SLAPD_DISCONNECT_NTSSL_TIMEOUT, "T2")
|
||||
ER2(SLAPD_DISCONNECT_SASL_FAIL, "S1")
|
||||
ER2(SLAPD_DISCONNECT_PROXY_INVALID_HEADER, "P3")
|
||||
ER2(SLAPD_DISCONNECT_PROXY_UNKNOWN, "P4")
|
||||
-
|
||||
+ER2(SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT, "T3")
|
||||
|
||||
#endif /* __DISCONNECT_ERROR_STRINGS_H_ */
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
From 6ae6fdbf9c42f30a5653215ff271d700e6a1a0ce Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 18 May 2026 14:27:04 -0400
|
||||
Subject: [PATCH 09/14] Issue 7500 - Prevent unsigned integer underflow during
|
||||
stalled import
|
||||
|
||||
Description:
|
||||
|
||||
During a stalled import the foreman's first ID could be greater than the
|
||||
history progress size which leads to underflowing the rate. Check if the
|
||||
foreman's first ID is greater than the progress size and just set it to
|
||||
zero.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7500
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
.../slapd/back-ldbm/db-bdb/bdb_import.c | 22 ++++++++++++++-----
|
||||
.../slapd/back-ldbm/db-mdb/mdb_import.c | 4 ++--
|
||||
2 files changed, 19 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
|
||||
index bdc4ee4f6..0815c48e9 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
|
||||
@@ -1765,16 +1765,28 @@ bdb_import_monitor_threads(ImportJob *job, int *status)
|
||||
/* Now calculate our rate of progress overall for this chunk */
|
||||
if (time_now != job->start_time) {
|
||||
/* log a cute chart of the worker progress */
|
||||
+ uint32_t history_size = 0;
|
||||
+ double rate = 0.0;
|
||||
+
|
||||
bdb_import_log_status_start(job);
|
||||
bdb_import_log_status_add_line(job,
|
||||
- "Index status for import of %s:", job->inst->inst_name);
|
||||
+ "Index status for import of %s:",
|
||||
+ job->inst->inst_name);
|
||||
bdb_import_log_status_add_line(job,
|
||||
- "-------Index Task-------State---Entry----Rate-");
|
||||
+ "-------Index Task-------State---Entry----Rate-");
|
||||
|
||||
bdb_import_push_progress_history(job, foreman->last_ID_processed,
|
||||
- time_now);
|
||||
- job->average_progress_rate =
|
||||
- (double)(HISTORY(IMPORT_JOB_PROG_HISTORY_SIZE - 1) + 1 - foreman->first_ID) /
|
||||
+ time_now);
|
||||
+
|
||||
+ history_size = HISTORY(IMPORT_JOB_PROG_HISTORY_SIZE - 1) + 1;
|
||||
+ if (foreman->first_ID > history_size) {
|
||||
+ /* Import is stalled and subtracting first_ID will
|
||||
+ * underflow the rate - so set it to 0.0 */
|
||||
+ rate = 0.0;
|
||||
+ } else {
|
||||
+ rate = (double)(history_size - foreman->first_ID);
|
||||
+ }
|
||||
+ job->average_progress_rate = rate /
|
||||
(double)(TIMES(IMPORT_JOB_PROG_HISTORY_SIZE - 1) - job->start_time);
|
||||
job->recent_progress_rate =
|
||||
PROGRESS(0, IMPORT_JOB_PROG_HISTORY_SIZE - 1);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
|
||||
index 51f1272f4..10ac903d2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
|
||||
@@ -586,7 +586,6 @@ dbmdb_import_monitor_threads(ImportJob *job, int *status)
|
||||
int count = 1; /* 1 to prevent premature status report */
|
||||
const int display_interval = 200;
|
||||
time_t time_now = 0;
|
||||
- int i = 0;
|
||||
|
||||
for (current_worker = job->worker_list; current_worker != NULL;
|
||||
current_worker = current_worker->next)
|
||||
@@ -615,12 +614,13 @@ dbmdb_import_monitor_threads(ImportJob *job, int *status)
|
||||
dbmdb_import_clear_progress_history(job);
|
||||
|
||||
while (!finished) {
|
||||
+ size_t max_slots = ctx->workerq.max_slots;
|
||||
DS_Sleep(tenthsecond);
|
||||
finished = 1;
|
||||
|
||||
/* Compute the number of entries processed by the workers */
|
||||
entry_processed = 0;
|
||||
- for (i=0; i<ctx->workerq.max_slots; i++) {
|
||||
+ for (size_t i = 0; i < max_slots; i++) {
|
||||
entry_processed += slots[i].count;
|
||||
}
|
||||
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
From edd9abc8901604dde1d739d87ca2906734d53dd3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 13 Jun 2024 13:35:09 +0200
|
||||
Subject: [PATCH] Issue 6103 - New connection timeout error breaks errormap
|
||||
|
||||
Description:
|
||||
Remove duplicate SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT error code.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6103
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/disconnect_error_strings.h | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
index f603a08ce..d49cc79a2 100644
|
||||
--- a/ldap/servers/slapd/disconnect_error_strings.h
|
||||
+++ b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
@@ -28,7 +28,6 @@ ER2(SLAPD_DISCONNECT_BER_FLUSH, "B4")
|
||||
ER2(SLAPD_DISCONNECT_IDLE_TIMEOUT, "T1")
|
||||
ER2(SLAPD_DISCONNECT_REVENTS, "R1")
|
||||
ER2(SLAPD_DISCONNECT_IO_TIMEOUT, "T2")
|
||||
-ER2(SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT, "T3")
|
||||
ER2(SLAPD_DISCONNECT_PLUGIN, "P1")
|
||||
ER2(SLAPD_DISCONNECT_UNBIND, "U1")
|
||||
ER2(SLAPD_DISCONNECT_POLL, "P2")
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,598 @@
|
||||
From 6a5aa0434fb32467fccb746915aa1a9ac740e4a6 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 10 Jun 2026 15:14:08 +0200
|
||||
Subject: [PATCH 10/14] Issue 7558 - During online import, the IDL should be
|
||||
created with in-depth first approach (#7559)
|
||||
|
||||
Bug description:
|
||||
The online initialization requires that the supplier builds a sorted IDL.
|
||||
It is sorted in the way that the parent entry appears before the children in the IDL.
|
||||
The current implementation goes through the parentid index from the first entry
|
||||
until the end (next). To make sure parent entry is already in IDL before adding a child
|
||||
it uses a list of ID ranges.
|
||||
This list works well if the ID are mostly consecutive and with limited number of holes,
|
||||
else the list grows (lot of singleton) and checking the list becomes costly as well.
|
||||
|
||||
Fix description:
|
||||
Instead of walking the parentid index from the first entry to the end it walks
|
||||
the parentid in depth first. So there is no need to check that the parent ID is already
|
||||
present in IDL
|
||||
|
||||
fixes: #7558
|
||||
|
||||
Reviewed by: Pierre Rogier (Thanks !!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/idl_new.c | 459 +++++++++++++++----------
|
||||
1 file changed, 285 insertions(+), 174 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
index 613d53815..29457500e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
@@ -45,13 +45,6 @@ struct idl_private
|
||||
int dummy;
|
||||
};
|
||||
|
||||
-/* Used to store leftover parentid and entry ids */
|
||||
-typedef struct _range_id_pair
|
||||
-{
|
||||
- ID key;
|
||||
- ID id;
|
||||
-} idl_range_id_pair;
|
||||
-
|
||||
/* lmdb iterator callback context */
|
||||
typedef struct {
|
||||
backend *be;
|
||||
@@ -62,18 +55,31 @@ typedef struct {
|
||||
struct timespec *expire_time;
|
||||
int lookthrough_limit;
|
||||
int operator;
|
||||
- idl_range_id_pair *leftover;
|
||||
- size_t leftoverlen;
|
||||
- size_t leftovercnt;
|
||||
IDList *idl;
|
||||
- IdRange_t *idrange_list;
|
||||
int flag_err;
|
||||
ID lastid;
|
||||
- ID suffix;
|
||||
uint64_t count;
|
||||
char *index_id;
|
||||
} idl_range_ctx_t;
|
||||
|
||||
+/* Context for depth-first parentid index walk (bulk import) */
|
||||
+typedef struct {
|
||||
+ backend *be;
|
||||
+ dbi_db_t *db;
|
||||
+ dbi_txn_t *txn;
|
||||
+ struct attrinfo *ai;
|
||||
+ IDList *idl;
|
||||
+ int *flag_err;
|
||||
+ int allidslimit;
|
||||
+ int sizelimit;
|
||||
+ struct timespec *expire_time;
|
||||
+ int lookthrough_limit;
|
||||
+ uint64_t count;
|
||||
+ char *index_id;
|
||||
+ char keybuf[32];
|
||||
+ dbi_val_t key;
|
||||
+} idl_parentid_walk_ctx_t;
|
||||
+
|
||||
|
||||
static int idl_tune = DEFAULT_IDL_TUNE; /* tuning parameters for IDL code */
|
||||
/* Currently none for new IDL code */
|
||||
@@ -383,22 +389,263 @@ keycmp(dbi_val_t *L, dbi_val_t *R, value_compare_fn_type cmp_fn)
|
||||
return cmp_fn(&Lv, &Rv);
|
||||
}
|
||||
|
||||
+static void
|
||||
+idl_parentid_sorted_suffix_init(backend *be, ID *suffix, const char *logfn)
|
||||
+{
|
||||
+ struct _back_info_index_key bck_info;
|
||||
+ int rc;
|
||||
+
|
||||
+ bck_info.index = SLAPI_ATTR_PARENTID;
|
||||
+ bck_info.key = "0";
|
||||
+ *suffix = 0;
|
||||
+
|
||||
+ rc = slapi_back_get_info(be, BACK_INFO_INDEX_KEY, (void **)&bck_info);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, logfn,
|
||||
+ "Total update: fail to retrieve suffix entryID, continue assuming it is the first entry\n");
|
||||
+ }
|
||||
+ if (bck_info.key_found) {
|
||||
+ *suffix = bck_info.id;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+idl_parentid_set_index_key(idl_parentid_walk_ctx_t *ctx, ID parent_id)
|
||||
+{
|
||||
+ ctx->key.ulen = sizeof(ctx->keybuf);
|
||||
+ ctx->key.size = PR_snprintf(ctx->keybuf, ctx->key.ulen, "%c%lu",
|
||||
+ EQ_PREFIX, (u_long)parent_id);
|
||||
+ ctx->key.size++; /* include the null terminator */
|
||||
+ dblayer_value_set_buffer(ctx->be, &ctx->key, ctx->keybuf, ctx->key.size);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+idl_parentid_walk_check_limits(idl_parentid_walk_ctx_t *ctx)
|
||||
+{
|
||||
+ if (ctx->idl) {
|
||||
+ if ((ctx->lookthrough_limit != -1) &&
|
||||
+ (ctx->idl->b_nids > (ID)ctx->lookthrough_limit)) {
|
||||
+ idl_free(&ctx->idl);
|
||||
+ ctx->idl = idl_allids(ctx->be);
|
||||
+ slapi_log_err(SLAPI_LOG_TRACE, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "lookthrough_limit exceeded\n");
|
||||
+ *(ctx->flag_err) = LDAP_ADMINLIMIT_EXCEEDED;
|
||||
+ return -1;
|
||||
+ }
|
||||
+ if ((ctx->sizelimit > 0) && (ctx->idl->b_nids > (ID)ctx->sizelimit)) {
|
||||
+ slapi_log_err(SLAPI_LOG_TRACE, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "sizelimit exceeded\n");
|
||||
+ *(ctx->flag_err) = LDAP_SIZELIMIT_EXCEEDED;
|
||||
+ return -1;
|
||||
+ }
|
||||
+ }
|
||||
+ if (slapi_timespec_expire_check(ctx->expire_time) == TIMER_EXPIRED) {
|
||||
+ slapi_log_err(SLAPI_LOG_TRACE, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "timelimit exceeded\n");
|
||||
+ *(ctx->flag_err) = LDAP_TIMELIMIT_EXCEEDED;
|
||||
+ return -1;
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+idl_parentid_walk_push(ID **stack, size_t *stack_size, size_t *stack_top, ID id)
|
||||
+{
|
||||
+ if (*stack_top >= *stack_size) {
|
||||
+ size_t new_size = (*stack_size == 0) ? 256 : (*stack_size * 2);
|
||||
+ ID *new_stack = (ID *)slapi_ch_realloc((char *)*stack, new_size * sizeof(ID));
|
||||
+
|
||||
+ if (new_stack == NULL) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+ *stack = new_stack;
|
||||
+ *stack_size = new_size;
|
||||
+ }
|
||||
+ (*stack)[(*stack_top)++] = id;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
- * Perform the range search in the idl layer instead of the index layer
|
||||
- * to improve the performance.
|
||||
+ * Walk the parentid index depth-first starting at root_id.
|
||||
+ * For each parent, look up index key "=parent_id" (direct get, not cursor next),
|
||||
+ * append the parent to the IDList, then visit each child the same way.
|
||||
*/
|
||||
+static int
|
||||
+idl_parentid_walk_tree(idl_parentid_walk_ctx_t *ctx, ID root_id, ID **stack,
|
||||
+ size_t *stack_size, size_t *stack_top)
|
||||
+{
|
||||
+ IDList *children = NULL;
|
||||
+ int fetch_err = NEW_IDL_NO_ALLID;
|
||||
+ size_t i;
|
||||
+
|
||||
+ if (idl_parentid_walk_push(stack, stack_size, stack_top, root_id) != 0) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ while (*stack_top > 0) {
|
||||
+ ID parent_id = (*stack)[--(*stack_top)];
|
||||
+
|
||||
+ if (idl_parentid_walk_check_limits(ctx) != 0) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ if (idl_append_extend(&ctx->idl, parent_id) != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Unable to extend id list for attribute (%s)\n", ctx->index_id);
|
||||
+ idl_free(&ctx->idl);
|
||||
+ return -1;
|
||||
+ }
|
||||
+ ctx->count++;
|
||||
+
|
||||
+#if defined(DB_ALLIDS_ON_READ)
|
||||
+ if ((NEW_IDL_NO_ALLID != *(ctx->flag_err)) && ctx->ai && (ctx->idl != NULL) &&
|
||||
+ idl_new_exceeds_allidslimit(ctx->count, ctx->ai, ctx->allidslimit)) {
|
||||
+ ctx->idl->b_nids = 1;
|
||||
+ ctx->idl->b_ids[0] = ALLID;
|
||||
+ return 0;
|
||||
+ }
|
||||
+#endif
|
||||
+ idl_parentid_set_index_key(ctx, parent_id);
|
||||
+ children = idl_fetch_ext(ctx->be, ctx->db, &ctx->key, ctx->txn, ctx->ai,
|
||||
+ &fetch_err, ctx->allidslimit);
|
||||
+ if (fetch_err != 0 && fetch_err != DBI_RC_NOTFOUND) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Failed to read parentid index key %s (err=%d)\n",
|
||||
+ (char *)ctx->key.data, fetch_err);
|
||||
+ *(ctx->flag_err) = fetch_err;
|
||||
+ return -1;
|
||||
+ }
|
||||
+ if (children == NULL) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (ALLIDS(children)) {
|
||||
+ idl_free(&ctx->idl);
|
||||
+ ctx->idl = idl_allids(ctx->be);
|
||||
+ idl_free(&children);
|
||||
+ return 0;
|
||||
+ }
|
||||
+ for (i = children->b_nids; i > 0; i--) {
|
||||
+ if (idl_parentid_walk_push(stack, stack_size, stack_top,
|
||||
+ children->b_ids[i - 1]) != 0) {
|
||||
+ idl_free(&children);
|
||||
+ return -1;
|
||||
+ }
|
||||
+ }
|
||||
+ idl_free(&children);
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
- * NOTE:
|
||||
- * In the total update (bulk import), an entry requires its ancestors already added.
|
||||
- * To guarantee it, the range search with parentid is used with setting the flag
|
||||
- * SLAPI_OP_RANGE_NO_IDL_SORT in operator.
|
||||
- * In bulk import the range search is parentid>=1 to retrieve all the entries
|
||||
- * But we need to order the IDL with the parents first => retrieve the suffix entry ID
|
||||
- * to store the children
|
||||
+ * Build an IDList from the parentid index with ancestors before descendants.
|
||||
+ * Uses direct key lookups (idl_fetch) and depth-first traversal from the
|
||||
+ * suffix entry, rather than a full index scan with membership checks.
|
||||
*
|
||||
- * If the flag is set,
|
||||
- * 1. the IDList is not sorted by the ID.
|
||||
- * 2. holding to add an ID to the IDList unless the key is found in the IDList.
|
||||
+ * Used during bulk import (SLAPI_OP_RANGE_NO_IDL_SORT).
|
||||
+ */
|
||||
+static IDList *
|
||||
+idl_new_parentid_sorted_range_fetch(
|
||||
+ backend *be,
|
||||
+ dbi_db_t *db,
|
||||
+ dbi_val_t *lowerkey,
|
||||
+ dbi_val_t *upperkey,
|
||||
+ dbi_txn_t *txn,
|
||||
+ struct attrinfo *ai,
|
||||
+ int *flag_err,
|
||||
+ int allidslimit,
|
||||
+ int sizelimit,
|
||||
+ struct timespec *expire_time,
|
||||
+ int lookthrough_limit,
|
||||
+ int operator)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+ ID suffix = 0;
|
||||
+ ID *stack = NULL;
|
||||
+ size_t stack_size = 0;
|
||||
+ size_t stack_top = 0;
|
||||
+ char *index_id = get_index_name(be, db, ai);
|
||||
+ idl_parentid_walk_ctx_t walk = {0};
|
||||
+ IDList *suffix_idl = NULL;
|
||||
+ int suffix_err = 0;
|
||||
+
|
||||
+ walk.be = be;
|
||||
+ walk.db = db;
|
||||
+ walk.txn = txn;
|
||||
+ walk.ai = ai;
|
||||
+ walk.flag_err = flag_err;
|
||||
+ walk.allidslimit = allidslimit;
|
||||
+ walk.sizelimit = sizelimit;
|
||||
+ walk.expire_time = expire_time;
|
||||
+ walk.lookthrough_limit = lookthrough_limit;
|
||||
+ walk.index_id = index_id;
|
||||
+ walk.idl = idl_alloc(IDLIST_MIN_BLOCK_SIZE);
|
||||
+ if (walk.idl == NULL) {
|
||||
+ *flag_err = ENOMEM;
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ idl_parentid_sorted_suffix_init(be, &suffix, "idl_new_parentid_sorted_range_fetch");
|
||||
+ if (suffix == 0) {
|
||||
+ idl_parentid_set_index_key(&walk, 0);
|
||||
+ suffix_idl = idl_fetch_ext(be, db, &walk.key, txn, ai, &suffix_err, allidslimit);
|
||||
+ if (suffix_idl && suffix_idl->b_nids > 0 && !ALLIDS(suffix_idl)) {
|
||||
+ suffix = suffix_idl->b_ids[0];
|
||||
+ }
|
||||
+ idl_free(&suffix_idl);
|
||||
+ }
|
||||
+ if (suffix == 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Unable to determine suffix entry ID for parentid tree walk\n");
|
||||
+ idl_free(&walk.idl);
|
||||
+ *flag_err = LDAP_UNWILLING_TO_PERFORM;
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) {
|
||||
+ char *included = ((operator & SLAPI_OP_RANGE) == SLAPI_OP_LESS) ? "not " : "";
|
||||
+ slapi_log_err(SLAPI_LOG_FILTER,
|
||||
+ "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Walking parentid index from suffix ID %u, keys %s to %s\n",
|
||||
+ suffix, (char *)lowerkey->data,
|
||||
+ upperkey && upperkey->data ? (char *)upperkey->data : "(none)");
|
||||
+ slapi_log_err(SLAPI_LOG_FILTER, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Candidate list is not sorted. lower key is %sincluded.\n", included);
|
||||
+ }
|
||||
+
|
||||
+ if (idl_parentid_walk_tree(&walk, suffix, &stack, &stack_size, &stack_top) != 0) {
|
||||
+ if (*flag_err == 0) {
|
||||
+ *flag_err = LDAP_UNWILLING_TO_PERFORM;
|
||||
+ }
|
||||
+ ret = *flag_err;
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free((void **)&stack);
|
||||
+
|
||||
+ if (walk.idl && (walk.idl->b_nids == 1) && (walk.idl->b_ids[0] == ALLID)) {
|
||||
+ idl_free(&walk.idl);
|
||||
+ walk.idl = idl_allids(be);
|
||||
+ slapi_log_err(SLAPI_LOG_TRACE, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "%s returns allids\n", index_id);
|
||||
+ } else {
|
||||
+ slapi_log_err(SLAPI_LOG_TRACE, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "%s returns nids=%lu\n", index_id, (u_long)IDL_NIDS(walk.idl));
|
||||
+ }
|
||||
+
|
||||
+ if (ret) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Failed to build parentid candidate list on %s index. Error is %d\n",
|
||||
+ index_id, ret);
|
||||
+ }
|
||||
+ *flag_err = ret;
|
||||
+ slapi_log_err(SLAPI_LOG_FILTER, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Found %d candidates; error code is: %d\n",
|
||||
+ walk.idl ? walk.idl->b_nids : 0, *flag_err);
|
||||
+ return walk.idl;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Perform the range search in the idl layer instead of the index layer
|
||||
+ * to improve the performance.
|
||||
*/
|
||||
IDList *
|
||||
idl_new_range_fetch(
|
||||
@@ -430,39 +677,19 @@ idl_new_range_fetch(
|
||||
back_txn s_txn;
|
||||
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
int coreop = operator&SLAPI_OP_RANGE;
|
||||
- ID key = 0xff; /* random- to suppress compiler warning */
|
||||
- ID suffix = 0; /* random- to suppress compiler warning */
|
||||
- idl_range_id_pair *leftover = NULL;
|
||||
- size_t leftoverlen = 32;
|
||||
- size_t leftovercnt = 0;
|
||||
char *index_id = get_index_name(be, db, ai);
|
||||
- IdRange_t *idrange_list = NULL;
|
||||
-
|
||||
|
||||
if (NULL == flag_err) {
|
||||
return NULL;
|
||||
}
|
||||
- if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- struct _back_info_index_key bck_info;
|
||||
- int rc;
|
||||
- /* We are doing a bulk import
|
||||
- * try to retrieve the suffix entry id from the index
|
||||
- */
|
||||
-
|
||||
- bck_info.index = SLAPI_ATTR_PARENTID;
|
||||
- bck_info.key = "0";
|
||||
-
|
||||
- if ((rc = slapi_back_get_info(be, BACK_INFO_INDEX_KEY, (void **)&bck_info))) {
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, "idl_new_range_fetch", "Total update: fail to retrieve suffix entryID, continue assuming it is the first entry\n");
|
||||
- }
|
||||
- if (bck_info.key_found) {
|
||||
- suffix = bck_info.id;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
if (NEW_IDL_NOOP == *flag_err) {
|
||||
return NULL;
|
||||
}
|
||||
+ if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
+ return idl_new_parentid_sorted_range_fetch(be, db, lowerkey, upperkey, txn, ai,
|
||||
+ flag_err, allidslimit, sizelimit,
|
||||
+ expire_time, lookthrough_limit, operator);
|
||||
+ }
|
||||
if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) {
|
||||
char *included = ((operator & SLAPI_OP_RANGE) == SLAPI_OP_LESS) ? "not " : "";
|
||||
const char *sorted = (operator & SLAPI_OP_RANGE_NO_IDL_SORT) ? "not " : "";
|
||||
@@ -552,9 +779,6 @@ idl_new_range_fetch(
|
||||
*flag_err = LDAP_TIMELIMIT_EXCEEDED;
|
||||
goto error;
|
||||
}
|
||||
- if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- key = (ID)strtol((char *)cur_key.data + 1, (char **)NULL, 10);
|
||||
- }
|
||||
while (DBI_RC_SUCCESS == dblayer_bulk_nextdata(&bulkdata, &dataret)) {
|
||||
if (dataret.size != sizeof(ID)) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "idl_new_range_fetch", "Database index is corrupt; "
|
||||
@@ -570,38 +794,7 @@ idl_new_range_fetch(
|
||||
}
|
||||
/* note the last id read to check for dups */
|
||||
lastid = id;
|
||||
- /* we got another ID, add it to our IDL */
|
||||
- if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- if ((count == 0) && (suffix == 0)) {
|
||||
- /* First time. Keep the suffix ID.
|
||||
- * note that 'suffix==0' mean we did not retrieve the suffix entry id
|
||||
- * from the parentid index (key '=0'), so let assume the first
|
||||
- * found entry is the one from the suffix
|
||||
- */
|
||||
- suffix = key;
|
||||
- idl_append_extend(&idl, id);
|
||||
- idrange_add_id(&idrange_list, id);
|
||||
- } else if ((key == suffix) || idl_id_is_in_idlist_ranges(idl, idrange_list, key)) {
|
||||
- /* the parent is the suffix or already in idl. */
|
||||
- idl_append_extend(&idl, id);
|
||||
- idrange_add_id(&idrange_list, id);
|
||||
- } else {
|
||||
- /* Otherwise, keep the {key,id} in leftover array */
|
||||
- if (!leftover) {
|
||||
- leftover = (idl_range_id_pair *)slapi_ch_calloc(leftoverlen, sizeof(idl_range_id_pair));
|
||||
- } else if (leftovercnt == leftoverlen) {
|
||||
- leftover = (idl_range_id_pair *)slapi_ch_realloc((char *)leftover, 2 * leftoverlen * sizeof(idl_range_id_pair));
|
||||
- memset(leftover + leftovercnt, 0, leftoverlen);
|
||||
- leftoverlen *= 2;
|
||||
- }
|
||||
- leftover[leftovercnt].key = key;
|
||||
- leftover[leftovercnt].id = id;
|
||||
- leftovercnt++;
|
||||
- }
|
||||
- } else {
|
||||
- idl_append_extend(&idl, id);
|
||||
- }
|
||||
-
|
||||
+ idl_append_extend(&idl, id);
|
||||
count++;
|
||||
}
|
||||
|
||||
@@ -684,26 +877,9 @@ error:
|
||||
*flag_err = ret;
|
||||
|
||||
/* sort idl */
|
||||
- if (idl && !ALLIDS(idl) && !(operator&SLAPI_OP_RANGE_NO_IDL_SORT)) {
|
||||
+ if (idl && !ALLIDS(idl)) {
|
||||
qsort((void *)&idl->b_ids[0], idl->b_nids, (size_t)sizeof(ID), idl_sort_cmp);
|
||||
}
|
||||
- if (operator&SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- size_t remaining = leftovercnt;
|
||||
-
|
||||
- while(remaining > 0) {
|
||||
- for (size_t i = 0; i < leftovercnt; i++) {
|
||||
- if (leftover[i].key > 0 && idl_id_is_in_idlist_ranges(idl, idrange_list, leftover[i].key) != 0) {
|
||||
- /* if the leftover key has its parent in the idl */
|
||||
- idl_append_extend(&idl, leftover[i].id);
|
||||
- idrange_add_id(&idrange_list, leftover[i].id);
|
||||
- leftover[i].key = 0;
|
||||
- remaining--;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
- slapi_ch_free((void **)&leftover);
|
||||
- idrange_free(&idrange_list);
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
idl ? idl->b_nids : 0, *flag_err);
|
||||
@@ -769,38 +945,7 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
"Detected duplicate id %d due to DB_MULTIPLE error - skipping\n", id);
|
||||
return DBI_RC_SUCCESS;
|
||||
}
|
||||
- /* we got another ID, add it to our IDL */
|
||||
- if (rctx->operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- ID keyval = (ID)strtol((char *)key->data + 1, (char **)NULL, 10);
|
||||
- if ((rctx->count == 0) && (rctx->suffix == 0)) {
|
||||
- /* First time. Keep the suffix ID.
|
||||
- * note that 'suffix==0' mean we did not retrieve the suffix entry id
|
||||
- * from the parentid index (key '=0'), so let assume the first
|
||||
- * found entry is the one from the suffix
|
||||
- */
|
||||
- rctx->suffix = keyval;
|
||||
- idl_append_extend(&rctx->idl, id);
|
||||
- idrange_add_id(&rctx->idrange_list, id);
|
||||
- } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist_ranges(rctx->idl, rctx->idrange_list, keyval)) {
|
||||
- /* the parent is the suffix or already in idl. */
|
||||
- idl_append_extend(&rctx->idl, id);
|
||||
- idrange_add_id(&rctx->idrange_list, id);
|
||||
- } else {
|
||||
- /* Otherwise, keep the {keyval,id} in leftover array */
|
||||
- if (!rctx->leftover) {
|
||||
- rctx->leftover = (idl_range_id_pair *)slapi_ch_calloc(rctx->leftoverlen, sizeof(idl_range_id_pair));
|
||||
- } else if (rctx->leftovercnt == rctx->leftoverlen) {
|
||||
- rctx->leftover = (idl_range_id_pair *)slapi_ch_realloc((char *)rctx->leftover, 2 * rctx->leftoverlen * sizeof(idl_range_id_pair));
|
||||
- memset(rctx->leftover + rctx->leftovercnt, 0, rctx->leftoverlen * sizeof(idl_range_id_pair));
|
||||
- rctx->leftoverlen *= 2;
|
||||
- }
|
||||
- rctx->leftover[rctx->leftovercnt].key = keyval;
|
||||
- rctx->leftover[rctx->leftovercnt].id = id;
|
||||
- rctx->leftovercnt++;
|
||||
- }
|
||||
- } else {
|
||||
- idl_append_extend(&rctx->idl, id);
|
||||
- }
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
#if defined(DB_ALLIDS_ON_READ)
|
||||
/* enforce the allids read limit */
|
||||
if ((NEW_IDL_NO_ALLID != rctx->flag_err) && rctx->ai && (rctx->idl != NULL) &&
|
||||
@@ -845,6 +990,11 @@ idl_lmdb_range_fetch(
|
||||
if ((NULL == flag_err) || (NEW_IDL_NOOP == *flag_err)) {
|
||||
return NULL;
|
||||
}
|
||||
+ if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
+ return idl_new_parentid_sorted_range_fetch(be, db, lowerkey, upperkey, txn, ai,
|
||||
+ flag_err, allidslimit, sizelimit,
|
||||
+ expire_time, lookthrough_limit, operator);
|
||||
+ }
|
||||
if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) {
|
||||
char *included = ((operator & SLAPI_OP_RANGE) == SLAPI_OP_LESS) ? "not " : "";
|
||||
const char *sorted = (operator & SLAPI_OP_RANGE_NO_IDL_SORT) ? "not " : "";
|
||||
@@ -877,32 +1027,11 @@ idl_lmdb_range_fetch(
|
||||
idl_range_ctx.expire_time = expire_time;
|
||||
idl_range_ctx.lookthrough_limit = lookthrough_limit;
|
||||
idl_range_ctx.operator = operator;
|
||||
- idl_range_ctx.leftover = NULL;
|
||||
- idl_range_ctx.leftoverlen = 32;
|
||||
- idl_range_ctx.leftovercnt = 0;
|
||||
idl_range_ctx.idl = idl_alloc(IDLIST_MIN_BLOCK_SIZE);
|
||||
idl_range_ctx.flag_err = 0;
|
||||
idl_range_ctx.lastid = 0;
|
||||
idl_range_ctx.count = 0;
|
||||
idl_range_ctx.index_id = index_id;
|
||||
- idl_range_ctx.idrange_list = NULL;
|
||||
- if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- struct _back_info_index_key bck_info;
|
||||
- /* We are doing a bulk import
|
||||
- * try to retrieve the suffix entry id from the index
|
||||
- */
|
||||
-
|
||||
- bck_info.index = SLAPI_ATTR_PARENTID;
|
||||
- bck_info.key = "0";
|
||||
-
|
||||
- if ((ret = slapi_back_get_info(be, BACK_INFO_INDEX_KEY, (void **)&bck_info))) {
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, "idl_lmdb_range_fetch",
|
||||
- "Total update: fail to retrieve suffix entryID, continue assuming it is the first entry\n");
|
||||
- }
|
||||
- if (bck_info.key_found) {
|
||||
- idl_range_ctx.suffix = bck_info.id;
|
||||
- }
|
||||
- }
|
||||
|
||||
/*
|
||||
* Iterate
|
||||
@@ -952,27 +1081,9 @@ error:
|
||||
}
|
||||
|
||||
/* sort idl */
|
||||
- if (!ALLIDS(idl_range_ctx.idl) && !(operator&SLAPI_OP_RANGE_NO_IDL_SORT)) {
|
||||
+ if (!ALLIDS(idl_range_ctx.idl)) {
|
||||
qsort((void *)&idl_range_ctx.idl->b_ids[0], idl_range_ctx.idl->b_nids, sizeof(ID), idl_sort_cmp);
|
||||
}
|
||||
- if (operator&SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
- size_t remaining = idl_range_ctx.leftovercnt;
|
||||
-
|
||||
- while(remaining > 0) {
|
||||
- for (size_t i = 0; i < idl_range_ctx.leftovercnt; i++) {
|
||||
- if (idl_range_ctx.leftover[i].key > 0 &&
|
||||
- idl_id_is_in_idlist_ranges(idl_range_ctx.idl, idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].key) != 0) {
|
||||
- /* if the leftover key has its parent in the idl */
|
||||
- idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
|
||||
- idrange_add_id(&idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].id);
|
||||
- idl_range_ctx.leftover[i].key = 0;
|
||||
- remaining--;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
- slapi_ch_free((void **)&idl_range_ctx.leftover);
|
||||
- idrange_free(&idl_range_ctx.idrange_list);
|
||||
*flag_err = idl_range_ctx.flag_err;
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_lmdb_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,220 +0,0 @@
|
||||
From 8cf981c00ae18d3efaeb10819282cd991621e9a2 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 22 May 2024 11:29:05 +0200
|
||||
Subject: [PATCH] Issue 6172 - RFE: improve the performance of evaluation of
|
||||
filter component when tested against a large valueset (like group members)
|
||||
(#6173)
|
||||
|
||||
Bug description:
|
||||
Before returning an entry (to a SRCH) the server checks that the entry matches the SRCH filter.
|
||||
If a filter component (equality) is testing the value (ava) against a
|
||||
large valueset (like uniquemember values), it takes a long time because
|
||||
of the large number of values and required normalization of the values.
|
||||
This can be improved taking benefit of sorted valueset. Those sorted
|
||||
valueset were created to improve updates of large valueset (groups) but
|
||||
at that time not implemented in SRCH path.
|
||||
|
||||
Fix description:
|
||||
In case of LDAP_FILTER_EQUALITY component, the server can get
|
||||
benefit of the sorted valuearray.
|
||||
To limit the risk of regression, we use the sorted valuearray
|
||||
only for the DN syntax attribute. Indeed the sorted valuearray was
|
||||
designed for those type of attribute.
|
||||
With those two limitations, there is no need of a toggle and
|
||||
the call to plugin_call_syntax_filter_ava can be replaced by
|
||||
a call to slapi_valueset_find.
|
||||
In both cases, sorted valueset and plugin_call_syntax_filter_ava, ava and
|
||||
values are normalized.
|
||||
In sorted valueset, the values have been normalized to insert the index
|
||||
in the sorted array and then comparison is done on normalized values.
|
||||
In plugin_call_syntax_filter_ava, all values in valuearray (of valueset) are normalized
|
||||
before comparison.
|
||||
|
||||
relates: #6172
|
||||
|
||||
Reviewed by: Pierre Rogier, Simon Pichugin (Big Thanks !!!)
|
||||
---
|
||||
.../tests/suites/filter/filter_test.py | 125 ++++++++++++++++++
|
||||
ldap/servers/slapd/filterentry.c | 22 ++-
|
||||
2 files changed, 146 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/filter/filter_test.py b/dirsrvtests/tests/suites/filter/filter_test.py
|
||||
index d6bfa5a3b..4baaf04a7 100644
|
||||
--- a/dirsrvtests/tests/suites/filter/filter_test.py
|
||||
+++ b/dirsrvtests/tests/suites/filter/filter_test.py
|
||||
@@ -9,7 +9,11 @@
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
+import time
|
||||
+from lib389.dirsrv_log import DirsrvAccessLog
|
||||
from lib389.tasks import *
|
||||
+from lib389.backend import Backends, Backend
|
||||
+from lib389.dbgen import dbgen_users, dbgen_groups
|
||||
from lib389.topologies import topology_st
|
||||
from lib389._constants import PASSWORD, DEFAULT_SUFFIX, DN_DM, SUFFIX
|
||||
from lib389.utils import *
|
||||
@@ -304,6 +308,127 @@ def test_extended_search(topology_st):
|
||||
ents = topology_st.standalone.search_s(SUFFIX, ldap.SCOPE_SUBTREE, myfilter)
|
||||
assert len(ents) == 1
|
||||
|
||||
+def test_match_large_valueset(topology_st):
|
||||
+ """Test that when returning a big number of entries
|
||||
+ and that we need to match the filter from a large valueset
|
||||
+ we get benefit to use the sorted valueset
|
||||
+
|
||||
+ :id: 7db5aa88-50e0-4c31-85dd-1d2072cb674c
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Create a users and groups backends and tune them
|
||||
+ 2. Generate a test ldif (2k users and 1K groups with all users)
|
||||
+ 3. Import test ldif file using Offline import (ldif2db).
|
||||
+ 4. Prim the 'groups' entrycache with a "fast" search
|
||||
+ 5. Search the 'groups' with a difficult matching value
|
||||
+ 6. check that etime from step 5 is less than a second
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Create a users and groups backends should PASS
|
||||
+ 2. Generate LDIF should PASS.
|
||||
+ 3. Offline import should PASS.
|
||||
+ 4. Priming should PASS.
|
||||
+ 5. Performance search should PASS.
|
||||
+ 6. Etime of performance search should PASS.
|
||||
+ """
|
||||
+
|
||||
+ log.info('Running test_match_large_valueset...')
|
||||
+ #
|
||||
+ # Test online/offline LDIF imports
|
||||
+ #
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.start()
|
||||
+ backends = Backends(inst)
|
||||
+ users_suffix = "ou=users,%s" % DEFAULT_SUFFIX
|
||||
+ users_backend = 'users'
|
||||
+ users_ldif = 'users_import.ldif'
|
||||
+ groups_suffix = "ou=groups,%s" % DEFAULT_SUFFIX
|
||||
+ groups_backend = 'groups'
|
||||
+ groups_ldif = 'groups_import.ldif'
|
||||
+ groups_entrycache = '200000000'
|
||||
+ users_number = 2000
|
||||
+ groups_number = 1000
|
||||
+
|
||||
+
|
||||
+ # For priming the cache we just want to be fast
|
||||
+ # taking the first value in the valueset is good
|
||||
+ # whether the valueset is sorted or not
|
||||
+ priming_user_rdn = "user0001"
|
||||
+
|
||||
+ # For performance testing, this is important to use
|
||||
+ # user1000 rather then user0001
|
||||
+ # Because user0001 is the first value in the valueset
|
||||
+ # whether we use the sorted valuearray or non sorted
|
||||
+ # valuearray the performance will be similar.
|
||||
+ # With middle value user1000, the performance boost of
|
||||
+ # the sorted valuearray will make the difference.
|
||||
+ perf_user_rdn = "user1000"
|
||||
+
|
||||
+ # Step 1. Prepare the backends and tune the groups entrycache
|
||||
+ try:
|
||||
+ be_users = backends.create(properties={'parent': DEFAULT_SUFFIX, 'nsslapd-suffix': users_suffix, 'name': users_backend})
|
||||
+ be_groups = backends.create(properties={'parent': DEFAULT_SUFFIX, 'nsslapd-suffix': groups_suffix, 'name': groups_backend})
|
||||
+
|
||||
+ # set the entry cache to 200Mb as the 1K groups of 2K users require at least 170Mb
|
||||
+ be_groups.replace('nsslapd-cachememsize', groups_entrycache)
|
||||
+ except:
|
||||
+ raise
|
||||
+
|
||||
+ # Step 2. Generate a test ldif (10k users entries)
|
||||
+ log.info("Generating users LDIF...")
|
||||
+ ldif_dir = inst.get_ldif_dir()
|
||||
+ users_import_ldif = "%s/%s" % (ldif_dir, users_ldif)
|
||||
+ groups_import_ldif = "%s/%s" % (ldif_dir, groups_ldif)
|
||||
+ dbgen_users(inst, users_number, users_import_ldif, suffix=users_suffix, generic=True, parent=users_suffix)
|
||||
+
|
||||
+ # Generate a test ldif (800 groups with 10k members) that fit in 700Mb entry cache
|
||||
+ props = {
|
||||
+ "name": "group",
|
||||
+ "suffix": groups_suffix,
|
||||
+ "parent": groups_suffix,
|
||||
+ "number": groups_number,
|
||||
+ "numMembers": users_number,
|
||||
+ "createMembers": False,
|
||||
+ "memberParent": users_suffix,
|
||||
+ "membershipAttr": "uniquemember",
|
||||
+ }
|
||||
+ dbgen_groups(inst, groups_import_ldif, props)
|
||||
+
|
||||
+ # Step 3. Do the both offline imports
|
||||
+ inst.stop()
|
||||
+ if not inst.ldif2db(users_backend, None, None, None, users_import_ldif):
|
||||
+ log.fatal('test_basic_import_export: Offline users import failed')
|
||||
+ assert False
|
||||
+ if not inst.ldif2db(groups_backend, None, None, None, groups_import_ldif):
|
||||
+ log.fatal('test_basic_import_export: Offline groups import failed')
|
||||
+ assert False
|
||||
+ inst.start()
|
||||
+
|
||||
+ # Step 4. first prime the cache
|
||||
+ # Just request the 'DN'. We are interested by the time of matching not by the time of transfert
|
||||
+ entries = topology_st.standalone.search_s(groups_suffix, ldap.SCOPE_SUBTREE, "(&(objectclass=groupOfUniqueNames)(uniquemember=uid=%s,%s))" % (priming_user_rdn, users_suffix), ['dn'])
|
||||
+ assert len(entries) == groups_number
|
||||
+
|
||||
+ # Step 5. Now do the real performance checking it should take less than a second
|
||||
+ # Just request the 'DN'. We are interested by the time of matching not by the time of transfert
|
||||
+ search_start = time.time()
|
||||
+ entries = topology_st.standalone.search_s(groups_suffix, ldap.SCOPE_SUBTREE, "(&(objectclass=groupOfUniqueNames)(uniquemember=uid=%s,%s))" % (perf_user_rdn, users_suffix), ['dn'])
|
||||
+ duration = time.time() - search_start
|
||||
+ log.info("Duration of the search was %f", duration)
|
||||
+
|
||||
+ # Step 6. Gather the etime from the access log
|
||||
+ inst.stop()
|
||||
+ access_log = DirsrvAccessLog(inst)
|
||||
+ search_result = access_log.match(".*RESULT err=0 tag=101 nentries=%s.*" % groups_number)
|
||||
+ log.info("Found patterns are %s", search_result[0])
|
||||
+ log.info("Found patterns are %s", search_result[1])
|
||||
+ etime = float(search_result[1].split('etime=')[1])
|
||||
+ log.info("Duration of the search from access log was %f", etime)
|
||||
+ assert len(entries) == groups_number
|
||||
+ assert (etime < 1)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
|
||||
index fd8fdda9f..cae5c7edc 100644
|
||||
--- a/ldap/servers/slapd/filterentry.c
|
||||
+++ b/ldap/servers/slapd/filterentry.c
|
||||
@@ -296,7 +296,27 @@ test_ava_filter(
|
||||
rc = -1;
|
||||
for (; a != NULL; a = a->a_next) {
|
||||
if (slapi_attr_type_cmp(ava->ava_type, a->a_type, SLAPI_TYPE_CMP_SUBTYPE) == 0) {
|
||||
- rc = plugin_call_syntax_filter_ava(a, ftype, ava);
|
||||
+ if ((ftype == LDAP_FILTER_EQUALITY) &&
|
||||
+ (slapi_attr_is_dn_syntax_type(a->a_type))) {
|
||||
+ /* This path is for a performance improvement */
|
||||
+
|
||||
+ /* In case of equality filter we can get benefit of the
|
||||
+ * sorted valuearray (from valueset).
|
||||
+ * This improvement is limited to DN syntax attributes for
|
||||
+ * which the sorted valueset was designed.
|
||||
+ */
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ sval = slapi_value_new_berval(&ava->ava_value);
|
||||
+ if (slapi_valueset_find((const Slapi_Attr *)a, &a->a_present_values, sval)) {
|
||||
+ rc = 0;
|
||||
+ }
|
||||
+ slapi_value_free(&sval);
|
||||
+ } else {
|
||||
+ /* When sorted valuearray optimization cannot be used
|
||||
+ * lets filter the value according to its syntax
|
||||
+ */
|
||||
+ rc = plugin_call_syntax_filter_ava(a, ftype, ava);
|
||||
+ }
|
||||
if (rc == 0) {
|
||||
break;
|
||||
}
|
||||
--
|
||||
2.46.0
|
||||
|
||||
@ -0,0 +1,517 @@
|
||||
From fb6bd04c04ab92fe51c3206f9c2ef0433ea27d87 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 23 Jan 2026 17:35:45 -0800
|
||||
Subject: [PATCH 11/14] Issue 7198 - Web console doesn't show sub-suffix when
|
||||
parent-suffix points to an entry (#7202)
|
||||
|
||||
Description: The web console doesn't show sub-suffixes when the
|
||||
nsslapd-parent-suffix attribute points to an entry rather than a backend
|
||||
suffix.
|
||||
For example, creating a sub-suffix ou=foo,ou=people,dc=example,dc=com
|
||||
with parent-suffix ou=people,dc=example,dc=com (where ou=people is just an
|
||||
entry, not a suffix) would not appear in the web console tree.
|
||||
|
||||
Fix: In backend_build_tree() and get_sub_suffixes(), the code only matched
|
||||
when nsslapd-parent-suffix exactly equaled an existing backend suffix.
|
||||
Now it also checks if the parent-suffix is an entry under the current
|
||||
suffix (ends with ,suffix) and is not itself a backend suffix. This
|
||||
correctly attaches sub-suffixes to their containing suffix when the
|
||||
parent-suffix points to an intermediate entry.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7198
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
.../suites/lib389/subsuffix_tree_test.py | 313 ++++++++++++++++++
|
||||
src/lib389/lib389/backend.py | 47 ++-
|
||||
src/lib389/lib389/cli_conf/backend.py | 34 +-
|
||||
3 files changed, 370 insertions(+), 24 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
|
||||
new file mode 100644
|
||||
index 000000000..fa10ba530
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
|
||||
@@ -0,0 +1,313 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2026 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import logging
|
||||
+import os
|
||||
+import pytest
|
||||
+from lib389.topologies import topology_st as topo
|
||||
+from lib389.backend import Backends
|
||||
+from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def setup_subsuffix_with_entry_parent(topo, request):
|
||||
+ """Setup a sub-suffix whose parent-suffix points to an entry, not a suffix."""
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ # Create ou=people entry under the root suffix
|
||||
+ log.info("Creating ou=people,dc=example,dc=com entry")
|
||||
+ ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
|
||||
+ ou_people = ous.get('people')
|
||||
+
|
||||
+ # Create sub-suffix with parent-suffix pointing to the entry
|
||||
+ log.info("Creating sub-suffix ou=foo,ou=people,dc=example,dc=com")
|
||||
+ backends = Backends(inst)
|
||||
+ subsuffix_dn = 'ou=foo,ou=people,dc=example,dc=com'
|
||||
+ parent_suffix_dn = 'ou=people,dc=example,dc=com'
|
||||
+
|
||||
+ foo_backend = backends.create(properties={
|
||||
+ 'cn': 'foo',
|
||||
+ 'nsslapd-suffix': subsuffix_dn,
|
||||
+ 'parent': parent_suffix_dn,
|
||||
+ })
|
||||
+
|
||||
+ # Create the suffix entry
|
||||
+ foo_ous = OrganizationalUnits(inst, parent_suffix_dn)
|
||||
+ foo_ou = foo_ous.create(properties={'ou': 'foo'})
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Cleaning up test backends and entries")
|
||||
+ try:
|
||||
+ foo_ou.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete foo_ou: {e}")
|
||||
+ try:
|
||||
+ foo_backend.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete foo_backend: {e}")
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+
|
||||
+ return {
|
||||
+ 'instance': inst,
|
||||
+ 'backends': backends,
|
||||
+ 'foo_backend': foo_backend,
|
||||
+ 'ou_people': ou_people,
|
||||
+ 'subsuffix_dn': subsuffix_dn,
|
||||
+ 'parent_suffix_dn': parent_suffix_dn,
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+def test_subsuffix_with_entry_parent_in_tree(topo, setup_subsuffix_with_entry_parent):
|
||||
+ """Test that a sub-suffix with parent pointing to an entry is visible in the tree.
|
||||
+
|
||||
+ :id: 256f36f5-76ad-4043-ad8d-1f9e2afc4e1d
|
||||
+ :setup: Standalone instance with sub-suffix whose parent is an entry
|
||||
+ :steps:
|
||||
+ 1. Verify the sub-suffix backend exists
|
||||
+ 2. Get sub-suffixes of the root backend
|
||||
+ 3. Verify the sub-suffix appears in the list
|
||||
+ :expectedresults:
|
||||
+ 1. Backend should exist
|
||||
+ 2. Sub-suffixes should be retrievable
|
||||
+ 3. Sub-suffix should be visible (this is where the bug manifested)
|
||||
+ """
|
||||
+ backends = setup_subsuffix_with_entry_parent['backends']
|
||||
+ foo_backend = setup_subsuffix_with_entry_parent['foo_backend']
|
||||
+ subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
|
||||
+
|
||||
+ # Step 1: Verify the sub-suffix backend exists
|
||||
+ assert foo_backend.exists(), "The foo backend should exist"
|
||||
+
|
||||
+ # Step 2: Get sub-suffixes of the root backend
|
||||
+ root_backend = backends.get(DEFAULT_SUFFIX)
|
||||
+ sub_suffixes = root_backend.get_sub_suffixes()
|
||||
+ log.info(f"Sub-suffixes found: {[s.get_attr_val_utf8('nsslapd-suffix') for s in sub_suffixes]}")
|
||||
+
|
||||
+ # Step 3: Verify sub-suffix is in the list
|
||||
+ sub_suffix_found = any(
|
||||
+ s.get_attr_val_utf8_l('nsslapd-suffix') == subsuffix_dn.lower()
|
||||
+ for s in sub_suffixes
|
||||
+ )
|
||||
+
|
||||
+ assert sub_suffix_found, (
|
||||
+ f"Sub-suffix {subsuffix_dn} should be visible in get_sub_suffixes(). "
|
||||
+ "The parent-suffix points to an entry, not a backend suffix."
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def test_subsuffix_in_backend_list(topo, setup_subsuffix_with_entry_parent):
|
||||
+ """Test that the sub-suffix appears in the backend list.
|
||||
+
|
||||
+ :id: 0ccc49af-91bb-4e8f-b0e1-1bd0b75c041b
|
||||
+ :setup: Standalone instance with sub-suffix configuration
|
||||
+ :steps:
|
||||
+ 1. Get all backends
|
||||
+ 2. Verify both root suffix and sub-suffix are present
|
||||
+ :expectedresults:
|
||||
+ 1. Should retrieve all backends
|
||||
+ 2. Both suffixes should be listed
|
||||
+ """
|
||||
+ backends = setup_subsuffix_with_entry_parent['backends']
|
||||
+ subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
|
||||
+
|
||||
+ be_list = backends.list()
|
||||
+ suffixes = [be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_list]
|
||||
+
|
||||
+ assert DEFAULT_SUFFIX.lower() in suffixes, \
|
||||
+ f"Root suffix {DEFAULT_SUFFIX} should be in the list"
|
||||
+ assert subsuffix_dn.lower() in suffixes, \
|
||||
+ f"Sub-suffix {subsuffix_dn} should be in the list"
|
||||
+
|
||||
+
|
||||
+def test_subsuffix_dn_boundary_matching():
|
||||
+ """Test that suffix matching respects DN component boundaries.
|
||||
+
|
||||
+ :id: 0b856e26-c394-4c36-b9ba-d7894aa2ed11
|
||||
+ :setup: None (unit test)
|
||||
+ :steps:
|
||||
+ 1. Test exact suffix match
|
||||
+ 2. Test proper DN ancestor match (ends with ,suffix)
|
||||
+ 3. Test that partial string matches are rejected
|
||||
+ :expectedresults:
|
||||
+ 1. Exact match should return True
|
||||
+ 2. Proper ancestor should return True
|
||||
+ 3. Partial string match should return False
|
||||
+ """
|
||||
+ from lib389.backend import is_subsuffix_of
|
||||
+
|
||||
+ all_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=dept,dc=example,dc=com'}
|
||||
+
|
||||
+ # Test 1: Exact match
|
||||
+ assert is_subsuffix_of('dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
|
||||
+ "Exact match should return True"
|
||||
+
|
||||
+ # Test 2: Parent is an entry under the suffix (not itself a suffix)
|
||||
+ assert is_subsuffix_of('ou=people,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
|
||||
+ "Parent entry under suffix should return True"
|
||||
+
|
||||
+ # Test 3: Parent IS a suffix - should return False (handled separately)
|
||||
+ assert not is_subsuffix_of('ou=dept,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
|
||||
+ "Parent that is itself a suffix should return False"
|
||||
+
|
||||
+ # Test 4: Edge case - wrong DN boundary (string ends with suffix but wrong boundary)
|
||||
+ edge_suffixes = {'dc=com', 'st,dc=com'}
|
||||
+ assert is_subsuffix_of('dc=test,dc=com', 'dc=com', edge_suffixes), \
|
||||
+ "dc=test,dc=com should match dc=com"
|
||||
+ assert not is_subsuffix_of('dc=test,dc=com', 'st,dc=com', edge_suffixes), \
|
||||
+ "dc=test,dc=com should NOT match st,dc=com (wrong DN boundary)"
|
||||
+
|
||||
+ # Test 5: None input
|
||||
+ assert not is_subsuffix_of(None, 'dc=com', all_suffixes), \
|
||||
+ "None parent should return False"
|
||||
+
|
||||
+ # Test 6: Closest ancestor - should only match the nearest suffix
|
||||
+ # Hierarchy: dc=com -> dc=example,dc=com -> ou=branch,dc=example,dc=com (suffix)
|
||||
+ # -> ou=dept,ou=branch,dc=example,dc=com (entry) -> subsuffix
|
||||
+ # The subsuffix should only appear under ou=branch, not under dc=example,dc=com
|
||||
+ nested_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=branch,dc=example,dc=com'}
|
||||
+ entry_parent = 'ou=dept,ou=branch,dc=example,dc=com'
|
||||
+ # Should match ou=branch (closest)
|
||||
+ assert is_subsuffix_of(entry_parent, 'ou=branch,dc=example,dc=com', nested_suffixes), \
|
||||
+ "Should match closest ancestor suffix (ou=branch)"
|
||||
+ # Should NOT match dc=example,dc=com (not closest)
|
||||
+ assert not is_subsuffix_of(entry_parent, 'dc=example,dc=com', nested_suffixes), \
|
||||
+ "Should NOT match distant ancestor (dc=example) - ou=branch is closer"
|
||||
+ # Should NOT match dc=com (not closest)
|
||||
+ assert not is_subsuffix_of(entry_parent, 'dc=com', nested_suffixes), \
|
||||
+ "Should NOT match distant ancestor (dc=com) - ou=branch is closer"
|
||||
+
|
||||
+ log.info("All DN boundary edge cases passed")
|
||||
+
|
||||
+
|
||||
+def test_deep_suffix_hierarchy(topo, request):
|
||||
+ """Test complex hierarchy: suffix -> suffix -> entry -> suffix -> suffix.
|
||||
+
|
||||
+ :id: fd06491a-defa-4780-8472-78c077febdfb
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create sub-suffix ou=branch (parent=dc=example,dc=com - a suffix)
|
||||
+ 2. Create entry ou=dept,ou=branch (not a suffix)
|
||||
+ 3. Create sub-suffix ou=team,ou=dept,ou=branch (parent=ou=dept - an entry)
|
||||
+ 4. Create sub-suffix ou=sub,ou=team,ou=dept,ou=branch (parent=ou=team - a suffix)
|
||||
+ 5. Verify all sub-suffixes are correctly placed in the tree
|
||||
+ :expectedresults:
|
||||
+ 1. Sub-suffix created successfully
|
||||
+ 2. Entry created successfully
|
||||
+ 3. Sub-suffix with entry parent created successfully
|
||||
+ 4. Sub-suffix with suffix parent created successfully
|
||||
+ 5. Tree hierarchy is correct
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ backends = Backends(inst)
|
||||
+
|
||||
+ # Define the hierarchy
|
||||
+ branch_suffix = f'ou=branch,{DEFAULT_SUFFIX}'
|
||||
+ dept_entry = f'ou=dept,{branch_suffix}' # This is an ENTRY, not a suffix
|
||||
+ team_suffix = f'ou=team,{dept_entry}'
|
||||
+ sub_suffix = f'ou=sub,{team_suffix}'
|
||||
+
|
||||
+ created_backends = []
|
||||
+ created_entries = []
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Cleaning up deep hierarchy test")
|
||||
+ for entry in reversed(created_entries):
|
||||
+ try:
|
||||
+ entry.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete entry: {e}")
|
||||
+ for be in reversed(created_backends):
|
||||
+ try:
|
||||
+ be.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete backend: {e}")
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+
|
||||
+ # Step 1: Create ou=branch sub-suffix (parent is root suffix)
|
||||
+ log.info(f"Creating sub-suffix {branch_suffix}")
|
||||
+ branch_be = backends.create(properties={
|
||||
+ 'cn': 'branch',
|
||||
+ 'nsslapd-suffix': branch_suffix,
|
||||
+ 'parent': DEFAULT_SUFFIX,
|
||||
+ })
|
||||
+ created_backends.append(branch_be)
|
||||
+ branch_ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
|
||||
+ branch_ou = branch_ous.create(properties={'ou': 'branch'})
|
||||
+ created_entries.append(branch_ou)
|
||||
+
|
||||
+ # Step 2: Create ou=dept entry under branch (NOT a suffix)
|
||||
+ log.info(f"Creating entry {dept_entry}")
|
||||
+ dept_ous = OrganizationalUnits(inst, branch_suffix)
|
||||
+ dept_ou = dept_ous.create(properties={'ou': 'dept'})
|
||||
+ created_entries.append(dept_ou)
|
||||
+
|
||||
+ # Step 3: Create ou=team sub-suffix (parent is dept ENTRY, not a suffix)
|
||||
+ log.info(f"Creating sub-suffix {team_suffix} with entry parent {dept_entry}")
|
||||
+ team_be = backends.create(properties={
|
||||
+ 'cn': 'team',
|
||||
+ 'nsslapd-suffix': team_suffix,
|
||||
+ 'parent': dept_entry, # Parent is an ENTRY!
|
||||
+ })
|
||||
+ created_backends.append(team_be)
|
||||
+ team_ous = OrganizationalUnits(inst, dept_entry)
|
||||
+ team_ou = team_ous.create(properties={'ou': 'team'})
|
||||
+ created_entries.append(team_ou)
|
||||
+
|
||||
+ # Step 4: Create ou=sub sub-suffix (parent is team suffix)
|
||||
+ log.info(f"Creating sub-suffix {sub_suffix} with suffix parent {team_suffix}")
|
||||
+ sub_be = backends.create(properties={
|
||||
+ 'cn': 'sub',
|
||||
+ 'nsslapd-suffix': sub_suffix,
|
||||
+ 'parent': team_suffix, # Parent is a SUFFIX
|
||||
+ })
|
||||
+ created_backends.append(sub_be)
|
||||
+ sub_ous = OrganizationalUnits(inst, team_suffix)
|
||||
+ sub_ou = sub_ous.create(properties={'ou': 'sub'})
|
||||
+ created_entries.append(sub_ou)
|
||||
+
|
||||
+ # Step 5: Verify the tree hierarchy
|
||||
+ log.info("Verifying tree hierarchy...")
|
||||
+
|
||||
+ # Root should have branch as sub-suffix
|
||||
+ root_be = backends.get(DEFAULT_SUFFIX)
|
||||
+ root_subs = root_be.get_sub_suffixes()
|
||||
+ root_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in root_subs]
|
||||
+ log.info(f"Root sub-suffixes: {root_sub_suffixes}")
|
||||
+ assert branch_suffix.lower() in root_sub_suffixes, \
|
||||
+ f"branch should be under root suffix"
|
||||
+
|
||||
+ # Branch should have team as sub-suffix (even though team's parent is an entry)
|
||||
+ branch_be_obj = backends.get(branch_suffix)
|
||||
+ branch_subs = branch_be_obj.get_sub_suffixes()
|
||||
+ branch_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in branch_subs]
|
||||
+ log.info(f"Branch sub-suffixes: {branch_sub_suffixes}")
|
||||
+ assert team_suffix.lower() in branch_sub_suffixes, \
|
||||
+ f"team should be under branch suffix (parent is entry under branch)"
|
||||
+
|
||||
+ # Team should have sub as sub-suffix
|
||||
+ team_be_obj = backends.get(team_suffix)
|
||||
+ team_subs = team_be_obj.get_sub_suffixes()
|
||||
+ team_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in team_subs]
|
||||
+ log.info(f"Team sub-suffixes: {team_sub_suffixes}")
|
||||
+ assert sub_suffix.lower() in team_sub_suffixes, \
|
||||
+ f"sub should be under team suffix"
|
||||
+
|
||||
+ log.info("Deep hierarchy test passed!")
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 42173eb06..7588e64e9 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -38,6 +38,36 @@ from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0004, DSBLE0005, D
|
||||
from lib389.plugins import USNPlugin
|
||||
|
||||
|
||||
+def is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
|
||||
+ """Check if sub_parent indicates this is a sub-suffix of be_suffix.
|
||||
+
|
||||
+ Returns True only if be_suffix is the CLOSEST ancestor suffix of sub_parent.
|
||||
+ This prevents a sub-suffix from appearing under multiple ancestors.
|
||||
+
|
||||
+ :param sub_parent: The nsslapd-parent-suffix value (lowercase)
|
||||
+ :param be_suffix: The suffix to check against (lowercase)
|
||||
+ :param all_suffixes: Set of all backend suffixes (lowercase)
|
||||
+ :returns: True if be_suffix is the closest ancestor suffix
|
||||
+ """
|
||||
+ if not sub_parent:
|
||||
+ return False
|
||||
+ if sub_parent == be_suffix:
|
||||
+ return True
|
||||
+ if sub_parent in all_suffixes:
|
||||
+ # sub_parent is itself a suffix, will be handled separately
|
||||
+ return False
|
||||
+ if not sub_parent.endswith(',' + be_suffix):
|
||||
+ return False
|
||||
+ # Find the closest (longest) matching suffix for this parent
|
||||
+ best_match = None
|
||||
+ for sfx in all_suffixes:
|
||||
+ if sub_parent == sfx or sub_parent.endswith(',' + sfx):
|
||||
+ if best_match is None or len(sfx) > len(best_match):
|
||||
+ best_match = sfx
|
||||
+ # Only return True if be_suffix is the closest match
|
||||
+ return best_match == be_suffix
|
||||
+
|
||||
+
|
||||
class BackendLegacy(object):
|
||||
proxied_methods = 'search_s getEntry'.split()
|
||||
|
||||
@@ -1111,22 +1141,27 @@ class Backend(DSLdapObject):
|
||||
vlv.create(rdn="cn=" + vlvname, properties=props, basedn=basedn)
|
||||
|
||||
def get_sub_suffixes(self):
|
||||
- """Return a list of Backend's
|
||||
- returns: a List of subsuffix entries
|
||||
+ """Return a list of Backend's that are sub-suffixes of this backend.
|
||||
+ :returns: A list of Backend instances that are sub-suffixes
|
||||
"""
|
||||
subsuffixes = []
|
||||
top_be_suffix = self.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
+ if not top_be_suffix:
|
||||
+ return subsuffixes
|
||||
+
|
||||
mts = self._mts.list()
|
||||
+ be_insts = Backends(self._instance).list()
|
||||
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
|
||||
+
|
||||
for mt in mts:
|
||||
parent_suffix = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
|
||||
if parent_suffix is None:
|
||||
continue
|
||||
- if parent_suffix == top_be_suffix:
|
||||
+
|
||||
+ if is_subsuffix_of(parent_suffix, top_be_suffix, all_suffixes):
|
||||
child_suffix = mt.get_attr_val_utf8_l('cn')
|
||||
- be_insts = Backends(self._instance).list()
|
||||
for be in be_insts:
|
||||
- be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
- if child_suffix == be_suffix:
|
||||
+ if child_suffix == be.get_attr_val_utf8_l('nsslapd-suffix'):
|
||||
subsuffixes.append(be)
|
||||
break
|
||||
return subsuffixes
|
||||
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
|
||||
index 80008d22d..0a9ce8f5a 100644
|
||||
--- a/src/lib389/lib389/cli_conf/backend.py
|
||||
+++ b/src/lib389/lib389/cli_conf/backend.py
|
||||
@@ -7,7 +7,7 @@
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
-from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView
|
||||
+from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView, is_subsuffix_of
|
||||
from lib389.configurations.sample import (
|
||||
create_base_domain,
|
||||
create_base_org,
|
||||
@@ -337,6 +337,7 @@ def is_db_replicated(inst, suffix):
|
||||
def backend_get_subsuffixes(inst, basedn, log, args):
|
||||
subsuffixes = []
|
||||
be_insts = MANY(inst).list()
|
||||
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
|
||||
for be in be_insts:
|
||||
be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
if be_suffix == args.be_name.lower():
|
||||
@@ -346,7 +347,7 @@ def backend_get_subsuffixes(inst, basedn, log, args):
|
||||
db_type = "suffix"
|
||||
sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
|
||||
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
|
||||
- if sub == be_suffix:
|
||||
+ if is_subsuffix_of(sub, be_suffix, all_suffixes):
|
||||
# We have a subsuffix (maybe a db link?)
|
||||
if is_db_link(inst, sub_be):
|
||||
db_type = "link"
|
||||
@@ -398,38 +399,34 @@ def build_node(suffix, be_name, subsuf=False, link=False, replicated=False):
|
||||
}
|
||||
|
||||
|
||||
-def backend_build_tree(inst, be_insts, nodes):
|
||||
- """Recursively build the tree
|
||||
- """
|
||||
- if len(nodes) == 0:
|
||||
- # Done
|
||||
+def backend_build_tree(inst, be_insts, nodes, all_suffixes):
|
||||
+ """Recursively build the tree."""
|
||||
+ if not nodes:
|
||||
return
|
||||
|
||||
for node in nodes:
|
||||
- node_suffix = node['id']
|
||||
+ node_suffix = node['id'].lower()
|
||||
# Get sub suffixes and chaining of node
|
||||
for be in be_insts:
|
||||
be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
- if be_suffix == node_suffix.lower():
|
||||
+ if be_suffix == node_suffix:
|
||||
# We have our parent, now find the children
|
||||
mts = be._mts.list()
|
||||
-
|
||||
for mt in mts:
|
||||
sub_parent = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
|
||||
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
|
||||
sub_suffix = mt.get_attr_val_utf8_l('cn')
|
||||
- if sub_parent == be_suffix:
|
||||
+ if is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
|
||||
# We have a subsuffix (maybe a db link?)
|
||||
link = is_db_link(inst, sub_be)
|
||||
replicated = is_db_replicated(inst, sub_suffix)
|
||||
node['children'].append(build_node(sub_suffix,
|
||||
- sub_be,
|
||||
- subsuf=True,
|
||||
- link=link,
|
||||
- replicated=replicated))
|
||||
-
|
||||
+ sub_be,
|
||||
+ subsuf=True,
|
||||
+ link=link,
|
||||
+ replicated=replicated))
|
||||
# Recurse over the new subsuffixes
|
||||
- backend_build_tree(inst, be_insts, node['children'])
|
||||
+ backend_build_tree(inst, be_insts, node['children'], all_suffixes)
|
||||
break
|
||||
|
||||
|
||||
@@ -470,7 +467,8 @@ def backend_get_tree(inst, basedn, log, args):
|
||||
else:
|
||||
# Build the tree
|
||||
be_insts = Backends(inst).list()
|
||||
- backend_build_tree(inst, be_insts, nodes)
|
||||
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
|
||||
+ backend_build_tree(inst, be_insts, nodes, all_suffixes)
|
||||
|
||||
# Done
|
||||
if args.json:
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
From a322d277a2e0320096f2a1e05025405a51d22ef8 Mon Sep 17 00:00:00 2001
|
||||
From f5199821675e3234335c5c28d31b0e9705685fd9 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Tue, 23 Jun 2026 10:07:00 +0100
|
||||
Subject: [PATCH 2/3] Issue 7593 - Reject invalid SASL packet length values in
|
||||
sasl_io_start_packet (#7594)
|
||||
Subject: [PATCH 12/14] Issue 7593 - Reject invalid SASL packet length values
|
||||
in sasl_io_start_packet (#7594)
|
||||
|
||||
Description:
|
||||
While processing SASL encrypted traffic, sasl_io_start_packet() reads a
|
||||
@ -133,7 +133,7 @@ index 000000000..ac1f3760f
|
||||
+ if not inst.status():
|
||||
+ inst.start()
|
||||
diff --git a/ldap/servers/slapd/sasl_io.c b/ldap/servers/slapd/sasl_io.c
|
||||
index 65199880f..614db692e 100644
|
||||
index d10ab8d2e..405c9186f 100644
|
||||
--- a/ldap/servers/slapd/sasl_io.c
|
||||
+++ b/ldap/servers/slapd/sasl_io.c
|
||||
@@ -16,6 +16,7 @@
|
||||
@ -1,163 +0,0 @@
|
||||
From 57051154bafaf50b83fc27dadbd89a49fd1c8c36 Mon Sep 17 00:00:00 2001
|
||||
From: Pierre Rogier <progier@redhat.com>
|
||||
Date: Fri, 14 Jun 2024 13:27:10 +0200
|
||||
Subject: [PATCH] Security fix for CVE-2024-5953
|
||||
|
||||
Description:
|
||||
A denial of service vulnerability was found in the 389 Directory Server.
|
||||
This issue may allow an authenticated user to cause a server denial
|
||||
of service while attempting to log in with a user with a malformed hash
|
||||
in their password.
|
||||
|
||||
Fix Description:
|
||||
To prevent buffer overflow when a bind request is processed, the bind fails
|
||||
if the hash size is not coherent without even attempting to process further
|
||||
the hashed password.
|
||||
|
||||
References:
|
||||
- https://nvd.nist.gov/vuln/detail/CVE-2024-5953
|
||||
- https://access.redhat.com/security/cve/CVE-2024-5953
|
||||
- https://bugzilla.redhat.com/show_bug.cgi?id=2292104
|
||||
---
|
||||
.../tests/suites/password/regression_test.py | 54 ++++++++++++++++++-
|
||||
ldap/servers/plugins/pwdstorage/md5_pwd.c | 9 +++-
|
||||
ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c | 6 +++
|
||||
3 files changed, 66 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/regression_test.py b/dirsrvtests/tests/suites/password/regression_test.py
|
||||
index 8f1facb6d..1fa581643 100644
|
||||
--- a/dirsrvtests/tests/suites/password/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/regression_test.py
|
||||
@@ -7,12 +7,14 @@
|
||||
#
|
||||
import pytest
|
||||
import time
|
||||
+import glob
|
||||
+import base64
|
||||
from lib389._constants import PASSWORD, DN_DM, DEFAULT_SUFFIX
|
||||
from lib389._constants import SUFFIX, PASSWORD, DN_DM, DN_CONFIG, PLUGIN_RETRO_CHANGELOG, DEFAULT_SUFFIX, DEFAULT_CHANGELOG_DB
|
||||
from lib389 import Entry
|
||||
from lib389.topologies import topology_m1 as topo_supplier
|
||||
-from lib389.idm.user import UserAccounts
|
||||
-from lib389.utils import ldap, os, logging, ensure_bytes, ds_is_newer
|
||||
+from lib389.idm.user import UserAccounts, UserAccount
|
||||
+from lib389.utils import ldap, os, logging, ensure_bytes, ds_is_newer, ds_supports_new_changelog
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
|
||||
@@ -39,6 +41,13 @@ TEST_PASSWORDS += ['CNpwtest1ZZZZ', 'ZZZZZCNpwtest1',
|
||||
TEST_PASSWORDS2 = (
|
||||
'CN12pwtest31', 'SN3pwtest231', 'UID1pwtest123', 'MAIL2pwtest12@redhat.com', '2GN1pwtest123', 'People123')
|
||||
|
||||
+SUPPORTED_SCHEMES = (
|
||||
+ "{SHA}", "{SSHA}", "{SHA256}", "{SSHA256}",
|
||||
+ "{SHA384}", "{SSHA384}", "{SHA512}", "{SSHA512}",
|
||||
+ "{crypt}", "{NS-MTA-MD5}", "{clear}", "{MD5}",
|
||||
+ "{SMD5}", "{PBKDF2_SHA256}", "{PBKDF2_SHA512}",
|
||||
+ "{GOST_YESCRYPT}", "{PBKDF2-SHA256}", "{PBKDF2-SHA512}" )
|
||||
+
|
||||
def _check_unhashed_userpw(inst, user_dn, is_present=False):
|
||||
"""Check if unhashed#user#password attribute is present or not in the changelog"""
|
||||
unhashed_pwd_attribute = 'unhashed#user#password'
|
||||
@@ -319,6 +328,47 @@ def test_unhashed_pw_switch(topo_supplier):
|
||||
# Add debugging steps(if any)...
|
||||
pass
|
||||
|
||||
+@pytest.mark.parametrize("scheme", SUPPORTED_SCHEMES )
|
||||
+def test_long_hashed_password(topo, create_user, scheme):
|
||||
+ """Check that hashed password with very long value does not cause trouble
|
||||
+
|
||||
+ :id: 252a1f76-114b-11ef-8a7a-482ae39447e5
|
||||
+ :setup: standalone Instance
|
||||
+ :parametrized: yes
|
||||
+ :steps:
|
||||
+ 1. Add a test user user
|
||||
+ 2. Set a long password with requested scheme
|
||||
+ 3. Bind on that user using a wrong password
|
||||
+ 4. Check that instance is still alive
|
||||
+ 5. Remove the added user
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should get ldap.INVALID_CREDENTIALS exception
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ # Make sure that server is started as this test may crash it
|
||||
+ inst.start()
|
||||
+ # Adding Test user (It may already exists if previous test failed)
|
||||
+ user2 = UserAccount(inst, dn='uid=test_user_1002,ou=People,dc=example,dc=com')
|
||||
+ if not user2.exists():
|
||||
+ user2 = users.create_test_user(uid=1002, gid=2002)
|
||||
+ # Setting hashed password
|
||||
+ passwd = 'A'*4000
|
||||
+ hashed_passwd = scheme.encode('utf-8') + base64.b64encode(passwd.encode('utf-8'))
|
||||
+ user2.replace('userpassword', hashed_passwd)
|
||||
+ # Bind on that user using a wrong password
|
||||
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
||||
+ conn = user2.bind(PASSWORD)
|
||||
+ # Check that instance is still alive
|
||||
+ assert inst.status()
|
||||
+ # Remove the added user
|
||||
+ user2.delete()
|
||||
+
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/ldap/servers/plugins/pwdstorage/md5_pwd.c b/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
||||
index 1e2cf58e7..b9a48d5ca 100644
|
||||
--- a/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
||||
+++ b/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
||||
@@ -37,6 +37,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
unsigned char hash_out[MD5_HASH_LEN];
|
||||
unsigned char b2a_out[MD5_HASH_LEN * 2]; /* conservative */
|
||||
SECItem binary_item;
|
||||
+ size_t dbpwd_len = strlen(dbpwd);
|
||||
|
||||
ctx = PK11_CreateDigestContext(SEC_OID_MD5);
|
||||
if (ctx == NULL) {
|
||||
@@ -45,6 +46,12 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
goto loser;
|
||||
}
|
||||
|
||||
+ if (dbpwd_len >= sizeof b2a_out) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
|
||||
+ "The hashed password stored in the user entry is longer than any valid md5 hash");
|
||||
+ goto loser;
|
||||
+ }
|
||||
+
|
||||
/* create the hash */
|
||||
PK11_DigestBegin(ctx);
|
||||
PK11_DigestOp(ctx, (const unsigned char *)userpwd, strlen(userpwd));
|
||||
@@ -57,7 +64,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
bver = NSSBase64_EncodeItem(NULL, (char *)b2a_out, sizeof b2a_out, &binary_item);
|
||||
/* bver points to b2a_out upon success */
|
||||
if (bver) {
|
||||
- rc = slapi_ct_memcmp(bver, dbpwd, strlen(dbpwd));
|
||||
+ rc = slapi_ct_memcmp(bver, dbpwd, dbpwd_len);
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
|
||||
"Could not base64 encode hashed value for password compare");
|
||||
diff --git a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
|
||||
index dcac4fcdd..82b8c9501 100644
|
||||
--- a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
|
||||
+++ b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
|
||||
@@ -255,6 +255,12 @@ pbkdf2_sha256_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
passItem.data = (unsigned char *)userpwd;
|
||||
passItem.len = strlen(userpwd);
|
||||
|
||||
+ if (pwdstorage_base64_decode_len(dbpwd, dbpwd_len) > sizeof dbhash) {
|
||||
+ /* Hashed value is too long and cannot match any value generated by pbkdf2_sha256_hash */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to base64 decode dbpwd value. (hashed value is too long)\n");
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
/* Decode the DBpwd to bytes from b64 */
|
||||
if (PL_Base64Decode(dbpwd, dbpwd_len, dbhash) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to base64 decode dbpwd value\n");
|
||||
--
|
||||
2.46.0
|
||||
|
||||
@ -1,178 +0,0 @@
|
||||
From e8a5b1deef1b455aafecb71efc029d2407b1b06f Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 16 Jul 2024 08:32:21 -0700
|
||||
Subject: [PATCH] Issue 4778 - Add COMPACT_CL5 task to dsconf replication
|
||||
(#6260)
|
||||
|
||||
Description: In 1.4.3, the changelog is not part of a backend.
|
||||
It can be compacted with nsds5task: CAMPACT_CL5 as part of the replication entry.
|
||||
Add the task as a compact-changelog command under the dsconf replication tool.
|
||||
Add tests for the feature and fix old tests.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/4778
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
.../tests/suites/config/compact_test.py | 36 ++++++++++++++---
|
||||
src/lib389/lib389/cli_conf/replication.py | 10 +++++
|
||||
src/lib389/lib389/replica.py | 40 +++++++++++++++++++
|
||||
3 files changed, 81 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/compact_test.py b/dirsrvtests/tests/suites/config/compact_test.py
|
||||
index 317258d0e..31d98d10c 100644
|
||||
--- a/dirsrvtests/tests/suites/config/compact_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/compact_test.py
|
||||
@@ -13,14 +13,14 @@ import time
|
||||
import datetime
|
||||
from lib389.tasks import DBCompactTask
|
||||
from lib389.backend import DatabaseConfig
|
||||
-from lib389.replica import Changelog5
|
||||
+from lib389.replica import Changelog5, Replicas
|
||||
from lib389.topologies import topology_m1 as topo
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def test_compact_db_task(topo):
|
||||
- """Specify a test case purpose or name here
|
||||
+ """Test compaction of database
|
||||
|
||||
:id: 1b3222ef-a336-4259-be21-6a52f76e1859
|
||||
:setup: Standalone Instance
|
||||
@@ -48,7 +48,7 @@ def test_compact_db_task(topo):
|
||||
|
||||
|
||||
def test_compaction_interval_and_time(topo):
|
||||
- """Specify a test case purpose or name here
|
||||
+ """Test compaction interval and time for database and changelog
|
||||
|
||||
:id: f361bee9-d7e7-4569-9255-d7b60dd9d92e
|
||||
:setup: Supplier Instance
|
||||
@@ -95,10 +95,36 @@ def test_compaction_interval_and_time(topo):
|
||||
|
||||
# Check compaction occurred as expected
|
||||
time.sleep(45)
|
||||
- assert not inst.searchErrorsLog("Compacting databases")
|
||||
+ assert not inst.searchErrorsLog("compacting replication changelogs")
|
||||
|
||||
time.sleep(90)
|
||||
- assert inst.searchErrorsLog("Compacting databases")
|
||||
+ assert inst.searchErrorsLog("compacting replication changelogs")
|
||||
+ inst.deleteErrorLogs(restart=False)
|
||||
+
|
||||
+
|
||||
+def test_compact_cl5_task(topo):
|
||||
+ """Test compaction of changelog5 database
|
||||
+
|
||||
+ :id: aadfa9f7-73c0-463a-912c-0a29aa1f8167
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Run compaction task
|
||||
+ 2. Check errors log to show task was run
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ """
|
||||
+ inst = topo.ms["supplier1"]
|
||||
+
|
||||
+ replicas = Replicas(inst)
|
||||
+ replicas.compact_changelog(log=log)
|
||||
+
|
||||
+ # Check compaction occurred as expected. But instead of time.sleep(5) check 1 sec in loop
|
||||
+ for _ in range(5):
|
||||
+ time.sleep(1)
|
||||
+ if inst.searchErrorsLog("compacting replication changelogs"):
|
||||
+ break
|
||||
+ assert inst.searchErrorsLog("compacting replication changelogs")
|
||||
inst.deleteErrorLogs(restart=False)
|
||||
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
|
||||
index 352c0ee5b..ccc394255 100644
|
||||
--- a/src/lib389/lib389/cli_conf/replication.py
|
||||
+++ b/src/lib389/lib389/cli_conf/replication.py
|
||||
@@ -1199,6 +1199,11 @@ def restore_cl_dir(inst, basedn, log, args):
|
||||
replicas.restore_changelog(replica_roots=args.REPLICA_ROOTS, log=log)
|
||||
|
||||
|
||||
+def compact_cl5(inst, basedn, log, args):
|
||||
+ replicas = Replicas(inst)
|
||||
+ replicas.compact_changelog(replica_roots=args.REPLICA_ROOTS, log=log)
|
||||
+
|
||||
+
|
||||
def create_parser(subparsers):
|
||||
|
||||
############################################
|
||||
@@ -1326,6 +1331,11 @@ def create_parser(subparsers):
|
||||
help="Specify one replica root whose changelog you want to restore. "
|
||||
"The replica root will be consumed from the LDIF file name if the option is omitted.")
|
||||
|
||||
+ compact_cl = repl_subcommands.add_parser('compact-changelog', help='Compact the changelog database')
|
||||
+ compact_cl.set_defaults(func=compact_cl5)
|
||||
+ compact_cl.add_argument('REPLICA_ROOTS', nargs="+",
|
||||
+ help="Specify replica roots whose changelog you want to compact.")
|
||||
+
|
||||
restore_changelogdir = restore_subcommands.add_parser('from-changelogdir', help='Restore LDIF files from changelogdir.')
|
||||
restore_changelogdir.set_defaults(func=restore_cl_dir)
|
||||
restore_changelogdir.add_argument('REPLICA_ROOTS', nargs="+",
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 94e1fdad5..1f321972d 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -1648,6 +1648,11 @@ class Replica(DSLdapObject):
|
||||
"""
|
||||
self.replace('nsds5task', 'ldif2cl')
|
||||
|
||||
+ def begin_task_compact_cl5(self):
|
||||
+ """Begin COMPACT_CL5 task
|
||||
+ """
|
||||
+ self.replace('nsds5task', 'COMPACT_CL5')
|
||||
+
|
||||
def get_suffix(self):
|
||||
"""Return the suffix
|
||||
"""
|
||||
@@ -1829,6 +1834,41 @@ class Replicas(DSLdapObjects):
|
||||
log.error(f"Changelog LDIF for '{repl_root}' was not found")
|
||||
continue
|
||||
|
||||
+ def compact_changelog(self, replica_roots=[], log=None):
|
||||
+ """Compact Directory Server replication changelog
|
||||
+
|
||||
+ :param replica_roots: Replica suffixes that need to be processed (and optional LDIF file path)
|
||||
+ :type replica_roots: list of str
|
||||
+ :param log: The logger object
|
||||
+ :type log: logger
|
||||
+ """
|
||||
+
|
||||
+ if log is None:
|
||||
+ log = self._log
|
||||
+
|
||||
+ # Check if the changelog entry exists
|
||||
+ try:
|
||||
+ cl = Changelog5(self._instance)
|
||||
+ cl.get_attr_val_utf8_l("nsslapd-changelogdir")
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ raise ValueError("Changelog entry was not found. Probably, the replication is not enabled on this instance")
|
||||
+
|
||||
+ # Get all the replicas on the server if --replica-roots option is not specified
|
||||
+ repl_roots = []
|
||||
+ if not replica_roots:
|
||||
+ for replica in self.list():
|
||||
+ repl_roots.append(replica.get_attr_val_utf8("nsDS5ReplicaRoot"))
|
||||
+ else:
|
||||
+ for repl_root in replica_roots:
|
||||
+ repl_roots.append(repl_root)
|
||||
+
|
||||
+ # Dump the changelog for the replica
|
||||
+
|
||||
+ # Dump the changelog for the replica
|
||||
+ for repl_root in repl_roots:
|
||||
+ replica = self.get(repl_root)
|
||||
+ replica.begin_task_compact_cl5()
|
||||
+
|
||||
|
||||
class BootstrapReplicationManager(DSLdapObject):
|
||||
"""A Replication Manager credential for bootstrapping the repl process.
|
||||
--
|
||||
2.47.0
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
From 0b8df3515772fbb06a422fd32239a56e244cba83 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Thu, 25 Jun 2026 10:45:21 +0100
|
||||
Subject: [PATCH 13/14] Issue 7593 - Fix testimony docstring for SASL overflow
|
||||
test (#7606)
|
||||
|
||||
Description:
|
||||
The test added in #7594 failed testimony validation because the docstring
|
||||
summary was not separated from the metadata fields with a blank line.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7593
|
||||
|
||||
Reviewed by: @progier389 (Thank you)
|
||||
---
|
||||
dirsrvtests/tests/suites/sasl/sasl_io_overflow_test.py | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/sasl/sasl_io_overflow_test.py b/dirsrvtests/tests/suites/sasl/sasl_io_overflow_test.py
|
||||
index ac1f3760f..43fc344df 100644
|
||||
--- a/dirsrvtests/tests/suites/sasl/sasl_io_overflow_test.py
|
||||
+++ b/dirsrvtests/tests/suites/sasl/sasl_io_overflow_test.py
|
||||
@@ -28,6 +28,7 @@ SASL_OVERFLOW_PAYLOAD_SIZE = 65536
|
||||
|
||||
def test_sasl_io_packet_length_overflow(topology_st):
|
||||
"""Malformed SASL length prefix must not crash the server
|
||||
+
|
||||
:id: 318f871d-2f17-461b-98ed-04cdff6ab41a
|
||||
:setup: Standalone instance
|
||||
:steps:
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
From d1cd9a5675e2953b7c8034ebb87a434cdd3ce0c3 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 2 Dec 2024 17:18:32 +0100
|
||||
Subject: [PATCH] Issue 6417 - If an entry RDN is identical to the suffix, then
|
||||
Entryrdn gets broken during a reindex (#6418)
|
||||
|
||||
Bug description:
|
||||
During a reindex, the entryrdn index is built at the end from
|
||||
each entry in the suffix.
|
||||
If one entry has a RDN that is identical to the suffix DN,
|
||||
then entryrdn_lookup_dn may erroneously return the suffix DN
|
||||
as the DN of the entry.
|
||||
|
||||
Fix description:
|
||||
When the lookup entry has no parent (because index is under
|
||||
work) the loop lookup the entry using the RDN.
|
||||
If this RDN matches the suffix DN, then it exits from the loop
|
||||
with the suffix DN.
|
||||
Before exiting it checks that the original lookup entryID
|
||||
is equal to suffix entryID. If it does not match
|
||||
the function fails and then the DN from the entry will be
|
||||
built from id2enty
|
||||
|
||||
fixes: #6417
|
||||
|
||||
Reviewed by: Pierre Rogier, Simon Pichugin (Thanks !!!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 11 ++++++++++-
|
||||
1 file changed, 10 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index 5797dd779..83b041192 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1224,7 +1224,16 @@ entryrdn_lookup_dn(backend *be,
|
||||
}
|
||||
goto bail;
|
||||
}
|
||||
- maybesuffix = 1;
|
||||
+ if (workid == 1) {
|
||||
+ /* The loop (workid) iterates from the starting 'id'
|
||||
+ * up to the suffix ID (i.e. '1').
|
||||
+ * A corner case (#6417) is if an entry, on the path
|
||||
+ * 'id' -> suffix, has the same RDN than the suffix.
|
||||
+ * In order to erroneously believe the loop hits the suffix
|
||||
+ * we need to check that 'workid' is '1' (suffix)
|
||||
+ */
|
||||
+ maybesuffix = 1;
|
||||
+ }
|
||||
} else {
|
||||
_entryrdn_cursor_print_error("entryrdn_lookup_dn",
|
||||
key.data, data.size, data.ulen, rc);
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,244 @@
|
||||
From c4e1a72eb5500b94a7d20bbfc7c0d3fd2a599cb9 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 10 Jul 2026 18:17:40 -0700
|
||||
Subject: [PATCH 14/14] Issue 7558 - Total init sends the suffix entry twice
|
||||
(#7640)
|
||||
|
||||
Description: Exclude the suffix entry from the depth-first parentid
|
||||
walk because total init sends it separately.
|
||||
Preserve NEW_IDL_NO_ALLID across all parentid fetches and honor it in
|
||||
the LMDB fetch path to maintain parent-first ordering.
|
||||
Update LMDB reindex handling so entries with an explicit parentid are
|
||||
treated as regular entries when their RDN matches the suffix.
|
||||
|
||||
Add a regression test with a wide moved subtree that verifies entry order
|
||||
and ensures the suffix is sent only once.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7558
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7604
|
||||
|
||||
Reviewed by: progier389 (Thanks!)
|
||||
---
|
||||
.../suites/replication/regression_m2_test.py | 105 +++++++++++++++++-
|
||||
.../back-ldbm/db-mdb/mdb_import_threads.c | 10 ++
|
||||
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/idl_new.c | 25 +++--
|
||||
4 files changed, 131 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index db5140b0b..11f3a1fc0 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -19,6 +19,7 @@ import time
|
||||
import random
|
||||
import string
|
||||
from shutil import rmtree
|
||||
+from lib389.backend import DatabaseConfig
|
||||
from lib389.dbgen import dbgen_users
|
||||
from lib389.idm.user import TEST_USER_PROPERTIES, UserAccount, UserAccounts
|
||||
from lib389.pwpolicy import PwPolicyManager
|
||||
@@ -1321,8 +1322,108 @@ def test_get_with_normalized_rid_dict():
|
||||
assert nrd.get('099') is None
|
||||
|
||||
|
||||
-@pytest.mark.ds49915
|
||||
-@pytest.mark.bz1626375
|
||||
+def test_online_init_no_duplicate_suffix(topo_m2, request):
|
||||
+ """Total init must preserve tree order without sending the suffix twice
|
||||
+
|
||||
+ :id: f0ccfb02-6c9d-48dd-9482-8a2c0763d485
|
||||
+ :setup: Two suppliers replication setup
|
||||
+ :steps:
|
||||
+ 1. Add a parent with more direct children than the ID list scan limit
|
||||
+ 2. Move the subtree below a newer top-level ancestor
|
||||
+ 3. Lower the ID list scan limit on supplier1
|
||||
+ 4. Perform online initialization from supplier1 to supplier2
|
||||
+ 5. Search supplier2 for entries with the suffix DN
|
||||
+ 6. Compare the number of entries on both suppliers
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Exactly one entry has the suffix DN
|
||||
+ 6. Both suppliers have the same number of entries
|
||||
+ """
|
||||
+ m1 = topo_m2.ms["supplier1"]
|
||||
+ m2 = topo_m2.ms["supplier2"]
|
||||
+
|
||||
+ scan_limit = 100
|
||||
+ parent_rdn = 'ou=total-init-scanlimit'
|
||||
+ ancestor_rdn = 'ou=total-init-ancestor'
|
||||
+ original_parent_dn = f'{parent_rdn},{DEFAULT_SUFFIX}'
|
||||
+ ancestor_dn = f'{ancestor_rdn},{DEFAULT_SUFFIX}'
|
||||
+ moved_parent_dn = f'{parent_rdn},{ancestor_dn}'
|
||||
+ db_config = DatabaseConfig(m1)
|
||||
+ original_scan_limit = db_config.get_attr_vals_utf8('nsslapd-idlistscanlimit')
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+
|
||||
+ def delete_test_subtrees(supplier):
|
||||
+ deleted = False
|
||||
+ for dn in (moved_parent_dn, original_parent_dn, ancestor_dn):
|
||||
+ try:
|
||||
+ supplier.delete_branch_s(dn, ldap.SCOPE_SUBTREE)
|
||||
+ deleted = True
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+ return deleted
|
||||
+
|
||||
+ def fin():
|
||||
+ for supplier in (m1, m2):
|
||||
+ if not supplier.status():
|
||||
+ supplier.start()
|
||||
+ db_config.set([('nsslapd-idlistscanlimit', original_scan_limit)])
|
||||
+ try:
|
||||
+ if delete_test_subtrees(m1):
|
||||
+ repl.wait_for_replication(m1, m2)
|
||||
+ else:
|
||||
+ delete_test_subtrees(m2)
|
||||
+ except Exception as err:
|
||||
+ log.warning("Replication cleanup failed, deleting supplier2 entries directly: %s", err)
|
||||
+ delete_test_subtrees(m2)
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ test_parent = OrganizationalUnits(m1, DEFAULT_SUFFIX).create(
|
||||
+ properties={'ou': 'total-init-scanlimit'}
|
||||
+ )
|
||||
+ # Force the parentid index lookup for this parent past the ALLIDS threshold.
|
||||
+ test_children = OrganizationalUnits(m1, test_parent.dn)
|
||||
+ for idx in range(scan_limit + 1):
|
||||
+ last_child = test_children.create(properties={'ou': f'child{idx}'})
|
||||
+
|
||||
+ test_ancestor = OrganizationalUnits(m1, DEFAULT_SUFFIX).create(
|
||||
+ properties={'ou': 'total-init-ancestor'}
|
||||
+ )
|
||||
+ assert int(test_ancestor.get_attr_val_utf8('entryid')) > int(
|
||||
+ last_child.get_attr_val_utf8('entryid')
|
||||
+ )
|
||||
+ test_parent.rename(parent_rdn, newsuperior=test_ancestor.dn)
|
||||
+ assert test_parent.dn.lower() == moved_parent_dn.lower()
|
||||
+ assert repl.wait_for_replication(m1, m2)
|
||||
+
|
||||
+ db_config.set([('nsslapd-idlistscanlimit', str(scan_limit))])
|
||||
+ assert db_config.get_attr_val_utf8('nsslapd-idlistscanlimit') == str(scan_limit)
|
||||
+
|
||||
+ agmt = Agreements(m1).list()[0]
|
||||
+ agmt.begin_reinit()
|
||||
+ (done, error) = agmt.wait_reinit()
|
||||
+ assert done is True
|
||||
+ assert error is False
|
||||
+
|
||||
+ # The consumer used to store the suffix entry twice: the supplier sent
|
||||
+ # it explicitly and again as part of the bulk import candidate list
|
||||
+ filter_all = '(|(objectclass=ldapsubentry)(objectclass=nstombstone)(nsuniqueid=*))'
|
||||
+ m2entries = m2.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, filter_all,
|
||||
+ escapehatch='i am sure')
|
||||
+ suffix_entries = [e for e in m2entries if e.dn.lower() == DEFAULT_SUFFIX.lower()]
|
||||
+ log.info("%d entries with the suffix DN found on supplier2", len(suffix_entries))
|
||||
+ assert len(suffix_entries) == 1
|
||||
+
|
||||
+ m1entries = m1.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, filter_all,
|
||||
+ escapehatch='i am sure')
|
||||
+ log.info("supplier1 has %d entries, supplier2 has %d entries",
|
||||
+ len(m1entries), len(m2entries))
|
||||
+ assert len(m1entries) == len(m2entries)
|
||||
+
|
||||
+
|
||||
def test_online_reinit_may_hang(topo_with_sigkill):
|
||||
"""Online reinitialization may hang when the first
|
||||
entry of the DB is RUV entry instead of the suffix
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
index c49412a70..a8027a28d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
@@ -856,6 +856,16 @@ dbmdb_import_entry_info_by_param(EntryInfoParam_t *param, WorkerQueueData_t *wqe
|
||||
}
|
||||
|
||||
dnrc = get_entry_type(wqelmt, ¶m->sdn);
|
||||
+ if (dnrc == DNRC_BAD_SUFFIX_ID && (param->flags & EIP_RDN)) {
|
||||
+ /* In reindex mode the sdn contains only the RDN. A non-root record
|
||||
+ * with an explicit parentid is therefore a regular entry even when
|
||||
+ * its RDN equals a one-RDN suffix. */
|
||||
+ char *pidstr = NULL;
|
||||
+ if (get_value_from_string(wqelmt->data, "parentid", &pidstr) == 0) {
|
||||
+ slapi_ch_free_string(&pidstr);
|
||||
+ dnrc = DNRC_OK;
|
||||
+ }
|
||||
+ }
|
||||
if (dnrc == DNRC_SUFFIX) {
|
||||
if ( param->eid != 1) {
|
||||
dnrc = DNRC_BAD_SUFFIX_ID;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
index 0b4a09026..6ddb1c43e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
@@ -2929,7 +2929,7 @@ dbmdb_idl_new_fetch(backend *be, dbi_db_t *db, dbi_val_t *inkey, dbi_txn_t *txn,
|
||||
}
|
||||
}
|
||||
|
||||
- if (allidslimit && count >= allidslimit) {
|
||||
+ if ((NEW_IDL_NO_ALLID != *flag_err) && allidslimit && count >= allidslimit) {
|
||||
idl = idl_allids(be);
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "dbmdb_idl_new_fetch", "%s returns allids (attribute: %s)\n",
|
||||
(char *)key.mv_data, index_id);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
index 29457500e..0fa48ea5f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
@@ -468,14 +468,14 @@ idl_parentid_walk_push(ID **stack, size_t *stack_size, size_t *stack_top, ID id)
|
||||
/*
|
||||
* Walk the parentid index depth-first starting at root_id.
|
||||
* For each parent, look up index key "=parent_id" (direct get, not cursor next),
|
||||
- * append the parent to the IDList, then visit each child the same way.
|
||||
+ * append each descendant (but not root_id) to the IDList, then visit its children.
|
||||
*/
|
||||
static int
|
||||
idl_parentid_walk_tree(idl_parentid_walk_ctx_t *ctx, ID root_id, ID **stack,
|
||||
size_t *stack_size, size_t *stack_top)
|
||||
{
|
||||
IDList *children = NULL;
|
||||
- int fetch_err = NEW_IDL_NO_ALLID;
|
||||
+ int fetch_err = 0;
|
||||
size_t i;
|
||||
|
||||
if (idl_parentid_walk_push(stack, stack_size, stack_top, root_id) != 0) {
|
||||
@@ -489,13 +489,18 @@ idl_parentid_walk_tree(idl_parentid_walk_ctx_t *ctx, ID root_id, ID **stack,
|
||||
return -1;
|
||||
}
|
||||
|
||||
- if (idl_append_extend(&ctx->idl, parent_id) != 0) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "idl_new_parentid_sorted_range_fetch",
|
||||
- "Unable to extend id list for attribute (%s)\n", ctx->index_id);
|
||||
- idl_free(&ctx->idl);
|
||||
- return -1;
|
||||
+ if (parent_id != root_id) {
|
||||
+ /* The suffix entry itself is not a candidate: bulk import
|
||||
+ * (total init) sends it separately before walking the tree,
|
||||
+ * and the pre-walk implementation never returned it either. */
|
||||
+ if (idl_append_extend(&ctx->idl, parent_id) != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "idl_new_parentid_sorted_range_fetch",
|
||||
+ "Unable to extend id list for attribute (%s)\n", ctx->index_id);
|
||||
+ idl_free(&ctx->idl);
|
||||
+ return -1;
|
||||
+ }
|
||||
+ ctx->count++;
|
||||
}
|
||||
- ctx->count++;
|
||||
|
||||
#if defined(DB_ALLIDS_ON_READ)
|
||||
if ((NEW_IDL_NO_ALLID != *(ctx->flag_err)) && ctx->ai && (ctx->idl != NULL) &&
|
||||
@@ -506,6 +511,10 @@ idl_parentid_walk_tree(idl_parentid_walk_ctx_t *ctx, ID root_id, ID **stack,
|
||||
}
|
||||
#endif
|
||||
idl_parentid_set_index_key(ctx, parent_id);
|
||||
+ /* idl_fetch_ext resets *err to 0 on success, so re-arm the
|
||||
+ * no-allids hint for every fetch: collapsing one parent's
|
||||
+ * children to ALLIDS would collapse the whole candidate list. */
|
||||
+ fetch_err = NEW_IDL_NO_ALLID;
|
||||
children = idl_fetch_ext(ctx->be, ctx->db, &ctx->key, ctx->txn, ctx->ai,
|
||||
&fetch_err, ctx->allidslimit);
|
||||
if (fetch_err != 0 && fetch_err != DBI_RC_NOTFOUND) {
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,267 +0,0 @@
|
||||
From 9b2fc77a36156ea987dcea6e2043f8e4c4a6b259 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 18 Jun 2024 14:21:07 +0200
|
||||
Subject: [PATCH] Issue 6224 - d2entry - Could not open id2entry err 0 - at
|
||||
startup when having sub-suffixes (#6225)
|
||||
|
||||
Problem:: d2entry - Could not open id2entry err 0 is logged at startup when having sub-suffixes
|
||||
Reason: The slapi_exist_referral internal search access a backend that is not yet started.
|
||||
Solution: Limit the internal search to a single backend
|
||||
|
||||
Issue: #6224
|
||||
|
||||
Reviewed by: @droideck Thanks!
|
||||
|
||||
(cherry picked from commit 796f703021e961fdd8cbc53b4ad4e20258af0e96)
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 1 +
|
||||
.../suites/mapping_tree/regression_test.py | 161 +++++++++++++++++-
|
||||
ldap/servers/slapd/backend.c | 7 +-
|
||||
3 files changed, 159 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 812936c62..84a9c6ec8 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -1222,6 +1222,7 @@ def test_referral_check(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+<<<<<<< HEAD
|
||||
def test_referral_subsuffix(topology_st, request):
|
||||
"""Test the results of an inverted parent suffix definition in the configuration.
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/mapping_tree/regression_test.py b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
index 99d4a1d5f..689ff9f59 100644
|
||||
--- a/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
@@ -11,10 +11,14 @@ import ldap
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
+import time
|
||||
from lib389.backend import Backends, Backend
|
||||
+from lib389._constants import HOST_STANDALONE, PORT_STANDALONE, DN_DM, PW_DM
|
||||
from lib389.dbgen import dbgen_users
|
||||
from lib389.mappingTree import MappingTrees
|
||||
from lib389.topologies import topology_st
|
||||
+from lib389.referral import Referrals, Referral
|
||||
+
|
||||
|
||||
try:
|
||||
from lib389.backend import BackendSuffixView
|
||||
@@ -31,14 +35,26 @@ else:
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
+PARENT_SUFFIX = "dc=parent"
|
||||
+CHILD1_SUFFIX = f"dc=child1,{PARENT_SUFFIX}"
|
||||
+CHILD2_SUFFIX = f"dc=child2,{PARENT_SUFFIX}"
|
||||
+
|
||||
+PARENT_REFERRAL_DN = f"cn=ref,ou=People,{PARENT_SUFFIX}"
|
||||
+CHILD1_REFERRAL_DN = f"cn=ref,ou=people,{CHILD1_SUFFIX}"
|
||||
+CHILD2_REFERRAL_DN = f"cn=ref,ou=people,{CHILD2_SUFFIX}"
|
||||
+
|
||||
+REFERRAL_CHECK_PEDIOD = 7
|
||||
+
|
||||
+
|
||||
+
|
||||
BESTRUCT = [
|
||||
- { "bename" : "parent", "suffix": "dc=parent" },
|
||||
- { "bename" : "child1", "suffix": "dc=child1,dc=parent" },
|
||||
- { "bename" : "child2", "suffix": "dc=child2,dc=parent" },
|
||||
+ { "bename" : "parent", "suffix": PARENT_SUFFIX },
|
||||
+ { "bename" : "child1", "suffix": CHILD1_SUFFIX },
|
||||
+ { "bename" : "child2", "suffix": CHILD2_SUFFIX },
|
||||
]
|
||||
|
||||
|
||||
-@pytest.fixture(scope="function")
|
||||
+@pytest.fixture(scope="module")
|
||||
def topo(topology_st, request):
|
||||
bes = []
|
||||
|
||||
@@ -50,6 +66,9 @@ def topo(topology_st, request):
|
||||
request.addfinalizer(fin)
|
||||
|
||||
inst = topology_st.standalone
|
||||
+ # Reduce nsslapd-referral-check-period to accelerate test
|
||||
+ topology_st.standalone.config.set("nsslapd-referral-check-period", str(REFERRAL_CHECK_PEDIOD))
|
||||
+
|
||||
ldif_files = {}
|
||||
for d in BESTRUCT:
|
||||
bename = d['bename']
|
||||
@@ -76,14 +95,13 @@ def topo(topology_st, request):
|
||||
inst.start()
|
||||
return topology_st
|
||||
|
||||
-# Parameters for test_change_repl_passwd
|
||||
-EXPECTED_ENTRIES = (("dc=parent", 39), ("dc=child1,dc=parent", 13), ("dc=child2,dc=parent", 13))
|
||||
+# Parameters for test_sub_suffixes
|
||||
@pytest.mark.parametrize(
|
||||
"orphan_param",
|
||||
[
|
||||
- pytest.param( ( True, { "dc=parent": 2, "dc=child1,dc=parent":1, "dc=child2,dc=parent":1}), id="orphan-is-true" ),
|
||||
- pytest.param( ( False, { "dc=parent": 3, "dc=child1,dc=parent":1, "dc=child2,dc=parent":1}), id="orphan-is-false" ),
|
||||
- pytest.param( ( None, { "dc=parent": 3, "dc=child1,dc=parent":1, "dc=child2,dc=parent":1}), id="no-orphan" ),
|
||||
+ pytest.param( ( True, { PARENT_SUFFIX: 2, CHILD1_SUFFIX:1, CHILD2_SUFFIX:1}), id="orphan-is-true" ),
|
||||
+ pytest.param( ( False, { PARENT_SUFFIX: 3, CHILD1_SUFFIX:1, CHILD2_SUFFIX:1}), id="orphan-is-false" ),
|
||||
+ pytest.param( ( None, { PARENT_SUFFIX: 3, CHILD1_SUFFIX:1, CHILD2_SUFFIX:1}), id="no-orphan" ),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -128,3 +146,128 @@ def test_sub_suffixes(topo, orphan_param):
|
||||
log.info('Test PASSED')
|
||||
|
||||
|
||||
+def test_one_level_search_on_sub_suffixes(topo):
|
||||
+ """ Perform one level scoped search accross suffix and sub-suffix
|
||||
+
|
||||
+ :id: 92f3139e-280e-11ef-a989-482ae39447e5
|
||||
+ :feature: mapping-tree
|
||||
+ :setup: Standalone instance with 3 additional backends:
|
||||
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
|
||||
+ :steps:
|
||||
+ 1. Perform a ONE LEVEL search on dc=parent
|
||||
+ 2. Check that all expected entries have been returned
|
||||
+ 3. Check that only the expected entries have been returned
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. each expected dn should be in the result set
|
||||
+ 3. Number of returned entries should be the same as the number of expected entries
|
||||
+ """
|
||||
+ expected_dns = ( 'dc=child1,dc=parent',
|
||||
+ 'dc=child2,dc=parent',
|
||||
+ 'ou=accounting,dc=parent',
|
||||
+ 'ou=product development,dc=parent',
|
||||
+ 'ou=product testing,dc=parent',
|
||||
+ 'ou=human resources,dc=parent',
|
||||
+ 'ou=payroll,dc=parent',
|
||||
+ 'ou=people,dc=parent',
|
||||
+ 'ou=groups,dc=parent', )
|
||||
+ entries = topo.standalone.search_s("dc=parent", ldap.SCOPE_ONELEVEL, "(objectClass=*)",
|
||||
+ attrlist=("dc","ou"), escapehatch='i am sure')
|
||||
+ log.info(f'one level search on dc=parent returned the following entries: {entries}')
|
||||
+ dns = [ entry.dn for entry in entries ]
|
||||
+ for dn in expected_dns:
|
||||
+ assert dn in dns
|
||||
+ assert len(entries) == len(expected_dns)
|
||||
+
|
||||
+
|
||||
+def test_sub_suffixes_errlog(topo):
|
||||
+ """ check the entries found on suffix/sub-suffix
|
||||
+ used int
|
||||
+
|
||||
+ :id: 1db9d52e-28de-11ef-b286-482ae39447e5
|
||||
+ :feature: mapping-tree
|
||||
+ :setup: Standalone instance with 3 additional backends:
|
||||
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
|
||||
+ :steps:
|
||||
+ 1. Check that id2entry error message is not in the error log.
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ assert not inst.searchErrorsLog('id2entry - Could not open id2entry err 0')
|
||||
+
|
||||
+
|
||||
+# Parameters for test_referral_subsuffix:
|
||||
+# a tuple pair containing:
|
||||
+# - list of referral dn that must be created
|
||||
+# - dict of searches basedn: expected_number_of_referrals
|
||||
+@pytest.mark.parametrize(
|
||||
+ "parameters",
|
||||
+ [
|
||||
+ pytest.param( ((PARENT_REFERRAL_DN, CHILD1_REFERRAL_DN), {PARENT_SUFFIX: 2, CHILD1_SUFFIX:1, CHILD2_SUFFIX:0}), id="Both"),
|
||||
+ pytest.param( ((PARENT_REFERRAL_DN,), {PARENT_SUFFIX: 1, CHILD1_SUFFIX:0, CHILD2_SUFFIX:0}) , id="Parent"),
|
||||
+ pytest.param( ((CHILD1_REFERRAL_DN,), {PARENT_SUFFIX: 1, CHILD1_SUFFIX:1, CHILD2_SUFFIX:0}) , id="Child"),
|
||||
+ pytest.param( ((), {PARENT_SUFFIX: 0, CHILD1_SUFFIX:0, CHILD2_SUFFIX:0}), id="None"),
|
||||
+ ])
|
||||
+
|
||||
+def test_referral_subsuffix(topo, request, parameters):
|
||||
+ """Test the results of an inverted parent suffix definition in the configuration.
|
||||
+
|
||||
+ For more details see:
|
||||
+ https://www.port389.org/docs/389ds/design/mapping_tree_assembly.html
|
||||
+
|
||||
+ :id: 4e111a22-2a5d-11ef-a890-482ae39447e5
|
||||
+ :feature: referrals
|
||||
+ :setup: Standalone instance with 3 additional backends:
|
||||
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+ :parametrized: yes
|
||||
+ :steps:
|
||||
+ refs,searches = referrals
|
||||
+
|
||||
+ 1. Create the referrals according to the current parameter
|
||||
+ 2. Wait enough time so they get detected
|
||||
+ 3. For each search base dn, in the current parameter, perform the two following steps
|
||||
+ 4. In 3. loop: Perform a search with provided base dn
|
||||
+ 5. In 3. loop: Check that the number of returned referrals is the expected one.
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ all steps succeeds
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting all referrals')
|
||||
+ for ref in Referrals(inst, PARENT_SUFFIX).list():
|
||||
+ ref.delete()
|
||||
+
|
||||
+ # Set cleanup callback
|
||||
+ if DEBUGGING:
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Remove all referrals
|
||||
+ fin()
|
||||
+ # Add requested referrals
|
||||
+ for dn in parameters[0]:
|
||||
+ refs = Referral(inst, dn=dn)
|
||||
+ refs.create(basedn=dn, properties={ 'cn': 'ref', 'ref': f'ldap://remote/{dn}'})
|
||||
+ # Wait that the internal search detects the referrals
|
||||
+ time.sleep(REFERRAL_CHECK_PEDIOD + 1)
|
||||
+ # Open a test connection
|
||||
+ ldc = ldap.initialize(f"ldap://{HOST_STANDALONE}:{PORT_STANDALONE}")
|
||||
+ ldc.set_option(ldap.OPT_REFERRALS,0)
|
||||
+ ldc.simple_bind_s(DN_DM,PW_DM)
|
||||
+
|
||||
+ # For each search base dn:
|
||||
+ for basedn,nbref in parameters[1].items():
|
||||
+ log.info(f"Referrals are: {parameters[0]}")
|
||||
+ # Perform a search with provided base dn
|
||||
+ result = ldc.search_s(basedn, ldap.SCOPE_SUBTREE, filterstr="(ou=People)")
|
||||
+ found_dns = [ dn for dn,entry in result if dn is not None ]
|
||||
+ found_refs = [ entry for dn,entry in result if dn is None ]
|
||||
+ log.info(f"Search on {basedn} returned {found_dns} and {found_refs}")
|
||||
+ # Check that the number of returned referrals is the expected one.
|
||||
+ log.info(f"Search returned {len(found_refs)} referrals. {nbref} are expected.")
|
||||
+ assert len(found_refs) == nbref
|
||||
+ ldc.unbind()
|
||||
diff --git a/ldap/servers/slapd/backend.c b/ldap/servers/slapd/backend.c
|
||||
index 498f683b1..f86b0b9b6 100644
|
||||
--- a/ldap/servers/slapd/backend.c
|
||||
+++ b/ldap/servers/slapd/backend.c
|
||||
@@ -230,12 +230,17 @@ slapi_exist_referral(Slapi_Backend *be)
|
||||
|
||||
/* search for ("smart") referral entries */
|
||||
search_pb = slapi_pblock_new();
|
||||
- server_ctrls = (LDAPControl **) slapi_ch_calloc(2, sizeof (LDAPControl *));
|
||||
+ server_ctrls = (LDAPControl **) slapi_ch_calloc(3, sizeof (LDAPControl *));
|
||||
server_ctrls[0] = (LDAPControl *) slapi_ch_malloc(sizeof (LDAPControl));
|
||||
server_ctrls[0]->ldctl_oid = slapi_ch_strdup(LDAP_CONTROL_MANAGEDSAIT);
|
||||
server_ctrls[0]->ldctl_value.bv_val = NULL;
|
||||
server_ctrls[0]->ldctl_value.bv_len = 0;
|
||||
server_ctrls[0]->ldctl_iscritical = '\0';
|
||||
+ server_ctrls[1] = (LDAPControl *) slapi_ch_malloc(sizeof (LDAPControl));
|
||||
+ server_ctrls[1]->ldctl_oid = slapi_ch_strdup(MTN_CONTROL_USE_ONE_BACKEND_EXT_OID);
|
||||
+ server_ctrls[1]->ldctl_value.bv_val = NULL;
|
||||
+ server_ctrls[1]->ldctl_value.bv_len = 0;
|
||||
+ server_ctrls[1]->ldctl_iscritical = '\0';
|
||||
slapi_search_internal_set_pb(search_pb, suffix, LDAP_SCOPE_SUBTREE,
|
||||
filter, NULL, 0, server_ctrls, NULL,
|
||||
(void *) plugin_get_default_component_id(), 0);
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
From ab06b3cebbe0287ef557c0307ca2ee86fe8cb761 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Thu, 21 Nov 2024 16:26:02 +0100
|
||||
Subject: [PATCH] Issue 6224 - Fix merge issue in 389-ds-base-2.1 for
|
||||
ds_log_test.py (#6414)
|
||||
|
||||
Fix a merge issue during cherry-pick over 389-ds-base-2.1 and 389-ds-base-1.4.3 branches
|
||||
|
||||
Issue: #6224
|
||||
|
||||
Reviewed by: @mreynolds389
|
||||
|
||||
(cherry picked from commit 2b541c64b8317209e4dafa4f82918d714039907c)
|
||||
---
|
||||
dirsrvtests/tests/suites/ds_logs/ds_logs_test.py | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 84a9c6ec8..812936c62 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -1222,7 +1222,6 @@ def test_referral_check(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
-<<<<<<< HEAD
|
||||
def test_referral_subsuffix(topology_st, request):
|
||||
"""Test the results of an inverted parent suffix definition in the configuration.
|
||||
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,214 +0,0 @@
|
||||
From 3fe2cf7cdedcdf5cafb59867e52a1fbe4a643571 Mon Sep 17 00:00:00 2001
|
||||
From: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
Date: Fri, 20 Dec 2024 22:37:15 +0900
|
||||
Subject: [PATCH] Issue 6224 - Remove test_referral_subsuffix from
|
||||
ds_logs_test.py (#6456)
|
||||
|
||||
Bug Description:
|
||||
|
||||
test_referral_subsuffix test was removed from main branch and some other
|
||||
ones for higher versions. But, it was not removed from 389-ds-base-1.4.3
|
||||
and 389-ds-base-2.1. The test doesn't work anymore with the fix for
|
||||
Issue 6224, because the added new control limited one backend for internal
|
||||
search. The test should be removed.
|
||||
|
||||
Fix Description:
|
||||
|
||||
remove the test from ds_logs_test.py
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6224
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 177 ------------------
|
||||
1 file changed, 177 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 812936c62..84d721756 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -1222,183 +1222,6 @@ def test_referral_check(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
-def test_referral_subsuffix(topology_st, request):
|
||||
- """Test the results of an inverted parent suffix definition in the configuration.
|
||||
-
|
||||
- For more details see:
|
||||
- https://www.port389.org/docs/389ds/design/mapping_tree_assembly.html
|
||||
-
|
||||
- :id: 4faf210a-4fde-4e4f-8834-865bdc8f4d37
|
||||
- :setup: Standalone instance
|
||||
- :steps:
|
||||
- 1. First create two Backends, without mapping trees.
|
||||
- 2. create the mapping trees for these backends
|
||||
- 3. reduce nsslapd-referral-check-period to accelerate test
|
||||
- 4. Remove error log file
|
||||
- 5. Create a referral entry on parent suffix
|
||||
- 6. Check that the server detected the referral
|
||||
- 7. Delete the referral entry
|
||||
- 8. Check that the server detected the deletion of the referral
|
||||
- 9. Remove error log file
|
||||
- 10. Create a referral entry on child suffix
|
||||
- 11. Check that the server detected the referral on both parent and child suffixes
|
||||
- 12. Delete the referral entry
|
||||
- 13. Check that the server detected the deletion of the referral on both parent and child suffixes
|
||||
- 14. Remove error log file
|
||||
- 15. Create a referral entry on parent suffix
|
||||
- 16. Check that the server detected the referral on both parent and child suffixes
|
||||
- 17. Delete the child referral entry
|
||||
- 18. Check that the server detected the deletion of the referral on child suffix but not on parent suffix
|
||||
- 19. Delete the parent referral entry
|
||||
- 20. Check that the server detected the deletion of the referral parent suffix
|
||||
-
|
||||
- :expectedresults:
|
||||
- all steps succeeds
|
||||
- """
|
||||
- inst = topology_st.standalone
|
||||
- # Step 1 First create two Backends, without mapping trees.
|
||||
- PARENT_SUFFIX='dc=parent,dc=com'
|
||||
- CHILD_SUFFIX='dc=child,%s' % PARENT_SUFFIX
|
||||
- be1 = create_backend(inst, 'Parent', PARENT_SUFFIX)
|
||||
- be2 = create_backend(inst, 'Child', CHILD_SUFFIX)
|
||||
- # Step 2 create the mapping trees for these backends
|
||||
- mts = MappingTrees(inst)
|
||||
- mt1 = mts.create(properties={
|
||||
- 'cn': PARENT_SUFFIX,
|
||||
- 'nsslapd-state': 'backend',
|
||||
- 'nsslapd-backend': 'Parent',
|
||||
- })
|
||||
- mt2 = mts.create(properties={
|
||||
- 'cn': CHILD_SUFFIX,
|
||||
- 'nsslapd-state': 'backend',
|
||||
- 'nsslapd-backend': 'Child',
|
||||
- 'nsslapd-parent-suffix': PARENT_SUFFIX,
|
||||
- })
|
||||
-
|
||||
- dc_ex = Domain(inst, dn=PARENT_SUFFIX)
|
||||
- assert dc_ex.exists()
|
||||
-
|
||||
- dc_st = Domain(inst, dn=CHILD_SUFFIX)
|
||||
- assert dc_st.exists()
|
||||
-
|
||||
- # Step 3 reduce nsslapd-referral-check-period to accelerate test
|
||||
- # requires a restart done on step 4
|
||||
- REFERRAL_CHECK=7
|
||||
- topology_st.standalone.config.set("nsslapd-referral-check-period", str(REFERRAL_CHECK))
|
||||
-
|
||||
- # Check that if we create a referral at parent level
|
||||
- # - referral is detected at parent backend
|
||||
- # - referral is not detected at child backend
|
||||
-
|
||||
- # Step 3 Remove error log file
|
||||
- topology_st.standalone.stop()
|
||||
- lpath = topology_st.standalone.ds_error_log._get_log_path()
|
||||
- os.unlink(lpath)
|
||||
- topology_st.standalone.start()
|
||||
-
|
||||
- # Step 4 Create a referral entry on parent suffix
|
||||
- rs_parent = Referrals(topology_st.standalone, PARENT_SUFFIX)
|
||||
-
|
||||
- referral_entry_parent = rs_parent.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
-
|
||||
- # Step 5 Check that the server detected the referral
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % PARENT_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- # Step 6 Delete the referral entry
|
||||
- referral_entry_parent.delete()
|
||||
-
|
||||
- # Step 7 Check that the server detected the deletion of the referral
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- # Check that if we create a referral at child level
|
||||
- # - referral is detected at parent backend
|
||||
- # - referral is detected at child backend
|
||||
-
|
||||
- # Step 8 Remove error log file
|
||||
- topology_st.standalone.stop()
|
||||
- lpath = topology_st.standalone.ds_error_log._get_log_path()
|
||||
- os.unlink(lpath)
|
||||
- topology_st.standalone.start()
|
||||
-
|
||||
- # Step 9 Create a referral entry on child suffix
|
||||
- rs_child = Referrals(topology_st.standalone, CHILD_SUFFIX)
|
||||
- referral_entry_child = rs_child.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
-
|
||||
- # Step 10 Check that the server detected the referral on both parent and child suffixes
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % PARENT_SUFFIX)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
-
|
||||
- # Step 11 Delete the referral entry
|
||||
- referral_entry_child.delete()
|
||||
-
|
||||
- # Step 12 Check that the server detected the deletion of the referral on both parent and child suffixes
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
-
|
||||
- # Check that if we create a referral at child level and parent level
|
||||
- # - referral is detected at parent backend
|
||||
- # - referral is detected at child backend
|
||||
-
|
||||
- # Step 13 Remove error log file
|
||||
- topology_st.standalone.stop()
|
||||
- lpath = topology_st.standalone.ds_error_log._get_log_path()
|
||||
- os.unlink(lpath)
|
||||
- topology_st.standalone.start()
|
||||
-
|
||||
- # Step 14 Create a referral entry on parent suffix
|
||||
- # Create a referral entry on child suffix
|
||||
- referral_entry_parent = rs_parent.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
- referral_entry_child = rs_child.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
-
|
||||
- # Step 15 Check that the server detected the referral on both parent and child suffixes
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % PARENT_SUFFIX)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
-
|
||||
- # Step 16 Delete the child referral entry
|
||||
- referral_entry_child.delete()
|
||||
-
|
||||
- # Step 17 Check that the server detected the deletion of the referral on child suffix but not on parent suffix
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- # Step 18 Delete the parent referral entry
|
||||
- referral_entry_parent.delete()
|
||||
-
|
||||
- # Step 19 Check that the server detected the deletion of the referral parent suffix
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- def fin():
|
||||
- log.info('Deleting referral')
|
||||
- try:
|
||||
- referral_entry_parent.delete()
|
||||
- referral.entry_child.delete()
|
||||
- except:
|
||||
- pass
|
||||
-
|
||||
- request.addfinalizer(fin)
|
||||
|
||||
def test_missing_backend_suffix(topology_st, request):
|
||||
"""Test that the server does not crash if a backend has no suffix
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
From 4121ffe7a44fbacf513758661e71e483eb11ee3c Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 6 Jan 2025 14:00:39 +0100
|
||||
Subject: [PATCH] Issue 6417 - (2nd) If an entry RDN is identical to the
|
||||
suffix, then Entryrdn gets broken during a reindex (#6460)
|
||||
|
||||
Bug description:
|
||||
The primary fix has a flaw as it assumes that the
|
||||
suffix ID is '1'.
|
||||
If the RUV entry is the first entry of the database
|
||||
the server loops indefinitely
|
||||
|
||||
Fix description:
|
||||
Read the suffix ID from the entryrdn index
|
||||
|
||||
fixes: #6417
|
||||
|
||||
Reviewed by: Pierre Rogier (also reviewed the first fix)
|
||||
---
|
||||
.../suites/replication/regression_m2_test.py | 9 +++++++++
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 19 ++++++++++++++++++-
|
||||
2 files changed, 27 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index abac46ada..72d4b9f89 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -1010,6 +1010,15 @@ def test_online_reinit_may_hang(topo_with_sigkill):
|
||||
"""
|
||||
M1 = topo_with_sigkill.ms["supplier1"]
|
||||
M2 = topo_with_sigkill.ms["supplier2"]
|
||||
+
|
||||
+ # The RFE 5367 (when enabled) retrieves the DN
|
||||
+ # from the dncache. This hides an issue
|
||||
+ # with primary fix for 6417.
|
||||
+ # We need to disable the RFE to verify that the primary
|
||||
+ # fix is properly fixed.
|
||||
+ if ds_is_newer('2.3.1'):
|
||||
+ M1.config.replace('nsslapd-return-original-entrydn', 'off')
|
||||
+
|
||||
M1.stop()
|
||||
ldif_file = '%s/supplier1.ldif' % M1.get_ldif_dir()
|
||||
M1.db2ldif(bename=DEFAULT_BENAME, suffixes=[DEFAULT_SUFFIX],
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index 83b041192..1bbb6252a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1115,6 +1115,7 @@ entryrdn_lookup_dn(backend *be,
|
||||
rdn_elem *elem = NULL;
|
||||
int maybesuffix = 0;
|
||||
int db_retry = 0;
|
||||
+ ID suffix_id = 1;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "entryrdn_lookup_dn",
|
||||
"--> entryrdn_lookup_dn\n");
|
||||
@@ -1175,6 +1176,22 @@ entryrdn_lookup_dn(backend *be,
|
||||
/* Setting the bulk fetch buffer */
|
||||
data.flags = DB_DBT_MALLOC;
|
||||
|
||||
+ /* Just in case the suffix ID is not '1' retrieve it from the database */
|
||||
+ keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
+ dblayer_value_set(be, &key, keybuf, strlen(keybuf) + 1);
|
||||
+ rc = dblayer_cursor_op(&ctx.cursor, DBI_OP_MOVE_TO_KEY, &key, &data);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
+ "Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
+ slapi_sdn_get_ndn(be->be_suffix),
|
||||
+ suffix_id);
|
||||
+ } else {
|
||||
+ elem = (rdn_elem *)data.data;
|
||||
+ suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
+ }
|
||||
+ dblayer_value_free(be, &data);
|
||||
+ dblayer_value_free(be, &key);
|
||||
+
|
||||
do {
|
||||
/* Setting up a key for the node to get its parent */
|
||||
slapi_ch_free_string(&keybuf);
|
||||
@@ -1224,7 +1241,7 @@ entryrdn_lookup_dn(backend *be,
|
||||
}
|
||||
goto bail;
|
||||
}
|
||||
- if (workid == 1) {
|
||||
+ if (workid == suffix_id) {
|
||||
/* The loop (workid) iterates from the starting 'id'
|
||||
* up to the suffix ID (i.e. '1').
|
||||
* A corner case (#6417) is if an entry, on the path
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
From 1ffcc9aa9a397180fe35283ee61b164471d073fb Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 7 Jan 2025 10:01:51 +0100
|
||||
Subject: [PATCH] Issue 6417 - (2nd) fix typo
|
||||
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 10 ++++++----
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index 1bbb6252a..e2b8273a2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1178,8 +1178,10 @@ entryrdn_lookup_dn(backend *be,
|
||||
|
||||
/* Just in case the suffix ID is not '1' retrieve it from the database */
|
||||
keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
- dblayer_value_set(be, &key, keybuf, strlen(keybuf) + 1);
|
||||
- rc = dblayer_cursor_op(&ctx.cursor, DBI_OP_MOVE_TO_KEY, &key, &data);
|
||||
+ key.data = keybuf;
|
||||
+ key.size = key.ulen = strlen(keybuf) + 1;
|
||||
+ key.flags = DB_DBT_USERMEM;
|
||||
+ rc = cursor->c_get(cursor, &key, &data, DB_SET);
|
||||
if (rc) {
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
"Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
@@ -1189,8 +1191,8 @@ entryrdn_lookup_dn(backend *be,
|
||||
elem = (rdn_elem *)data.data;
|
||||
suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
}
|
||||
- dblayer_value_free(be, &data);
|
||||
- dblayer_value_free(be, &key);
|
||||
+ slapi_ch_free(&data.data);
|
||||
+ slapi_ch_free_string(&keybuf);
|
||||
|
||||
do {
|
||||
/* Setting up a key for the node to get its parent */
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
From 9e1284122a929fe14633a2aa6e2de4d72891f98f Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 13 Jan 2025 17:41:18 +0100
|
||||
Subject: [PATCH] Issue 6417 - (3rd) If an entry RDN is identical to the
|
||||
suffix, then Entryrdn gets broken during a reindex (#6480)
|
||||
|
||||
Bug description:
|
||||
The previous fix had a flaw.
|
||||
In case entryrdn_lookup_dn is called with an undefined suffix
|
||||
the lookup of the suffix trigger a crash.
|
||||
For example it can occur during internal search of an
|
||||
unexisting map (view plugin).
|
||||
The issue exists in all releases but is hidden since 2.3.
|
||||
|
||||
Fix description:
|
||||
testing the suffix is defined
|
||||
|
||||
fixes: #6417
|
||||
|
||||
Reviewed by: Pierre Rogier (THnaks !)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 36 +++++++++++---------
|
||||
1 file changed, 20 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index e2b8273a2..01c77156f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1176,23 +1176,27 @@ entryrdn_lookup_dn(backend *be,
|
||||
/* Setting the bulk fetch buffer */
|
||||
data.flags = DB_DBT_MALLOC;
|
||||
|
||||
- /* Just in case the suffix ID is not '1' retrieve it from the database */
|
||||
- keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
- key.data = keybuf;
|
||||
- key.size = key.ulen = strlen(keybuf) + 1;
|
||||
- key.flags = DB_DBT_USERMEM;
|
||||
- rc = cursor->c_get(cursor, &key, &data, DB_SET);
|
||||
- if (rc) {
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
- "Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
- slapi_sdn_get_ndn(be->be_suffix),
|
||||
- suffix_id);
|
||||
- } else {
|
||||
- elem = (rdn_elem *)data.data;
|
||||
- suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
+ /* Just in case the suffix ID is not '1' retrieve it from the database
|
||||
+ * if the suffix is not defined suffix_id remains '1'
|
||||
+ */
|
||||
+ if (be->be_suffix) {
|
||||
+ keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
+ key.data = keybuf;
|
||||
+ key.size = key.ulen = strlen(keybuf) + 1;
|
||||
+ key.flags = DB_DBT_USERMEM;
|
||||
+ rc = cursor->c_get(cursor, &key, &data, DB_SET);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
+ "Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
+ slapi_sdn_get_ndn(be->be_suffix),
|
||||
+ suffix_id);
|
||||
+ } else {
|
||||
+ elem = (rdn_elem *) data.data;
|
||||
+ suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
+ }
|
||||
+ slapi_ch_free(&data.data);
|
||||
+ slapi_ch_free_string(&keybuf);
|
||||
}
|
||||
- slapi_ch_free(&data.data);
|
||||
- slapi_ch_free_string(&keybuf);
|
||||
|
||||
do {
|
||||
/* Setting up a key for the node to get its parent */
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,297 +0,0 @@
|
||||
From d2f9dd82e3610ee9b73feea981c680c03bb21394 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 16 Jan 2025 08:42:53 -0500
|
||||
Subject: [PATCH] Issue 6509 - Race condition with Paged Result searches
|
||||
|
||||
Description:
|
||||
|
||||
There is a race condition with Paged Result searches when a new operation comes
|
||||
in while a paged search is finishing. This triggers an invalid time out error
|
||||
and closes the connection with a T3 code.
|
||||
|
||||
The problem is that we do not use the "PagedResult lock" when checking the
|
||||
connection's paged result data for a timeout event. This causes the paged
|
||||
result timeout value to change unexpectedly and trigger a false timeout when a
|
||||
new operation arrives.
|
||||
|
||||
Now we check the timeout without hte conn lock, if its expired it could
|
||||
be a race condition and false positive. Try the lock again and test the
|
||||
timeout. This also prevents blocking non-paged result searches from
|
||||
getting held up by the lock when it's not necessary.
|
||||
|
||||
This also fixes some memory leaks that occur when an error happens.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6509
|
||||
|
||||
Reviewed by: tbordaz & proger (Thanks!!)
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 61 ++++++++++++++++++-------------
|
||||
ldap/servers/slapd/opshared.c | 58 ++++++++++++++---------------
|
||||
ldap/servers/slapd/pagedresults.c | 9 +++++
|
||||
ldap/servers/slapd/slap.h | 2 +-
|
||||
4 files changed, 75 insertions(+), 55 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index bb80dae36..13dfe250d 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1578,7 +1578,29 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
if (c->c_state == CONN_STATE_FREE) {
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else {
|
||||
- /* we try to acquire the connection mutex, if it is already
|
||||
+ /* Check for a timeout for PAGED RESULTS */
|
||||
+ if (pagedresults_is_timedout_nolock(c)) {
|
||||
+ /*
|
||||
+ * There could be a race condition so lets try again with the
|
||||
+ * right lock
|
||||
+ */
|
||||
+ pthread_mutex_t *pr_mutex = pageresult_lock_get_addr(c);
|
||||
+ if (pthread_mutex_trylock(pr_mutex) == EBUSY) {
|
||||
+ c = next;
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (pagedresults_is_timedout_nolock(c)) {
|
||||
+ pthread_mutex_unlock(pr_mutex);
|
||||
+ disconnect_server(c, c->c_connid, -1,
|
||||
+ SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT,
|
||||
+ 0);
|
||||
+ } else {
|
||||
+ pthread_mutex_unlock(pr_mutex);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * we try to acquire the connection mutex, if it is already
|
||||
* acquired by another thread, don't wait
|
||||
*/
|
||||
if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
@@ -1586,35 +1608,24 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
continue;
|
||||
}
|
||||
if (c->c_flags & CONN_FLAG_CLOSING) {
|
||||
- /* A worker thread has marked that this connection
|
||||
- * should be closed by calling disconnect_server.
|
||||
- * move this connection out of the active list
|
||||
- * the last thread to use the connection will close it
|
||||
+ /*
|
||||
+ * A worker thread, or paged result timeout, has marked that
|
||||
+ * this connection should be closed by calling
|
||||
+ * disconnect_server(). Move this connection out of the active
|
||||
+ * list then the last thread to use the connection will close
|
||||
+ * it.
|
||||
*/
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else if (c->c_sd == SLAPD_INVALID_SOCKET) {
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else if (c->c_prfd != NULL) {
|
||||
if ((!c->c_gettingber) && (c->c_threadnumber < c->c_max_threads_per_conn)) {
|
||||
- int add_fd = 1;
|
||||
- /* check timeout for PAGED RESULTS */
|
||||
- if (pagedresults_is_timedout_nolock(c)) {
|
||||
- /* Exceeded the paged search timelimit; disconnect the client */
|
||||
- disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
- SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT,
|
||||
- 0);
|
||||
- connection_table_move_connection_out_of_active_list(ct,
|
||||
- c);
|
||||
- add_fd = 0; /* do not poll on this fd */
|
||||
- }
|
||||
- if (add_fd) {
|
||||
- ct->fd[count].fd = c->c_prfd;
|
||||
- ct->fd[count].in_flags = SLAPD_POLL_FLAGS;
|
||||
- /* slot i of the connection table is mapped to slot
|
||||
- * count of the fds array */
|
||||
- c->c_fdi = count;
|
||||
- count++;
|
||||
- }
|
||||
+ ct->fd[listnum][count].fd = c->c_prfd;
|
||||
+ ct->fd[listnum][count].in_flags = SLAPD_POLL_FLAGS;
|
||||
+ /* slot i of the connection table is mapped to slot
|
||||
+ * count of the fds array */
|
||||
+ c->c_fdi = count;
|
||||
+ count++;
|
||||
} else {
|
||||
if (c->c_threadnumber >= c->c_max_threads_per_conn) {
|
||||
c->c_maxthreadsblocked++;
|
||||
@@ -1675,7 +1686,7 @@ handle_pr_read_ready(Connection_Table *ct, PRIntn num_poll __attribute__((unused
|
||||
continue;
|
||||
}
|
||||
|
||||
- /* Try to get connection mutex, if not available just skip the connection and
|
||||
+ /* Try to get connection mutex, if not available just skip the connection and
|
||||
* process other connections events. May generates cpu load for listening thread
|
||||
* if connection mutex is held for a long time
|
||||
*/
|
||||
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
|
||||
index 7ab4117cd..a29eed052 100644
|
||||
--- a/ldap/servers/slapd/opshared.c
|
||||
+++ b/ldap/servers/slapd/opshared.c
|
||||
@@ -250,7 +250,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
char *errtext = NULL;
|
||||
int nentries, pnentries;
|
||||
int flag_search_base_found = 0;
|
||||
- int flag_no_such_object = 0;
|
||||
+ bool flag_no_such_object = false;
|
||||
int flag_referral = 0;
|
||||
int flag_psearch = 0;
|
||||
int err_code = LDAP_SUCCESS;
|
||||
@@ -315,7 +315,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
rc = -1;
|
||||
goto free_and_return_nolock;
|
||||
}
|
||||
-
|
||||
+
|
||||
/* Set the time we actually started the operation */
|
||||
slapi_operation_set_time_started(operation);
|
||||
|
||||
@@ -798,11 +798,11 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
}
|
||||
|
||||
/* subtree searches :
|
||||
- * if the search was started above the backend suffix
|
||||
- * - temporarily set the SLAPI_SEARCH_TARGET_SDN to the
|
||||
- * base of the node so that we don't get a NO SUCH OBJECT error
|
||||
- * - do not change the scope
|
||||
- */
|
||||
+ * if the search was started above the backend suffix
|
||||
+ * - temporarily set the SLAPI_SEARCH_TARGET_SDN to the
|
||||
+ * base of the node so that we don't get a NO SUCH OBJECT error
|
||||
+ * - do not change the scope
|
||||
+ */
|
||||
if (scope == LDAP_SCOPE_SUBTREE) {
|
||||
if (slapi_sdn_issuffix(be_suffix, basesdn)) {
|
||||
if (free_sdn) {
|
||||
@@ -825,53 +825,53 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
switch (rc) {
|
||||
case 1:
|
||||
/* if the backend returned LDAP_NO_SUCH_OBJECT for a SEARCH request,
|
||||
- * it will not have sent back a result - otherwise, it will have
|
||||
- * sent a result */
|
||||
+ * it will not have sent back a result - otherwise, it will have
|
||||
+ * sent a result */
|
||||
rc = SLAPI_FAIL_GENERAL;
|
||||
slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err);
|
||||
if (err == LDAP_NO_SUCH_OBJECT) {
|
||||
/* may be the object exist somewhere else
|
||||
- * wait the end of the loop to send back this error
|
||||
- */
|
||||
- flag_no_such_object = 1;
|
||||
+ * wait the end of the loop to send back this error
|
||||
+ */
|
||||
+ flag_no_such_object = true;
|
||||
} else {
|
||||
/* err something other than LDAP_NO_SUCH_OBJECT, so the backend will
|
||||
- * have sent the result -
|
||||
- * Set a flag here so we don't return another result. */
|
||||
+ * have sent the result -
|
||||
+ * Set a flag here so we don't return another result. */
|
||||
sent_result = 1;
|
||||
}
|
||||
- /* fall through */
|
||||
+ /* fall through */
|
||||
|
||||
case -1: /* an error occurred */
|
||||
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err);
|
||||
/* PAGED RESULTS */
|
||||
if (op_is_pagedresults(operation)) {
|
||||
/* cleanup the slot */
|
||||
pthread_mutex_lock(pagedresults_mutex);
|
||||
+ if (err != LDAP_NO_SUCH_OBJECT && !flag_no_such_object) {
|
||||
+ /* Free the results if not "no_such_object" */
|
||||
+ void *sr = NULL;
|
||||
+ slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr);
|
||||
+ be->be_search_results_release(&sr);
|
||||
+ }
|
||||
pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
|
||||
rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1);
|
||||
pthread_mutex_unlock(pagedresults_mutex);
|
||||
}
|
||||
- if (1 == flag_no_such_object) {
|
||||
- break;
|
||||
- }
|
||||
- slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err);
|
||||
- if (err == LDAP_NO_SUCH_OBJECT) {
|
||||
- /* may be the object exist somewhere else
|
||||
- * wait the end of the loop to send back this error
|
||||
- */
|
||||
- flag_no_such_object = 1;
|
||||
+
|
||||
+ if (err == LDAP_NO_SUCH_OBJECT || flag_no_such_object) {
|
||||
+ /* Maybe the object exists somewhere else, wait to the end
|
||||
+ * of the loop to send back this error */
|
||||
+ flag_no_such_object = true;
|
||||
break;
|
||||
} else {
|
||||
- /* for error other than LDAP_NO_SUCH_OBJECT
|
||||
- * the error has already been sent
|
||||
- * stop the search here
|
||||
- */
|
||||
+ /* For error other than LDAP_NO_SUCH_OBJECT the error has
|
||||
+ * already been sent stop the search here */
|
||||
cache_return_target_entry(pb, be, operation);
|
||||
goto free_and_return;
|
||||
}
|
||||
|
||||
/* when rc == SLAPI_FAIL_DISKFULL this case is executed */
|
||||
-
|
||||
case SLAPI_FAIL_DISKFULL:
|
||||
operation_out_of_disk_space();
|
||||
cache_return_target_entry(pb, be, operation);
|
||||
diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
|
||||
index db87e486e..4aa1fa3e5 100644
|
||||
--- a/ldap/servers/slapd/pagedresults.c
|
||||
+++ b/ldap/servers/slapd/pagedresults.c
|
||||
@@ -121,12 +121,15 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
|
||||
if (ber_scanf(ber, "{io}", pagesize, &cookie) == LBER_ERROR) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "pagedresults_parse_control_value",
|
||||
"<= corrupted control value\n");
|
||||
+ ber_free(ber, 1);
|
||||
return LDAP_PROTOCOL_ERROR;
|
||||
}
|
||||
if (!maxreqs) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "pagedresults_parse_control_value",
|
||||
"Simple paged results requests per conn exceeded the limit: %d\n",
|
||||
maxreqs);
|
||||
+ ber_free(ber, 1);
|
||||
+ slapi_ch_free_string(&cookie.bv_val);
|
||||
return LDAP_UNWILLING_TO_PERFORM;
|
||||
}
|
||||
|
||||
@@ -376,6 +379,10 @@ pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *
|
||||
}
|
||||
prp->pr_flags |= CONN_FLAG_PAGEDRESULTS_ABANDONED;
|
||||
prp->pr_flags &= ~CONN_FLAG_PAGEDRESULTS_PROCESSING;
|
||||
+ if (conn->c_pagedresults.prl_count > 0) {
|
||||
+ _pr_cleanup_one_slot(prp);
|
||||
+ conn->c_pagedresults.prl_count--;
|
||||
+ }
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
@@ -940,7 +947,9 @@ pagedresults_is_timedout_nolock(Connection *conn)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "<-- pagedresults_is_timedout", "<= false 2\n");
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 072f6f962..469874fd1 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -74,7 +74,7 @@ static char ptokPBE[34] = "Internal (Software) Token ";
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
-
|
||||
+#include <stdbool.h>
|
||||
#include <time.h> /* For timespec definitions */
|
||||
|
||||
/* Provides our int types and platform specific requirements. */
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
From 27cd055197bc3cae458a1f86621aa5410c66dd2c Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 20 Jan 2025 15:51:24 -0500
|
||||
Subject: [PATCH] Issue 6509 - Fix cherry pick issue (race condition in Paged
|
||||
results)
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6509
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 13dfe250d..57e07e5f5 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1620,8 +1620,8 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else if (c->c_prfd != NULL) {
|
||||
if ((!c->c_gettingber) && (c->c_threadnumber < c->c_max_threads_per_conn)) {
|
||||
- ct->fd[listnum][count].fd = c->c_prfd;
|
||||
- ct->fd[listnum][count].in_flags = SLAPD_POLL_FLAGS;
|
||||
+ ct->fd[count].fd = c->c_prfd;
|
||||
+ ct->fd[count].in_flags = SLAPD_POLL_FLAGS;
|
||||
/* slot i of the connection table is mapped to slot
|
||||
* count of the fds array */
|
||||
c->c_fdi = count;
|
||||
--
|
||||
2.48.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,236 +0,0 @@
|
||||
From 1845aed98becaba6b975342229cb5e0de79d208d Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 29 Jan 2025 17:41:55 +0000
|
||||
Subject: [PATCH] Issue 6436 - MOD on a large group slow if substring index is
|
||||
present (#6437)
|
||||
|
||||
Bug Description: If the substring index is configured for the group
|
||||
membership attribute ( member or uniqueMember ), the removal of a
|
||||
member from a large static group is pretty slow.
|
||||
|
||||
Fix Description: A solution to this issue would be to introduce
|
||||
a new index to track a membership atttribute index. In the interm,
|
||||
we add a check to healthcheck to inform the user of the implications
|
||||
of this configuration.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6436
|
||||
|
||||
Reviewed by: @Firstyear, @tbordaz, @droideck (Thanks)
|
||||
---
|
||||
.../suites/healthcheck/health_config_test.py | 89 ++++++++++++++++++-
|
||||
src/lib389/lib389/lint.py | 15 ++++
|
||||
src/lib389/lib389/plugins.py | 37 +++++++-
|
||||
3 files changed, 137 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_config_test.py b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
index 6d3d08bfa..747699486 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
@@ -212,6 +212,7 @@ def test_healthcheck_RI_plugin_missing_indexes(topology_st):
|
||||
MEMBER_DN = 'cn=member,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
|
||||
standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
|
||||
log.info('Enable RI plugin')
|
||||
plugin = ReferentialIntegrityPlugin(standalone)
|
||||
@@ -233,7 +234,7 @@ def test_healthcheck_RI_plugin_missing_indexes(topology_st):
|
||||
|
||||
|
||||
def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
- """Check if HealthCheck returns DSMOLE0002 code
|
||||
+ """Check if HealthCheck returns DSMOLE0001 code
|
||||
|
||||
:id: 236b0ec2-13da-48fb-b65a-db7406d56d5d
|
||||
:setup: Standalone instance
|
||||
@@ -248,8 +249,8 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
- 3. Healthcheck reports DSMOLE0002 code and related details
|
||||
- 4. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 3. Healthcheck reports DSMOLE0001 code and related details
|
||||
+ 4. Healthcheck reports DSMOLE0001 code and related details
|
||||
5. Success
|
||||
6. Healthcheck reports no issue found
|
||||
7. Healthcheck reports no issue found
|
||||
@@ -259,6 +260,7 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
MO_GROUP_ATTR = 'creatorsname'
|
||||
|
||||
standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
|
||||
log.info('Enable MO plugin')
|
||||
plugin = MemberOfPlugin(standalone)
|
||||
@@ -279,6 +281,87 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
+def test_healthcheck_MO_plugin_substring_index(topology_st):
|
||||
+ """Check if HealthCheck returns DSMOLE0002 code when the
|
||||
+ member, uniquemember attribute contains a substring index type
|
||||
+
|
||||
+ :id: 10954811-24ac-4886-8183-e30892f8e02d
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Configure the instance with MO Plugin
|
||||
+ 3. Change index type to substring for member attribute
|
||||
+ 4. Use HealthCheck without --json option
|
||||
+ 5. Use HealthCheck with --json option
|
||||
+ 6. Change index type back to equality for member attribute
|
||||
+ 7. Use HealthCheck without --json option
|
||||
+ 8. Use HealthCheck with --json option
|
||||
+ 9. Change index type to substring for uniquemember attribute
|
||||
+ 10. Use HealthCheck without --json option
|
||||
+ 11. Use HealthCheck with --json option
|
||||
+ 12. Change index type back to equality for uniquemember attribute
|
||||
+ 13. Use HealthCheck without --json option
|
||||
+ 14. Use HealthCheck with --json option
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 5. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 6. Success
|
||||
+ 7. Healthcheck reports no issue found
|
||||
+ 8. Healthcheck reports no issue found
|
||||
+ 9. Success
|
||||
+ 10. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 11. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 12. Success
|
||||
+ 13. Healthcheck reports no issue found
|
||||
+ 14. Healthcheck reports no issue found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = 'DSMOLE0002'
|
||||
+ MEMBER_DN = 'cn=member,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
+ UNIQUE_MEMBER_DN = 'cn=uniquemember,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
+
|
||||
+ log.info('Enable MO plugin')
|
||||
+ plugin = MemberOfPlugin(standalone)
|
||||
+ plugin.disable()
|
||||
+ plugin.enable()
|
||||
+
|
||||
+ log.info('Change the index type of the member attribute index to substring')
|
||||
+ index = Index(topology_st.standalone, MEMBER_DN)
|
||||
+ index.replace('nsIndexType', 'sub')
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ log.info('Set the index type of the member attribute index back to eq')
|
||||
+ index.replace('nsIndexType', 'eq')
|
||||
+
|
||||
+ 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('Change the index type of the uniquemember attribute index to substring')
|
||||
+ index = Index(topology_st.standalone, UNIQUE_MEMBER_DN)
|
||||
+ index.replace('nsIndexType', 'sub')
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ log.info('Set the index type of the uniquemember attribute index back to eq')
|
||||
+ index.replace('nsIndexType', 'eq')
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ # Restart the instance after changing the plugin to avoid breaking the other tests
|
||||
+ standalone.restart()
|
||||
+
|
||||
+
|
||||
@pytest.mark.ds50873
|
||||
@pytest.mark.bz1685160
|
||||
@pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented")
|
||||
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||||
index 4d9cbb666..3d3c79ea3 100644
|
||||
--- a/src/lib389/lib389/lint.py
|
||||
+++ b/src/lib389/lib389/lint.py
|
||||
@@ -231,6 +231,21 @@ database after adding the missing index type. Here is an example using dsconf:
|
||||
"""
|
||||
}
|
||||
|
||||
+DSMOLE0002 = {
|
||||
+ 'dsle': 'DSMOLE0002',
|
||||
+ 'severity': 'LOW',
|
||||
+ 'description': 'Removal of a member can be slow ',
|
||||
+ 'items': ['cn=memberof plugin,cn=plugins,cn=config', ],
|
||||
+ 'detail': """If the substring index is configured for a membership attribute. The removal of a member
|
||||
+from the large group can be slow.
|
||||
+
|
||||
+""",
|
||||
+ 'fix': """If not required, you can remove the substring index type using dsconf:
|
||||
+
|
||||
+ # dsconf slapd-YOUR_INSTANCE backend index set --attr=ATTR BACKEND --del-type=sub
|
||||
+"""
|
||||
+}
|
||||
+
|
||||
# Disk Space check. Note - PARTITION is replaced by the calling function
|
||||
DSDSLE0001 = {
|
||||
'dsle': 'DSDSLE0001',
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 6bf1843ad..185398e5b 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -12,7 +12,7 @@ import copy
|
||||
import os.path
|
||||
from lib389 import tasks
|
||||
from lib389._mapped_object import DSLdapObjects, DSLdapObject
|
||||
-from lib389.lint import DSRILE0001, DSRILE0002, DSMOLE0001
|
||||
+from lib389.lint import DSRILE0001, DSRILE0002, DSMOLE0001, DSMOLE0002
|
||||
from lib389.utils import ensure_str, ensure_list_bytes
|
||||
from lib389.schema import Schema
|
||||
from lib389._constants import (
|
||||
@@ -827,6 +827,41 @@ class MemberOfPlugin(Plugin):
|
||||
report['check'] = f'memberof:attr_indexes'
|
||||
yield report
|
||||
|
||||
+ def _lint_member_substring_index(self):
|
||||
+ if self.status():
|
||||
+ from lib389.backend import Backends
|
||||
+ backends = Backends(self._instance).list()
|
||||
+ membership_attrs = ['member', 'uniquemember']
|
||||
+ container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
|
||||
+ for backend in backends:
|
||||
+ suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
+ if suffix == "cn=changelog":
|
||||
+ # Always skip retro changelog
|
||||
+ continue
|
||||
+ if container is not None:
|
||||
+ # Check if this backend is in the scope
|
||||
+ if not container.endswith(suffix):
|
||||
+ # skip this backend that is not in the scope
|
||||
+ continue
|
||||
+ indexes = backend.get_indexes()
|
||||
+ for attr in membership_attrs:
|
||||
+ report = copy.deepcopy(DSMOLE0002)
|
||||
+ try:
|
||||
+ index = indexes.get(attr)
|
||||
+ types = index.get_attr_vals_utf8_l("nsIndexType")
|
||||
+ if "sub" in types:
|
||||
+ report['detail'] = report['detail'].replace('ATTR', attr)
|
||||
+ report['detail'] = report['detail'].replace('BACKEND', suffix)
|
||||
+ report['fix'] = report['fix'].replace('ATTR', attr)
|
||||
+ report['fix'] = report['fix'].replace('BACKEND', suffix)
|
||||
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
||||
+ report['items'].append(suffix)
|
||||
+ report['items'].append(attr)
|
||||
+ report['check'] = f'attr:substring_index'
|
||||
+ yield report
|
||||
+ except KeyError:
|
||||
+ continue
|
||||
+
|
||||
def get_attr(self):
|
||||
"""Get memberofattr attribute"""
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,651 +0,0 @@
|
||||
From dba27e56161943fbcf54ecbc28337e2c81b07979 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 13 Jan 2025 18:03:07 +0100
|
||||
Subject: [PATCH] Issue 6494 - Various errors when using extended matching rule
|
||||
on vlv sort filter (#6495)
|
||||
|
||||
* Issue 6494 - Various errors when using extended matching rule on vlv sort filter
|
||||
|
||||
Various issues when configuring and using extended matching rule within a vlv sort filter:
|
||||
|
||||
Race condition about the keys storage while indexing leading to various heap and data corruption. (lmdb only)
|
||||
Crash while indexing if vlv are misconfigured because NULL key is not checked.
|
||||
Read after block because of data type mismatch between SlapiValue and berval
|
||||
Memory leaks
|
||||
Solution:
|
||||
|
||||
Serialize the vlv index key generation if vlv filter has an extended matching rule.
|
||||
Check null keys
|
||||
Always provides SlapiValue even ifg we want to get keys as bervals
|
||||
Free properly the resources
|
||||
Issue: #6494
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit 4bd27ecc4e1d21c8af5ab8cad795d70477179a98)
|
||||
(cherry picked from commit 223a20250cbf29a546dcb398cfc76024d2f91347)
|
||||
(cherry picked from commit 280043740a525eaf0438129fd8b99ca251c62366)
|
||||
---
|
||||
.../tests/suites/indexes/regression_test.py | 29 +++
|
||||
.../tests/suites/vlv/regression_test.py | 183 ++++++++++++++++++
|
||||
ldap/servers/slapd/back-ldbm/cleanup.c | 8 +
|
||||
ldap/servers/slapd/back-ldbm/dblayer.c | 22 ++-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_attr.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/matchrule.c | 8 +-
|
||||
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 3 +-
|
||||
ldap/servers/slapd/back-ldbm/sort.c | 37 ++--
|
||||
ldap/servers/slapd/back-ldbm/vlv.c | 26 +--
|
||||
ldap/servers/slapd/back-ldbm/vlv_srch.c | 4 +-
|
||||
ldap/servers/slapd/generation.c | 5 +
|
||||
ldap/servers/slapd/plugin_mr.c | 12 +-
|
||||
src/lib389/lib389/backend.py | 10 +
|
||||
13 files changed, 292 insertions(+), 57 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
index fc6db727f..2196fb2ed 100644
|
||||
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
@@ -227,6 +227,35 @@ def test_reject_virtual_attr_for_indexing(topo):
|
||||
break
|
||||
|
||||
|
||||
+def test_reindex_extended_matching_rule(topo, add_backend_and_ldif_50K_users):
|
||||
+ """Check that index with extended matching rule are reindexed properly.
|
||||
+
|
||||
+ :id: 8a3198e8-cc5a-11ef-a3e7-482ae39447e5
|
||||
+ :setup: Standalone instance + a second backend with 50K users
|
||||
+ :steps:
|
||||
+ 1. Configure uid with 2.5.13.2 matching rule
|
||||
+ 1. Configure cn with 2.5.13.2 matching rule
|
||||
+ 2. Reindex
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ tasks = Tasks(inst)
|
||||
+ be2 = Backends(topo.standalone).get_backend(SUFFIX2)
|
||||
+ index = be2.get_index('uid')
|
||||
+ index.replace('nsMatchingRule', '2.5.13.2')
|
||||
+ index = be2.get_index('cn')
|
||||
+ index.replace('nsMatchingRule', '2.5.13.2')
|
||||
+
|
||||
+ assert tasks.reindex(
|
||||
+ suffix=SUFFIX2,
|
||||
+ args={TASK_WAIT: True}
|
||||
+ ) == 0
|
||||
+
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 3b66de8b5..6ab709bd3 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -22,6 +22,146 @@ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+class BackendHandler:
|
||||
+ def __init__(self, inst, bedict, scope=ldap.SCOPE_ONELEVEL):
|
||||
+ self.inst = inst
|
||||
+ self.bedict = bedict
|
||||
+ self.bes = Backends(inst)
|
||||
+ self.scope = scope
|
||||
+ self.data = {}
|
||||
+
|
||||
+ def find_backend(self, bename):
|
||||
+ for be in self.bes.list():
|
||||
+ if be.get_attr_val_utf8_l('cn') == bename:
|
||||
+ return be
|
||||
+ return None
|
||||
+
|
||||
+ def cleanup(self):
|
||||
+ benames = list(self.bedict.keys())
|
||||
+ benames.reverse()
|
||||
+ for bename in benames:
|
||||
+ be = self.find_backend(bename)
|
||||
+ if be:
|
||||
+ be.delete()
|
||||
+
|
||||
+ def setup(self):
|
||||
+ # Create backends, add vlv index and populate the backends.
|
||||
+ for bename,suffix in self.bedict.items():
|
||||
+ be = self.bes.create(properties={
|
||||
+ 'cn': bename,
|
||||
+ 'nsslapd-suffix': suffix,
|
||||
+ })
|
||||
+ # Add suffix entry
|
||||
+ Organization(self.inst, dn=suffix).create(properties={ 'o': bename, })
|
||||
+ # Configure vlv
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(
|
||||
+ self.inst, basedn=suffix,
|
||||
+ bename=bename, scope=self.scope,
|
||||
+ prefix=f'vlv_1lvl_{bename}')
|
||||
+ # Reindex
|
||||
+ reindex_task = Tasks(self.inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=suffix,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+ # Add ou=People entry
|
||||
+ OrganizationalUnits(self.inst, suffix).create(properties={'ou': 'People'})
|
||||
+ # Add another ou that will be deleted before the export
|
||||
+ # so that import will change the vlv search basedn entryid
|
||||
+ ou2 = OrganizationalUnits(self.inst, suffix).create(properties={'ou': 'dummy ou'})
|
||||
+ # Add a demo user so that vlv_check is happy
|
||||
+ dn = f'uid=demo_user,ou=people,{suffix}'
|
||||
+ UserAccount(self.inst, dn=dn).create( properties= {
|
||||
+ 'uid': 'demo_user',
|
||||
+ 'cn': 'Demo user',
|
||||
+ 'sn': 'Demo user',
|
||||
+ 'uidNumber': '99998',
|
||||
+ 'gidNumber': '99998',
|
||||
+ 'homeDirectory': '/var/empty',
|
||||
+ 'loginShell': '/bin/false',
|
||||
+ 'userpassword': DEMO_PW })
|
||||
+ # Add regular user
|
||||
+ add_users(self.inst, 10, suffix=suffix)
|
||||
+ # Removing ou2
|
||||
+ ou2.delete()
|
||||
+ # And export
|
||||
+ tasks = Tasks(self.inst)
|
||||
+ ldif = f'{self.inst.get_ldif_dir()}/db-{bename}.ldif'
|
||||
+ assert tasks.exportLDIF(suffix=suffix,
|
||||
+ output_file=ldif,
|
||||
+ args={TASK_WAIT: True}) == 0
|
||||
+ # Add the various parameters in topology_st.belist
|
||||
+ self.data[bename] = { 'be': be,
|
||||
+ 'suffix': suffix,
|
||||
+ 'ldif': ldif,
|
||||
+ 'vlv_search' : vlv_search,
|
||||
+ 'vlv_index' : vlv_index,
|
||||
+ 'dn' : dn}
|
||||
+
|
||||
+
|
||||
+def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
+ scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
|
||||
+ vlv_searches = VLVSearch(inst)
|
||||
+ vlv_search_properties = {
|
||||
+ "objectclass": ["top", "vlvSearch"],
|
||||
+ "cn": f"{prefix}Srch",
|
||||
+ "vlvbase": basedn,
|
||||
+ "vlvfilter": "(uid=*)",
|
||||
+ "vlvscope": str(scope),
|
||||
+ }
|
||||
+ vlv_searches.create(
|
||||
+ basedn=f"cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_search_properties
|
||||
+ )
|
||||
+
|
||||
+ vlv_index = VLVIndex(inst)
|
||||
+ vlv_index_properties = {
|
||||
+ "objectclass": ["top", "vlvIndex"],
|
||||
+ "cn": f"{prefix}Idx",
|
||||
+ "vlvsort": vlvsort,
|
||||
+ }
|
||||
+ vlv_index.create(
|
||||
+ basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_index_properties
|
||||
+ )
|
||||
+ return vlv_searches, vlv_index
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def vlv_setup_with_uid_mr(topology_st, request):
|
||||
+ inst = topology_st.standalone
|
||||
+ bename = 'be1'
|
||||
+ besuffix = f'o={bename}'
|
||||
+ beh = BackendHandler(inst, { bename: besuffix })
|
||||
+
|
||||
+ def fin():
|
||||
+ # Cleanup function
|
||||
+ if not DEBUGGING and inst.exists() and inst.status():
|
||||
+ beh.cleanup()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Make sure that our backend are not already present.
|
||||
+ beh.cleanup()
|
||||
+
|
||||
+ # Then add the new backend
|
||||
+ beh.setup()
|
||||
+
|
||||
+ index = Index(inst, f'cn=uid,cn=index,cn={bename},cn=ldbm database,cn=plugins,cn=config')
|
||||
+ index.add('nsMatchingRule', '2.5.13.2')
|
||||
+ reindex_task = Tasks(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=besuffix,
|
||||
+ attrname='uid',
|
||||
+ args={TASK_WAIT: True}
|
||||
+ ) == 0
|
||||
+
|
||||
+ topology_st.beh = beh
|
||||
+ return topology_st
|
||||
+
|
||||
+
|
||||
@pytest.mark.DS47966
|
||||
def test_bulk_import_when_the_backend_with_vlv_was_recreated(topology_m2):
|
||||
"""
|
||||
@@ -105,6 +245,49 @@ def test_bulk_import_when_the_backend_with_vlv_was_recreated(topology_m2):
|
||||
entries = M2.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(objectclass=*)")
|
||||
|
||||
|
||||
+def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
+ """
|
||||
+ Testing vlv having specific matching rule
|
||||
+
|
||||
+ :id: 5e04afe2-beec-11ef-aa84-482ae39447e5
|
||||
+ :setup: Standalone with uid have a matching rule index
|
||||
+ :steps:
|
||||
+ 1. Append vlvIndex entries then vlvSearch entry in the dse.ldif
|
||||
+ 2. Restart the server
|
||||
+ :expectedresults:
|
||||
+ 1. Should Success.
|
||||
+ 2. Should Success.
|
||||
+ """
|
||||
+ inst = vlv_setup_with_uid_mr.standalone
|
||||
+ beh = vlv_setup_with_uid_mr.beh
|
||||
+ bename, besuffix = next(iter(beh.bedict.items()))
|
||||
+ vlv_searches, vlv_index = create_vlv_search_and_index(
|
||||
+ inst, basedn=besuffix, bename=bename,
|
||||
+ vlvsort="uid:2.5.13.2")
|
||||
+ # Reindex the vlv
|
||||
+ reindex_task = Tasks(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=besuffix,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ inst.restart()
|
||||
+ users = UserAccounts(inst, besuffix)
|
||||
+ user_properties = {
|
||||
+ 'uid': f'a new testuser',
|
||||
+ 'cn': f'a new testuser',
|
||||
+ 'sn': 'user',
|
||||
+ 'uidNumber': '0',
|
||||
+ 'gidNumber': '0',
|
||||
+ 'homeDirectory': 'foo'
|
||||
+ }
|
||||
+ user = users.create(properties=user_properties)
|
||||
+ user.delete()
|
||||
+ assert inst.status()
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/cleanup.c b/ldap/servers/slapd/back-ldbm/cleanup.c
|
||||
index 6b2e9faef..939d8bc4f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/cleanup.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/cleanup.c
|
||||
@@ -15,12 +15,14 @@
|
||||
|
||||
#include "back-ldbm.h"
|
||||
#include "dblayer.h"
|
||||
+#include "vlv_srch.h"
|
||||
|
||||
int
|
||||
ldbm_back_cleanup(Slapi_PBlock *pb)
|
||||
{
|
||||
struct ldbminfo *li;
|
||||
Slapi_Backend *be;
|
||||
+ struct vlvSearch *nextp;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "ldbm_back_cleanup", "ldbm backend cleaning up\n");
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &li);
|
||||
@@ -45,6 +47,12 @@ ldbm_back_cleanup(Slapi_PBlock *pb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ /* Release the vlv list */
|
||||
+ for (struct vlvSearch *p=be->vlvSearchList; p; p=nextp) {
|
||||
+ nextp = p->vlv_next;
|
||||
+ vlvSearch_delete(&p);
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* We check if li is NULL. Because of an issue in how we create backends
|
||||
* we share the li and plugin info between many unique backends. This causes
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/dblayer.c b/ldap/servers/slapd/back-ldbm/dblayer.c
|
||||
index 05cc5b891..6b8ce0016 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/dblayer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/dblayer.c
|
||||
@@ -494,8 +494,12 @@ int
|
||||
dblayer_close(struct ldbminfo *li, int dbmode)
|
||||
{
|
||||
dblayer_private *priv = (dblayer_private *)li->li_dblayer_private;
|
||||
-
|
||||
- return priv->dblayer_close_fn(li, dbmode);
|
||||
+ int rc = priv->dblayer_close_fn(li, dbmode);
|
||||
+ if (rc == 0) {
|
||||
+ /* Clean thread specific data */
|
||||
+ dblayer_destroy_txn_stack();
|
||||
+ }
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
/* Routines for opening and closing random files in the DB_ENV.
|
||||
@@ -621,6 +625,9 @@ dblayer_erase_index_file(backend *be, struct attrinfo *a, PRBool use_lock, int n
|
||||
return 0;
|
||||
}
|
||||
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
+ if (NULL == li) {
|
||||
+ return 0;
|
||||
+ }
|
||||
dblayer_private *priv = (dblayer_private *)li->li_dblayer_private;
|
||||
|
||||
return priv->dblayer_rm_db_file_fn(be, a, use_lock, no_force_chkpt);
|
||||
@@ -1382,3 +1389,14 @@ dblayer_pop_pvt_txn(void)
|
||||
}
|
||||
return;
|
||||
}
|
||||
+
|
||||
+void
|
||||
+dblayer_destroy_txn_stack(void)
|
||||
+{
|
||||
+ /*
|
||||
+ * Cleanup for the main thread to avoid false/positive leaks from libasan
|
||||
+ * Note: data is freed because PR_SetThreadPrivate calls the
|
||||
+ * dblayer_cleanup_txn_stack callback
|
||||
+ */
|
||||
+ PR_SetThreadPrivate(thread_private_txn_stack, NULL);
|
||||
+}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attr.c b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
|
||||
index 708756d3e..70700ca1d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_attr.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
|
||||
@@ -54,7 +54,7 @@ attrinfo_delete(struct attrinfo **pp)
|
||||
idl_release_private(*pp);
|
||||
(*pp)->ai_key_cmp_fn = NULL;
|
||||
slapi_ch_free((void **)&((*pp)->ai_type));
|
||||
- slapi_ch_free((void **)(*pp)->ai_index_rules);
|
||||
+ charray_free((*pp)->ai_index_rules);
|
||||
slapi_ch_free((void **)&((*pp)->ai_attrcrypt));
|
||||
attr_done(&((*pp)->ai_sattr));
|
||||
attrinfo_delete_idlistinfo(&(*pp)->ai_idlistinfo);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/matchrule.c b/ldap/servers/slapd/back-ldbm/matchrule.c
|
||||
index 5d516b9f8..5365e8acf 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/matchrule.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/matchrule.c
|
||||
@@ -107,7 +107,7 @@ destroy_matchrule_indexer(Slapi_PBlock *pb)
|
||||
* is destroyed
|
||||
*/
|
||||
int
|
||||
-matchrule_values_to_keys(Slapi_PBlock *pb, struct berval **input_values, struct berval ***output_values)
|
||||
+matchrule_values_to_keys(Slapi_PBlock *pb, Slapi_Value **input_values, struct berval ***output_values)
|
||||
{
|
||||
IFP mrINDEX = NULL;
|
||||
|
||||
@@ -135,10 +135,8 @@ matchrule_values_to_keys_sv(Slapi_PBlock *pb, Slapi_Value **input_values, Slapi_
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_SV_FN, &mrINDEX);
|
||||
if (NULL == mrINDEX) { /* old school - does not have SV function */
|
||||
int rc;
|
||||
- struct berval **bvi = NULL, **bvo = NULL;
|
||||
- valuearray_get_bervalarray(input_values, &bvi);
|
||||
- rc = matchrule_values_to_keys(pb, bvi, &bvo);
|
||||
- ber_bvecfree(bvi);
|
||||
+ struct berval **bvo = NULL;
|
||||
+ rc = matchrule_values_to_keys(pb, input_values, &bvo);
|
||||
/* note - the indexer owns bvo and will free it when destroyed */
|
||||
valuearray_init_bervalarray(bvo, output_values);
|
||||
/* store output values in SV form - caller expects SLAPI_PLUGIN_MR_KEYS is Slapi_Value** */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
index d93ff9239..157788fa4 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
@@ -84,6 +84,7 @@ int dblayer_release_index_file(backend *be, struct attrinfo *a, DB *pDB);
|
||||
int dblayer_erase_index_file(backend *be, struct attrinfo *a, PRBool use_lock, int no_force_chkpt);
|
||||
int dblayer_get_id2entry(backend *be, DB **ppDB);
|
||||
int dblayer_release_id2entry(backend *be, DB *pDB);
|
||||
+void dblayer_destroy_txn_stack(void);
|
||||
int dblayer_txn_init(struct ldbminfo *li, back_txn *txn);
|
||||
int dblayer_txn_begin(backend *be, back_txnid parent_txn, back_txn *txn);
|
||||
int dblayer_txn_begin_ext(struct ldbminfo *li, back_txnid parent_txn, back_txn *txn, PRBool use_lock);
|
||||
@@ -560,7 +561,7 @@ int compute_allids_limit(Slapi_PBlock *pb, struct ldbminfo *li);
|
||||
*/
|
||||
int create_matchrule_indexer(Slapi_PBlock **pb, char *matchrule, char *type);
|
||||
int destroy_matchrule_indexer(Slapi_PBlock *pb);
|
||||
-int matchrule_values_to_keys(Slapi_PBlock *pb, struct berval **input_values, struct berval ***output_values);
|
||||
+int matchrule_values_to_keys(Slapi_PBlock *pb, Slapi_Value **input_values, struct berval ***output_values);
|
||||
int matchrule_values_to_keys_sv(Slapi_PBlock *pb, Slapi_Value **input_values, Slapi_Value ***output_values);
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/sort.c b/ldap/servers/slapd/back-ldbm/sort.c
|
||||
index 70ac60803..196af753f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/sort.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/sort.c
|
||||
@@ -536,30 +536,18 @@ compare_entries_sv(ID *id_a, ID *id_b, sort_spec *s, baggage_carrier *bc, int *e
|
||||
valuearray_get_bervalarray(valueset_get_valuearray(&attr_b->a_present_values), &value_b);
|
||||
} else {
|
||||
/* Match rule case */
|
||||
- struct berval **actual_value_a = NULL;
|
||||
- struct berval **actual_value_b = NULL;
|
||||
- struct berval **temp_value = NULL;
|
||||
-
|
||||
- valuearray_get_bervalarray(valueset_get_valuearray(&attr_a->a_present_values), &actual_value_a);
|
||||
- valuearray_get_bervalarray(valueset_get_valuearray(&attr_b->a_present_values), &actual_value_b);
|
||||
- matchrule_values_to_keys(this_one->mr_pb, actual_value_a, &temp_value);
|
||||
- /* Now copy it, so the second call doesn't crap on it */
|
||||
- value_a = slapi_ch_bvecdup(temp_value); /* Really, we'd prefer to not call the chXXX variant...*/
|
||||
- matchrule_values_to_keys(this_one->mr_pb, actual_value_b, &value_b);
|
||||
-
|
||||
- if ((actual_value_a && !value_a) ||
|
||||
- (actual_value_b && !value_b)) {
|
||||
- ber_bvecfree(actual_value_a);
|
||||
- ber_bvecfree(actual_value_b);
|
||||
- CACHE_RETURN(&inst->inst_cache, &a);
|
||||
- CACHE_RETURN(&inst->inst_cache, &b);
|
||||
- *error = 1;
|
||||
- return 0;
|
||||
+ Slapi_Value **va_a = valueset_get_valuearray(&attr_a->a_present_values);
|
||||
+ Slapi_Value **va_b = valueset_get_valuearray(&attr_b->a_present_values);
|
||||
+
|
||||
+ matchrule_values_to_keys(this_one->mr_pb, va_a, &value_a);
|
||||
+ /* Plugin owns the memory ==> duplicate the key before next call garble it */
|
||||
+ value_a = slapi_ch_bvecdup(value_a);
|
||||
+ matchrule_values_to_keys(this_one->mr_pb, va_b, &value_b);
|
||||
+
|
||||
+ if ((va_a && !value_a) || (va_b && !value_b)) {
|
||||
+ result = 0;
|
||||
+ goto bail;
|
||||
}
|
||||
- if (actual_value_a)
|
||||
- ber_bvecfree(actual_value_a);
|
||||
- if (actual_value_b)
|
||||
- ber_bvecfree(actual_value_b);
|
||||
}
|
||||
/* Compare them */
|
||||
if (!order) {
|
||||
@@ -582,9 +570,10 @@ compare_entries_sv(ID *id_a, ID *id_b, sort_spec *s, baggage_carrier *bc, int *e
|
||||
}
|
||||
/* If so, proceed to the next attribute for comparison */
|
||||
}
|
||||
+ *error = 0;
|
||||
+bail:
|
||||
CACHE_RETURN(&inst->inst_cache, &a);
|
||||
CACHE_RETURN(&inst->inst_cache, &b);
|
||||
- *error = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
index 121fb3667..70e0bac85 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
@@ -605,7 +605,7 @@ vlv_getindices(IFP callback_fn, void *param, backend *be)
|
||||
* generate the same composite key, so we append the EntryID
|
||||
* to ensure the uniqueness of the key.
|
||||
*
|
||||
- * Always creates a key. Never returns NULL.
|
||||
+ * May return NULL in case of errors (typically in some configuration error cases)
|
||||
*/
|
||||
static struct vlv_key *
|
||||
vlv_create_key(struct vlvIndex *p, struct backentry *e)
|
||||
@@ -659,10 +659,8 @@ vlv_create_key(struct vlvIndex *p, struct backentry *e)
|
||||
/* Matching rule. Do the magic mangling. Plugin owns the memory. */
|
||||
if (p->vlv_mrpb[sortattr] != NULL) {
|
||||
/* xxxPINAKI */
|
||||
- struct berval **bval = NULL;
|
||||
Slapi_Value **va = valueset_get_valuearray(&attr->a_present_values);
|
||||
- valuearray_get_bervalarray(va, &bval);
|
||||
- matchrule_values_to_keys(p->vlv_mrpb[sortattr], bval, &value);
|
||||
+ matchrule_values_to_keys(p->vlv_mrpb[sortattr], va, &value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +777,13 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li __attribute__((unused)),
|
||||
}
|
||||
|
||||
key = vlv_create_key(pIndex, entry);
|
||||
+ if (key == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "vlv_create_key", "Unable to generate vlv %s index key."
|
||||
+ " There may be a configuration issue.\n", pIndex->vlv_name);
|
||||
+ dblayer_release_index_file(be, pIndex->vlv_attrinfo, db);
|
||||
+ return rc;
|
||||
+ }
|
||||
+
|
||||
if (NULL != txn) {
|
||||
db_txn = txn->back_txn_txn;
|
||||
} else {
|
||||
@@ -949,11 +954,11 @@ vlv_create_matching_rule_value(Slapi_PBlock *pb, struct berval *original_value)
|
||||
struct berval **value = NULL;
|
||||
if (pb != NULL) {
|
||||
struct berval **outvalue = NULL;
|
||||
- struct berval *invalue[2];
|
||||
- invalue[0] = original_value; /* jcm: cast away const */
|
||||
- invalue[1] = NULL;
|
||||
+ Slapi_Value v_in = {0};
|
||||
+ Slapi_Value *va_in[2] = { &v_in, NULL };
|
||||
+ slapi_value_init_berval(&v_in, original_value);
|
||||
/* The plugin owns the memory it returns in outvalue */
|
||||
- matchrule_values_to_keys(pb, invalue, &outvalue);
|
||||
+ matchrule_values_to_keys(pb, va_in, &outvalue);
|
||||
if (outvalue != NULL) {
|
||||
value = slapi_ch_bvecdup(outvalue);
|
||||
}
|
||||
@@ -1610,11 +1615,8 @@ retry:
|
||||
PRBool needFree = PR_FALSE;
|
||||
|
||||
if (sort_control->mr_pb != NULL) {
|
||||
- struct berval **tmp_entry_value = NULL;
|
||||
-
|
||||
- valuearray_get_bervalarray(csn_value, &tmp_entry_value);
|
||||
/* Matching rule. Do the magic mangling. Plugin owns the memory. */
|
||||
- matchrule_values_to_keys(sort_control->mr_pb, /* xxxPINAKI needs modification attr->a_vals */ tmp_entry_value, &entry_value);
|
||||
+ matchrule_values_to_keys(sort_control->mr_pb, csn_value, &entry_value);
|
||||
} else {
|
||||
valuearray_get_bervalarray(csn_value, &entry_value);
|
||||
needFree = PR_TRUE; /* entry_value is a copy */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv_srch.c b/ldap/servers/slapd/back-ldbm/vlv_srch.c
|
||||
index fe1208d59..11d1c715b 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv_srch.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv_srch.c
|
||||
@@ -203,6 +203,9 @@ vlvSearch_delete(struct vlvSearch **ppvs)
|
||||
{
|
||||
if (ppvs != NULL && *ppvs != NULL) {
|
||||
struct vlvIndex *pi, *ni;
|
||||
+ if ((*ppvs)->vlv_e) {
|
||||
+ slapi_entry_free((struct slapi_entry *)((*ppvs)->vlv_e));
|
||||
+ }
|
||||
slapi_sdn_free(&((*ppvs)->vlv_dn));
|
||||
slapi_ch_free((void **)&((*ppvs)->vlv_name));
|
||||
slapi_sdn_free(&((*ppvs)->vlv_base));
|
||||
@@ -217,7 +220,6 @@ vlvSearch_delete(struct vlvSearch **ppvs)
|
||||
pi = ni;
|
||||
}
|
||||
slapi_ch_free((void **)ppvs);
|
||||
- *ppvs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/generation.c b/ldap/servers/slapd/generation.c
|
||||
index c4f20f793..89f097322 100644
|
||||
--- a/ldap/servers/slapd/generation.c
|
||||
+++ b/ldap/servers/slapd/generation.c
|
||||
@@ -93,9 +93,13 @@ get_server_dataversion()
|
||||
lenstr *l = NULL;
|
||||
Slapi_Backend *be;
|
||||
char *cookie;
|
||||
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
+ /* Serialize to avoid race condition */
|
||||
+ pthread_mutex_lock(&mutex);
|
||||
/* we already cached the copy - just return it */
|
||||
if (server_dataversion_id != NULL) {
|
||||
+ pthread_mutex_unlock(&mutex);
|
||||
return server_dataversion_id;
|
||||
}
|
||||
|
||||
@@ -130,5 +134,6 @@ get_server_dataversion()
|
||||
server_dataversion_id = slapi_ch_strdup(l->ls_buf);
|
||||
}
|
||||
lenstr_free(&l);
|
||||
+ pthread_mutex_unlock(&mutex);
|
||||
return server_dataversion_id;
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c
|
||||
index 13f76fe52..6cf88b7de 100644
|
||||
--- a/ldap/servers/slapd/plugin_mr.c
|
||||
+++ b/ldap/servers/slapd/plugin_mr.c
|
||||
@@ -391,28 +391,18 @@ mr_wrap_mr_index_sv_fn(Slapi_PBlock *pb)
|
||||
return rc;
|
||||
}
|
||||
|
||||
-/* this function takes SLAPI_PLUGIN_MR_VALUES as struct berval ** and
|
||||
+/* this function takes SLAPI_PLUGIN_MR_VALUES as Slapi_Value ** and
|
||||
returns SLAPI_PLUGIN_MR_KEYS as struct berval **
|
||||
*/
|
||||
static int
|
||||
mr_wrap_mr_index_fn(Slapi_PBlock *pb)
|
||||
{
|
||||
int rc = -1;
|
||||
- struct berval **in_vals = NULL;
|
||||
struct berval **out_vals = NULL;
|
||||
struct mr_private *mrpriv = NULL;
|
||||
- Slapi_Value **in_vals_sv = NULL;
|
||||
Slapi_Value **out_vals_sv = NULL;
|
||||
|
||||
- slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUES, &in_vals); /* get bervals */
|
||||
- /* convert bervals to sv ary */
|
||||
- valuearray_init_bervalarray(in_vals, &in_vals_sv);
|
||||
- slapi_pblock_set(pb, SLAPI_PLUGIN_MR_VALUES, in_vals_sv); /* use sv */
|
||||
rc = mr_wrap_mr_index_sv_fn(pb);
|
||||
- /* clean up in_vals_sv */
|
||||
- valuearray_free(&in_vals_sv);
|
||||
- /* restore old in_vals */
|
||||
- slapi_pblock_set(pb, SLAPI_PLUGIN_MR_VALUES, in_vals);
|
||||
/* get result sv keys */
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_MR_KEYS, &out_vals_sv);
|
||||
/* convert to bvec */
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 9acced205..cee073ea7 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -1029,6 +1029,16 @@ class Backends(DSLdapObjects):
|
||||
for be in sorted(self.list(), key=lambda be: len(be.get_suffix()), reverse=True):
|
||||
be.delete()
|
||||
|
||||
+ def get_backend(self, suffix):
|
||||
+ """
|
||||
+ Return the backend associated with the provided suffix.
|
||||
+ """
|
||||
+ suffix_l = suffix.lower()
|
||||
+ for be in self.list():
|
||||
+ if be.get_attr_val_utf8_l('nsslapd-suffix') == suffix_l:
|
||||
+ return be
|
||||
+ return None
|
||||
+
|
||||
|
||||
class DatabaseConfig(DSLdapObject):
|
||||
"""Backend Database configuration
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,230 +0,0 @@
|
||||
From bd2829d04491556c35a0b36b591c09a69baf6546 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 11 Dec 2023 11:58:40 +0100
|
||||
Subject: [PATCH] Issue 6004 - idletimeout may be ignored (#6005)
|
||||
|
||||
* Issue 6004 - idletimeout may be ignored
|
||||
|
||||
Problem: idletimeout is still not handled when binding as non root (unless there are some activity
|
||||
on another connection)
|
||||
Fix:
|
||||
Add a slapi_eq_repeat_rel handler that walks all active connection every seconds and check if the timeout is expired.
|
||||
Note about CI test:
|
||||
Notice that idletimeout is never enforced for connections bound as root (i.e cn=directory manager).
|
||||
|
||||
Issue #6004
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit 86b5969acbe124eec8c89bcf1ab2156b2b140c17)
|
||||
(cherry picked from commit bdb0a72b4953678e5418406b3c202dfa2c7469a2)
|
||||
(cherry picked from commit 61cebc191cd4090072dda691b9956dbde4cf7c48)
|
||||
---
|
||||
.../tests/suites/config/regression_test.py | 82 ++++++++++++++++++-
|
||||
ldap/servers/slapd/daemon.c | 52 +++++++++++-
|
||||
2 files changed, 128 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/regression_test.py b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
index 0000dd82d..8dbba8cd2 100644
|
||||
--- a/dirsrvtests/tests/suites/config/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
@@ -6,20 +6,49 @@
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
#
|
||||
+import os
|
||||
import logging
|
||||
import pytest
|
||||
+import time
|
||||
from lib389.utils import *
|
||||
from lib389.dseldif import DSEldif
|
||||
-from lib389.config import LDBMConfig
|
||||
+from lib389.config import BDB_LDBMConfig, LDBMConfig, Config
|
||||
from lib389.backend import Backends
|
||||
from lib389.topologies import topology_st as topo
|
||||
+from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
|
||||
+from lib389._constants import DEFAULT_SUFFIX, PASSWORD, DN_DM
|
||||
|
||||
pytestmark = pytest.mark.tier0
|
||||
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
CUSTOM_MEM = '9100100100'
|
||||
+IDLETIMEOUT = 5
|
||||
+DN_TEST_USER = f'uid={TEST_USER_PROPERTIES["uid"]},ou=People,{DEFAULT_SUFFIX}'
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="module")
|
||||
+def idletimeout_topo(topo, request):
|
||||
+ """Create an instance with a test user and set idletimeout"""
|
||||
+ inst = topo.standalone
|
||||
+ config = Config(inst)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user = users.create(properties={
|
||||
+ **TEST_USER_PROPERTIES,
|
||||
+ 'userpassword' : PASSWORD,
|
||||
+ })
|
||||
+ config.replace('nsslapd-idletimeout', str(IDLETIMEOUT))
|
||||
+
|
||||
+ def fin():
|
||||
+ if not DEBUGGING:
|
||||
+ config.reset('nsslapd-idletimeout')
|
||||
+ user.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ return topo
|
||||
|
||||
|
||||
# Function to return value of available memory in kb
|
||||
@@ -79,7 +108,7 @@ def test_maxbersize_repl(topo):
|
||||
nsslapd-errorlog-logmaxdiskspace are set in certain order
|
||||
|
||||
:id: 743e912c-2be4-4f5f-9c2a-93dcb18f51a0
|
||||
- :setup: MMR with two suppliers
|
||||
+ :setup: Standalone Instance
|
||||
:steps:
|
||||
1. Stop the instance
|
||||
2. Set nsslapd-errorlog-maxlogsize before/after
|
||||
@@ -112,3 +141,52 @@ def test_maxbersize_repl(topo):
|
||||
log.info("Assert no init_dse_file errors in the error log")
|
||||
assert not inst.ds_error_log.match('.*ERR - init_dse_file.*')
|
||||
|
||||
+
|
||||
+def test_bdb_config(topo):
|
||||
+ """Check that bdb config entry exists
|
||||
+
|
||||
+ :id: edbc6f54-7c98-11ee-b1c0-482ae39447e5
|
||||
+ :setup: standalone
|
||||
+ :steps:
|
||||
+ 1. Check that bdb config instance exists.
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ assert BDB_LDBMConfig(inst).exists()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("dn,expected_result", [(DN_TEST_USER, True), (DN_DM, False)])
|
||||
+def test_idletimeout(idletimeout_topo, dn, expected_result):
|
||||
+ """Check that bdb config entry exists
|
||||
+
|
||||
+ :id: b20f2826-942a-11ee-827b-482ae39447e5
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance with test user and idletimeout
|
||||
+ :steps:
|
||||
+ 1. Open new ldap connection
|
||||
+ 2. Bind with the provided dn
|
||||
+ 3. Wait longer than idletimeout
|
||||
+ 4. Try to bind again the provided dn and check if
|
||||
+ connection is closed or not.
|
||||
+ 5. Check if result is the expected one.
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = idletimeout_topo.standalone
|
||||
+
|
||||
+ l = ldap.initialize(f'ldap://localhost:{inst.port}')
|
||||
+ l.bind_s(dn, PASSWORD)
|
||||
+ time.sleep(IDLETIMEOUT+1)
|
||||
+ try:
|
||||
+ l.bind_s(dn, PASSWORD)
|
||||
+ result = False
|
||||
+ except ldap.SERVER_DOWN:
|
||||
+ result = True
|
||||
+ assert expected_result == result
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 57e07e5f5..6df109760 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -68,6 +68,8 @@
|
||||
#define SLAPD_ACCEPT_WAKEUP_TIMER 250
|
||||
#endif
|
||||
|
||||
+#define MILLISECONDS_PER_SECOND 1000
|
||||
+
|
||||
int slapd_wakeup_timer = SLAPD_WAKEUP_TIMER; /* time in ms to wakeup */
|
||||
int slapd_accept_wakeup_timer = SLAPD_ACCEPT_WAKEUP_TIMER; /* time in ms to wakeup */
|
||||
#ifdef notdef /* GGOODREPL */
|
||||
@@ -1045,6 +1047,48 @@ slapd_sockets_ports_free(daemon_ports_t *ports_info)
|
||||
#endif
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Tells if idle timeout has expired
|
||||
+ */
|
||||
+static inline int __attribute__((always_inline))
|
||||
+has_idletimeout_expired(Connection *c, time_t curtime)
|
||||
+{
|
||||
+ return (c->c_state != CONN_STATE_FREE && !c->c_gettingber &&
|
||||
+ c->c_idletimeout > 0 && NULL == c->c_ops &&
|
||||
+ curtime - c->c_idlesince >= c->c_idletimeout);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * slapi_eq_repeat_rel callback that checks that idletimeout has not expired.
|
||||
+ */
|
||||
+void
|
||||
+check_idletimeout(time_t when __attribute__((unused)), void *arg __attribute__((unused)) )
|
||||
+{
|
||||
+ Connection_Table *ct = the_connection_table;
|
||||
+ time_t curtime = slapi_current_rel_time_t();
|
||||
+ /* Walk all active connections of all connection listeners */
|
||||
+ for (int list_num = 0; list_num < ct->list_num; list_num++) {
|
||||
+ for (Connection *c = connection_table_get_first_active_connection(ct, list_num);
|
||||
+ c != NULL; c = connection_table_get_next_active_connection(ct, c)) {
|
||||
+ if (!has_idletimeout_expired(c, curtime)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ /* Looks like idletimeout has expired, lets acquire the lock
|
||||
+ * and double check.
|
||||
+ */
|
||||
+ if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (has_idletimeout_expired(c, curtime)) {
|
||||
+ /* idle timeout has expired */
|
||||
+ disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
+ SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
+ }
|
||||
+ pthread_mutex_unlock(&(c->c_mutex));
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
void
|
||||
slapd_daemon(daemon_ports_t *ports)
|
||||
{
|
||||
@@ -1258,7 +1302,9 @@ slapd_daemon(daemon_ports_t *ports)
|
||||
"MAINPID=%lu",
|
||||
(unsigned long)getpid());
|
||||
#endif
|
||||
-
|
||||
+ slapi_eq_repeat_rel(check_idletimeout, NULL,
|
||||
+ slapi_current_rel_time_t(),
|
||||
+ MILLISECONDS_PER_SECOND);
|
||||
/* The meat of the operation is in a loop on a call to select */
|
||||
while (!g_get_shutdown()) {
|
||||
int select_return = 0;
|
||||
@@ -1734,9 +1780,7 @@ handle_pr_read_ready(Connection_Table *ct, PRIntn num_poll __attribute__((unused
|
||||
disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
SLAPD_DISCONNECT_POLL, EPIPE);
|
||||
}
|
||||
- } else if (c->c_idletimeout > 0 &&
|
||||
- (curtime - c->c_idlesince) >= c->c_idletimeout &&
|
||||
- NULL == c->c_ops) {
|
||||
+ } else if (has_idletimeout_expired(c, curtime)) {
|
||||
/* idle timeout */
|
||||
disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
From e9fe6e074130406328b8e932a5c2efa814d190a0 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 5 Feb 2025 09:41:30 +0100
|
||||
Subject: [PATCH] Issue 6004 - (2nd) idletimeout may be ignored (#6569)
|
||||
|
||||
Problem:
|
||||
multiple listener threads was implemented in 2.x and after
|
||||
This is missing in 1.4.3 so the cherry pick should be adapted
|
||||
Fix:
|
||||
skip the loop with listeners
|
||||
|
||||
Issue #6004
|
||||
|
||||
Reviewed by: Jamie Chapman (Thanks !)
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 36 +++++++++++++++++-------------------
|
||||
1 file changed, 17 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 6df109760..bef75e4a3 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1066,26 +1066,24 @@ check_idletimeout(time_t when __attribute__((unused)), void *arg __attribute__((
|
||||
{
|
||||
Connection_Table *ct = the_connection_table;
|
||||
time_t curtime = slapi_current_rel_time_t();
|
||||
- /* Walk all active connections of all connection listeners */
|
||||
- for (int list_num = 0; list_num < ct->list_num; list_num++) {
|
||||
- for (Connection *c = connection_table_get_first_active_connection(ct, list_num);
|
||||
- c != NULL; c = connection_table_get_next_active_connection(ct, c)) {
|
||||
- if (!has_idletimeout_expired(c, curtime)) {
|
||||
- continue;
|
||||
- }
|
||||
- /* Looks like idletimeout has expired, lets acquire the lock
|
||||
- * and double check.
|
||||
- */
|
||||
- if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
- continue;
|
||||
- }
|
||||
- if (has_idletimeout_expired(c, curtime)) {
|
||||
- /* idle timeout has expired */
|
||||
- disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
- SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
- }
|
||||
- pthread_mutex_unlock(&(c->c_mutex));
|
||||
+ /* Walk all active connections */
|
||||
+ for (Connection *c = connection_table_get_first_active_connection(ct);
|
||||
+ c != NULL; c = connection_table_get_next_active_connection(ct, c)) {
|
||||
+ if (!has_idletimeout_expired(c, curtime)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ /* Looks like idletimeout has expired, lets acquire the lock
|
||||
+ * and double check.
|
||||
+ */
|
||||
+ if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (has_idletimeout_expired(c, curtime)) {
|
||||
+ /* idle timeout has expired */
|
||||
+ disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
+ SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
}
|
||||
+ pthread_mutex_unlock(&(c->c_mutex));
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
From b2edc371c5ca4fd24ef469c64829c48824098e7f Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 8 Jan 2025 12:57:52 -0500
|
||||
Subject: [PATCH] Issue 6485 - Fix double free in USN cleanup task
|
||||
|
||||
Description:
|
||||
|
||||
ASAN report shows double free of bind dn in the USN cleanup task data. The bind
|
||||
dn was passed as a reference so it should never have to be freed by the cleanup
|
||||
task.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6485
|
||||
|
||||
Reviewed by: tbordaz(Thanks!)
|
||||
---
|
||||
ldap/servers/plugins/usn/usn_cleanup.c | 6 ++----
|
||||
1 file changed, 2 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/usn/usn_cleanup.c b/ldap/servers/plugins/usn/usn_cleanup.c
|
||||
index bdb55e6b1..7eaf0f88f 100644
|
||||
--- a/ldap/servers/plugins/usn/usn_cleanup.c
|
||||
+++ b/ldap/servers/plugins/usn/usn_cleanup.c
|
||||
@@ -240,7 +240,7 @@ usn_cleanup_add(Slapi_PBlock *pb,
|
||||
char *suffix = NULL;
|
||||
char *backend = NULL;
|
||||
char *maxusn = NULL;
|
||||
- char *bind_dn;
|
||||
+ char *bind_dn = NULL;
|
||||
struct usn_cleanup_data *cleanup_data = NULL;
|
||||
int rv = SLAPI_DSE_CALLBACK_OK;
|
||||
Slapi_Task *task = NULL;
|
||||
@@ -323,8 +323,7 @@ usn_cleanup_add(Slapi_PBlock *pb,
|
||||
suffix = NULL; /* don't free in this function */
|
||||
cleanup_data->maxusn_to_delete = maxusn;
|
||||
maxusn = NULL; /* don't free in this function */
|
||||
- cleanup_data->bind_dn = bind_dn;
|
||||
- bind_dn = NULL; /* don't free in this function */
|
||||
+ cleanup_data->bind_dn = slapi_ch_strdup(bind_dn);
|
||||
slapi_task_set_data(task, cleanup_data);
|
||||
|
||||
/* start the USN tombstone cleanup task as a separate thread */
|
||||
@@ -363,7 +362,6 @@ usn_cleanup_task_destructor(Slapi_Task *task)
|
||||
slapi_ch_free_string(&mydata->suffix);
|
||||
slapi_ch_free_string(&mydata->maxusn_to_delete);
|
||||
slapi_ch_free_string(&mydata->bind_dn);
|
||||
- /* Need to cast to avoid a compiler warning */
|
||||
slapi_ch_free((void **)&mydata);
|
||||
}
|
||||
}
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
From 679262c0c292413851d2d004b588ecfd7d91c85a Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Tue, 11 Feb 2025 18:06:34 +0000
|
||||
Subject: [PATCH] Issue 5841 - dsconf incorrectly setting up Pass-Through
|
||||
Authentication (#6601)
|
||||
|
||||
Bug description:
|
||||
During init, PAMPassThroughAuthConfigs defines an "objectclass=nsslapdplugin"
|
||||
plugin object. During filter creation, dsconf fails as objectclass=nsslapdplugin
|
||||
is not present in the PAM PT config entry. This objectclass has been removed in
|
||||
all other branches, branch 1.4.3 was skipped as there are cherry pick conflicts.
|
||||
|
||||
Fix description:
|
||||
Remove nsslapdplugin from the plugin objecti, objectclass list.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/5841
|
||||
|
||||
Reviewed by: @progier389 (Thank you)
|
||||
---
|
||||
src/lib389/lib389/plugins.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 185398e5b..25b49dae4 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -1579,7 +1579,7 @@ class PAMPassThroughAuthConfigs(DSLdapObjects):
|
||||
|
||||
def __init__(self, instance, basedn="cn=PAM Pass Through Auth,cn=plugins,cn=config"):
|
||||
super(PAMPassThroughAuthConfigs, self).__init__(instance)
|
||||
- self._objectclasses = ['top', 'extensibleObject', 'nsslapdplugin', 'pamConfig']
|
||||
+ self._objectclasses = ['top', 'extensibleObject', 'pamConfig']
|
||||
self._filterattrs = ['cn']
|
||||
self._scope = ldap.SCOPE_ONELEVEL
|
||||
self._childobject = PAMPassThroughAuthConfig
|
||||
--
|
||||
2.48.1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,319 +0,0 @@
|
||||
From 7d534efdcd96b13524dae587c3c5994ed01924ab Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 16 Feb 2024 13:52:36 -0800
|
||||
Subject: [PATCH] Issue 6067 - Improve dsidm CLI No Such Entry handling (#6079)
|
||||
|
||||
Description: Add additional error processing to dsidm CLI tool for when basedn
|
||||
or OU subentries are absent.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6067
|
||||
|
||||
Reviewed by: @vashirov (Thanks!)
|
||||
---
|
||||
src/lib389/cli/dsidm | 21 ++++++++-------
|
||||
src/lib389/lib389/cli_idm/__init__.py | 38 ++++++++++++++++++++++++++-
|
||||
src/lib389/lib389/cli_idm/account.py | 4 +--
|
||||
src/lib389/lib389/cli_idm/service.py | 4 ++-
|
||||
src/lib389/lib389/idm/group.py | 10 ++++---
|
||||
src/lib389/lib389/idm/posixgroup.py | 5 ++--
|
||||
src/lib389/lib389/idm/services.py | 5 ++--
|
||||
src/lib389/lib389/idm/user.py | 5 ++--
|
||||
8 files changed, 67 insertions(+), 25 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
|
||||
index 1b739b103..970973f4f 100755
|
||||
--- a/src/lib389/cli/dsidm
|
||||
+++ b/src/lib389/cli/dsidm
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -19,6 +19,7 @@ import argparse
|
||||
import argcomplete
|
||||
from lib389.utils import get_instance_list, instance_choices
|
||||
from lib389._constants import DSRC_HOME
|
||||
+from lib389.cli_idm import _get_basedn_arg
|
||||
from lib389.cli_idm import account as cli_account
|
||||
from lib389.cli_idm import initialise as cli_init
|
||||
from lib389.cli_idm import organizationalunit as cli_ou
|
||||
@@ -117,14 +118,6 @@ if __name__ == '__main__':
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
- if dsrc_inst['basedn'] is None:
|
||||
- errmsg = "Must provide a basedn!"
|
||||
- if args.json:
|
||||
- sys.stderr.write('{"desc": "%s"}\n' % errmsg)
|
||||
- else:
|
||||
- log.error(errmsg)
|
||||
- sys.exit(1)
|
||||
-
|
||||
if not args.verbose:
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
@@ -135,7 +128,15 @@ if __name__ == '__main__':
|
||||
result = False
|
||||
try:
|
||||
inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
|
||||
- result = args.func(inst, dsrc_inst['basedn'], log, args)
|
||||
+ basedn = _get_basedn_arg(inst, args, log, msg="Enter basedn")
|
||||
+ if basedn is None:
|
||||
+ errmsg = "Must provide a basedn!"
|
||||
+ if args.json:
|
||||
+ sys.stderr.write('{"desc": "%s"}\n' % errmsg)
|
||||
+ else:
|
||||
+ log.error(errmsg)
|
||||
+ sys.exit(1)
|
||||
+ result = args.func(inst, basedn, log, args)
|
||||
if args.verbose:
|
||||
log.info("Command successful.")
|
||||
except Exception as e:
|
||||
diff --git a/src/lib389/lib389/cli_idm/__init__.py b/src/lib389/lib389/cli_idm/__init__.py
|
||||
index 0dab54847..e3622246d 100644
|
||||
--- a/src/lib389/lib389/cli_idm/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_idm/__init__.py
|
||||
@@ -1,15 +1,30 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
+import sys
|
||||
import ldap
|
||||
from getpass import getpass
|
||||
import json
|
||||
+from lib389._mapped_object import DSLdapObject
|
||||
+from lib389.cli_base import _get_dn_arg
|
||||
+from lib389.idm.user import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_USER
|
||||
+from lib389.idm.group import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_GROUP
|
||||
+from lib389.idm.posixgroup import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_POSIXGROUP
|
||||
+from lib389.idm.services import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_SERVICES
|
||||
+
|
||||
+# The key is module name, the value is default RDN
|
||||
+BASEDN_RDNS = {
|
||||
+ 'user': DEFAULT_BASEDN_RDN_USER,
|
||||
+ 'group': DEFAULT_BASEDN_RDN_GROUP,
|
||||
+ 'posixgroup': DEFAULT_BASEDN_RDN_POSIXGROUP,
|
||||
+ 'service': DEFAULT_BASEDN_RDN_SERVICES,
|
||||
+}
|
||||
|
||||
|
||||
def _get_arg(args, msg=None):
|
||||
@@ -37,6 +52,27 @@ def _get_args(args, kws):
|
||||
return kwargs
|
||||
|
||||
|
||||
+def _get_basedn_arg(inst, args, log, msg=None):
|
||||
+ basedn_arg = _get_dn_arg(args.basedn, msg="Enter basedn")
|
||||
+ if not DSLdapObject(inst, basedn_arg).exists():
|
||||
+ raise ValueError(f'The base DN "{basedn_arg}" does not exist.')
|
||||
+
|
||||
+ # Get the RDN based on the last part of the module name if applicable
|
||||
+ # (lib389.cli_idm.user -> user)
|
||||
+ try:
|
||||
+ command_name = args.func.__module__.split('.')[-1]
|
||||
+ object_rdn = BASEDN_RDNS[command_name]
|
||||
+ # Check if the DN for our command exists
|
||||
+ command_basedn = f'{object_rdn},{basedn_arg}'
|
||||
+ if not DSLdapObject(inst, command_basedn).exists():
|
||||
+ errmsg = f'The DN "{command_basedn}" does not exist.'
|
||||
+ errmsg += f' It is required for "{command_name}" subcommand. Please create it first.'
|
||||
+ raise ValueError(errmsg)
|
||||
+ except KeyError:
|
||||
+ pass
|
||||
+ return basedn_arg
|
||||
+
|
||||
+
|
||||
# This is really similar to get_args, but generates from an array
|
||||
def _get_attributes(args, attrs):
|
||||
kwargs = {}
|
||||
diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py
|
||||
index 5d7b9cc77..15f766588 100644
|
||||
--- a/src/lib389/lib389/cli_idm/account.py
|
||||
+++ b/src/lib389/lib389/cli_idm/account.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2023, Red Hat inc,
|
||||
+# Copyright (C) 2024, Red Hat inc,
|
||||
# Copyright (C) 2018, William Brown <william@blackhats.net.au>
|
||||
# All rights reserved.
|
||||
#
|
||||
@@ -91,7 +91,6 @@ def entry_status(inst, basedn, log, args):
|
||||
|
||||
|
||||
def subtree_status(inst, basedn, log, args):
|
||||
- basedn = _get_dn_arg(args.basedn, msg="Enter basedn to check")
|
||||
filter = ""
|
||||
scope = ldap.SCOPE_SUBTREE
|
||||
epoch_inactive_time = None
|
||||
@@ -121,7 +120,6 @@ def subtree_status(inst, basedn, log, args):
|
||||
|
||||
|
||||
def bulk_update(inst, basedn, log, args):
|
||||
- basedn = _get_dn_arg(args.basedn, msg="Enter basedn to search")
|
||||
search_filter = "(objectclass=*)"
|
||||
scope = ldap.SCOPE_SUBTREE
|
||||
scope_str = "sub"
|
||||
diff --git a/src/lib389/lib389/cli_idm/service.py b/src/lib389/lib389/cli_idm/service.py
|
||||
index c62fc12d1..c2b2c8c84 100644
|
||||
--- a/src/lib389/lib389/cli_idm/service.py
|
||||
+++ b/src/lib389/lib389/cli_idm/service.py
|
||||
@@ -57,7 +57,9 @@ def rename(inst, basedn, log, args, warn=True):
|
||||
_generic_rename(inst, basedn, log.getChild('_generic_rename'), MANY, rdn, args)
|
||||
|
||||
def create_parser(subparsers):
|
||||
- service_parser = subparsers.add_parser('service', help='Manage service accounts', formatter_class=CustomHelpFormatter)
|
||||
+ service_parser = subparsers.add_parser('service',
|
||||
+ help='Manage service accounts. The organizationalUnit (by default "ou=Services") '
|
||||
+ 'needs to exist prior to managing service accounts.', formatter_class=CustomHelpFormatter)
|
||||
|
||||
subcommands = service_parser.add_subparsers(help='action')
|
||||
|
||||
diff --git a/src/lib389/lib389/idm/group.py b/src/lib389/lib389/idm/group.py
|
||||
index 1b60a1f51..2cf2c7b23 100644
|
||||
--- a/src/lib389/lib389/idm/group.py
|
||||
+++ b/src/lib389/lib389/idm/group.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -16,6 +16,8 @@ MUST_ATTRIBUTES = [
|
||||
'cn',
|
||||
]
|
||||
RDN = 'cn'
|
||||
+DEFAULT_BASEDN_RDN = 'ou=Groups'
|
||||
+DEFAULT_BASEDN_RDN_ADMIN_GROUPS = 'ou=People'
|
||||
|
||||
|
||||
class Group(DSLdapObject):
|
||||
@@ -93,7 +95,7 @@ class Groups(DSLdapObjects):
|
||||
:type basedn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=Groups'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(Groups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'groupOfNames',
|
||||
@@ -140,7 +142,7 @@ class UniqueGroup(DSLdapObject):
|
||||
class UniqueGroups(DSLdapObjects):
|
||||
# WARNING!!!
|
||||
# Use group, not unique group!!!
|
||||
- def __init__(self, instance, basedn, rdn='ou=Groups'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(UniqueGroups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'groupOfUniqueNames',
|
||||
@@ -203,7 +205,7 @@ class nsAdminGroups(DSLdapObjects):
|
||||
:type rdn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=People'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN_ADMIN_GROUPS):
|
||||
super(nsAdminGroups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'nsAdminGroup'
|
||||
diff --git a/src/lib389/lib389/idm/posixgroup.py b/src/lib389/lib389/idm/posixgroup.py
|
||||
index d1debcf12..45735c579 100644
|
||||
--- a/src/lib389/lib389/idm/posixgroup.py
|
||||
+++ b/src/lib389/lib389/idm/posixgroup.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -17,6 +17,7 @@ MUST_ATTRIBUTES = [
|
||||
'gidNumber',
|
||||
]
|
||||
RDN = 'cn'
|
||||
+DEFAULT_BASEDN_RDN = 'ou=Groups'
|
||||
|
||||
|
||||
class PosixGroup(DSLdapObject):
|
||||
@@ -72,7 +73,7 @@ class PosixGroups(DSLdapObjects):
|
||||
:type basedn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=Groups'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(PosixGroups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'groupOfNames',
|
||||
diff --git a/src/lib389/lib389/idm/services.py b/src/lib389/lib389/idm/services.py
|
||||
index d1e5b4693..e750a32c4 100644
|
||||
--- a/src/lib389/lib389/idm/services.py
|
||||
+++ b/src/lib389/lib389/idm/services.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2021 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -16,6 +16,7 @@ RDN = 'cn'
|
||||
MUST_ATTRIBUTES = [
|
||||
'cn',
|
||||
]
|
||||
+DEFAULT_BASEDN_RDN = 'ou=Services'
|
||||
|
||||
class ServiceAccount(Account):
|
||||
"""A single instance of Service entry
|
||||
@@ -59,7 +60,7 @@ class ServiceAccounts(DSLdapObjects):
|
||||
:type basedn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=Services'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(ServiceAccounts, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'applicationProcess',
|
||||
diff --git a/src/lib389/lib389/idm/user.py b/src/lib389/lib389/idm/user.py
|
||||
index 1206a6e08..3b21ccf1c 100644
|
||||
--- a/src/lib389/lib389/idm/user.py
|
||||
+++ b/src/lib389/lib389/idm/user.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -23,6 +23,7 @@ MUST_ATTRIBUTES = [
|
||||
'homeDirectory',
|
||||
]
|
||||
RDN = 'uid'
|
||||
+DEFAULT_BASEDN_RDN = 'ou=People'
|
||||
|
||||
TEST_USER_PROPERTIES = {
|
||||
'uid': 'testuser',
|
||||
@@ -201,7 +202,7 @@ class UserAccounts(DSLdapObjects):
|
||||
:type rdn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=People'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(UserAccounts, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'account',
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
From ee03e8443a108cff0cc4c7a03962fdc3a1fbf94d Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Wed, 16 Oct 2024 19:24:55 -0700
|
||||
Subject: [PATCH] Issue 6067 - Update dsidm to prioritize basedn from .dsrc
|
||||
over interactive input (#6362)
|
||||
|
||||
Description: Modifies dsidm CLI tool to check for the basedn in the .dsrc configuration file
|
||||
when the -b option is not provided.
|
||||
Previously, users were required to always specify the basedn interactively if -b was omitted,
|
||||
even if it was available in .dsrc.
|
||||
Now, the basedn is determined by first checking the -b option, then the .dsrc file, and finally
|
||||
prompting the user if neither is set.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6067
|
||||
|
||||
Reviewed by: @Firstyear (Thanks!)
|
||||
---
|
||||
src/lib389/cli/dsidm | 2 +-
|
||||
src/lib389/lib389/cli_idm/__init__.py | 4 ++--
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
|
||||
index 970973f4f..d318664bc 100755
|
||||
--- a/src/lib389/cli/dsidm
|
||||
+++ b/src/lib389/cli/dsidm
|
||||
@@ -128,7 +128,7 @@ if __name__ == '__main__':
|
||||
result = False
|
||||
try:
|
||||
inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
|
||||
- basedn = _get_basedn_arg(inst, args, log, msg="Enter basedn")
|
||||
+ basedn = _get_basedn_arg(inst, args, dsrc_inst['basedn'], log, msg="Enter basedn")
|
||||
if basedn is None:
|
||||
errmsg = "Must provide a basedn!"
|
||||
if args.json:
|
||||
diff --git a/src/lib389/lib389/cli_idm/__init__.py b/src/lib389/lib389/cli_idm/__init__.py
|
||||
index e3622246d..1f3e2dc86 100644
|
||||
--- a/src/lib389/lib389/cli_idm/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_idm/__init__.py
|
||||
@@ -52,8 +52,8 @@ def _get_args(args, kws):
|
||||
return kwargs
|
||||
|
||||
|
||||
-def _get_basedn_arg(inst, args, log, msg=None):
|
||||
- basedn_arg = _get_dn_arg(args.basedn, msg="Enter basedn")
|
||||
+def _get_basedn_arg(inst, args, basedn, log, msg=None):
|
||||
+ basedn_arg = _get_dn_arg(basedn, msg="Enter basedn")
|
||||
if not DSLdapObject(inst, basedn_arg).exists():
|
||||
raise ValueError(f'The base DN "{basedn_arg}" does not exist.')
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,520 +0,0 @@
|
||||
From b8c079c770d3eaa4de49e997d42e1501c28a153b Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 8 Jul 2024 11:19:09 +0200
|
||||
Subject: [PATCH] Issue 6155 - ldap-agent fails to start because of permission
|
||||
error (#6179)
|
||||
|
||||
Issue: dirsrv-snmp service fails to starts when SELinux is enforced because of AVC preventing to open some files
|
||||
One workaround is to use the dac_override capability but it is a bad practice.
|
||||
Fix: Setting proper permissions:
|
||||
|
||||
Running ldap-agent with uid=root and gid=dirsrv to be able to access both snmp and dirsrv resources.
|
||||
Setting read permission on the group for the dse.ldif file
|
||||
Setting r/w permissions on the group for the snmp semaphore and mmap file
|
||||
For that one special care is needed because ns-slapd umask overrides the file creation permission
|
||||
as is better to avoid changing the umask (changing umask within the code is not thread safe,
|
||||
and the current 0022 umask value is correct for most of the files) so the safest way is to chmod the snmp file
|
||||
if the needed permission are not set.
|
||||
Issue: #6155
|
||||
|
||||
Reviewed by: @droideck , @vashirov (Thanks ! )
|
||||
|
||||
(cherry picked from commit eb7e57d77b557b63c65fdf38f9069893b021f049)
|
||||
---
|
||||
.github/scripts/generate_matrix.py | 4 +-
|
||||
dirsrvtests/tests/suites/snmp/snmp.py | 214 ++++++++++++++++++++++++++
|
||||
ldap/servers/slapd/agtmmap.c | 72 ++++++++-
|
||||
ldap/servers/slapd/agtmmap.h | 13 ++
|
||||
ldap/servers/slapd/dse.c | 6 +-
|
||||
ldap/servers/slapd/slap.h | 6 +
|
||||
ldap/servers/slapd/snmp_collator.c | 4 +-
|
||||
src/lib389/lib389/instance/setup.py | 5 +
|
||||
wrappers/systemd-snmp.service.in | 1 +
|
||||
9 files changed, 313 insertions(+), 12 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/snmp/snmp.py
|
||||
|
||||
diff --git a/.github/scripts/generate_matrix.py b/.github/scripts/generate_matrix.py
|
||||
index 584374597..8d67a1dc7 100644
|
||||
--- a/.github/scripts/generate_matrix.py
|
||||
+++ b/.github/scripts/generate_matrix.py
|
||||
@@ -21,8 +21,8 @@ else:
|
||||
# Use tests from the source
|
||||
suites = next(os.walk('dirsrvtests/tests/suites/'))[1]
|
||||
|
||||
- # Filter out snmp as it is an empty directory:
|
||||
- suites.remove('snmp')
|
||||
+ # Filter out webui because of broken tests
|
||||
+ suites.remove('webui')
|
||||
|
||||
# Run each replication test module separately to speed things up
|
||||
suites.remove('replication')
|
||||
diff --git a/dirsrvtests/tests/suites/snmp/snmp.py b/dirsrvtests/tests/suites/snmp/snmp.py
|
||||
new file mode 100644
|
||||
index 000000000..0952deb40
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/snmp/snmp.py
|
||||
@@ -0,0 +1,214 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import os
|
||||
+import pytest
|
||||
+import logging
|
||||
+import subprocess
|
||||
+import ldap
|
||||
+from datetime import datetime
|
||||
+from shutil import copyfile
|
||||
+from lib389.topologies import topology_m2 as topo_m2
|
||||
+from lib389.utils import selinux_present
|
||||
+
|
||||
+
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+if DEBUGGING:
|
||||
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+else:
|
||||
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+SNMP_USER = 'user_name'
|
||||
+SNMP_PASSWORD = 'authentication_password'
|
||||
+SNMP_PRIVATE = 'private_password'
|
||||
+
|
||||
+# LDAP OID in MIB
|
||||
+LDAP_OID = '.1.3.6.1.4.1.2312.6.1.1'
|
||||
+LDAPCONNECTIONS_OID = f'{LDAP_OID}.21'
|
||||
+
|
||||
+
|
||||
+def run_cmd(cmd, check_returncode=True):
|
||||
+ """Run a command"""
|
||||
+
|
||||
+ log.info(f'Run: {cmd}')
|
||||
+ result = subprocess.run(cmd, capture_output=True, universal_newlines=True)
|
||||
+ log.info(f'STDOUT of {cmd} is:\n{result.stdout}')
|
||||
+ log.info(f'STDERR of {cmd} is:\n{result.stderr}')
|
||||
+ if check_returncode:
|
||||
+ result.check_returncode()
|
||||
+ return result
|
||||
+
|
||||
+
|
||||
+def add_lines(lines, filename):
|
||||
+ """Add lines that are not already present at the end of a file"""
|
||||
+
|
||||
+ log.info(f'add_lines({lines}, {filename})')
|
||||
+ try:
|
||||
+ with open(filename, 'r') as fd:
|
||||
+ for line in fd:
|
||||
+ try:
|
||||
+ lines.remove(line.strip())
|
||||
+ except ValueError:
|
||||
+ pass
|
||||
+ except FileNotFoundError:
|
||||
+ pass
|
||||
+ if lines:
|
||||
+ with open(filename, 'a') as fd:
|
||||
+ for line in lines:
|
||||
+ fd.write(f'{line}\n')
|
||||
+
|
||||
+
|
||||
+def remove_lines(lines, filename):
|
||||
+ """Remove lines in a file"""
|
||||
+
|
||||
+ log.info(f'remove_lines({lines}, {filename})')
|
||||
+ file_lines = []
|
||||
+ with open(filename, 'r') as fd:
|
||||
+ for line in fd:
|
||||
+ if not line.strip() in lines:
|
||||
+ file_lines.append(line)
|
||||
+ with open(filename, 'w') as fd:
|
||||
+ for line in file_lines:
|
||||
+ fd.write(line)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="module")
|
||||
+def setup_snmp(topo_m2, request):
|
||||
+ """Install snmp and configure it
|
||||
+
|
||||
+ Returns the time just before dirsrv-snmp get restarted
|
||||
+ """
|
||||
+
|
||||
+ inst1 = topo_m2.ms["supplier1"]
|
||||
+ inst2 = topo_m2.ms["supplier2"]
|
||||
+
|
||||
+ # Check for the test prerequisites
|
||||
+ if os.getuid() != 0:
|
||||
+ pytest.skip('This test should be run by root superuser')
|
||||
+ return None
|
||||
+ if not inst1.with_systemd_running():
|
||||
+ pytest.skip('This test requires systemd')
|
||||
+ return None
|
||||
+ required_packages = {
|
||||
+ '389-ds-base-snmp': os.path.join(inst1.get_sbin_dir(), 'ldap-agent'),
|
||||
+ 'net-snmp': '/etc/snmp/snmpd.conf', }
|
||||
+ skip_msg = ""
|
||||
+ for package,file in required_packages.items():
|
||||
+ if not os.path.exists(file):
|
||||
+ skip_msg += f"Package {package} is not installed ({file} is missing).\n"
|
||||
+ if skip_msg != "":
|
||||
+ pytest.skip(f'This test requires the following package(s): {skip_msg}')
|
||||
+ return None
|
||||
+
|
||||
+ # Install snmp
|
||||
+ # run_cmd(['/usr/bin/dnf', 'install', '-y', 'net-snmp', 'net-snmp-utils', '389-ds-base-snmp'])
|
||||
+
|
||||
+ # Prepare the lines to add/remove in files:
|
||||
+ # master agentx
|
||||
+ # snmp user (user_name - authentication_password - private_password)
|
||||
+ # ldap_agent ds instances
|
||||
+ #
|
||||
+ # Adding rwuser and createUser lines is the same as running:
|
||||
+ # net-snmp-create-v3-user -A authentication_password -a SHA -X private_password -x AES user_name
|
||||
+ # but has the advantage of removing the user at cleanup phase
|
||||
+ #
|
||||
+ agent_cfg = '/etc/dirsrv/config/ldap-agent.conf'
|
||||
+ lines_dict = { '/etc/snmp/snmpd.conf' : ['master agentx', f'rwuser {SNMP_USER}'],
|
||||
+ '/var/lib/net-snmp/snmpd.conf' : [
|
||||
+ f'createUser {SNMP_USER} SHA "{SNMP_PASSWORD}" AES "{SNMP_PRIVATE}"',],
|
||||
+ agent_cfg : [] }
|
||||
+ for inst in topo_m2:
|
||||
+ lines_dict[agent_cfg].append(f'server slapd-{inst.serverid}')
|
||||
+
|
||||
+ # Prepare the cleanup
|
||||
+ def fin():
|
||||
+ run_cmd(['systemctl', 'stop', 'dirsrv-snmp'])
|
||||
+ if not DEBUGGING:
|
||||
+ run_cmd(['systemctl', 'stop', 'snmpd'])
|
||||
+ try:
|
||||
+ os.remove('/usr/share/snmp/mibs/redhat-directory.mib')
|
||||
+ except FileNotFoundError:
|
||||
+ pass
|
||||
+ for filename,lines in lines_dict.items():
|
||||
+ remove_lines(lines, filename)
|
||||
+ run_cmd(['systemctl', 'start', 'snmpd'])
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Copy RHDS MIB in default MIB search path (Ugly because I have not found how to change the search path)
|
||||
+ copyfile('/usr/share/dirsrv/mibs/redhat-directory.mib', '/usr/share/snmp/mibs/redhat-directory.mib')
|
||||
+
|
||||
+ run_cmd(['systemctl', 'stop', 'snmpd'])
|
||||
+ for filename,lines in lines_dict.items():
|
||||
+ add_lines(lines, filename)
|
||||
+
|
||||
+ run_cmd(['systemctl', 'start', 'snmpd'])
|
||||
+
|
||||
+ curtime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
+
|
||||
+ run_cmd(['systemctl', 'start', 'dirsrv-snmp'])
|
||||
+ return curtime
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(not os.path.exists('/usr/bin/snmpwalk'), reason="net-snmp-utils package is not installed")
|
||||
+def test_snmpwalk(topo_m2, setup_snmp):
|
||||
+ """snmp smoke tests.
|
||||
+
|
||||
+ :id: e5d29998-1c21-11ef-a654-482ae39447e5
|
||||
+ :setup: Two suppliers replication setup, snmp
|
||||
+ :steps:
|
||||
+ 1. use snmpwalk to display LDAP statistics
|
||||
+ 2. use snmpwalk to get the number of open connections
|
||||
+ :expectedresults:
|
||||
+ 1. Success and no messages in stderr
|
||||
+ 2. The number of open connections should be positive
|
||||
+ """
|
||||
+
|
||||
+ inst1 = topo_m2.ms["supplier1"]
|
||||
+ inst2 = topo_m2.ms["supplier2"]
|
||||
+
|
||||
+
|
||||
+ cmd = [ '/usr/bin/snmpwalk', '-v3', '-u', SNMP_USER, '-l', 'AuthPriv',
|
||||
+ '-m', '+RHDS-MIB', '-A', SNMP_PASSWORD, '-a', 'SHA',
|
||||
+ '-X', SNMP_PRIVATE, '-x', 'AES', 'localhost',
|
||||
+ LDAP_OID ]
|
||||
+ result = run_cmd(cmd)
|
||||
+ assert not result.stderr
|
||||
+
|
||||
+ cmd = [ '/usr/bin/snmpwalk', '-v3', '-u', SNMP_USER, '-l', 'AuthPriv',
|
||||
+ '-m', '+RHDS-MIB', '-A', SNMP_PASSWORD, '-a', 'SHA',
|
||||
+ '-X', SNMP_PRIVATE, '-x', 'AES', 'localhost',
|
||||
+ f'{LDAPCONNECTIONS_OID}.{inst1.port}', '-Ov' ]
|
||||
+ result = run_cmd(cmd)
|
||||
+ nbconns = int(result.stdout.split()[1])
|
||||
+ log.info(f'There are {nbconns} open connections on {inst1.serverid}')
|
||||
+ assert nbconns > 0
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(not selinux_present(), reason="SELinux is not enabled")
|
||||
+def test_snmp_avc(topo_m2, setup_snmp):
|
||||
+ """snmp smoke tests.
|
||||
+
|
||||
+ :id: fb79728e-1d0d-11ef-9213-482ae39447e5
|
||||
+ :setup: Two suppliers replication setup, snmp
|
||||
+ :steps:
|
||||
+ 1. Get the system journal about ldap-agent
|
||||
+ :expectedresults:
|
||||
+ 1. No AVC should be present
|
||||
+ """
|
||||
+ result = run_cmd(['journalctl', '-S', setup_snmp, '-g', 'ldap-agent'])
|
||||
+ assert not 'AVC' in result.stdout
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/slapd/agtmmap.c b/ldap/servers/slapd/agtmmap.c
|
||||
index bc5fe1ee1..4dc67dcfb 100644
|
||||
--- a/ldap/servers/slapd/agtmmap.c
|
||||
+++ b/ldap/servers/slapd/agtmmap.c
|
||||
@@ -34,6 +34,70 @@
|
||||
agt_mmap_context_t mmap_tbl[2] = {{AGT_MAP_UNINIT, -1, (caddr_t)-1},
|
||||
{AGT_MAP_UNINIT, -1, (caddr_t)-1}};
|
||||
|
||||
+#define CHECK_MAP_FAILURE(addr) ((addr)==NULL || (addr) == (caddr_t) -1)
|
||||
+
|
||||
+
|
||||
+/****************************************************************************
|
||||
+ *
|
||||
+ * agt_set_fmode () - try to increase file mode if some flags are missing.
|
||||
+ *
|
||||
+ *
|
||||
+ * Inputs:
|
||||
+ * fd -> The file descriptor.
|
||||
+ *
|
||||
+ * mode -> the wanted mode
|
||||
+ *
|
||||
+ * Outputs: None
|
||||
+ * Return Values: None
|
||||
+ *
|
||||
+ ****************************************************************************/
|
||||
+static void
|
||||
+agt_set_fmode(int fd, mode_t mode)
|
||||
+{
|
||||
+ /* ns-slapd umask is 0022 which is usually fine.
|
||||
+ * but ldap-agen needs S_IWGRP permission on snmp semaphore and mmap file
|
||||
+ * ( when SELinux is enforced process with uid=0 does not bypass the file permission
|
||||
+ * (unless the unfamous dac_override capability is set)
|
||||
+ * Changing umask could lead to race conditions so it is better to check the
|
||||
+ * file permission and change them if needed and if the process own the file.
|
||||
+ */
|
||||
+ struct stat fileinfo = {0};
|
||||
+ if (fstat(fd, &fileinfo) == 0 && fileinfo.st_uid == getuid() &&
|
||||
+ (fileinfo.st_mode & mode) != mode) {
|
||||
+ (void) fchmod(fd, fileinfo.st_mode | mode);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/****************************************************************************
|
||||
+ *
|
||||
+ * agt_sem_open () - Like sem_open but ignores umask
|
||||
+ *
|
||||
+ *
|
||||
+ * Inputs: see sem_open man page.
|
||||
+ * Outputs: see sem_open man page.
|
||||
+ * Return Values: see sem_open man page.
|
||||
+ *
|
||||
+ ****************************************************************************/
|
||||
+sem_t *
|
||||
+agt_sem_open(const char *name, int oflag, mode_t mode, unsigned int value)
|
||||
+{
|
||||
+ sem_t *sem = sem_open(name, oflag, mode, value);
|
||||
+ char *semname = NULL;
|
||||
+
|
||||
+ if (sem != NULL) {
|
||||
+ if (asprintf(&semname, "/dev/shm/sem.%s", name+1) > 0) {
|
||||
+ int fd = open(semname, O_RDONLY);
|
||||
+ if (fd >= 0) {
|
||||
+ agt_set_fmode(fd, mode);
|
||||
+ (void) close(fd);
|
||||
+ }
|
||||
+ free(semname);
|
||||
+ semname = NULL;
|
||||
+ }
|
||||
+ }
|
||||
+ return sem;
|
||||
+}
|
||||
+
|
||||
/****************************************************************************
|
||||
*
|
||||
* agt_mopen_stats () - open and Memory Map the stats file. agt_mclose_stats()
|
||||
@@ -52,7 +116,6 @@ agt_mmap_context_t mmap_tbl[2] = {{AGT_MAP_UNINIT, -1, (caddr_t)-1},
|
||||
* as defined in <errno.h>, otherwise.
|
||||
*
|
||||
****************************************************************************/
|
||||
-
|
||||
int
|
||||
agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
{
|
||||
@@ -64,6 +127,7 @@ agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
int err;
|
||||
size_t sz;
|
||||
struct stat fileinfo;
|
||||
+ mode_t rw_mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH;
|
||||
|
||||
switch (mode) {
|
||||
case O_RDONLY:
|
||||
@@ -128,10 +192,7 @@ agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
break;
|
||||
|
||||
case O_RDWR:
|
||||
- fd = open(path,
|
||||
- O_RDWR | O_CREAT,
|
||||
- S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
|
||||
-
|
||||
+ fd = open(path, O_RDWR | O_CREAT, rw_mode);
|
||||
if (fd < 0) {
|
||||
err = errno;
|
||||
#if (0)
|
||||
@@ -140,6 +201,7 @@ agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
rc = err;
|
||||
goto bail;
|
||||
}
|
||||
+ agt_set_fmode(fd, rw_mode);
|
||||
|
||||
if (fstat(fd, &fileinfo) != 0) {
|
||||
close(fd);
|
||||
diff --git a/ldap/servers/slapd/agtmmap.h b/ldap/servers/slapd/agtmmap.h
|
||||
index fb27ab2c4..99a8584a3 100644
|
||||
--- a/ldap/servers/slapd/agtmmap.h
|
||||
+++ b/ldap/servers/slapd/agtmmap.h
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
+#include <semaphore.h>
|
||||
#include <errno.h>
|
||||
#include "nspr.h"
|
||||
|
||||
@@ -188,6 +189,18 @@ int agt_mclose_stats(int hdl);
|
||||
|
||||
int agt_mread_stats(int hdl, struct hdr_stats_t *, struct ops_stats_t *, struct entries_stats_t *);
|
||||
|
||||
+/****************************************************************************
|
||||
+ *
|
||||
+ * agt_sem_open () - Like sem_open but ignores umask
|
||||
+ *
|
||||
+ *
|
||||
+ * Inputs: see sem_open man page.
|
||||
+ * Outputs: see sem_open man page.
|
||||
+ * Return Values: see sem_open man page.
|
||||
+ *
|
||||
+ ****************************************************************************/
|
||||
+sem_t *agt_sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
|
||||
+
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
|
||||
index b04fafde6..f1e48c6b1 100644
|
||||
--- a/ldap/servers/slapd/dse.c
|
||||
+++ b/ldap/servers/slapd/dse.c
|
||||
@@ -683,7 +683,7 @@ dse_read_one_file(struct dse *pdse, const char *filename, Slapi_PBlock *pb, int
|
||||
"The configuration file %s could not be accessed, error %d\n",
|
||||
filename, rc);
|
||||
rc = 0; /* Fail */
|
||||
- } else if ((prfd = PR_Open(filename, PR_RDONLY, SLAPD_DEFAULT_FILE_MODE)) == NULL) {
|
||||
+ } else if ((prfd = PR_Open(filename, PR_RDONLY, SLAPD_DEFAULT_DSE_FILE_MODE)) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "dse_read_one_file",
|
||||
"The configuration file %s could not be read. " SLAPI_COMPONENT_NAME_NSPR " %d (%s)\n",
|
||||
filename,
|
||||
@@ -871,7 +871,7 @@ dse_rw_permission_to_one_file(const char *name, int loglevel)
|
||||
PRFileDesc *prfd;
|
||||
|
||||
prfd = PR_Open(name, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
|
||||
- SLAPD_DEFAULT_FILE_MODE);
|
||||
+ SLAPD_DEFAULT_DSE_FILE_MODE);
|
||||
if (NULL == prfd) {
|
||||
prerr = PR_GetError();
|
||||
accesstype = "create";
|
||||
@@ -970,7 +970,7 @@ dse_write_file_nolock(struct dse *pdse)
|
||||
fpw.fpw_prfd = NULL;
|
||||
|
||||
if (NULL != pdse->dse_filename) {
|
||||
- if ((fpw.fpw_prfd = PR_Open(pdse->dse_tmpfile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, SLAPD_DEFAULT_FILE_MODE)) == NULL) {
|
||||
+ if ((fpw.fpw_prfd = PR_Open(pdse->dse_tmpfile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, SLAPD_DEFAULT_DSE_FILE_MODE)) == NULL) {
|
||||
rc = PR_GetOSError();
|
||||
slapi_log_err(SLAPI_LOG_ERR, "dse_write_file_nolock", "Cannot open "
|
||||
"temporary DSE file \"%s\" for update: OS error %d (%s)\n",
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 469874fd1..927576b70 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -238,6 +238,12 @@ typedef void (*VFPV)(); /* takes undefined arguments */
|
||||
*/
|
||||
|
||||
#define SLAPD_DEFAULT_FILE_MODE S_IRUSR | S_IWUSR
|
||||
+/* ldap_agent run as uid=root gid=dirsrv and requires S_IRGRP | S_IWGRP
|
||||
+ * on semaphore and mmap file if SELinux is enforced.
|
||||
+ */
|
||||
+#define SLAPD_DEFAULT_SNMP_FILE_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
|
||||
+/* ldap_agent run as uid=root gid=dirsrv and requires S_IRGRP on dse.ldif if SELinux is enforced. */
|
||||
+#define SLAPD_DEFAULT_DSE_FILE_MODE S_IRUSR | S_IWUSR | S_IRGRP
|
||||
#define SLAPD_DEFAULT_DIR_MODE S_IRWXU
|
||||
#define SLAPD_DEFAULT_IDLE_TIMEOUT 3600 /* seconds - 0 == never */
|
||||
#define SLAPD_DEFAULT_IDLE_TIMEOUT_STR "3600"
|
||||
diff --git a/ldap/servers/slapd/snmp_collator.c b/ldap/servers/slapd/snmp_collator.c
|
||||
index c998d4262..bd7020585 100644
|
||||
--- a/ldap/servers/slapd/snmp_collator.c
|
||||
+++ b/ldap/servers/slapd/snmp_collator.c
|
||||
@@ -474,7 +474,7 @@ static void
|
||||
snmp_collator_create_semaphore(void)
|
||||
{
|
||||
/* First just try to create the semaphore. This should usually just work. */
|
||||
- if ((stats_sem = sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
+ if ((stats_sem = agt_sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_SNMP_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
if (errno == EEXIST) {
|
||||
/* It appears that we didn't exit cleanly last time and left the semaphore
|
||||
* around. Recreate it since we don't know what state it is in. */
|
||||
@@ -486,7 +486,7 @@ snmp_collator_create_semaphore(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
- if ((stats_sem = sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
+ if ((stats_sem = agt_sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_SNMP_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
/* No dice */
|
||||
slapi_log_err(SLAPI_LOG_EMERG, "snmp_collator_create_semaphore",
|
||||
"Failed to create semaphore for stats file (/dev/shm/sem.%s). Error %d (%s).\n",
|
||||
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
|
||||
index 036664447..fca03383e 100644
|
||||
--- a/src/lib389/lib389/instance/setup.py
|
||||
+++ b/src/lib389/lib389/instance/setup.py
|
||||
@@ -10,6 +10,7 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
+import stat
|
||||
import pwd
|
||||
import grp
|
||||
import re
|
||||
@@ -773,6 +774,10 @@ class SetupDs(object):
|
||||
ldapi_autobind="on",
|
||||
)
|
||||
file_dse.write(dse_fmt)
|
||||
+ # Set minimum permission required by snmp ldap-agent
|
||||
+ status = os.fstat(file_dse.fileno())
|
||||
+ os.fchmod(file_dse.fileno(), status.st_mode | stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
+ os.chown(os.path.join(slapd['config_dir'], 'dse.ldif'), slapd['user_uid'], slapd['group_gid'])
|
||||
|
||||
self.log.info("Create file system structures ...")
|
||||
# Create all the needed paths
|
||||
diff --git a/wrappers/systemd-snmp.service.in b/wrappers/systemd-snmp.service.in
|
||||
index f18766cb4..d344367a0 100644
|
||||
--- a/wrappers/systemd-snmp.service.in
|
||||
+++ b/wrappers/systemd-snmp.service.in
|
||||
@@ -9,6 +9,7 @@ After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
+Group=@defaultgroup@
|
||||
PIDFile=/run/dirsrv/ldap-agent.pid
|
||||
ExecStart=@sbindir@/ldap-agent @configdir@/ldap-agent.conf
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
From 12870f410545fb055f664b588df2a2b7ab1c228e Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 4 Mar 2024 07:22:00 +0100
|
||||
Subject: [PATCH] Issue 5305 - OpenLDAP version autodetection doesn't work
|
||||
|
||||
Bug Description:
|
||||
An error is logged during a build in `mock` with Bash 4.4:
|
||||
|
||||
```
|
||||
checking for --with-libldap-r... ./configure: command substitution: line 22848: syntax error near unexpected token `>'
|
||||
./configure: command substitution: line 22848: `ldapsearch -VV 2> >(sed -n '/ldapsearch/ s/.*ldapsearch \([0-9]\+\.[0-9]\+\.[0-9]\+\) .*/\1/p')'
|
||||
no
|
||||
```
|
||||
|
||||
`mock` runs Bash as `sh` (POSIX mode). Support for process substitution
|
||||
in POSIX mode was added in version 5.1:
|
||||
https://lists.gnu.org/archive/html/bug-bash/2020-12/msg00002.html
|
||||
|
||||
> Process substitution is now available in posix mode.
|
||||
|
||||
Fix Description:
|
||||
* Add missing `BuildRequires` for openldap-clients
|
||||
* Replace process substitution with a pipe
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/5305
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
configure.ac | 2 +-
|
||||
rpm/389-ds-base.spec.in | 1 +
|
||||
2 files changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index ffc2aac14..a690765a3 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -912,7 +912,7 @@ AC_ARG_WITH(libldap-r, AS_HELP_STRING([--with-libldap-r],[Use lldap_r shared lib
|
||||
AC_SUBST(with_libldap_r)
|
||||
fi
|
||||
],
|
||||
-OPENLDAP_VERSION=`ldapsearch -VV 2> >(sed -n '/ldapsearch/ s/.*ldapsearch \([[[0-9]]]\+\.[[[0-9]]]\+\.[[[0-9]]]\+\) .*/\1/p')`
|
||||
+OPENLDAP_VERSION=`ldapsearch -VV 2>&1 | sed -n '/ldapsearch/ s/.*ldapsearch \([[[0-9]]]\+\.[[[0-9]]]\+\.[[[0-9]]]\+\) .*/\1/p'`
|
||||
AX_COMPARE_VERSION([$OPENLDAP_VERSION], [lt], [2.5], [ with_libldap_r=yes ], [ with_libldap_r=no ])
|
||||
AC_MSG_RESULT($with_libldap_r))
|
||||
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index cd86138ea..b8c14cd14 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -65,6 +65,7 @@ Provides: ldif2ldbm
|
||||
# Attach the buildrequires to the top level package:
|
||||
BuildRequires: nspr-devel
|
||||
BuildRequires: nss-devel >= 3.34
|
||||
+BuildRequires: openldap-clients
|
||||
BuildRequires: openldap-devel
|
||||
BuildRequires: libdb-devel
|
||||
BuildRequires: cyrus-sasl-devel
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,245 +0,0 @@
|
||||
From eca6f5fe18f768fd407d38c85624a5212bcf16ab Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Wed, 27 Sep 2023 15:40:33 -0700
|
||||
Subject: [PATCH] Issue 1925 - Add a CI test (#5936)
|
||||
|
||||
Description: Verify that the issue is not present. Cover the scenario when
|
||||
we remove existing VLVs, create new VLVs (with the same name) and then
|
||||
we do online re-indexing.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/1925
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
|
||||
(cherry picked from the 9633e8d32d28345409680f8e462fb4a53d3b4f83)
|
||||
---
|
||||
.../tests/suites/vlv/regression_test.py | 175 +++++++++++++++---
|
||||
1 file changed, 145 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 6ab709bd3..536fe950f 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2018 Red Hat, Inc.
|
||||
+# Copyright (C) 2023 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -9,12 +9,16 @@
|
||||
import pytest, time
|
||||
from lib389.tasks import *
|
||||
from lib389.utils import *
|
||||
-from lib389.topologies import topology_m2
|
||||
+from lib389.topologies import topology_m2, topology_st
|
||||
from lib389.replica import *
|
||||
from lib389._constants import *
|
||||
+from lib389.properties import TASK_WAIT
|
||||
from lib389.index import *
|
||||
from lib389.mappingTree import *
|
||||
from lib389.backend import *
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from ldap.controls.vlv import VLVRequestControl
|
||||
+from ldap.controls.sss import SSSRequestControl
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -22,6 +26,88 @@ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+def open_new_ldapi_conn(dsinstance):
|
||||
+ ldapurl, certdir = get_ldapurl_from_serverid(dsinstance)
|
||||
+ assert 'ldapi://' in ldapurl
|
||||
+ conn = ldap.initialize(ldapurl)
|
||||
+ conn.sasl_interactive_bind_s("", ldap.sasl.external())
|
||||
+ return conn
|
||||
+
|
||||
+
|
||||
+def check_vlv_search(conn):
|
||||
+ before_count=1
|
||||
+ after_count=3
|
||||
+ offset=3501
|
||||
+
|
||||
+ vlv_control = VLVRequestControl(criticality=True,
|
||||
+ before_count=before_count,
|
||||
+ after_count=after_count,
|
||||
+ offset=offset,
|
||||
+ content_count=0,
|
||||
+ greater_than_or_equal=None,
|
||||
+ context_id=None)
|
||||
+
|
||||
+ sss_control = SSSRequestControl(criticality=True, ordering_rules=['cn'])
|
||||
+ result = conn.search_ext_s(
|
||||
+ base='dc=example,dc=com',
|
||||
+ scope=ldap.SCOPE_SUBTREE,
|
||||
+ filterstr='(uid=*)',
|
||||
+ serverctrls=[vlv_control, sss_control]
|
||||
+ )
|
||||
+ imin = offset + 998 - before_count
|
||||
+ imax = offset + 998 + after_count
|
||||
+
|
||||
+ for i, (dn, entry) in enumerate(result, start=imin):
|
||||
+ assert i <= imax
|
||||
+ expected_dn = f'uid=testuser{i},ou=People,dc=example,dc=com'
|
||||
+ log.debug(f'found {dn} expected {expected_dn}')
|
||||
+ assert dn.lower() == expected_dn.lower()
|
||||
+
|
||||
+
|
||||
+def add_users(inst, users_num):
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ log.info(f'Adding {users_num} users')
|
||||
+ for i in range(0, users_num):
|
||||
+ uid = 1000 + i
|
||||
+ user_properties = {
|
||||
+ 'uid': f'testuser{uid}',
|
||||
+ 'cn': f'testuser{uid}',
|
||||
+ 'sn': 'user',
|
||||
+ 'uidNumber': str(uid),
|
||||
+ 'gidNumber': str(uid),
|
||||
+ 'homeDirectory': f'/home/testuser{uid}'
|
||||
+ }
|
||||
+ users.create(properties=user_properties)
|
||||
+
|
||||
+
|
||||
+
|
||||
+def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
+ scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
|
||||
+ vlv_searches = VLVSearch(inst)
|
||||
+ vlv_search_properties = {
|
||||
+ "objectclass": ["top", "vlvSearch"],
|
||||
+ "cn": f"{prefix}Srch",
|
||||
+ "vlvbase": basedn,
|
||||
+ "vlvfilter": "(uid=*)",
|
||||
+ "vlvscope": str(scope),
|
||||
+ }
|
||||
+ vlv_searches.create(
|
||||
+ basedn=f"cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_search_properties
|
||||
+ )
|
||||
+
|
||||
+ vlv_index = VLVIndex(inst)
|
||||
+ vlv_index_properties = {
|
||||
+ "objectclass": ["top", "vlvIndex"],
|
||||
+ "cn": f"{prefix}Idx",
|
||||
+ "vlvsort": vlvsort,
|
||||
+ }
|
||||
+ vlv_index.create(
|
||||
+ basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_index_properties
|
||||
+ )
|
||||
+ return vlv_searches, vlv_index
|
||||
+
|
||||
class BackendHandler:
|
||||
def __init__(self, inst, bedict, scope=ldap.SCOPE_ONELEVEL):
|
||||
self.inst = inst
|
||||
@@ -101,34 +187,6 @@ class BackendHandler:
|
||||
'dn' : dn}
|
||||
|
||||
|
||||
-def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
- scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
|
||||
- vlv_searches = VLVSearch(inst)
|
||||
- vlv_search_properties = {
|
||||
- "objectclass": ["top", "vlvSearch"],
|
||||
- "cn": f"{prefix}Srch",
|
||||
- "vlvbase": basedn,
|
||||
- "vlvfilter": "(uid=*)",
|
||||
- "vlvscope": str(scope),
|
||||
- }
|
||||
- vlv_searches.create(
|
||||
- basedn=f"cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
- properties=vlv_search_properties
|
||||
- )
|
||||
-
|
||||
- vlv_index = VLVIndex(inst)
|
||||
- vlv_index_properties = {
|
||||
- "objectclass": ["top", "vlvIndex"],
|
||||
- "cn": f"{prefix}Idx",
|
||||
- "vlvsort": vlvsort,
|
||||
- }
|
||||
- vlv_index.create(
|
||||
- basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
- properties=vlv_index_properties
|
||||
- )
|
||||
- return vlv_searches, vlv_index
|
||||
-
|
||||
-
|
||||
@pytest.fixture
|
||||
def vlv_setup_with_uid_mr(topology_st, request):
|
||||
inst = topology_st.standalone
|
||||
@@ -245,6 +303,62 @@ def test_bulk_import_when_the_backend_with_vlv_was_recreated(topology_m2):
|
||||
entries = M2.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(objectclass=*)")
|
||||
|
||||
|
||||
+def test_vlv_recreation_reindex(topology_st):
|
||||
+ """Test VLV recreation and reindexing.
|
||||
+
|
||||
+ :id: 29f4567f-4ac0-410f-bc99-a32e217a939f
|
||||
+ :setup: Standalone instance.
|
||||
+ :steps:
|
||||
+ 1. Create new VLVs and do the reindex.
|
||||
+ 2. Test the new VLVs.
|
||||
+ 3. Remove the existing VLVs.
|
||||
+ 4. Create new VLVs (with the same name).
|
||||
+ 5. Perform online re-indexing of the new VLVs.
|
||||
+ 6. Test the new VLVs.
|
||||
+ :expectedresults:
|
||||
+ 1. Should Success.
|
||||
+ 2. Should Success.
|
||||
+ 3. Should Success.
|
||||
+ 4. Should Success.
|
||||
+ 5. Should Success.
|
||||
+ 6. Should Success.
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ reindex_task = Tasks(inst)
|
||||
+
|
||||
+ # Create and test VLVs
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ add_users(inst, 5000)
|
||||
+
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ assert len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(cn=*)")) > 0
|
||||
+ check_vlv_search(conn)
|
||||
+
|
||||
+ # Remove and recreate VLVs
|
||||
+ vlv_index.delete()
|
||||
+ vlv_search.delete()
|
||||
+
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ assert len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(cn=*)")) > 0
|
||||
+ check_vlv_search(conn)
|
||||
+
|
||||
+
|
||||
def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
"""
|
||||
Testing vlv having specific matching rule
|
||||
@@ -288,6 +402,7 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
assert inst.status()
|
||||
|
||||
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
From af3fa90f91efda86f4337e8823bca6581ab61792 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 7 Feb 2025 09:43:08 +0100
|
||||
Subject: [PATCH] Issue 6494 - (2nd) Various errors when using extended
|
||||
matching rule on vlv sort filter
|
||||
|
||||
---
|
||||
.../tests/suites/indexes/regression_test.py | 40 +++++++++++++++++++
|
||||
1 file changed, 40 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
index 2196fb2ed..b5bcccc8f 100644
|
||||
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
@@ -11,17 +11,57 @@ import os
|
||||
import pytest
|
||||
import ldap
|
||||
from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX
|
||||
+from lib389.backend import Backend, Backends, DatabaseConfig
|
||||
from lib389.cos import CosClassicDefinition, CosClassicDefinitions, CosTemplate
|
||||
+from lib389.dbgen import dbgen_users
|
||||
from lib389.index import Indexes
|
||||
from lib389.backend import Backends
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.utils import ds_is_older
|
||||
from lib389.idm.nscontainer import nsContainer
|
||||
+from lib389.properties import TASK_WAIT
|
||||
+from lib389.tasks import Tasks, Task
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
|
||||
+SUFFIX2 = 'dc=example2,dc=com'
|
||||
+BENAME2 = 'be2'
|
||||
+
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def add_backend_and_ldif_50K_users(request, topo):
|
||||
+ """
|
||||
+ Add an empty backend and associated 50K users ldif file
|
||||
+ """
|
||||
+
|
||||
+ tasks = Tasks(topo.standalone)
|
||||
+ import_ldif = f'{topo.standalone.ldifdir}/be2_50K_users.ldif'
|
||||
+ be2 = Backend(topo.standalone)
|
||||
+ be2.create(properties={
|
||||
+ 'cn': BENAME2,
|
||||
+ 'nsslapd-suffix': SUFFIX2,
|
||||
+ },
|
||||
+ )
|
||||
+
|
||||
+ def fin():
|
||||
+ nonlocal be2
|
||||
+ if not DEBUGGING:
|
||||
+ be2.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ parent = f'ou=people,{SUFFIX2}'
|
||||
+ dbgen_users(topo.standalone, 50000, import_ldif, SUFFIX2, generic=True, parent=parent)
|
||||
+ assert tasks.importLDIF(
|
||||
+ suffix=SUFFIX2,
|
||||
+ input_file=import_ldif,
|
||||
+ args={TASK_WAIT: True}
|
||||
+ ) == 0
|
||||
+
|
||||
+ return import_ldif
|
||||
+
|
||||
@pytest.fixture(scope="function")
|
||||
def add_a_group_with_users(request, topo):
|
||||
"""
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
From 0ad0eb34972c99f30334d7d420f3056e0e794d74 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 7 Feb 2025 14:33:46 +0100
|
||||
Subject: [PATCH] Issue 6494 - (3rd) Various errors when using extended
|
||||
matching rule on vlv sort filter
|
||||
|
||||
(cherry picked from the commit f2f917ca55c34c81b578bce1dd5275abff6abb72)
|
||||
---
|
||||
dirsrvtests/tests/suites/vlv/regression_test.py | 8 ++++++--
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 536fe950f..d069fdbaf 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -16,12 +16,16 @@ from lib389.properties import TASK_WAIT
|
||||
from lib389.index import *
|
||||
from lib389.mappingTree import *
|
||||
from lib389.backend import *
|
||||
-from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.user import UserAccounts, UserAccount
|
||||
+from lib389.idm.organization import Organization
|
||||
+from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from ldap.controls.vlv import VLVRequestControl
|
||||
from ldap.controls.sss import SSSRequestControl
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
+DEMO_PW = 'secret12'
|
||||
+
|
||||
logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -169,7 +173,7 @@ class BackendHandler:
|
||||
'loginShell': '/bin/false',
|
||||
'userpassword': DEMO_PW })
|
||||
# Add regular user
|
||||
- add_users(self.inst, 10, suffix=suffix)
|
||||
+ add_users(self.inst, 10)
|
||||
# Removing ou2
|
||||
ou2.delete()
|
||||
# And export
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
From 52041811b200292af6670490c9ebc1f599439a22 Mon Sep 17 00:00:00 2001
|
||||
From: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
Date: Sat, 22 Mar 2025 01:25:25 +0900
|
||||
Subject: [PATCH] Issue 6494 - (4th) Various errors when using extended
|
||||
matching rule on vlv sort filter
|
||||
|
||||
test_vlv_with_mr uses vlv_setup_with_uid_mr fixture to setup backend
|
||||
and testusers. add_users function is called in beh.setup without any
|
||||
suffix for the created backend. As a result, testusers always are
|
||||
created in the DEFAULT_SUFFIX only by add_users function. Another test
|
||||
like test_vlv_recreation_reindex can create the same test user in
|
||||
DEFAULT_SUFFIX, and it caused the ALREADY_EXISTS failure in
|
||||
test_vlv_with_mr test.
|
||||
|
||||
In main branch, add_users have suffix argument. Test users are created
|
||||
on the specific suffix, and the backend is cleaned up after the test.
|
||||
This PR is to follow the same implementation.
|
||||
|
||||
Also, suppressing ldap.ALREADY_EXISTS makes the add_users func to be
|
||||
used easily.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6494
|
||||
---
|
||||
dirsrvtests/tests/suites/vlv/regression_test.py | 11 ++++++-----
|
||||
1 file changed, 6 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index d069fdbaf..e9408117b 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -21,6 +21,7 @@ from lib389.idm.organization import Organization
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from ldap.controls.vlv import VLVRequestControl
|
||||
from ldap.controls.sss import SSSRequestControl
|
||||
+from contextlib import suppress
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -68,8 +69,8 @@ def check_vlv_search(conn):
|
||||
assert dn.lower() == expected_dn.lower()
|
||||
|
||||
|
||||
-def add_users(inst, users_num):
|
||||
- users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+def add_users(inst, users_num, suffix=DEFAULT_SUFFIX):
|
||||
+ users = UserAccounts(inst, suffix)
|
||||
log.info(f'Adding {users_num} users')
|
||||
for i in range(0, users_num):
|
||||
uid = 1000 + i
|
||||
@@ -81,8 +82,8 @@ def add_users(inst, users_num):
|
||||
'gidNumber': str(uid),
|
||||
'homeDirectory': f'/home/testuser{uid}'
|
||||
}
|
||||
- users.create(properties=user_properties)
|
||||
-
|
||||
+ with suppress(ldap.ALREADY_EXISTS):
|
||||
+ users.create(properties=user_properties)
|
||||
|
||||
|
||||
def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
@@ -173,7 +174,7 @@ class BackendHandler:
|
||||
'loginShell': '/bin/false',
|
||||
'userpassword': DEMO_PW })
|
||||
# Add regular user
|
||||
- add_users(self.inst, 10)
|
||||
+ add_users(self.inst, 10, suffix=suffix)
|
||||
# Removing ou2
|
||||
ou2.delete()
|
||||
# And export
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,357 +0,0 @@
|
||||
From b812afe4da6db134c1221eb48a6155480e4c2cb3 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 14 Jan 2025 13:55:03 -0500
|
||||
Subject: [PATCH] Issue 6497 - lib389 - Configure replication for multiple
|
||||
suffixes (#6498)
|
||||
|
||||
Bug Description: When trying to set up replication across multiple suffixes -
|
||||
particularly if one of those suffixes is a subsuffix - lib389 fails to properly
|
||||
configure the replication agreements, service accounts, and required groups.
|
||||
The references to the replication_managers group and service account
|
||||
naming do not correctly account for non-default additional suffixes.
|
||||
|
||||
Fix Description: Ensure replication DNs and credentials are correctly tied to each suffix.
|
||||
Enable DSLdapObject.present method to compare values as
|
||||
a normalized DNs if they are DNs.
|
||||
Add a test (test_multi_subsuffix_replication) to verify multi-suffix
|
||||
replication across four suppliers.
|
||||
Fix tests that are related to repl service accounts.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6497
|
||||
|
||||
Reviewed: @progier389 (Thanks!)
|
||||
---
|
||||
.../tests/suites/ds_tools/replcheck_test.py | 4 +-
|
||||
.../suites/replication/acceptance_test.py | 153 ++++++++++++++++++
|
||||
.../cleanallruv_shutdown_crash_test.py | 4 +-
|
||||
.../suites/replication/regression_m2_test.py | 2 +-
|
||||
.../replication/tls_client_auth_repl_test.py | 4 +-
|
||||
src/lib389/lib389/_mapped_object.py | 21 ++-
|
||||
src/lib389/lib389/replica.py | 10 +-
|
||||
7 files changed, 182 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_tools/replcheck_test.py b/dirsrvtests/tests/suites/ds_tools/replcheck_test.py
|
||||
index f61fc432d..dfa1d9423 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_tools/replcheck_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_tools/replcheck_test.py
|
||||
@@ -67,10 +67,10 @@ def topo_tls_ldapi(topo):
|
||||
|
||||
# Create the replication dns
|
||||
services = ServiceAccounts(m1, DEFAULT_SUFFIX)
|
||||
- repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
|
||||
+ repl_m1 = services.get(f'{DEFAULT_SUFFIX}:{m1.host}:{m1.sslport}')
|
||||
repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())
|
||||
|
||||
- repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
|
||||
+ repl_m2 = services.get(f'{DEFAULT_SUFFIX}:{m2.host}:{m2.sslport}')
|
||||
repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())
|
||||
|
||||
# Check the replication is "done".
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index d1cfa8bdb..fc8622051 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -9,6 +9,7 @@
|
||||
import pytest
|
||||
import logging
|
||||
import time
|
||||
+from lib389.backend import Backend
|
||||
from lib389.replica import Replicas
|
||||
from lib389.tasks import *
|
||||
from lib389.utils import *
|
||||
@@ -325,6 +326,158 @@ def test_modify_stripattrs(topo_m4):
|
||||
assert attr_value in entries[0].data['nsds5replicastripattrs']
|
||||
|
||||
|
||||
+def test_multi_subsuffix_replication(topo_m4):
|
||||
+ """Check that replication works with multiple subsuffixes
|
||||
+
|
||||
+ :id: ac1aaeae-173e-48e7-847f-03b9867443c4
|
||||
+ :setup: Four suppliers replication setup
|
||||
+ :steps:
|
||||
+ 1. Create additional suffixes
|
||||
+ 2. Setup replication for all suppliers
|
||||
+ 3. Generate test data for each suffix (add, modify, remove)
|
||||
+ 4. Wait for replication to complete across all suppliers for each suffix
|
||||
+ 5. Check that all expected data is present on all suppliers
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success (the data is replicated everywhere)
|
||||
+ """
|
||||
+
|
||||
+ SUFFIX_2 = "dc=test2"
|
||||
+ SUFFIX_3 = f"dc=test3,{DEFAULT_SUFFIX}"
|
||||
+ all_suffixes = [DEFAULT_SUFFIX, SUFFIX_2, SUFFIX_3]
|
||||
+
|
||||
+ test_users_by_suffix = {suffix: [] for suffix in all_suffixes}
|
||||
+ created_backends = []
|
||||
+
|
||||
+ suppliers = [
|
||||
+ topo_m4.ms["supplier1"],
|
||||
+ topo_m4.ms["supplier2"],
|
||||
+ topo_m4.ms["supplier3"],
|
||||
+ topo_m4.ms["supplier4"]
|
||||
+ ]
|
||||
+
|
||||
+ try:
|
||||
+ # Setup additional backends and replication for the new suffixes
|
||||
+ for suffix in [SUFFIX_2, SUFFIX_3]:
|
||||
+ repl = ReplicationManager(suffix)
|
||||
+ for supplier in suppliers:
|
||||
+ # Create a new backend for this suffix
|
||||
+ props = {
|
||||
+ 'cn': f'userRoot_{suffix.split(",")[0][3:]}',
|
||||
+ 'nsslapd-suffix': suffix
|
||||
+ }
|
||||
+ be = Backend(supplier)
|
||||
+ be.create(properties=props)
|
||||
+ be.create_sample_entries('001004002')
|
||||
+
|
||||
+ # Track the backend so we can remove it later
|
||||
+ created_backends.append((supplier, props['cn']))
|
||||
+
|
||||
+ # Enable replication
|
||||
+ if supplier == suppliers[0]:
|
||||
+ repl.create_first_supplier(supplier)
|
||||
+ else:
|
||||
+ repl.join_supplier(suppliers[0], supplier)
|
||||
+
|
||||
+ # Create a full mesh topology for this suffix
|
||||
+ for i, supplier_i in enumerate(suppliers):
|
||||
+ for j, supplier_j in enumerate(suppliers):
|
||||
+ if i != j:
|
||||
+ repl.ensure_agreement(supplier_i, supplier_j)
|
||||
+
|
||||
+ # Generate test data for each suffix (add, modify, remove)
|
||||
+ for suffix in all_suffixes:
|
||||
+ # Create some user entries in supplier1
|
||||
+ for i in range(20):
|
||||
+ user_dn = f'uid=test_user_{i},{suffix}'
|
||||
+ test_user = UserAccount(suppliers[0], user_dn)
|
||||
+ test_user.create(properties={
|
||||
+ 'uid': f'test_user_{i}',
|
||||
+ 'cn': f'Test User {i}',
|
||||
+ 'sn': f'User{i}',
|
||||
+ 'userPassword': 'password',
|
||||
+ 'uidNumber': str(1000 + i),
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': f'/home/test_user_{i}'
|
||||
+ })
|
||||
+ test_users_by_suffix[suffix].append(test_user)
|
||||
+
|
||||
+ # Perform modifications on these entries
|
||||
+ for user in test_users_by_suffix[suffix]:
|
||||
+ # Add some attributes
|
||||
+ for j in range(3):
|
||||
+ user.add('description', f'Description {j}')
|
||||
+ # Replace an attribute
|
||||
+ user.replace('cn', f'Modified User {user.get_attr_val_utf8("uid")}')
|
||||
+ # Delete the attributes we added
|
||||
+ for j in range(3):
|
||||
+ try:
|
||||
+ user.remove('description', f'Description {j}')
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+ # Wait for replication to complete across all suppliers, for each suffix
|
||||
+ for suffix in all_suffixes:
|
||||
+ repl = ReplicationManager(suffix)
|
||||
+ for i, supplier_i in enumerate(suppliers):
|
||||
+ for j, supplier_j in enumerate(suppliers):
|
||||
+ if i != j:
|
||||
+ repl.wait_for_replication(supplier_i, supplier_j)
|
||||
+
|
||||
+ # Verify that each user and modification replicated to all suppliers
|
||||
+ for suffix in all_suffixes:
|
||||
+ for i in range(20):
|
||||
+ user_dn = f'uid=test_user_{i},{suffix}'
|
||||
+ # Retrieve this user from all suppliers
|
||||
+ all_user_objs = topo_m4.all_get_dsldapobject(user_dn, UserAccount)
|
||||
+ # Ensure it exists in all 4 suppliers
|
||||
+ assert len(all_user_objs) == 4, (
|
||||
+ f"User {user_dn} not found on all suppliers. "
|
||||
+ f"Found only on {len(all_user_objs)} suppliers."
|
||||
+ )
|
||||
+ # Check modifications: 'cn' should now be 'Modified User test_user_{i}'
|
||||
+ for user_obj in all_user_objs:
|
||||
+ expected_cn = f"Modified User test_user_{i}"
|
||||
+ actual_cn = user_obj.get_attr_val_utf8("cn")
|
||||
+ assert actual_cn == expected_cn, (
|
||||
+ f"User {user_dn} has unexpected 'cn': {actual_cn} "
|
||||
+ f"(expected '{expected_cn}') on supplier {user_obj._instance.serverid}"
|
||||
+ )
|
||||
+ # And check that 'description' attributes were removed
|
||||
+ desc_vals = user_obj.get_attr_vals_utf8('description')
|
||||
+ for j in range(3):
|
||||
+ assert f"Description {j}" not in desc_vals, (
|
||||
+ f"User {user_dn} on supplier {user_obj._instance.serverid} "
|
||||
+ f"still has 'Description {j}'"
|
||||
+ )
|
||||
+ finally:
|
||||
+ for suffix, test_users in test_users_by_suffix.items():
|
||||
+ for user in test_users:
|
||||
+ try:
|
||||
+ if user.exists():
|
||||
+ user.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+ for suffix in [SUFFIX_2, SUFFIX_3]:
|
||||
+ repl = ReplicationManager(suffix)
|
||||
+ for supplier in suppliers:
|
||||
+ try:
|
||||
+ repl.remove_supplier(supplier)
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+ for (supplier, backend_name) in created_backends:
|
||||
+ be = Backend(supplier, backend_name)
|
||||
+ try:
|
||||
+ be.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
def test_new_suffix(topo_m4, new_suffix):
|
||||
"""Check that we can enable replication on a new suffix
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py b/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py
|
||||
index b4b74e339..fe9955e7e 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py
|
||||
@@ -66,10 +66,10 @@ def test_clean_shutdown_crash(topology_m2):
|
||||
|
||||
log.info('Creating replication dns')
|
||||
services = ServiceAccounts(m1, DEFAULT_SUFFIX)
|
||||
- repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
|
||||
+ repl_m1 = services.get(f'{DEFAULT_SUFFIX}:{m1.host}:{m1.sslport}')
|
||||
repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())
|
||||
|
||||
- repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
|
||||
+ repl_m2 = services.get(f'{DEFAULT_SUFFIX}:{m2.host}:{m2.sslport}')
|
||||
repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())
|
||||
|
||||
log.info('Changing auth type')
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index 72d4b9f89..9c707615f 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -64,7 +64,7 @@ class _AgmtHelper:
|
||||
self.binddn = f'cn={cn},cn=config'
|
||||
else:
|
||||
self.usedn = False
|
||||
- self.cn = f'{self.from_inst.host}:{self.from_inst.sslport}'
|
||||
+ self.cn = ldap.dn.escape_dn_chars(f'{DEFAULT_SUFFIX}:{self.from_inst.host}:{self.from_inst.sslport}')
|
||||
self.binddn = f'cn={self.cn}, ou=Services, {DEFAULT_SUFFIX}'
|
||||
self.original_state = []
|
||||
self._pass = False
|
||||
diff --git a/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py b/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py
|
||||
index a00dc5b78..ca17554c7 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py
|
||||
@@ -56,10 +56,10 @@ def tls_client_auth(topo_m2):
|
||||
|
||||
# Create the replication dns
|
||||
services = ServiceAccounts(m1, DEFAULT_SUFFIX)
|
||||
- repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
|
||||
+ repl_m1 = services.get(f'{DEFAULT_SUFFIX}:{m1.host}:{m1.sslport}')
|
||||
repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())
|
||||
|
||||
- repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
|
||||
+ repl_m2 = services.get(f'{DEFAULT_SUFFIX}:{m2.host}:{m2.sslport}')
|
||||
repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())
|
||||
|
||||
# Check the replication is "done".
|
||||
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
|
||||
index b7391d8cc..ae00c95d0 100644
|
||||
--- a/src/lib389/lib389/_mapped_object.py
|
||||
+++ b/src/lib389/lib389/_mapped_object.py
|
||||
@@ -19,7 +19,7 @@ from lib389._constants import DIRSRV_STATE_ONLINE
|
||||
from lib389._mapped_object_lint import DSLint, DSLints
|
||||
from lib389.utils import (
|
||||
ensure_bytes, ensure_str, ensure_int, ensure_list_bytes, ensure_list_str,
|
||||
- ensure_list_int, display_log_value, display_log_data
|
||||
+ ensure_list_int, display_log_value, display_log_data, is_a_dn, normalizeDN
|
||||
)
|
||||
|
||||
# This function filter and term generation provided thanks to
|
||||
@@ -292,15 +292,28 @@ class DSLdapObject(DSLogging, DSLint):
|
||||
_search_ext_s(self._instance,self._dn, ldap.SCOPE_BASE, self._object_filter, attrlist=[attr, ],
|
||||
serverctrls=self._server_controls, clientctrls=self._client_controls,
|
||||
escapehatch='i am sure')[0]
|
||||
- values = self.get_attr_vals_bytes(attr)
|
||||
+ values = self.get_attr_vals_utf8(attr)
|
||||
self._log.debug("%s contains %s" % (self._dn, values))
|
||||
|
||||
if value is None:
|
||||
# We are just checking if SOMETHING is present ....
|
||||
return len(values) > 0
|
||||
+
|
||||
+ # Otherwise, we are checking a specific value
|
||||
+ if is_a_dn(value):
|
||||
+ normalized_value = normalizeDN(value)
|
||||
else:
|
||||
- # Check if a value really does exist.
|
||||
- return ensure_bytes(value).lower() in [x.lower() for x in values]
|
||||
+ normalized_value = ensure_bytes(value).lower()
|
||||
+
|
||||
+ # Normalize each returned value depending on whether it is a DN
|
||||
+ normalized_values = []
|
||||
+ for v in values:
|
||||
+ if is_a_dn(v):
|
||||
+ normalized_values.append(normalizeDN(v))
|
||||
+ else:
|
||||
+ normalized_values.append(ensure_bytes(v.lower()))
|
||||
+
|
||||
+ return normalized_value in normalized_values
|
||||
|
||||
def add(self, key, value):
|
||||
"""Add an attribute with a value
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 1f321972d..cd46e86d5 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -2011,7 +2011,7 @@ class ReplicationManager(object):
|
||||
return repl_group
|
||||
else:
|
||||
try:
|
||||
- repl_group = groups.get('replication_managers')
|
||||
+ repl_group = groups.get(dn=f'cn=replication_managers,{self._suffix}')
|
||||
return repl_group
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
self._log.warning("{} doesn't have cn=replication_managers,{} entry \
|
||||
@@ -2035,7 +2035,7 @@ class ReplicationManager(object):
|
||||
services = ServiceAccounts(from_instance, self._suffix)
|
||||
# Generate the password and save the credentials
|
||||
# for putting them into agreements in the future
|
||||
- service_name = '{}:{}'.format(to_instance.host, port)
|
||||
+ service_name = f'{self._suffix}:{to_instance.host}:{port}'
|
||||
creds = password_generate()
|
||||
repl_service = services.ensure_state(properties={
|
||||
'cn': service_name,
|
||||
@@ -2299,7 +2299,7 @@ class ReplicationManager(object):
|
||||
Internal Only.
|
||||
"""
|
||||
|
||||
- rdn = '{}:{}'.format(from_instance.host, from_instance.sslport)
|
||||
+ rdn = f'{self._suffix}:{from_instance.host}:{from_instance.sslport}'
|
||||
try:
|
||||
creds = self._repl_creds[rdn]
|
||||
except KeyError:
|
||||
@@ -2499,8 +2499,8 @@ class ReplicationManager(object):
|
||||
# Touch something then wait_for_replication.
|
||||
from_groups = Groups(from_instance, basedn=self._suffix, rdn=None)
|
||||
to_groups = Groups(to_instance, basedn=self._suffix, rdn=None)
|
||||
- from_group = from_groups.get('replication_managers')
|
||||
- to_group = to_groups.get('replication_managers')
|
||||
+ from_group = from_groups.get(dn=f'cn=replication_managers,{self._suffix}')
|
||||
+ to_group = to_groups.get(dn=f'cn=replication_managers,{self._suffix}')
|
||||
|
||||
change = str(uuid.uuid4())
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
From ebe986c78c6cd4e1f10172d8a8a11faf814fbc22 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 6 Mar 2025 16:49:53 -0500
|
||||
Subject: [PATCH] Issue 6655 - fix replication release replica decoding error
|
||||
|
||||
Description:
|
||||
|
||||
When a start replication session extended op is received acquire and
|
||||
release exclusive access before returning the result to the client.
|
||||
Otherwise there is a race condition where a "end" replication extended
|
||||
op can arrive before the replica is released and that leads to a
|
||||
decoding error on the other replica.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6655
|
||||
|
||||
Reviewed by: spichugi, tbordaz, and vashirov(Thanks!!!)
|
||||
---
|
||||
.../suites/replication/acceptance_test.py | 12 ++++++++++
|
||||
ldap/servers/plugins/replication/repl_extop.c | 24 ++++++++++++-------
|
||||
2 files changed, 27 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index fc8622051..0f18edb44 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -1,5 +1,9 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+<<<<<<< HEAD
|
||||
# Copyright (C) 2021 Red Hat, Inc.
|
||||
+=======
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+>>>>>>> a623c3f90 (Issue 6655 - fix replication release replica decoding error)
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -453,6 +457,13 @@ def test_multi_subsuffix_replication(topo_m4):
|
||||
f"User {user_dn} on supplier {user_obj._instance.serverid} "
|
||||
f"still has 'Description {j}'"
|
||||
)
|
||||
+
|
||||
+ # Check there are no decoding errors
|
||||
+ assert not topo_m4.ms["supplier1"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier2"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier3"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier4"].ds_error_log.match('.*decoding failed.*')
|
||||
+
|
||||
finally:
|
||||
for suffix, test_users in test_users_by_suffix.items():
|
||||
for user in test_users:
|
||||
@@ -507,6 +518,7 @@ def test_new_suffix(topo_m4, new_suffix):
|
||||
repl.remove_supplier(m1)
|
||||
repl.remove_supplier(m2)
|
||||
|
||||
+
|
||||
def test_many_attrs(topo_m4, create_entry):
|
||||
"""Check a replication with many attributes (add and delete)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
|
||||
index 14b756df1..dacc611c0 100644
|
||||
--- a/ldap/servers/plugins/replication/repl_extop.c
|
||||
+++ b/ldap/servers/plugins/replication/repl_extop.c
|
||||
@@ -1134,6 +1134,12 @@ send_response:
|
||||
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID);
|
||||
}
|
||||
|
||||
+ /* connext (release our hold on it at least) */
|
||||
+ if (NULL != connext) {
|
||||
+ /* don't free it, just let go of it */
|
||||
+ consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
+ }
|
||||
+
|
||||
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval);
|
||||
slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name,
|
||||
"multimaster_extop_StartNSDS50ReplicationRequest - "
|
||||
@@ -1251,12 +1257,6 @@ send_response:
|
||||
if (NULL != ruv_bervals) {
|
||||
ber_bvecfree(ruv_bervals);
|
||||
}
|
||||
- /* connext (our hold on it at least) */
|
||||
- if (NULL != connext) {
|
||||
- /* don't free it, just let go of it */
|
||||
- consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
- connext = NULL;
|
||||
- }
|
||||
|
||||
return return_value;
|
||||
}
|
||||
@@ -1389,6 +1389,13 @@ multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
send_response:
|
||||
+ /* connext (release our hold on it at least) */
|
||||
+ if (NULL != connext) {
|
||||
+ /* don't free it, just let go of it */
|
||||
+ consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
+ connext = NULL;
|
||||
+ }
|
||||
+
|
||||
/* Send the response code */
|
||||
if ((resp_bere = der_alloc()) == NULL) {
|
||||
goto free_and_return;
|
||||
@@ -1419,11 +1426,10 @@ free_and_return:
|
||||
if (NULL != resp_bval) {
|
||||
ber_bvfree(resp_bval);
|
||||
}
|
||||
- /* connext (our hold on it at least) */
|
||||
+ /* connext (release our hold on it if not already released) */
|
||||
if (NULL != connext) {
|
||||
/* don't free it, just let go of it */
|
||||
consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
- connext = NULL;
|
||||
}
|
||||
|
||||
return return_value;
|
||||
@@ -1516,7 +1522,7 @@ multimaster_extop_abort_cleanruv(Slapi_PBlock *pb)
|
||||
rid);
|
||||
}
|
||||
/*
|
||||
- * Get the replica
|
||||
+ * Get the replica
|
||||
*/
|
||||
if ((r = replica_get_replica_from_root(repl_root)) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "multimaster_extop_abort_cleanruv - "
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
From 5b12463bfeb518f016acb14bc118b5f8ad3eef5e Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 15 May 2025 09:22:22 +0200
|
||||
Subject: [PATCH] Issue 6655 - fix merge conflict
|
||||
|
||||
---
|
||||
dirsrvtests/tests/suites/replication/acceptance_test.py | 4 ----
|
||||
1 file changed, 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index 0f18edb44..6b5186127 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -1,9 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-<<<<<<< HEAD
|
||||
-# Copyright (C) 2021 Red Hat, Inc.
|
||||
-=======
|
||||
# Copyright (C) 2025 Red Hat, Inc.
|
||||
->>>>>>> a623c3f90 (Issue 6655 - fix replication release replica decoding error)
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,291 +0,0 @@
|
||||
From 8d62124fb4d0700378b6f0669cc9d47338a8151c Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 25 Mar 2025 09:20:50 +0100
|
||||
Subject: [PATCH] Issue 6571 - Nested group does not receive memberOf attribute
|
||||
(#6679)
|
||||
|
||||
Bug description:
|
||||
There is a risk to create a loop in group membership.
|
||||
For example G2 is member of G1 and G1 is member of G2.
|
||||
Memberof plugins iterates from a node to its ancestors
|
||||
to update the 'memberof' values of the node.
|
||||
The plugin uses a valueset ('already_seen_ndn_vals')
|
||||
to keep the track of the nodes it already visited.
|
||||
It uses this valueset to detect a possible loop and
|
||||
in that case it does not add the ancestor as the
|
||||
memberof value of the node.
|
||||
This is an error in case there are multiples paths
|
||||
up to an ancestor.
|
||||
|
||||
Fix description:
|
||||
The ancestor should be added to the node systematically,
|
||||
just in case the ancestor is in 'already_seen_ndn_vals'
|
||||
it skips the final recursion
|
||||
|
||||
fixes: #6571
|
||||
|
||||
Reviewed by: Pierre Rogier, Mark Reynolds (Thanks !!!)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 109 ++++++++++++++++++
|
||||
.../tests/suites/plugins/memberof_test.py | 5 +
|
||||
ldap/servers/plugins/memberof/memberof.c | 52 ++++-----
|
||||
3 files changed, 137 insertions(+), 29 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 4c681a909..dba908975 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -467,6 +467,21 @@ def _find_memberof_ext(server, user_dn=None, group_dn=None, find_result=True):
|
||||
else:
|
||||
assert (not found)
|
||||
|
||||
+def _check_membership(server, entry, expected_members, expected_memberof):
|
||||
+ assert server
|
||||
+ assert entry
|
||||
+
|
||||
+ memberof = entry.get_attr_vals('memberof')
|
||||
+ member = entry.get_attr_vals('member')
|
||||
+ assert len(member) == len(expected_members)
|
||||
+ assert len(memberof) == len(expected_memberof)
|
||||
+ for e in expected_members:
|
||||
+ server.log.info("Checking %s has member %s" % (entry.dn, e.dn))
|
||||
+ assert e.dn.encode() in member
|
||||
+ for e in expected_memberof:
|
||||
+ server.log.info("Checking %s is member of %s" % (entry.dn, e.dn))
|
||||
+ assert e.dn.encode() in memberof
|
||||
+
|
||||
|
||||
@pytest.mark.ds49161
|
||||
def test_memberof_group(topology_st):
|
||||
@@ -535,6 +550,100 @@ def test_memberof_group(topology_st):
|
||||
_find_memberof_ext(inst, dn1, g2n, True)
|
||||
_find_memberof_ext(inst, dn2, g2n, True)
|
||||
|
||||
+def test_multipaths(topology_st, request):
|
||||
+ """Test memberof succeeds to update memberof when
|
||||
+ there are multiple paths from a leaf to an intermediate node
|
||||
+
|
||||
+ :id: 35aa704a-b895-4153-9dcb-1e8a13612ebf
|
||||
+
|
||||
+ :setup: Single instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Create a graph G1->U1, G2->G21->U1
|
||||
+ 2. Add G2 as member of G1: G1->U1, G1->G2->G21->U1
|
||||
+ 3. Check members and memberof in entries G1,G2,G21,User1
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Graph should be created
|
||||
+ 2. succeed
|
||||
+ 3. Membership is okay
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.enable()
|
||||
+ memberof.replace('memberOfEntryScope', SUFFIX)
|
||||
+ if (memberof.get_memberofdeferredupdate() and memberof.get_memberofdeferredupdate().lower() == "on"):
|
||||
+ delay = 3
|
||||
+ else:
|
||||
+ delay = 0
|
||||
+ inst.restart()
|
||||
+
|
||||
+ #
|
||||
+ # Create the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ---------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 ----> Grp21 ------/
|
||||
+ #
|
||||
+ users = UserAccounts(inst, SUFFIX, rdn=None)
|
||||
+ user1 = users.create(properties={'uid': "user1",
|
||||
+ 'cn': "user1",
|
||||
+ 'sn': 'SN',
|
||||
+ 'description': 'leaf',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/user1'
|
||||
+ })
|
||||
+ group = Groups(inst, SUFFIX, rdn=None)
|
||||
+ g1 = group.create(properties={'cn': 'group1',
|
||||
+ 'member': user1.dn,
|
||||
+ 'description': 'group1'})
|
||||
+ g21 = group.create(properties={'cn': 'group21',
|
||||
+ 'member': user1.dn,
|
||||
+ 'description': 'group21'})
|
||||
+ g2 = group.create(properties={'cn': 'group2',
|
||||
+ 'member': [g21.dn],
|
||||
+ 'description': 'group2'})
|
||||
+
|
||||
+ # Enable debug logs if necessary
|
||||
+ #inst.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #inst.config.set('nsslapd-accesslog-level','260')
|
||||
+ #inst.config.set('nsslapd-plugin-logging', 'on')
|
||||
+ #inst.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #inst.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # \ ^
|
||||
+ # \ /
|
||||
+ # --> Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.add_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[g2, user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g1])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ def fin():
|
||||
+ try:
|
||||
+ user1.delete()
|
||||
+ g1.delete()
|
||||
+ g2.delete()
|
||||
+ g21.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+ request.addfinalizer(fin)
|
||||
|
||||
def _config_memberof_entrycache_on_modrdn_failure(server):
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/memberof_test.py b/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
index 2de1389fd..621c45daf 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
@@ -2168,9 +2168,14 @@ def test_complex_group_scenario_6(topology_st):
|
||||
|
||||
# add Grp[1-4] (uniqueMember) to grp5
|
||||
# it creates a membership loop !!!
|
||||
+ topology_st.standalone.config.replace('nsslapd-errorlog-level', '65536')
|
||||
mods = [(ldap.MOD_ADD, 'uniqueMember', memofegrp020_5)]
|
||||
for grp in [memofegrp020_1, memofegrp020_2, memofegrp020_3, memofegrp020_4]:
|
||||
topology_st.standalone.modify_s(ensure_str(grp), mods)
|
||||
+ topology_st.standalone.config.replace('nsslapd-errorlog-level', '0')
|
||||
+
|
||||
+ results = topology_st.standalone.ds_error_log.match('.*detecting a loop in group.*')
|
||||
+ assert results
|
||||
|
||||
time.sleep(5)
|
||||
# assert user[1-4] are member of grp20_[1-4]
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index e75b99b14..32bdcf3f1 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1592,7 +1592,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
ht_grp = ancestors_cache_lookup(config, (const void *)ndn);
|
||||
if (ht_grp) {
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%x)\n", ndn, ht_grp);
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%lx)\n", ndn, (ulong) ht_grp);
|
||||
#endif
|
||||
add_ancestors_cbdata(ht_grp, callback_data);
|
||||
*cached = 1;
|
||||
@@ -1600,7 +1600,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
}
|
||||
}
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", ndn);
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", slapi_sdn_get_ndn(sdn));
|
||||
#endif
|
||||
|
||||
/* Escape the dn, and build the search filter. */
|
||||
@@ -3233,7 +3233,8 @@ cache_ancestors(MemberOfConfig *config, Slapi_Value **member_ndn_val, memberof_g
|
||||
return;
|
||||
}
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- if (double_check = ancestors_cache_lookup(config, (const void*) key)) {
|
||||
+ double_check = ancestors_cache_lookup(config, (const void*) key);
|
||||
+ if (double_check) {
|
||||
dump_cache_entry(double_check, "read back");
|
||||
}
|
||||
#endif
|
||||
@@ -3263,13 +3264,13 @@ merge_ancestors(Slapi_Value **member_ndn_val, memberof_get_groups_data *v1, memb
|
||||
sval_dn = slapi_value_new_string(slapi_value_get_string(sval));
|
||||
if (sval_dn) {
|
||||
/* Use the normalized dn from v1 to search it
|
||||
- * in v2
|
||||
- */
|
||||
+ * in v2
|
||||
+ */
|
||||
val_sdn = slapi_sdn_new_dn_byval(slapi_value_get_string(sval_dn));
|
||||
sval_ndn = slapi_value_new_string(slapi_sdn_get_ndn(val_sdn));
|
||||
if (!slapi_valueset_find(
|
||||
((memberof_get_groups_data *)v2)->config->group_slapiattrs[0], v2_group_norm_vals, sval_ndn)) {
|
||||
-/* This ancestor was not already present in v2 => Add it
|
||||
+ /* This ancestor was not already present in v2 => Add it
|
||||
* Using slapi_valueset_add_value it consumes val
|
||||
* so do not free sval
|
||||
*/
|
||||
@@ -3318,7 +3319,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn, memberof_get
|
||||
|
||||
merge_ancestors(&member_ndn_val, &member_data, data);
|
||||
if (!cached && member_data.use_cache)
|
||||
- cache_ancestors(config, &member_ndn_val, &member_data);
|
||||
+ cache_ancestors(config, &member_ndn_val, data);
|
||||
|
||||
slapi_value_free(&member_ndn_val);
|
||||
slapi_valueset_free(groupvals);
|
||||
@@ -3379,25 +3380,6 @@ memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
- /* Have we been here before? Note that we don't loop through all of the group_slapiattrs
|
||||
- * in config. We only need this attribute for it's syntax so the comparison can be
|
||||
- * performed. Since all of the grouping attributes are validated to use the Dinstinguished
|
||||
- * Name syntax, we can safely just use the first group_slapiattr. */
|
||||
- if (slapi_valueset_find(
|
||||
- ((memberof_get_groups_data *)callback_data)->config->group_slapiattrs[0], already_seen_ndn_vals, group_ndn_val)) {
|
||||
- /* we either hit a recursive grouping, or an entry is
|
||||
- * a member of a group through multiple paths. Either
|
||||
- * way, we can just skip processing this entry since we've
|
||||
- * already gone through this part of the grouping hierarchy. */
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_get_groups_callback - Possible group recursion"
|
||||
- " detected in %s\n",
|
||||
- group_ndn);
|
||||
- slapi_value_free(&group_ndn_val);
|
||||
- ((memberof_get_groups_data *)callback_data)->use_cache = PR_FALSE;
|
||||
- goto bail;
|
||||
- }
|
||||
-
|
||||
/* if the group does not belong to an excluded subtree, adds it to the valueset */
|
||||
if (memberof_entry_in_scope(config, group_sdn)) {
|
||||
/* Push group_dn_val into the valueset. This memory is now owned
|
||||
@@ -3407,9 +3389,21 @@ memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
group_dn_val = slapi_value_new_string(group_dn);
|
||||
slapi_valueset_add_value_ext(groupvals, group_dn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
|
||||
- /* push this ndn to detect group recursion */
|
||||
- already_seen_ndn_val = slapi_value_new_string(group_ndn);
|
||||
- slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
+ if (slapi_valueset_find(
|
||||
+ ((memberof_get_groups_data *)callback_data)->config->group_slapiattrs[0], already_seen_ndn_vals, group_ndn_val)) {
|
||||
+ /* The group group_ndn_val has already been processed
|
||||
+ * skip the final recursion to prevent infinite loop
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_get_groups_callback - detecting a loop in group %s (stop building memberof)\n",
|
||||
+ group_ndn);
|
||||
+ ((memberof_get_groups_data *)callback_data)->use_cache = PR_FALSE;
|
||||
+ goto bail;
|
||||
+ } else {
|
||||
+ /* keep this ndn to detect a possible group recursion */
|
||||
+ already_seen_ndn_val = slapi_value_new_string(group_ndn);
|
||||
+ slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
+ }
|
||||
}
|
||||
if (!config->skip_nested || config->fixup_task) {
|
||||
/* now recurse to find ancestors groups of e */
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,272 +0,0 @@
|
||||
From 17da0257b24749765777a4e64c3626cb39cca639 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 31 Mar 2025 11:05:01 +0200
|
||||
Subject: [PATCH] Issue 6571 - (2nd) Nested group does not receive memberOf
|
||||
attribute (#6697)
|
||||
|
||||
Bug description:
|
||||
erroneous debug change made in previous fix
|
||||
where cache_ancestors is called with the wrong parameter
|
||||
|
||||
Fix description:
|
||||
Restore the orginal param 'member_data'
|
||||
Increase the set of tests around multipaths
|
||||
|
||||
fixes: #6571
|
||||
|
||||
review by: Simon Pichugin (Thanks !!)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 154 ++++++++++++++++++
|
||||
ldap/servers/plugins/memberof/memberof.c | 50 +++++-
|
||||
2 files changed, 203 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index dba908975..9ba40a0c3 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -598,6 +598,8 @@ def test_multipaths(topology_st, request):
|
||||
'homeDirectory': '/home/user1'
|
||||
})
|
||||
group = Groups(inst, SUFFIX, rdn=None)
|
||||
+ g0 = group.create(properties={'cn': 'group0',
|
||||
+ 'description': 'group0'})
|
||||
g1 = group.create(properties={'cn': 'group1',
|
||||
'member': user1.dn,
|
||||
'description': 'group1'})
|
||||
@@ -635,6 +637,158 @@ def test_multipaths(topology_st, request):
|
||||
_check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
_check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
|
||||
+ #inst.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #inst.config.set('nsslapd-accesslog-level','260')
|
||||
+ #inst.config.set('nsslapd-plugin-logging', 'on')
|
||||
+ #inst.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #inst.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.remove_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # \__________ ^
|
||||
+ # | /
|
||||
+ # v /
|
||||
+ # Grp2 --> Grp21 ----
|
||||
+ #
|
||||
+ g1.add_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.remove_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g0.add_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ ^
|
||||
+ # / /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g0.add_member(g1.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1,g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ \_____________ ^
|
||||
+ # / | /
|
||||
+ # / V /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g1.add_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1, g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g1, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ \_____________ ^
|
||||
+ # / | /
|
||||
+ # / V /
|
||||
+ # Grp0 ---> Grp2 Grp21 ---
|
||||
+ #
|
||||
+ g2.remove_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1, g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g1, g0])
|
||||
+
|
||||
def fin():
|
||||
try:
|
||||
user1.delete()
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 32bdcf3f1..f79b083a9 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -3258,6 +3258,35 @@ merge_ancestors(Slapi_Value **member_ndn_val, memberof_get_groups_data *v1, memb
|
||||
Slapi_ValueSet *v2_group_norm_vals = *((memberof_get_groups_data *)v2)->group_norm_vals;
|
||||
int merged_cnt = 0;
|
||||
|
||||
+#if MEMBEROF_CACHE_DEBUG
|
||||
+ {
|
||||
+ Slapi_Value *val = 0;
|
||||
+ int hint = 0;
|
||||
+ struct berval *bv;
|
||||
+ hint = slapi_valueset_first_value(v2_groupvals, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "merge_ancestors: V2 contains %s\n",
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(v2_groupvals, hint, &val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_first_value(v1_groupvals, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "merge_ancestors: add %s (from V1)\n",
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(v1_groupvals, hint, &val);
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
hint = slapi_valueset_first_value(v1_groupvals, &sval);
|
||||
while (sval) {
|
||||
if (memberof_compare(config, member_ndn_val, &sval)) {
|
||||
@@ -3319,7 +3348,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn, memberof_get
|
||||
|
||||
merge_ancestors(&member_ndn_val, &member_data, data);
|
||||
if (!cached && member_data.use_cache)
|
||||
- cache_ancestors(config, &member_ndn_val, data);
|
||||
+ cache_ancestors(config, &member_ndn_val, &member_data);
|
||||
|
||||
slapi_value_free(&member_ndn_val);
|
||||
slapi_valueset_free(groupvals);
|
||||
@@ -4285,6 +4314,25 @@ memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
|
||||
|
||||
/* get a list of all of the groups this user belongs to */
|
||||
groups = memberof_get_groups(config, sdn);
|
||||
+#if MEMBEROF_CACHE_DEBUG
|
||||
+ {
|
||||
+ Slapi_Value *val = 0;
|
||||
+ int hint = 0;
|
||||
+ struct berval *bv;
|
||||
+ hint = slapi_valueset_first_value(groups, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_fix_memberof_callback: %s belongs to %s\n",
|
||||
+ ndn,
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(groups, hint, &val);
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
|
||||
if (config->group_filter) {
|
||||
if (slapi_filter_test_simple(e, config->group_filter)) {
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,192 +0,0 @@
|
||||
From ff364a4b1c88e1a8f678e056af88cce50cd8717c Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 28 Mar 2025 17:32:14 +0100
|
||||
Subject: [PATCH] Issue 6698 - NPE after configuring invalid filtered role
|
||||
(#6699)
|
||||
|
||||
Server crash when doing search after configuring filtered role with invalid filter.
|
||||
Reason: The part of the filter that should be overwritten are freed before knowing that the filter is invalid.
|
||||
Solution: Check first that the filter is valid before freeing the filtere bits
|
||||
|
||||
Issue: #6698
|
||||
|
||||
Reviewed by: @tbordaz , @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit 31e120d2349eda7a41380cf78fc04cf41e394359)
|
||||
---
|
||||
dirsrvtests/tests/suites/roles/basic_test.py | 80 ++++++++++++++++++--
|
||||
ldap/servers/slapd/filter.c | 17 ++++-
|
||||
2 files changed, 88 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
index 875ac47c1..b79816c58 100644
|
||||
--- a/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
@@ -28,6 +28,7 @@ from lib389.dbgen import dbgen_users
|
||||
from lib389.tasks import ImportTask
|
||||
from lib389.utils import get_default_db_lib
|
||||
from lib389.rewriters import *
|
||||
+from lib389._mapped_object import DSLdapObject
|
||||
from lib389.backend import Backends
|
||||
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
@@ -427,7 +428,6 @@ def test_vattr_on_filtered_role_restart(topo, request):
|
||||
log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
|
||||
assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
|
||||
|
||||
-
|
||||
log.info("Check the virtual attribute definition is found (after a required delay)")
|
||||
topo.standalone.restart()
|
||||
time.sleep(5)
|
||||
@@ -541,7 +541,7 @@ def test_managed_and_filtered_role_rewrite(topo, request):
|
||||
indexes = backend.get_indexes()
|
||||
try:
|
||||
index = indexes.create(properties={
|
||||
- 'cn': attrname,
|
||||
+ 'cn': attrname,
|
||||
'nsSystemIndex': 'false',
|
||||
'nsIndexType': ['eq', 'pres']
|
||||
})
|
||||
@@ -593,7 +593,6 @@ def test_managed_and_filtered_role_rewrite(topo, request):
|
||||
dn = "uid=%s0000%d,%s" % (RDN, i, PARENT)
|
||||
topo.standalone.modify_s(dn, [(ldap.MOD_REPLACE, 'nsRoleDN', [role.dn.encode()])])
|
||||
|
||||
-
|
||||
# Now check that search is fast, evaluating only 4 entries
|
||||
search_start = time.time()
|
||||
entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
@@ -676,7 +675,7 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
indexes = backend.get_indexes()
|
||||
try:
|
||||
index = indexes.create(properties={
|
||||
- 'cn': attrname,
|
||||
+ 'cn': attrname,
|
||||
'nsSystemIndex': 'false',
|
||||
'nsIndexType': ['eq', 'pres']
|
||||
})
|
||||
@@ -730,7 +729,7 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
|
||||
# Enable plugin level to check message
|
||||
topo.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
||||
-
|
||||
+
|
||||
# Now check that search is fast, evaluating only 4 entries
|
||||
search_start = time.time()
|
||||
entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(|(nsrole=%s)(nsrole=cn=not_such_entry_role,%s))" % (role.dn, DEFAULT_SUFFIX))
|
||||
@@ -758,6 +757,77 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+
|
||||
+def test_rewriter_with_invalid_filter(topo, request):
|
||||
+ """Test that server does not crash when having
|
||||
+ invalid filter in filtered role
|
||||
+
|
||||
+ :id: 5013b0b2-0af6-11f0-8684-482ae39447e5
|
||||
+ :setup: standalone server
|
||||
+ :steps:
|
||||
+ 1. Setup filtered role with good filter
|
||||
+ 2. Setup nsrole rewriter
|
||||
+ 3. Restart the server
|
||||
+ 4. Search for entries
|
||||
+ 5. Setup filtered role with bad filter
|
||||
+ 6. Search for entries
|
||||
+ :expectedresults:
|
||||
+ 1. Operation should succeed
|
||||
+ 2. Operation should succeed
|
||||
+ 3. Operation should succeed
|
||||
+ 4. Operation should succeed
|
||||
+ 5. Operation should succeed
|
||||
+ 6. Operation should succeed
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ entries = []
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.start()
|
||||
+ for entry in entries:
|
||||
+ entry.delete()
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Setup filtered role
|
||||
+ roles = FilteredRoles(inst, f'ou=people,{DEFAULT_SUFFIX}')
|
||||
+ filter_ko = '(&((objectClass=top)(objectClass=nsPerson))'
|
||||
+ filter_ok = '(&(objectClass=top)(objectClass=nsPerson))'
|
||||
+ role_properties = {
|
||||
+ 'cn': 'TestFilteredRole',
|
||||
+ 'nsRoleFilter': filter_ok,
|
||||
+ 'description': 'Test good filter',
|
||||
+ }
|
||||
+ role = roles.create(properties=role_properties)
|
||||
+ entries.append(role)
|
||||
+
|
||||
+ # Setup nsrole rewriter
|
||||
+ rewriters = Rewriters(inst)
|
||||
+ rewriter_properties = {
|
||||
+ "cn": "nsrole",
|
||||
+ "nsslapd-libpath": 'libroles-plugin',
|
||||
+ "nsslapd-filterrewriter": 'role_nsRole_filter_rewriter',
|
||||
+ }
|
||||
+ rewriter = rewriters.ensure_state(properties=rewriter_properties)
|
||||
+ entries.append(rewriter)
|
||||
+
|
||||
+ # Restart thge instance
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Search for entries
|
||||
+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
+
|
||||
+ # Set bad filter
|
||||
+ role_properties = {
|
||||
+ 'cn': 'TestFilteredRole',
|
||||
+ 'nsRoleFilter': filter_ko,
|
||||
+ 'description': 'Test bad filter',
|
||||
+ }
|
||||
+ role.ensure_state(properties=role_properties)
|
||||
+
|
||||
+ # Search for entries
|
||||
+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s -v %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/slapd/filter.c b/ldap/servers/slapd/filter.c
|
||||
index ce09891b8..f541b8fc1 100644
|
||||
--- a/ldap/servers/slapd/filter.c
|
||||
+++ b/ldap/servers/slapd/filter.c
|
||||
@@ -1038,9 +1038,11 @@ slapi_filter_get_subfilt(
|
||||
}
|
||||
|
||||
/*
|
||||
- * Before calling this function, you must free all the parts
|
||||
+ * The function does not know how to free all the parts
|
||||
* which will be overwritten (i.e. slapi_free_the_filter_bits),
|
||||
- * this function dosn't know how to do that
|
||||
+ * so the caller must take care of that.
|
||||
+ * But it must do so AFTER calling slapi_filter_replace_ex to
|
||||
+ * avoid getting invalid filter if slapi_filter_replace_ex fails.
|
||||
*/
|
||||
int
|
||||
slapi_filter_replace_ex(Slapi_Filter *f, char *s)
|
||||
@@ -1099,8 +1101,15 @@ slapi_filter_free_bits(Slapi_Filter *f)
|
||||
int
|
||||
slapi_filter_replace_strfilter(Slapi_Filter *f, char *strfilter)
|
||||
{
|
||||
- slapi_filter_free_bits(f);
|
||||
- return (slapi_filter_replace_ex(f, strfilter));
|
||||
+ /* slapi_filter_replace_ex may fail and we cannot
|
||||
+ * free filter bits before calling it.
|
||||
+ */
|
||||
+ Slapi_Filter save_f = *f;
|
||||
+ int ret = slapi_filter_replace_ex(f, strfilter);
|
||||
+ if (ret == 0) {
|
||||
+ slapi_filter_free_bits(&save_f);
|
||||
+ }
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,455 +0,0 @@
|
||||
From 446a23d0ed2d3ffa76c5fb5e9576d6876bdbf04f Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 28 Mar 2025 11:28:54 -0700
|
||||
Subject: [PATCH] Issue 6686 - CLI - Re-enabling user accounts that reached
|
||||
inactivity limit fails with error (#6687)
|
||||
|
||||
Description: When attempting to unlock a user account that has been locked due
|
||||
to exceeding the Account Policy Plugin's inactivity limit, the dsidm account
|
||||
unlock command fails with a Python type error: "float() argument must be a
|
||||
string or a number, not 'NoneType'".
|
||||
|
||||
Enhance the unlock method to properly handle different account locking states,
|
||||
including inactivity limit exceeded states.
|
||||
Add test cases to verify account inactivity locking/unlocking functionality
|
||||
with CoS and role-based indirect locking.
|
||||
|
||||
Fix CoS template class to include the required 'ldapsubentry' objectClass.
|
||||
Improv error messages to provide better guidance on unlocking indirectly
|
||||
locked accounts.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6686
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
.../clu/dsidm_account_inactivity_test.py | 329 ++++++++++++++++++
|
||||
src/lib389/lib389/cli_idm/account.py | 25 +-
|
||||
src/lib389/lib389/idm/account.py | 28 +-
|
||||
3 files changed, 377 insertions(+), 5 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py b/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
new file mode 100644
|
||||
index 000000000..88a34abf6
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
@@ -0,0 +1,329 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import ldap
|
||||
+import time
|
||||
+import pytest
|
||||
+import logging
|
||||
+import os
|
||||
+from datetime import datetime, timedelta
|
||||
+
|
||||
+from lib389 import DEFAULT_SUFFIX, DN_PLUGIN, DN_CONFIG
|
||||
+from lib389.cli_idm.account import entry_status, unlock
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.cli_base import FakeArgs
|
||||
+from lib389.utils import ds_is_older
|
||||
+from lib389.plugins import AccountPolicyPlugin, AccountPolicyConfigs
|
||||
+from lib389.idm.role import FilteredRoles
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.cos import CosTemplate, CosPointerDefinition
|
||||
+from lib389.idm.domain import Domain
|
||||
+from . import check_value_in_log_and_reset
|
||||
+
|
||||
+pytestmark = pytest.mark.tier0
|
||||
+
|
||||
+logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+# Constants
|
||||
+PLUGIN_ACCT_POLICY = "Account Policy Plugin"
|
||||
+ACCP_DN = f"cn={PLUGIN_ACCT_POLICY},{DN_PLUGIN}"
|
||||
+ACCP_CONF = f"{DN_CONFIG},{ACCP_DN}"
|
||||
+POLICY_NAME = "Account Inactivity Policy"
|
||||
+POLICY_DN = f"cn={POLICY_NAME},{DEFAULT_SUFFIX}"
|
||||
+COS_TEMPLATE_NAME = "TemplateCoS"
|
||||
+COS_TEMPLATE_DN = f"cn={COS_TEMPLATE_NAME},{DEFAULT_SUFFIX}"
|
||||
+COS_DEFINITION_NAME = "DefinitionCoS"
|
||||
+COS_DEFINITION_DN = f"cn={COS_DEFINITION_NAME},{DEFAULT_SUFFIX}"
|
||||
+TEST_USER_NAME = "test_inactive_user"
|
||||
+TEST_USER_DN = f"uid={TEST_USER_NAME},{DEFAULT_SUFFIX}"
|
||||
+TEST_USER_PW = "password"
|
||||
+INACTIVITY_LIMIT = 30
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def account_policy_setup(topology_st, request):
|
||||
+ """Set up account policy plugin, configuration, and CoS objects"""
|
||||
+ log.info("Setting up Account Policy Plugin and CoS")
|
||||
+
|
||||
+ # Enable Account Policy Plugin
|
||||
+ plugin = AccountPolicyPlugin(topology_st.standalone)
|
||||
+ if not plugin.status():
|
||||
+ plugin.enable()
|
||||
+ plugin.set('nsslapd-pluginarg0', ACCP_CONF)
|
||||
+
|
||||
+ # Configure Account Policy
|
||||
+ accp_configs = AccountPolicyConfigs(topology_st.standalone)
|
||||
+ accp_config = accp_configs.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': 'config',
|
||||
+ 'alwaysrecordlogin': 'yes',
|
||||
+ 'stateattrname': 'lastLoginTime',
|
||||
+ 'altstateattrname': '1.1',
|
||||
+ 'specattrname': 'acctPolicySubentry',
|
||||
+ 'limitattrname': 'accountInactivityLimit'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Add ACI for anonymous access if it doesn't exist
|
||||
+ domain = Domain(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ anon_aci = '(targetattr="*")(version 3.0; acl "Anonymous read access"; allow (read,search,compare) userdn="ldap:///anyone";)'
|
||||
+ domain.ensure_present('aci', anon_aci)
|
||||
+
|
||||
+ # Restart the server to apply plugin configuration
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ # Create or update account policy entry
|
||||
+ accp_configs = AccountPolicyConfigs(topology_st.standalone, basedn=DEFAULT_SUFFIX)
|
||||
+ policy = accp_configs.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': POLICY_NAME,
|
||||
+ 'objectClass': ['top', 'ldapsubentry', 'extensibleObject', 'accountpolicy'],
|
||||
+ 'accountInactivityLimit': str(INACTIVITY_LIMIT)
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create or update CoS template entry
|
||||
+ cos_template = CosTemplate(topology_st.standalone, dn=COS_TEMPLATE_DN)
|
||||
+ cos_template.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': COS_TEMPLATE_NAME,
|
||||
+ 'objectClass': ['top', 'cosTemplate', 'extensibleObject'],
|
||||
+ 'acctPolicySubentry': policy.dn
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create or update CoS definition entry
|
||||
+ cos_def = CosPointerDefinition(topology_st.standalone, dn=COS_DEFINITION_DN)
|
||||
+ cos_def.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': COS_DEFINITION_NAME,
|
||||
+ 'objectClass': ['top', 'ldapsubentry', 'cosSuperDefinition', 'cosPointerDefinition'],
|
||||
+ 'cosTemplateDn': COS_TEMPLATE_DN,
|
||||
+ 'cosAttribute': 'acctPolicySubentry default operational-default'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Restart server to ensure CoS is applied
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Cleaning up Account Policy settings')
|
||||
+ try:
|
||||
+ # Delete CoS and policy entries
|
||||
+ if cos_def.exists():
|
||||
+ cos_def.delete()
|
||||
+ if cos_template.exists():
|
||||
+ cos_template.delete()
|
||||
+ if policy.exists():
|
||||
+ policy.delete()
|
||||
+
|
||||
+ # Disable the plugin
|
||||
+ if plugin.status():
|
||||
+ plugin.disable()
|
||||
+ topology_st.standalone.restart()
|
||||
+ except Exception as e:
|
||||
+ log.error(f'Failed to clean up: {e}')
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return topology_st.standalone
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def create_test_user(topology_st, account_policy_setup, request):
|
||||
+ """Create a test user for the inactivity test"""
|
||||
+ log.info('Creating test user')
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ user = users.ensure_state(
|
||||
+ properties={
|
||||
+ 'uid': TEST_USER_NAME,
|
||||
+ 'cn': TEST_USER_NAME,
|
||||
+ 'sn': TEST_USER_NAME,
|
||||
+ 'userPassword': TEST_USER_PW,
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': f'/home/{TEST_USER_NAME}'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting test user')
|
||||
+ if user.exists():
|
||||
+ user.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ return user
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older("1.4.2"), reason="Indirect account locking not implemented")
|
||||
+def test_dsidm_account_inactivity_lock_unlock(topology_st, create_test_user):
|
||||
+ """Test dsidm account unlock functionality with indirectly locked accounts
|
||||
+
|
||||
+ :id: d7b57083-6111-4dbf-af84-6fca7fc7fb31
|
||||
+ :setup: Standalone instance with Account Policy Plugin and CoS configured
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. Bind as the test user to set lastLoginTime
|
||||
+ 3. Check account status - should be active
|
||||
+ 4. Set user's lastLoginTime to a time in the past that exceeds inactivity limit
|
||||
+ 5. Check account status - should be locked due to inactivity
|
||||
+ 6. Attempt to bind as the user - should fail with constraint violation
|
||||
+ 7. Unlock the account using dsidm account unlock
|
||||
+ 8. Verify account status is active again
|
||||
+ 9. Verify the user can bind again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Account status shows as activated
|
||||
+ 4. Success
|
||||
+ 5. Account status shows as inactivity limit exceeded
|
||||
+ 6. Bind attempt fails with constraint violation
|
||||
+ 7. Account unlocked successfully
|
||||
+ 8. Account status shows as activated
|
||||
+ 9. User can bind successfully
|
||||
+ """
|
||||
+ standalone = topology_st.standalone
|
||||
+ user = create_test_user
|
||||
+
|
||||
+ # Set up FakeArgs for dsidm commands
|
||||
+ args = FakeArgs()
|
||||
+ args.dn = user.dn
|
||||
+ args.json = False
|
||||
+ args.details = False
|
||||
+
|
||||
+ # 1. Check initial account status - should be active
|
||||
+ log.info('Step 1: Checking initial account status')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 2. Bind as test user to set initial lastLoginTime
|
||||
+ log.info('Step 2: Binding as test user to set lastLoginTime')
|
||||
+ try:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ conn.unbind()
|
||||
+ log.info("Successfully bound as test user")
|
||||
+ except ldap.LDAPError as e:
|
||||
+ pytest.fail(f"Failed to bind as test user: {e}")
|
||||
+
|
||||
+ # 3. Set lastLoginTime to a time in the past that exceeds inactivity limit
|
||||
+ log.info('Step 3: Setting lastLoginTime to the past')
|
||||
+ past_time = datetime.utcnow() - timedelta(seconds=INACTIVITY_LIMIT * 2)
|
||||
+ past_time_str = past_time.strftime('%Y%m%d%H%M%SZ')
|
||||
+ user.replace('lastLoginTime', past_time_str)
|
||||
+
|
||||
+ # 4. Check account status - should now be locked due to inactivity
|
||||
+ log.info('Step 4: Checking account status after setting old lastLoginTime')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: inactivity limit exceeded')
|
||||
+
|
||||
+ # 5. Attempt to bind as the user - should fail
|
||||
+ log.info('Step 5: Attempting to bind as user (should fail)')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION) as excinfo:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ assert "Account inactivity limit exceeded" in str(excinfo.value)
|
||||
+
|
||||
+ # 6. Unlock the account using dsidm account unlock
|
||||
+ log.info('Step 6: Unlocking the account with dsidm')
|
||||
+ unlock(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st,
|
||||
+ check_value='now unlocked by resetting lastLoginTime')
|
||||
+
|
||||
+ # 7. Verify account status is active again
|
||||
+ log.info('Step 7: Checking account status after unlock')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 8. Verify the user can bind again
|
||||
+ log.info('Step 8: Verifying user can bind again')
|
||||
+ try:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ conn.unbind()
|
||||
+ log.info("Successfully bound as test user after unlock")
|
||||
+ except ldap.LDAPError as e:
|
||||
+ pytest.fail(f"Failed to bind as test user after unlock: {e}")
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older("1.4.2"), reason="Indirect account locking not implemented")
|
||||
+def test_dsidm_indirectly_locked_via_role(topology_st, create_test_user):
|
||||
+ """Test dsidm account unlock functionality with accounts indirectly locked via role
|
||||
+
|
||||
+ :id: 7bfe69bb-cf99-4214-a763-051ab2b9cf89
|
||||
+ :setup: Standalone instance with Role and user configured
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. Create a Filtered Role that includes the test user
|
||||
+ 3. Lock the role
|
||||
+ 4. Check account status - should be indirectly locked through the role
|
||||
+ 5. Attempt to unlock the account - should fail with appropriate message
|
||||
+ 6. Unlock the role
|
||||
+ 7. Verify account status is active again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Account status shows as indirectly locked
|
||||
+ 5. Unlock attempt fails with appropriate error message
|
||||
+ 6. Success
|
||||
+ 7. Account status shows as activated
|
||||
+ """
|
||||
+ standalone = topology_st.standalone
|
||||
+ user = create_test_user
|
||||
+
|
||||
+ # Use FilteredRoles and ensure_state for role creation
|
||||
+ log.info('Step 1: Creating Filtered Role')
|
||||
+ roles = FilteredRoles(standalone, DEFAULT_SUFFIX)
|
||||
+ role = roles.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': 'TestFilterRole',
|
||||
+ 'nsRoleFilter': f'(uid={TEST_USER_NAME})'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Set up FakeArgs for dsidm commands
|
||||
+ args = FakeArgs()
|
||||
+ args.dn = user.dn
|
||||
+ args.json = False
|
||||
+ args.details = False
|
||||
+
|
||||
+ # 2. Check account status before locking role
|
||||
+ log.info('Step 2: Checking account status before locking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 3. Lock the role
|
||||
+ log.info('Step 3: Locking the role')
|
||||
+ role.lock()
|
||||
+
|
||||
+ # 4. Check account status - should be indirectly locked
|
||||
+ log.info('Step 4: Checking account status after locking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: indirectly locked through a Role')
|
||||
+
|
||||
+ # 5. Attempt to unlock the account - should fail
|
||||
+ log.info('Step 5: Attempting to unlock indirectly locked account')
|
||||
+ unlock(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st,
|
||||
+ check_value='Account is locked through role')
|
||||
+
|
||||
+ # 6. Unlock the role
|
||||
+ log.info('Step 6: Unlocking the role')
|
||||
+ role.unlock()
|
||||
+
|
||||
+ # 7. Verify account status is active again
|
||||
+ log.info('Step 7: Checking account status after unlocking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
\ No newline at end of file
|
||||
diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py
|
||||
index 15f766588..a0dfd8f65 100644
|
||||
--- a/src/lib389/lib389/cli_idm/account.py
|
||||
+++ b/src/lib389/lib389/cli_idm/account.py
|
||||
@@ -176,8 +176,29 @@ def unlock(inst, basedn, log, args):
|
||||
dn = _get_dn_arg(args.dn, msg="Enter dn to unlock")
|
||||
accounts = Accounts(inst, basedn)
|
||||
acct = accounts.get(dn=dn)
|
||||
- acct.unlock()
|
||||
- log.info(f'Entry {dn} is unlocked')
|
||||
+
|
||||
+ try:
|
||||
+ # Get the account status before attempting to unlock
|
||||
+ status = acct.status()
|
||||
+ state = status["state"]
|
||||
+
|
||||
+ # Attempt to unlock the account
|
||||
+ acct.unlock()
|
||||
+
|
||||
+ # Success message
|
||||
+ log.info(f'Entry {dn} is unlocked')
|
||||
+ if state == AccountState.DIRECTLY_LOCKED:
|
||||
+ log.info(f'The entry was directly locked')
|
||||
+ elif state == AccountState.INACTIVITY_LIMIT_EXCEEDED:
|
||||
+ log.info(f'The entry was locked due to inactivity and is now unlocked by resetting lastLoginTime')
|
||||
+
|
||||
+ except ValueError as e:
|
||||
+ # Provide a more detailed error message based on failure reason
|
||||
+ if "through role" in str(e):
|
||||
+ log.error(f"Cannot unlock {dn}: {str(e)}")
|
||||
+ log.info("To unlock this account, you must modify the role that's locking it.")
|
||||
+ else:
|
||||
+ log.error(f"Failed to unlock {dn}: {str(e)}")
|
||||
|
||||
|
||||
def reset_password(inst, basedn, log, args):
|
||||
diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py
|
||||
index 4b823b662..faf6f6f16 100644
|
||||
--- a/src/lib389/lib389/idm/account.py
|
||||
+++ b/src/lib389/lib389/idm/account.py
|
||||
@@ -140,7 +140,8 @@ class Account(DSLdapObject):
|
||||
"nsAccountLock", state_attr])
|
||||
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, state_attr)
|
||||
- if not last_login_time:
|
||||
+ # if last_login_time not exist then check alt_state_attr only if its not disabled and exist
|
||||
+ if not last_login_time and alt_state_attr in account_data:
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, alt_state_attr)
|
||||
|
||||
create_time = self._dict_get_with_ignore_indexerror(account_data, "createTimestamp")
|
||||
@@ -203,12 +204,33 @@ class Account(DSLdapObject):
|
||||
self.replace('nsAccountLock', 'true')
|
||||
|
||||
def unlock(self):
|
||||
- """Unset nsAccountLock"""
|
||||
+ """Unset nsAccountLock if it's set and reset lastLoginTime if account is locked due to inactivity"""
|
||||
|
||||
current_status = self.status()
|
||||
+
|
||||
if current_status["state"] == AccountState.ACTIVATED:
|
||||
raise ValueError("Account is already active")
|
||||
- self.remove('nsAccountLock', None)
|
||||
+
|
||||
+ if current_status["state"] == AccountState.DIRECTLY_LOCKED:
|
||||
+ # Account is directly locked with nsAccountLock attribute
|
||||
+ self.remove('nsAccountLock', None)
|
||||
+ elif current_status["state"] == AccountState.INACTIVITY_LIMIT_EXCEEDED:
|
||||
+ # Account is locked due to inactivity - reset lastLoginTime to current time
|
||||
+ # The lastLoginTime attribute stores its value in GMT/UTC time (Zulu time zone)
|
||||
+ current_time = time.strftime('%Y%m%d%H%M%SZ', time.gmtime())
|
||||
+ self.replace('lastLoginTime', current_time)
|
||||
+ elif current_status["state"] == AccountState.INDIRECTLY_LOCKED:
|
||||
+ # Account is locked through a role
|
||||
+ role_dn = current_status.get("role_dn")
|
||||
+ if role_dn:
|
||||
+ raise ValueError(f"Account is locked through role {role_dn}. "
|
||||
+ f"Please modify the role to unlock this account.")
|
||||
+ else:
|
||||
+ raise ValueError("Account is locked through an unknown role. "
|
||||
+ "Please check the roles configuration to unlock this account.")
|
||||
+ else:
|
||||
+ # Should not happen, but just in case
|
||||
+ raise ValueError(f"Unknown lock state: {current_status['state'].value}")
|
||||
|
||||
# If the account can be bound to, this will attempt to do so. We don't check
|
||||
# for exceptions, just pass them back!
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
From 09a284ee43c2b4346da892f8756f97accd15ca68 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Wed, 4 Dec 2024 21:59:40 -0500
|
||||
Subject: [PATCH] Issue 6302 - Allow to run replication status without a prompt
|
||||
(#6410)
|
||||
|
||||
Description: We should allow running replication status and
|
||||
other similar commands without requesting a password and bind DN.
|
||||
|
||||
This way, the current instance's root DN and root PW will be used on other
|
||||
instances when requesting CSN info. If they are incorrect,
|
||||
then the info won't be printed, but otherwise, the agreement status
|
||||
will be displayed correctly.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6302
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_conf/replication.py | 15 +++------------
|
||||
1 file changed, 3 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
|
||||
index 399d0d2f8..cd4a331a8 100644
|
||||
--- a/src/lib389/lib389/cli_conf/replication.py
|
||||
+++ b/src/lib389/lib389/cli_conf/replication.py
|
||||
@@ -319,12 +319,9 @@ def list_suffixes(inst, basedn, log, args):
|
||||
def get_repl_status(inst, basedn, log, args):
|
||||
replicas = Replicas(inst)
|
||||
replica = replicas.get(args.suffix)
|
||||
- pw_and_dn_prompt = False
|
||||
if args.bind_passwd_file is not None:
|
||||
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
|
||||
- if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
|
||||
- pw_and_dn_prompt = True
|
||||
- status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=pw_and_dn_prompt)
|
||||
+ status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=args.bind_passwd_prompt)
|
||||
if args.json:
|
||||
log.info(json.dumps({"type": "list", "items": status}, indent=4))
|
||||
else:
|
||||
@@ -335,12 +332,9 @@ def get_repl_status(inst, basedn, log, args):
|
||||
def get_repl_winsync_status(inst, basedn, log, args):
|
||||
replicas = Replicas(inst)
|
||||
replica = replicas.get(args.suffix)
|
||||
- pw_and_dn_prompt = False
|
||||
if args.bind_passwd_file is not None:
|
||||
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
|
||||
- if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
|
||||
- pw_and_dn_prompt = True
|
||||
- status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True, pwprompt=pw_and_dn_prompt)
|
||||
+ status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True, pwprompt=args.bind_passwd_prompt)
|
||||
if args.json:
|
||||
log.info(json.dumps({"type": "list", "items": status}, indent=4))
|
||||
else:
|
||||
@@ -874,12 +868,9 @@ def poke_agmt(inst, basedn, log, args):
|
||||
|
||||
def get_agmt_status(inst, basedn, log, args):
|
||||
agmt = get_agmt(inst, args)
|
||||
- pw_and_dn_prompt = False
|
||||
if args.bind_passwd_file is not None:
|
||||
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
|
||||
- if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
|
||||
- pw_and_dn_prompt = True
|
||||
- status = agmt.status(use_json=args.json, binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=pw_and_dn_prompt)
|
||||
+ status = agmt.status(use_json=args.json, binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=args.bind_passwd_prompt)
|
||||
log.info(status)
|
||||
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
From 2b73c3596e724f314b0e09cf6209e0151260f7e5 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Wed, 9 Jul 2025 12:08:09 +0300
|
||||
Subject: [PATCH] Issue 6857 - uiduniq: allow specifying match rules in the
|
||||
filter
|
||||
|
||||
Allow uniqueness plugin to work with attributes where uniqueness should
|
||||
be enforced using different matching rule than the one defined for the
|
||||
attribute itself.
|
||||
|
||||
Since uniqueness plugin configuration can contain multiple attributes,
|
||||
add matching rule right to the attribute as it is used in the LDAP rule
|
||||
(e.g. 'attribute:caseIgnoreMatch:' to force 'attribute' to be searched
|
||||
with case-insensitive matching rule instead of the original matching
|
||||
rule.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6857
|
||||
|
||||
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/uiduniq/uid.c | 7 +++++++
|
||||
1 file changed, 7 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
|
||||
index 5b763b551..15cf88477 100644
|
||||
--- a/ldap/servers/plugins/uiduniq/uid.c
|
||||
+++ b/ldap/servers/plugins/uiduniq/uid.c
|
||||
@@ -1031,7 +1031,14 @@ preop_add(Slapi_PBlock *pb)
|
||||
}
|
||||
|
||||
for (i = 0; attrNames && attrNames[i]; i++) {
|
||||
+ char *attr_match = strchr(attrNames[i], ':');
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = '\0';
|
||||
+ }
|
||||
err = slapi_entry_attr_find(e, attrNames[i], &attr);
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = ':';
|
||||
+ }
|
||||
if (!err) {
|
||||
/*
|
||||
* Passed all the requirements - this is an operation we
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,399 +0,0 @@
|
||||
From 3ba73d2aa55f18ff73d4b3901ce101133745effc Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 9 Jul 2025 14:18:50 -0400
|
||||
Subject: [PATCH] Issue 6859 - str2filter is not fully applying matching rules
|
||||
|
||||
Description:
|
||||
|
||||
When we have an extended filter, one with a MR applied, it is ignored during
|
||||
internal searches:
|
||||
|
||||
"(cn:CaseExactMatch:=Value)"
|
||||
|
||||
For internal searches we use str2filter() and it doesn't fully apply extended
|
||||
search filter matching rules
|
||||
|
||||
Also needed to update attr uniqueness plugin to apply this change for mod
|
||||
operations (previously only Adds were correctly handling these attribute
|
||||
filters)
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6857
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6859
|
||||
|
||||
Reviewed by: spichugi & tbordaz(Thanks!!)
|
||||
---
|
||||
.../tests/suites/plugins/attruniq_test.py | 295 +++++++++++++++++-
|
||||
ldap/servers/plugins/uiduniq/uid.c | 7 +
|
||||
ldap/servers/slapd/plugin_mr.c | 2 +-
|
||||
ldap/servers/slapd/str2filter.c | 8 +
|
||||
4 files changed, 309 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
index b190e0ec1..b338f405f 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2021 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -80,4 +80,295 @@ def test_modrdn_attr_uniqueness(topology_st):
|
||||
log.debug(excinfo.value)
|
||||
|
||||
log.debug('Move user2 to group1')
|
||||
- user2.rename(f'uid={user2.rdn}', group1.dn)
|
||||
\ No newline at end of file
|
||||
+
|
||||
+ user2.rename(f'uid={user2.rdn}', group1.dn)
|
||||
+
|
||||
+ # Cleanup for next test
|
||||
+ user1.delete()
|
||||
+ user2.delete()
|
||||
+ attruniq.disable()
|
||||
+ attruniq.delete()
|
||||
+
|
||||
+
|
||||
+def test_multiple_attr_uniqueness(topology_st):
|
||||
+ """ Test that attribute uniqueness works properly with multiple attributes
|
||||
+
|
||||
+ :id: c49aa5c1-7e65-45fd-b064-55e0b815e9bc
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Setup attribute uniqueness plugin to ensure uniqueness of attributes 'mail' and 'mailAlternateAddress'
|
||||
+ 2. Add user with unique 'mail=non-uniq@value.net' and 'mailAlternateAddress=alt-mail@value.net'
|
||||
+ 3. Try adding another user with 'mail=non-uniq@value.net'
|
||||
+ 4. Try adding another user with 'mailAlternateAddress=alt-mail@value.net'
|
||||
+ 5. Try adding another user with 'mail=alt-mail@value.net'
|
||||
+ 6. Try adding another user with 'mailAlternateAddress=non-uniq@value.net'
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should raise CONSTRAINT_VIOLATION
|
||||
+ 4. Should raise CONSTRAINT_VIOLATION
|
||||
+ 5. Should raise CONSTRAINT_VIOLATION
|
||||
+ 6. Should raise CONSTRAINT_VIOLATION
|
||||
+ """
|
||||
+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
+
|
||||
+ try:
|
||||
+ log.debug(f'Setup PLUGIN_ATTR_UNIQUENESS plugin for {MAIL_ATTR_VALUE} attribute for the group2')
|
||||
+ attruniq.create(properties={'cn': 'attruniq'})
|
||||
+ attruniq.add_unique_attribute('mail')
|
||||
+ attruniq.add_unique_attribute('mailAlternateAddress')
|
||||
+ attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
|
||||
+ attruniq.enable()
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('test_multiple_attribute_uniqueness: Failed to configure plugin for "mail": error {}'.format(e.args[0]['desc']))
|
||||
+ assert False
|
||||
+
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ testuser1 = users.create_test_user(100,100)
|
||||
+ testuser1.add('objectclass', 'extensibleObject')
|
||||
+ testuser1.add('mail', MAIL_ATTR_VALUE)
|
||||
+ testuser1.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT)
|
||||
+
|
||||
+ testuser2 = users.create_test_user(200, 200)
|
||||
+ testuser2.add('objectclass', 'extensibleObject')
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mail', MAIL_ATTR_VALUE)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mail', MAIL_ATTR_VALUE_ALT)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE)
|
||||
+
|
||||
+ # Cleanup
|
||||
+ testuser1.delete()
|
||||
+ testuser2.delete()
|
||||
+ attruniq.disable()
|
||||
+ attruniq.delete()
|
||||
+
|
||||
+
|
||||
+def test_exclude_subtrees(topology_st):
|
||||
+ """ Test attribute uniqueness with exclude scope
|
||||
+
|
||||
+ :id: 43d29a60-40e1-4ebd-b897-6ef9f20e9f27
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Setup and enable attribute uniqueness plugin for telephonenumber unique attribute
|
||||
+ 2. Create subtrees and test users
|
||||
+ 3. Add a unique attribute to a user within uniqueness scope
|
||||
+ 4. Add exclude subtree
|
||||
+ 5. Try to add existing value attribute to an entry within uniqueness scope
|
||||
+ 6. Try to add existing value attribute to an entry within exclude scope
|
||||
+ 7. Remove the attribute from affected entries
|
||||
+ 8. Add a unique attribute to a user within exclude scope
|
||||
+ 9. Try to add existing value attribute to an entry within uniqueness scope
|
||||
+ 10. Try to add existing value attribute to another entry within uniqueness scope
|
||||
+ 11. Remove the attribute from affected entries
|
||||
+ 12. Add another exclude subtree
|
||||
+ 13. Add a unique attribute to a user within uniqueness scope
|
||||
+ 14. Try to add existing value attribute to an entry within uniqueness scope
|
||||
+ 15. Try to add existing value attribute to an entry within exclude scope
|
||||
+ 16. Try to add existing value attribute to an entry within another exclude scope
|
||||
+ 17. Clean up entries
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Should raise CONSTRAINT_VIOLATION
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ 9. Success
|
||||
+ 10. Should raise CONSTRAINT_VIOLATION
|
||||
+ 11. Success
|
||||
+ 12. Success
|
||||
+ 13. Success
|
||||
+ 14. Should raise CONSTRAINT_VIOLATION
|
||||
+ 15. Success
|
||||
+ 16. Success
|
||||
+ 17. Success
|
||||
+ """
|
||||
+ log.info('Setup attribute uniqueness plugin')
|
||||
+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
+ attruniq.create(properties={'cn': 'attruniq'})
|
||||
+ attruniq.add_unique_attribute('telephonenumber')
|
||||
+ attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ attruniq.enable()
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ log.info('Create subtrees container')
|
||||
+ containers = nsContainers(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ cont1 = containers.create(properties={'cn': EXCLUDED_CONTAINER_CN})
|
||||
+ cont2 = containers.create(properties={'cn': EXCLUDED_BIS_CONTAINER_CN})
|
||||
+ cont3 = containers.create(properties={'cn': ENFORCED_CONTAINER_CN})
|
||||
+
|
||||
+ log.info('Create test users')
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
||||
+ rdn='cn={}'.format(ENFORCED_CONTAINER_CN))
|
||||
+ users_excluded = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
||||
+ rdn='cn={}'.format(EXCLUDED_CONTAINER_CN))
|
||||
+ users_excluded2 = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
||||
+ rdn='cn={}'.format(EXCLUDED_BIS_CONTAINER_CN))
|
||||
+
|
||||
+ user1 = users.create(properties={'cn': USER_1_CN,
|
||||
+ 'uid': USER_1_CN,
|
||||
+ 'sn': USER_1_CN,
|
||||
+ 'uidNumber': '1',
|
||||
+ 'gidNumber': '11',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_1_CN)})
|
||||
+ user2 = users.create(properties={'cn': USER_2_CN,
|
||||
+ 'uid': USER_2_CN,
|
||||
+ 'sn': USER_2_CN,
|
||||
+ 'uidNumber': '2',
|
||||
+ 'gidNumber': '22',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_2_CN)})
|
||||
+ user3 = users_excluded.create(properties={'cn': USER_3_CN,
|
||||
+ 'uid': USER_3_CN,
|
||||
+ 'sn': USER_3_CN,
|
||||
+ 'uidNumber': '3',
|
||||
+ 'gidNumber': '33',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_3_CN)})
|
||||
+ user4 = users_excluded2.create(properties={'cn': USER_4_CN,
|
||||
+ 'uid': USER_4_CN,
|
||||
+ 'sn': USER_4_CN,
|
||||
+ 'uidNumber': '4',
|
||||
+ 'gidNumber': '44',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_4_CN)})
|
||||
+
|
||||
+ UNIQUE_VALUE = '1234'
|
||||
+
|
||||
+ try:
|
||||
+ log.info('Create user with unique attribute')
|
||||
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user1.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Add exclude subtree')
|
||||
+ attruniq.add_exclude_subtree(EXCLUDED_CONTAINER_DN)
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ log.info('Verify an already used attribute value cannot be added within the same subtree')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify an entry with same attribute value can be added within exclude subtree')
|
||||
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Cleanup unique attribute values')
|
||||
+ user1.remove_all('telephonenumber')
|
||||
+ user3.remove_all('telephonenumber')
|
||||
+
|
||||
+ log.info('Add a unique value to an entry in excluded scope')
|
||||
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify the same value can be added to an entry within uniqueness scope')
|
||||
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user1.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify that yet another same value cannot be added to another entry within uniqueness scope')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Cleanup unique attribute values')
|
||||
+ user1.remove_all('telephonenumber')
|
||||
+ user3.remove_all('telephonenumber')
|
||||
+
|
||||
+ log.info('Add another exclude subtree')
|
||||
+ attruniq.add_exclude_subtree(EXCLUDED_BIS_CONTAINER_DN)
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ log.info('Verify an already used attribute value cannot be added within the same subtree')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify an already used attribute can be added to an entry in exclude scope')
|
||||
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
||||
+ user4.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user4.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ finally:
|
||||
+ log.info('Clean up users, containers and attribute uniqueness plugin')
|
||||
+ user1.delete()
|
||||
+ user2.delete()
|
||||
+ user3.delete()
|
||||
+ user4.delete()
|
||||
+ cont1.delete()
|
||||
+ cont2.delete()
|
||||
+ cont3.delete()
|
||||
+ attruniq.disable()
|
||||
+ attruniq.delete()
|
||||
+
|
||||
+
|
||||
+def test_matchingrule_attr(topology_st):
|
||||
+ """ Test list extension MR attribute. Check for "cn" using CES (versus it
|
||||
+ being defined as CIS)
|
||||
+
|
||||
+ :id: 5cde4342-6fa3-4225-b23d-0af918981075
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Setup and enable attribute uniqueness plugin to use CN attribute
|
||||
+ with a matching rule of CaseExactMatch.
|
||||
+ 2. Add user with CN value is lowercase
|
||||
+ 3. Add second user with same lowercase CN which should be rejected
|
||||
+ 4. Add second user with same CN value but with mixed case
|
||||
+ 5. Modify second user replacing CN value to lc which should be rejected
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ attruniq = AttributeUniquenessPlugin(inst,
|
||||
+ dn="cn=attribute uniqueness,cn=plugins,cn=config")
|
||||
+ attruniq.add_unique_attribute('cn:CaseExactMatch:')
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ attruniq.enable()
|
||||
+ inst.restart()
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ users.create(properties={'cn': "common_name",
|
||||
+ 'uid': "uid_name",
|
||||
+ 'sn': "uid_name",
|
||||
+ 'uidNumber': '1',
|
||||
+ 'gidNumber': '11',
|
||||
+ 'homeDirectory': '/home/uid_name'})
|
||||
+
|
||||
+ log.info('Add entry with the exact CN value which should be rejected')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ users.create(properties={'cn': "common_name",
|
||||
+ 'uid': "uid_name2",
|
||||
+ 'sn': "uid_name2",
|
||||
+ 'uidNumber': '11',
|
||||
+ 'gidNumber': '111',
|
||||
+ 'homeDirectory': '/home/uid_name2'})
|
||||
+
|
||||
+ log.info('Add entry with the mixed case CN value which should be allowed')
|
||||
+ user = users.create(properties={'cn': "Common_Name",
|
||||
+ 'uid': "uid_name2",
|
||||
+ 'sn': "uid_name2",
|
||||
+ 'uidNumber': '11',
|
||||
+ 'gidNumber': '111',
|
||||
+ 'homeDirectory': '/home/uid_name2'})
|
||||
+
|
||||
+ log.info('Mod entry with exact case CN value which should be rejected')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user.replace('cn', 'common_name')
|
||||
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
|
||||
index 15cf88477..5a0d61b86 100644
|
||||
--- a/ldap/servers/plugins/uiduniq/uid.c
|
||||
+++ b/ldap/servers/plugins/uiduniq/uid.c
|
||||
@@ -1179,6 +1179,10 @@ preop_modify(Slapi_PBlock *pb)
|
||||
for (; mods && *mods; mods++) {
|
||||
mod = *mods;
|
||||
for (i = 0; attrNames && attrNames[i]; i++) {
|
||||
+ char *attr_match = strchr(attrNames[i], ':');
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = '\0';
|
||||
+ }
|
||||
if ((slapi_attr_type_cmp(mod->mod_type, attrNames[i], 1) == 0) && /* mod contains target attr */
|
||||
(mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
|
||||
(mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
|
||||
@@ -1187,6 +1191,9 @@ preop_modify(Slapi_PBlock *pb)
|
||||
{
|
||||
addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
|
||||
}
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = ':';
|
||||
+ }
|
||||
}
|
||||
}
|
||||
if (modcount == 0) {
|
||||
diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c
|
||||
index 6cf88b7de..f40c9a39b 100644
|
||||
--- a/ldap/servers/slapd/plugin_mr.c
|
||||
+++ b/ldap/servers/slapd/plugin_mr.c
|
||||
@@ -624,7 +624,7 @@ attempt_mr_filter_create(mr_filter_t *f, struct slapdplugin *mrp, Slapi_PBlock *
|
||||
int rc;
|
||||
IFP mrf_create = NULL;
|
||||
f->mrf_match = NULL;
|
||||
- pblock_init(pb);
|
||||
+ slapi_pblock_init(pb);
|
||||
if (!(rc = slapi_pblock_set(pb, SLAPI_PLUGIN, mrp)) &&
|
||||
!(rc = slapi_pblock_get(pb, SLAPI_PLUGIN_MR_FILTER_CREATE_FN, &mrf_create)) &&
|
||||
mrf_create != NULL &&
|
||||
diff --git a/ldap/servers/slapd/str2filter.c b/ldap/servers/slapd/str2filter.c
|
||||
index 9fdc500f7..5620b7439 100644
|
||||
--- a/ldap/servers/slapd/str2filter.c
|
||||
+++ b/ldap/servers/slapd/str2filter.c
|
||||
@@ -344,6 +344,14 @@ str2simple(char *str, int unescape_filter)
|
||||
return NULL; /* error */
|
||||
} else {
|
||||
f->f_choice = LDAP_FILTER_EXTENDED;
|
||||
+ if (f->f_mr_oid) {
|
||||
+ /* apply the MR indexers */
|
||||
+ rc = plugin_mr_filter_create(&f->f_mr);
|
||||
+ if (rc) {
|
||||
+ slapi_filter_free(f, 1);
|
||||
+ return NULL; /* error */
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
} else if (str_find_star(value) == NULL) {
|
||||
f->f_choice = LDAP_FILTER_EQUALITY;
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
From 77cc17e5dfb7ed71a320844d14a90c99c1474cc3 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 20 May 2025 08:13:24 -0400
|
||||
Subject: [PATCH] Issue 6787 - Improve error message when bulk import
|
||||
connection is closed
|
||||
|
||||
Description:
|
||||
|
||||
If an online replication initialization connection is closed a vague error
|
||||
message is reported when the init is aborted:
|
||||
|
||||
factory_destructor - ERROR bulk import abandoned
|
||||
|
||||
It should be clear that the import is being abandoned because the connection
|
||||
was closed and identify the conn id.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6787
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
|
||||
(cherry picked from commit d472dd83d49f8dce6d71e202cbb4d897218ceffb)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c | 6 ++++--
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
|
||||
index 67d6e3abc..e433f3db2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
|
||||
@@ -3432,9 +3432,10 @@ factory_constructor(void *object __attribute__((unused)), void *parent __attribu
|
||||
}
|
||||
|
||||
void
|
||||
-factory_destructor(void *extension, void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+factory_destructor(void *extension, void *object, void *parent __attribute__((unused)))
|
||||
{
|
||||
ImportJob *job = (ImportJob *)extension;
|
||||
+ Connection *conn = (Connection *)object;
|
||||
PRThread *thread;
|
||||
|
||||
if (extension == NULL)
|
||||
@@ -3446,7 +3447,8 @@ factory_destructor(void *extension, void *object __attribute__((unused)), void *
|
||||
*/
|
||||
thread = job->main_thread;
|
||||
slapi_log_err(SLAPI_LOG_ERR, "factory_destructor",
|
||||
- "ERROR bulk import abandoned\n");
|
||||
+ "ERROR bulk import abandoned: conn=%ld was closed\n",
|
||||
+ conn->c_connid);
|
||||
import_abort_all(job, 1);
|
||||
/* wait for import_main to finish... */
|
||||
PR_JoinThread(thread);
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,536 +0,0 @@
|
||||
From 8cba0dd699541d562d74502f35176df33f188512 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 30 May 2025 11:12:43 +0000
|
||||
Subject: [PATCH] Issue 6641 - modrdn fails when a user is member of multiple
|
||||
groups (#6643)
|
||||
|
||||
Bug description:
|
||||
Rename of a user that is member of multiple AM groups fail when MO and
|
||||
RI plugins are enabled.
|
||||
|
||||
Fix description:
|
||||
MO plugin - After updating the entry member attribute, check the return
|
||||
value. Retry the delete if the attr value exists and retry the add if the
|
||||
attr value is missing.
|
||||
|
||||
RI plugin - A previous commit checked if the attr value was not present
|
||||
before adding a mod. This commit was reverted in favour of overriding
|
||||
the internal op return value, consistent with other plugins.
|
||||
|
||||
CI test from Viktor Ashirov <vashirov@redhat.com>
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6641
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6566
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @vashirov (Thank you)
|
||||
|
||||
(cherry picked from commit 132ce4ab158679475cb83dbe28cc4fd7ced5cd19)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/plugins/modrdn_test.py | 174 ++++++++++++++++++
|
||||
ldap/servers/plugins/automember/automember.c | 11 +-
|
||||
ldap/servers/plugins/memberof/memberof.c | 123 +++++--------
|
||||
ldap/servers/plugins/referint/referint.c | 30 +--
|
||||
ldap/servers/slapd/modify.c | 51 +++++
|
||||
ldap/servers/slapd/slapi-plugin.h | 1 +
|
||||
6 files changed, 301 insertions(+), 89 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/modrdn_test.py b/dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
new file mode 100644
|
||||
index 000000000..be79b0c3c
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
@@ -0,0 +1,174 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import pytest
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.idm.group import Groups
|
||||
+from lib389.idm.user import nsUserAccounts
|
||||
+from lib389.plugins import (
|
||||
+ AutoMembershipDefinitions,
|
||||
+ AutoMembershipPlugin,
|
||||
+ AutoMembershipRegexRules,
|
||||
+ MemberOfPlugin,
|
||||
+ ReferentialIntegrityPlugin,
|
||||
+)
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+USER_PROPERTIES = {
|
||||
+ "uid": "userwith",
|
||||
+ "cn": "userwith",
|
||||
+ "uidNumber": "1000",
|
||||
+ "gidNumber": "2000",
|
||||
+ "homeDirectory": "/home/testuser",
|
||||
+ "displayName": "test user",
|
||||
+}
|
||||
+
|
||||
+
|
||||
+def test_modrdn_of_a_member_of_2_automember_groups(topology_st):
|
||||
+ """Test that a member of 2 automember groups can be renamed
|
||||
+
|
||||
+ :id: 0e40bdc4-a2d2-4bb8-8368-e02c8920bad2
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Enable automember plugin
|
||||
+ 2. Create definiton for users with A in the name
|
||||
+ 3. Create regex rule for users with A in the name
|
||||
+ 4. Create definiton for users with Z in the name
|
||||
+ 5. Create regex rule for users with Z in the name
|
||||
+ 6. Enable memberof plugin
|
||||
+ 7. Enable referential integrity plugin
|
||||
+ 8. Restart the instance
|
||||
+ 9. Create groups
|
||||
+ 10. Create users userwitha, userwithz, userwithaz
|
||||
+ 11. Rename userwithaz
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ 9. Success
|
||||
+ 10. Success
|
||||
+ 11. Success
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ # Enable automember plugin
|
||||
+ automember_plugin = AutoMembershipPlugin(inst)
|
||||
+ automember_plugin.enable()
|
||||
+
|
||||
+ # Create definiton for users with A in the name
|
||||
+ automembers = AutoMembershipDefinitions(inst)
|
||||
+ automember = automembers.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithA",
|
||||
+ "autoMemberScope": DEFAULT_SUFFIX,
|
||||
+ "autoMemberFilter": "objectclass=posixAccount",
|
||||
+ "autoMemberGroupingAttr": "member:dn",
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create regex rule for users with A in the name
|
||||
+ automembers_regex_rule = AutoMembershipRegexRules(inst, f"{automember.dn}")
|
||||
+ automembers_regex_rule.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithA",
|
||||
+ "autoMemberInclusiveRegex": ["cn=.*a.*"],
|
||||
+ "autoMemberTargetGroup": [f"cn=userswithA,ou=Groups,{DEFAULT_SUFFIX}"],
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create definiton for users with Z in the name
|
||||
+ automember = automembers.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithZ",
|
||||
+ "autoMemberScope": DEFAULT_SUFFIX,
|
||||
+ "autoMemberFilter": "objectclass=posixAccount",
|
||||
+ "autoMemberGroupingAttr": "member:dn",
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create regex rule for users with Z in the name
|
||||
+ automembers_regex_rule = AutoMembershipRegexRules(inst, f"{automember.dn}")
|
||||
+ automembers_regex_rule.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithZ",
|
||||
+ "autoMemberInclusiveRegex": ["cn=.*z.*"],
|
||||
+ "autoMemberTargetGroup": [f"cn=userswithZ,ou=Groups,{DEFAULT_SUFFIX}"],
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Enable memberof plugin
|
||||
+ memberof_plugin = MemberOfPlugin(inst)
|
||||
+ memberof_plugin.enable()
|
||||
+
|
||||
+ # Enable referential integrity plugin
|
||||
+ referint_plugin = ReferentialIntegrityPlugin(inst)
|
||||
+ referint_plugin.enable()
|
||||
+
|
||||
+ # Restart the instance
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Create groups
|
||||
+ groups = Groups(inst, DEFAULT_SUFFIX)
|
||||
+ groupA = groups.create(properties={"cn": "userswithA"})
|
||||
+ groupZ = groups.create(properties={"cn": "userswithZ"})
|
||||
+
|
||||
+ # Create users
|
||||
+ users = nsUserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # userwitha
|
||||
+ user_props = USER_PROPERTIES.copy()
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "a",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "a",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+
|
||||
+ # userwithz
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "z",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "z",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+
|
||||
+ # userwithaz
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "az",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "az",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+ user_orig_dn = user.dn
|
||||
+
|
||||
+ # Rename userwithaz
|
||||
+ user.rename(new_rdn="uid=userwith")
|
||||
+ user_new_dn = user.dn
|
||||
+
|
||||
+ assert user.get_attr_val_utf8("uid") != "userwithaz"
|
||||
+
|
||||
+ # Check groups contain renamed username
|
||||
+ assert groupA.is_member(user_new_dn)
|
||||
+ assert groupZ.is_member(user_new_dn)
|
||||
+
|
||||
+ # Check groups dont contain original username
|
||||
+ assert not groupA.is_member(user_orig_dn)
|
||||
+ assert not groupZ.is_member(user_orig_dn)
|
||||
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
|
||||
index 419adb052..fde92ee12 100644
|
||||
--- a/ldap/servers/plugins/automember/automember.c
|
||||
+++ b/ldap/servers/plugins/automember/automember.c
|
||||
@@ -1754,13 +1754,12 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
}
|
||||
|
||||
mod_pb = slapi_pblock_new();
|
||||
- slapi_modify_internal_set_pb(mod_pb, group_dn,
|
||||
- mods, 0, 0, automember_get_plugin_id(), 0);
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+ /* Do a single mod with error overrides for DEL/ADD */
|
||||
+ result = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_dn_byval(group_dn), mods,
|
||||
+ automember_get_plugin_id(), 0);
|
||||
|
||||
if(add){
|
||||
- if ((result != LDAP_SUCCESS) && (result != LDAP_TYPE_OR_VALUE_EXISTS)) {
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_update_member_value - Unable to add \"%s\" as "
|
||||
"a \"%s\" value to group \"%s\" (%s).\n",
|
||||
@@ -1770,7 +1769,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
}
|
||||
} else {
|
||||
/* delete value */
|
||||
- if ((result != LDAP_SUCCESS) && (result != LDAP_NO_SUCH_ATTRIBUTE)) {
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_update_member_value - Unable to delete \"%s\" as "
|
||||
"a \"%s\" value from group \"%s\" (%s).\n",
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index f79b083a9..f3dc7cf00 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1482,18 +1482,9 @@ memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
mod.mod_op = LDAP_MOD_DELETE;
|
||||
mod.mod_type = ((memberof_del_dn_data *)callback_data)->type;
|
||||
mod.mod_values = val;
|
||||
-
|
||||
- slapi_modify_internal_set_pb_ext(
|
||||
- mod_pb, slapi_entry_get_sdn(e),
|
||||
- mods, 0, 0,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
-
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
-
|
||||
- slapi_pblock_get(mod_pb,
|
||||
- SLAPI_PLUGIN_INTOP_RESULT,
|
||||
- &rc);
|
||||
-
|
||||
+ /* Internal mod with error overrides for DEL/ADD */
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, slapi_entry_get_sdn(e), mods,
|
||||
+ memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
|
||||
if (rc == LDAP_NO_SUCH_ATTRIBUTE && val[0] == NULL) {
|
||||
@@ -1966,6 +1957,7 @@ memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
|
||||
return rc;
|
||||
}
|
||||
+
|
||||
LDAPMod **
|
||||
my_copy_mods(LDAPMod **orig_mods)
|
||||
{
|
||||
@@ -2774,33 +2766,6 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_o
|
||||
replace_mod.mod_values = replace_val;
|
||||
}
|
||||
rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- if (rc == LDAP_NO_SUCH_ATTRIBUTE || rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- /*
|
||||
- * For some reason the new modrdn value is present, so retry
|
||||
- * the delete by itself and ignore the add op by tweaking
|
||||
- * the mod array.
|
||||
- */
|
||||
- mods[1] = NULL;
|
||||
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- } else {
|
||||
- /*
|
||||
- * The memberof value to be replaced does not exist so just
|
||||
- * add the new value. Shuffle the mod array to apply only
|
||||
- * the add operation.
|
||||
- */
|
||||
- mods[0] = mods[1];
|
||||
- mods[1] = NULL;
|
||||
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- /*
|
||||
- * The entry already has the expected memberOf value, no
|
||||
- * problem just return success.
|
||||
- */
|
||||
- rc = LDAP_SUCCESS;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4454,43 +4419,57 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
|
||||
Slapi_PBlock *mod_pb = NULL;
|
||||
int added_oc = 0;
|
||||
int rc = 0;
|
||||
+ LDAPMod *single_mod[2];
|
||||
|
||||
- while (1) {
|
||||
- mod_pb = slapi_pblock_new();
|
||||
- slapi_modify_internal_set_pb(
|
||||
- mod_pb, dn, mods, 0, 0,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
-
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
- if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
- if (!add_oc || added_oc) {
|
||||
- /*
|
||||
- * We aren't auto adding an objectclass, or we already
|
||||
- * added the objectclass, and we are still failing.
|
||||
- */
|
||||
+ if (!dn || !mods) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Invalid argument: %s%s is NULL\n",
|
||||
+ !dn ? "dn " : "",
|
||||
+ !mods ? "mods " : "");
|
||||
+ return LDAP_PARAM_ERROR;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ mod_pb = slapi_pblock_new();
|
||||
+ /* Split multiple mods into individual mod operations */
|
||||
+ for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
|
||||
+ single_mod[0] = mods[i];
|
||||
+ single_mod[1] = NULL;
|
||||
+
|
||||
+ while (1) {
|
||||
+ slapi_pblock_init(mod_pb);
|
||||
+
|
||||
+ /* Internal mod with error overrides for DEL/ADD */
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_normdn_byref(dn), single_mod,
|
||||
+ memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
+ if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
+ if (!add_oc || added_oc) {
|
||||
+ /*
|
||||
+ * We aren't auto adding an objectclass, or we already
|
||||
+ * added the objectclass, and we are still failing.
|
||||
+ */
|
||||
+ break;
|
||||
+ }
|
||||
+ rc = memberof_add_objectclass(add_oc, dn);
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Entry %s - schema violation caught - repair operation %s\n",
|
||||
+ dn ? dn : "unknown",
|
||||
+ rc ? "failed" : "succeeded");
|
||||
+ if (rc) {
|
||||
+ /* Failed to add objectclass */
|
||||
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
|
||||
+ break;
|
||||
+ }
|
||||
+ added_oc = 1;
|
||||
+ } else if (rc) {
|
||||
+ /* Some other fatal error */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_add_memberof_attr - Internal modify failed. rc=%d\n", rc);
|
||||
break;
|
||||
- }
|
||||
- rc = memberof_add_objectclass(add_oc, dn);
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "Entry %s - schema violation caught - repair operation %s\n",
|
||||
- dn ? dn : "unknown",
|
||||
- rc ? "failed" : "succeeded");
|
||||
- if (rc) {
|
||||
- /* Failed to add objectclass */
|
||||
- rc = LDAP_OBJECT_CLASS_VIOLATION;
|
||||
+ } else {
|
||||
+ /* success */
|
||||
break;
|
||||
}
|
||||
- added_oc = 1;
|
||||
- slapi_pblock_destroy(mod_pb);
|
||||
- } else if (rc) {
|
||||
- /* Some other fatal error */
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_add_memberof_attr - Internal modify failed. rc=%d\n", rc);
|
||||
- break;
|
||||
- } else {
|
||||
- /* success */
|
||||
- break;
|
||||
}
|
||||
}
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
|
||||
index 28240c1f6..c5e259d8d 100644
|
||||
--- a/ldap/servers/plugins/referint/referint.c
|
||||
+++ b/ldap/servers/plugins/referint/referint.c
|
||||
@@ -711,19 +711,28 @@ static int
|
||||
_do_modify(Slapi_PBlock *mod_pb, Slapi_DN *entrySDN, LDAPMod **mods)
|
||||
{
|
||||
int rc = 0;
|
||||
+ LDAPMod *mod[2];
|
||||
|
||||
- slapi_pblock_init(mod_pb);
|
||||
+ /* Split multiple modifications into individual modify operations */
|
||||
+ for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
|
||||
+ mod[0] = mods[i];
|
||||
+ mod[1] = NULL;
|
||||
|
||||
- if (allow_repl) {
|
||||
- /* Must set as a replicated operation */
|
||||
- slapi_modify_internal_set_pb_ext(mod_pb, entrySDN, mods, NULL, NULL,
|
||||
- referint_plugin_identity, OP_FLAG_REPLICATED);
|
||||
- } else {
|
||||
- slapi_modify_internal_set_pb_ext(mod_pb, entrySDN, mods, NULL, NULL,
|
||||
- referint_plugin_identity, 0);
|
||||
+ slapi_pblock_init(mod_pb);
|
||||
+
|
||||
+ /* Do a single mod with error overrides for DEL/ADD */
|
||||
+ if (allow_repl) {
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
|
||||
+ referint_plugin_identity, OP_FLAG_REPLICATED);
|
||||
+ } else {
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
|
||||
+ referint_plugin_identity, 0);
|
||||
+ }
|
||||
+
|
||||
+ if (rc != LDAP_SUCCESS) {
|
||||
+ return rc;
|
||||
+ }
|
||||
}
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -1033,7 +1042,6 @@ _update_all_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
|
||||
/* (case 1) */
|
||||
slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
|
||||
slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newDN);
|
||||
-
|
||||
} else if (p) {
|
||||
/* (case 2) */
|
||||
slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index 669bb104c..455eb63ec 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -492,6 +492,57 @@ slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_IDENTITY, plugin_identity);
|
||||
}
|
||||
|
||||
+/* Performs a single LDAP modify operation with error overrides.
|
||||
+ *
|
||||
+ * If specific errors occur, such as attempting to add an existing attribute or
|
||||
+ * delete a non-existent one, the function overrides the error and returns success:
|
||||
+ * - LDAP_MOD_ADD -> LDAP_TYPE_OR_VALUE_EXISTS (ignored)
|
||||
+ * - LDAP_MOD_DELETE -> LDAP_NO_SUCH_ATTRIBUTE (ignored)
|
||||
+ *
|
||||
+ * Any other errors encountered during the operation will be returned as-is.
|
||||
+ */
|
||||
+int
|
||||
+slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_id, int op_flags)
|
||||
+{
|
||||
+ int rc = 0;
|
||||
+ int result = 0;
|
||||
+ int result_reset = 0;
|
||||
+ int mod_op = 0;
|
||||
+
|
||||
+ if (!pb || !sdn || !mod || !mod[0]) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "slapi_single_modify_internal_override",
|
||||
+ "Invalid argument: %s%s%s%s is NULL\n",
|
||||
+ !pb ? "pb " : "",
|
||||
+ !sdn ? "sdn " : "",
|
||||
+ !mod ? "mod " : "",
|
||||
+ !mod[0] ? "mod[0] " : "");
|
||||
+
|
||||
+ return LDAP_PARAM_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ slapi_modify_internal_set_pb_ext(pb, sdn, mod, NULL, NULL, plugin_id, op_flags);
|
||||
+ slapi_modify_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
+ mod_op = mod[0]->mod_op & LDAP_MOD_OP;
|
||||
+ if ((mod_op == LDAP_MOD_ADD && result == LDAP_TYPE_OR_VALUE_EXISTS) ||
|
||||
+ (mod_op == LDAP_MOD_DELETE && result == LDAP_NO_SUCH_ATTRIBUTE)) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_single_modify_internal_override",
|
||||
+ "Overriding return code - plugin:%s dn:%s mod_op:%d result:%d\n",
|
||||
+ plugin_id ? plugin_id->sci_component_name : "unknown",
|
||||
+ sdn ? sdn->udn : "unknown", mod_op, result);
|
||||
+
|
||||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_INTOP_RESULT, &result_reset);
|
||||
+ rc = LDAP_SUCCESS;
|
||||
+ } else {
|
||||
+ rc = result;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
/* Helper functions */
|
||||
|
||||
static int
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 9fdcaccc8..a84a60c92 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -5965,6 +5965,7 @@ void slapi_add_entry_internal_set_pb(Slapi_PBlock *pb, Slapi_Entry *e, LDAPContr
|
||||
int slapi_add_internal_set_pb(Slapi_PBlock *pb, const char *dn, LDAPMod **attrs, LDAPControl **controls, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
void slapi_modify_internal_set_pb(Slapi_PBlock *pb, const char *dn, LDAPMod **mods, LDAPControl **controls, const char *uniqueid, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
void slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mods, LDAPControl **controls, const char *uniqueid, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
+int slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
/**
|
||||
* Set \c Slapi_PBlock to perform modrdn/rename internally
|
||||
*
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,415 +0,0 @@
|
||||
From ccaaaa31a86eb059315580249838d72e4a51bf8b Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 14 Jan 2025 18:12:56 +0100
|
||||
Subject: [PATCH] Issue 6470 - Some replication status data are reset upon a
|
||||
restart (#6471)
|
||||
|
||||
Bug description:
|
||||
The replication agreement contains operational attributes
|
||||
related to the total init: nsds5replicaLastInitStart,
|
||||
nsds5replicaLastInitEnd, nsds5replicaLastInitStatus.
|
||||
Those attributes are reset at restart
|
||||
|
||||
Fix description:
|
||||
When reading the replication agreement from config
|
||||
(agmt_new_from_entry) restore the attributes into
|
||||
the in-memory RA.
|
||||
Updates the RA config entry from the in-memory RA
|
||||
during shutdown/cleanallruv/enable_ra
|
||||
|
||||
fixes: #6470
|
||||
|
||||
Reviewed by: Simon Pichugin (Thanks !!)
|
||||
|
||||
(cherry picked from commit 90071a334517be523e498bded5b663c50c40ee3f)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../suites/replication/single_master_test.py | 128 ++++++++++++++++
|
||||
ldap/servers/plugins/replication/repl5.h | 4 +
|
||||
ldap/servers/plugins/replication/repl5_agmt.c | 140 +++++++++++++++++-
|
||||
.../plugins/replication/repl5_agmtlist.c | 1 +
|
||||
.../replication/repl5_replica_config.c | 1 +
|
||||
.../plugins/replication/repl_globals.c | 3 +
|
||||
6 files changed, 273 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/single_master_test.py b/dirsrvtests/tests/suites/replication/single_master_test.py
|
||||
index e927e6cfd..f448d2342 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/single_master_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/single_master_test.py
|
||||
@@ -13,6 +13,7 @@ from lib389.utils import *
|
||||
from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
|
||||
|
||||
from lib389.replica import ReplicationManager, Replicas
|
||||
+from lib389.agreement import Agreements
|
||||
from lib389.backend import Backends
|
||||
|
||||
from lib389.topologies import topology_m1c1 as topo_r # Replication
|
||||
@@ -154,6 +155,133 @@ def test_lastupdate_attr_before_init(topo_nr):
|
||||
json_obj = json.loads(json_status)
|
||||
log.debug("JSON status message: {}".format(json_obj))
|
||||
|
||||
+def test_total_init_operational_attr(topo_r):
|
||||
+ """Check that operation attributes nsds5replicaLastInitStatus
|
||||
+ nsds5replicaLastInitStart and nsds5replicaLastInitEnd
|
||||
+ are preserved between restart
|
||||
+
|
||||
+ :id: 6ba00bb1-87c0-47dd-86e0-ccf892b3985b
|
||||
+ :customerscenario: True
|
||||
+ :setup: Replication setup with supplier and consumer instances,
|
||||
+ test user on supplier
|
||||
+ :steps:
|
||||
+ 1. Check that user was replicated to consumer
|
||||
+ 2. Trigger a first total init
|
||||
+ 3. Check status/start/end values are set on the supplier
|
||||
+ 4. Restart supplier
|
||||
+ 5. Check previous status/start/end values are preserved
|
||||
+ 6. Trigger a second total init
|
||||
+ 7. Check status/start/end values are set on the supplier
|
||||
+ 8. Restart supplier
|
||||
+ 9. Check previous status/start/end values are preserved
|
||||
+ 10. Check status/start/end values are different between
|
||||
+ first and second total init
|
||||
+ :expectedresults:
|
||||
+ 1. The user should be replicated to consumer
|
||||
+ 2. Total init should be successful
|
||||
+ 3. It must exist a values
|
||||
+ 4. Operation should be successful
|
||||
+ 5. Check values are identical before/after restart
|
||||
+ 6. Total init should be successful
|
||||
+ 7. It must exist a values
|
||||
+ 8. Operation should be successful
|
||||
+ 9. Check values are identical before/after restart
|
||||
+ 10. values must differ between first/second total init
|
||||
+ """
|
||||
+
|
||||
+ supplier = topo_r.ms["supplier1"]
|
||||
+ consumer = topo_r.cs["consumer1"]
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # Create a test user
|
||||
+ m_users = UserAccounts(topo_r.ms["supplier1"], DEFAULT_SUFFIX)
|
||||
+ m_user = m_users.ensure_state(properties=TEST_USER_PROPERTIES)
|
||||
+ m_user.ensure_present('mail', 'testuser@redhat.com')
|
||||
+
|
||||
+ # Then check it is replicated
|
||||
+ log.info("Check that replication is working")
|
||||
+ repl.wait_for_replication(supplier, consumer)
|
||||
+ c_users = UserAccounts(topo_r.cs["consumer1"], DEFAULT_SUFFIX)
|
||||
+ c_user = c_users.get('testuser')
|
||||
+ assert c_user
|
||||
+
|
||||
+ # Retrieve the replication agreement S1->C1
|
||||
+ replica_supplier = Replicas(supplier).get(DEFAULT_SUFFIX)
|
||||
+ agmts_supplier = Agreements(supplier, replica_supplier.dn)
|
||||
+ supplier_consumer = None
|
||||
+ for agmt in agmts_supplier.list():
|
||||
+ if (agmt.get_attr_val_utf8('nsDS5ReplicaPort') == str(consumer.port) and
|
||||
+ agmt.get_attr_val_utf8('nsDS5ReplicaHost') == consumer.host):
|
||||
+ supplier_consumer = agmt
|
||||
+ break
|
||||
+ assert supplier_consumer
|
||||
+
|
||||
+ # Trigger a first total init and check that
|
||||
+ # start/end/status is updated AND preserved during a restart
|
||||
+ log.info("First total init")
|
||||
+ supplier_consumer.begin_reinit()
|
||||
+ (done, error) = supplier_consumer.wait_reinit()
|
||||
+ assert done is True
|
||||
+
|
||||
+ status_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert status_1
|
||||
+
|
||||
+ initStart_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert initStart_1
|
||||
+
|
||||
+ initEnd_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert initEnd_1
|
||||
+
|
||||
+ log.info("Check values from first total init are preserved")
|
||||
+ supplier.restart()
|
||||
+ post_restart_status_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert post_restart_status_1
|
||||
+ assert post_restart_status_1 == status_1
|
||||
+
|
||||
+ post_restart_initStart_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert post_restart_initStart_1
|
||||
+ assert post_restart_initStart_1 == initStart_1
|
||||
+
|
||||
+ post_restart_initEnd_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert post_restart_initEnd_1 == initEnd_1
|
||||
+
|
||||
+ # Trigger a second total init and check that
|
||||
+ # start/end/status is updated (differ from previous values)
|
||||
+ # AND new values are preserved during a restart
|
||||
+ time.sleep(1)
|
||||
+ log.info("Second total init")
|
||||
+ supplier_consumer.begin_reinit()
|
||||
+ (done, error) = supplier_consumer.wait_reinit()
|
||||
+ assert done is True
|
||||
+
|
||||
+ status_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert status_2
|
||||
+
|
||||
+ initStart_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert initStart_2
|
||||
+
|
||||
+ initEnd_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert initEnd_2
|
||||
+
|
||||
+ log.info("Check values from second total init are preserved")
|
||||
+ supplier.restart()
|
||||
+ post_restart_status_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert post_restart_status_2
|
||||
+ assert post_restart_status_2 == status_2
|
||||
+
|
||||
+ post_restart_initStart_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert post_restart_initStart_2
|
||||
+ assert post_restart_initStart_2 == initStart_2
|
||||
+
|
||||
+ post_restart_initEnd_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert post_restart_initEnd_2 == initEnd_2
|
||||
+
|
||||
+ # Check that values are updated by total init
|
||||
+ log.info("Check values from first/second total init are different")
|
||||
+ assert status_2 == status_1
|
||||
+ assert initStart_2 != initStart_1
|
||||
+ assert initEnd_2 != initEnd_1
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
|
||||
index c2fbff8c0..65e2059e7 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5.h
|
||||
+++ b/ldap/servers/plugins/replication/repl5.h
|
||||
@@ -165,6 +165,9 @@ extern const char *type_nsds5ReplicaBootstrapCredentials;
|
||||
extern const char *type_nsds5ReplicaBootstrapBindMethod;
|
||||
extern const char *type_nsds5ReplicaBootstrapTransportInfo;
|
||||
extern const char *type_replicaKeepAliveUpdateInterval;
|
||||
+extern const char *type_nsds5ReplicaLastInitStart;
|
||||
+extern const char *type_nsds5ReplicaLastInitEnd;
|
||||
+extern const char *type_nsds5ReplicaLastInitStatus;
|
||||
|
||||
/* Attribute names for windows replication agreements */
|
||||
extern const char *type_nsds7WindowsReplicaArea;
|
||||
@@ -430,6 +433,7 @@ void agmt_notify_change(Repl_Agmt *ra, Slapi_PBlock *pb);
|
||||
Object *agmt_get_consumer_ruv(Repl_Agmt *ra);
|
||||
ReplicaId agmt_get_consumer_rid(Repl_Agmt *ra, void *conn);
|
||||
int agmt_set_consumer_ruv(Repl_Agmt *ra, RUV *ruv);
|
||||
+void agmt_update_init_status(Repl_Agmt *ra);
|
||||
void agmt_update_consumer_ruv(Repl_Agmt *ra);
|
||||
CSN *agmt_get_consumer_schema_csn(Repl_Agmt *ra);
|
||||
void agmt_set_consumer_schema_csn(Repl_Agmt *ra, CSN *csn);
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
index a71343dec..c3b8d298c 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
@@ -56,6 +56,7 @@
|
||||
#include "repl5_prot_private.h"
|
||||
#include "cl5_api.h"
|
||||
#include "slapi-plugin.h"
|
||||
+#include "slap.h"
|
||||
|
||||
#define DEFAULT_TIMEOUT 120 /* (seconds) default outbound LDAP connection */
|
||||
#define DEFAULT_FLOWCONTROL_WINDOW 1000 /* #entries sent without acknowledgment */
|
||||
@@ -510,10 +511,33 @@ agmt_new_from_entry(Slapi_Entry *e)
|
||||
ra->last_update_status[0] = '\0';
|
||||
ra->update_in_progress = PR_FALSE;
|
||||
ra->stop_in_progress = PR_FALSE;
|
||||
- ra->last_init_end_time = 0UL;
|
||||
- ra->last_init_start_time = 0UL;
|
||||
- ra->last_init_status[0] = '\0';
|
||||
- ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_MASTERS + 1,
|
||||
+ val = (char *)slapi_entry_attr_get_ref(e, type_nsds5ReplicaLastInitEnd);
|
||||
+ if (val) {
|
||||
+ time_t init_end_time;
|
||||
+
|
||||
+ init_end_time = parse_genTime((char *) val);
|
||||
+ if (init_end_time == NO_TIME || init_end_time == SLAPD_END_TIME) {
|
||||
+ ra->last_init_end_time = 0UL;
|
||||
+ } else {
|
||||
+ ra->last_init_end_time = init_end_time;
|
||||
+ }
|
||||
+ }
|
||||
+ val = (char *)slapi_entry_attr_get_ref(e, type_nsds5ReplicaLastInitStart);
|
||||
+ if (val) {
|
||||
+ time_t init_start_time;
|
||||
+
|
||||
+ init_start_time = parse_genTime((char *) val);
|
||||
+ if (init_start_time == NO_TIME || init_start_time == SLAPD_END_TIME) {
|
||||
+ ra->last_init_start_time = 0UL;
|
||||
+ } else {
|
||||
+ ra->last_init_start_time = init_start_time;
|
||||
+ }
|
||||
+ }
|
||||
+ val = (char *)slapi_entry_attr_get_ref(e, type_nsds5ReplicaLastInitStatus);
|
||||
+ if (val) {
|
||||
+ strcpy(ra->last_init_status, val);
|
||||
+ }
|
||||
+ ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_SUPPLIERS + 1,
|
||||
sizeof(struct changecounter *));
|
||||
ra->num_changecounters = 0;
|
||||
ra->max_changecounters = MAX_NUM_OF_MASTERS;
|
||||
@@ -2504,6 +2528,113 @@ agmt_set_consumer_ruv(Repl_Agmt *ra, RUV *ruv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+void
|
||||
+agmt_update_init_status(Repl_Agmt *ra)
|
||||
+{
|
||||
+ int rc;
|
||||
+ Slapi_PBlock *pb;
|
||||
+ LDAPMod **mods;
|
||||
+ int nb_mods = 0;
|
||||
+ int mod_idx;
|
||||
+ Slapi_Mod smod_start_time = {0};
|
||||
+ Slapi_Mod smod_end_time = {0};
|
||||
+ Slapi_Mod smod_status = {0};
|
||||
+
|
||||
+ PR_ASSERT(ra);
|
||||
+ PR_Lock(ra->lock);
|
||||
+
|
||||
+ if (ra->last_init_start_time) {
|
||||
+ nb_mods++;
|
||||
+ }
|
||||
+ if (ra->last_init_end_time) {
|
||||
+ nb_mods++;
|
||||
+ }
|
||||
+ if (ra->last_init_status[0] != '\0') {
|
||||
+ nb_mods++;
|
||||
+ }
|
||||
+ if (nb_mods == 0) {
|
||||
+ /* shortcut. no need to go further */
|
||||
+ PR_Unlock(ra->lock);
|
||||
+ return;
|
||||
+ }
|
||||
+ mods = (LDAPMod **) slapi_ch_malloc((nb_mods + 1) * sizeof(LDAPMod *));
|
||||
+ mod_idx = 0;
|
||||
+ if (ra->last_init_start_time) {
|
||||
+ struct berval val;
|
||||
+ char *time_tmp = NULL;
|
||||
+ slapi_mod_init(&smod_start_time, 1);
|
||||
+ slapi_mod_set_type(&smod_start_time, type_nsds5ReplicaLastInitStart);
|
||||
+ slapi_mod_set_operation(&smod_start_time, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
|
||||
+
|
||||
+ time_tmp = format_genTime(ra->last_init_start_time);
|
||||
+ val.bv_val = time_tmp;
|
||||
+ val.bv_len = strlen(time_tmp);
|
||||
+ slapi_mod_add_value(&smod_start_time, &val);
|
||||
+ slapi_ch_free((void **)&time_tmp);
|
||||
+ mods[mod_idx] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_start_time);
|
||||
+ mod_idx++;
|
||||
+ }
|
||||
+ if (ra->last_init_end_time) {
|
||||
+ struct berval val;
|
||||
+ char *time_tmp = NULL;
|
||||
+ slapi_mod_init(&smod_end_time, 1);
|
||||
+ slapi_mod_set_type(&smod_end_time, type_nsds5ReplicaLastInitEnd);
|
||||
+ slapi_mod_set_operation(&smod_end_time, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
|
||||
+
|
||||
+ time_tmp = format_genTime(ra->last_init_end_time);
|
||||
+ val.bv_val = time_tmp;
|
||||
+ val.bv_len = strlen(time_tmp);
|
||||
+ slapi_mod_add_value(&smod_end_time, &val);
|
||||
+ slapi_ch_free((void **)&time_tmp);
|
||||
+ mods[mod_idx] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_end_time);
|
||||
+ mod_idx++;
|
||||
+ }
|
||||
+ if (ra->last_init_status[0] != '\0') {
|
||||
+ struct berval val;
|
||||
+ char *init_status = NULL;
|
||||
+ slapi_mod_init(&smod_status, 1);
|
||||
+ slapi_mod_set_type(&smod_status, type_nsds5ReplicaLastInitStatus);
|
||||
+ slapi_mod_set_operation(&smod_status, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
|
||||
+
|
||||
+ init_status = slapi_ch_strdup(ra->last_init_status);
|
||||
+ val.bv_val = init_status;
|
||||
+ val.bv_len = strlen(init_status);
|
||||
+ slapi_mod_add_value(&smod_status, &val);
|
||||
+ slapi_ch_free((void **)&init_status);
|
||||
+ mods[mod_idx] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_status);
|
||||
+ mod_idx++;
|
||||
+ }
|
||||
+
|
||||
+ if (nb_mods) {
|
||||
+ /* it is ok to release the lock here because we are done with the agreement data.
|
||||
+ we have to do it before issuing the modify operation because it causes
|
||||
+ agmtlist_notify_all to be called which uses the same lock - hence the deadlock */
|
||||
+ PR_Unlock(ra->lock);
|
||||
+
|
||||
+ pb = slapi_pblock_new();
|
||||
+ mods[nb_mods] = NULL;
|
||||
+
|
||||
+ slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL,
|
||||
+ repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
|
||||
+ slapi_modify_internal_pb(pb);
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+ if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmt_update_consumer_ruv - "
|
||||
+ "%s: agmt_update_consumer_ruv: "
|
||||
+ "failed to update consumer's RUV; LDAP error - %d\n",
|
||||
+ ra->long_name, rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+ } else {
|
||||
+ PR_Unlock(ra->lock);
|
||||
+ }
|
||||
+ slapi_mod_done(&smod_start_time);
|
||||
+ slapi_mod_done(&smod_end_time);
|
||||
+ slapi_mod_done(&smod_status);
|
||||
+}
|
||||
+
|
||||
void
|
||||
agmt_update_consumer_ruv(Repl_Agmt *ra)
|
||||
{
|
||||
@@ -3123,6 +3254,7 @@ agmt_set_enabled_from_entry(Repl_Agmt *ra, Slapi_Entry *e, char *returntext)
|
||||
PR_Unlock(ra->lock);
|
||||
agmt_stop(ra);
|
||||
agmt_update_consumer_ruv(ra);
|
||||
+ agmt_update_init_status(ra);
|
||||
agmt_set_last_update_status(ra, 0, 0, "agreement disabled");
|
||||
return rc;
|
||||
}
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c
|
||||
index 18b641f8c..e3b1e814c 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_agmtlist.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_agmtlist.c
|
||||
@@ -782,6 +782,7 @@ agmtlist_shutdown()
|
||||
ra = (Repl_Agmt *)object_get_data(ro);
|
||||
agmt_stop(ra);
|
||||
agmt_update_consumer_ruv(ra);
|
||||
+ agmt_update_init_status(ra);
|
||||
next_ro = objset_next_obj(agmt_set, ro);
|
||||
/* Object ro was released in objset_next_obj,
|
||||
* but the address ro can be still used to remove ro from objset. */
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
|
||||
index aea2cf506..8cc7423bf 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_replica_config.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
|
||||
@@ -2006,6 +2006,7 @@ clean_agmts(cleanruv_data *data)
|
||||
cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Cleaning agmt...");
|
||||
agmt_stop(agmt);
|
||||
agmt_update_consumer_ruv(agmt);
|
||||
+ agmt_update_init_status(agmt);
|
||||
agmt_start(agmt);
|
||||
agmt_obj = agmtlist_get_next_agreement_for_replica(data->replica, agmt_obj);
|
||||
}
|
||||
diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c
|
||||
index 797ca957f..e6b89c33b 100644
|
||||
--- a/ldap/servers/plugins/replication/repl_globals.c
|
||||
+++ b/ldap/servers/plugins/replication/repl_globals.c
|
||||
@@ -118,6 +118,9 @@ const char *type_nsds5ReplicaBootstrapBindDN = "nsds5ReplicaBootstrapBindDN";
|
||||
const char *type_nsds5ReplicaBootstrapCredentials = "nsds5ReplicaBootstrapCredentials";
|
||||
const char *type_nsds5ReplicaBootstrapBindMethod = "nsds5ReplicaBootstrapBindMethod";
|
||||
const char *type_nsds5ReplicaBootstrapTransportInfo = "nsds5ReplicaBootstrapTransportInfo";
|
||||
+const char *type_nsds5ReplicaLastInitStart = "nsds5replicaLastInitStart";
|
||||
+const char *type_nsds5ReplicaLastInitEnd = "nsds5replicaLastInitEnd";
|
||||
+const char *type_nsds5ReplicaLastInitStatus = "nsds5replicaLastInitStatus";
|
||||
|
||||
/* windows sync specific attributes */
|
||||
const char *type_nsds7WindowsReplicaArea = "nsds7WindowsReplicaSubtree";
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,671 +0,0 @@
|
||||
From dfb7e19fdcefe4af683a235ea7113956248571e3 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Thu, 17 Nov 2022 14:21:17 +0100
|
||||
Subject: [PATCH] Issue 3729 - RFE Extend log of operations statistics in
|
||||
access log (#5508)
|
||||
|
||||
Bug description:
|
||||
Create a per operation framework to collect/display
|
||||
statistics about internal ressource consumption
|
||||
|
||||
Fix description:
|
||||
|
||||
The fix contains 2 parts
|
||||
The framework, that registers a per operation object extension
|
||||
(op_stat_init). The extension is used to store/retrieve
|
||||
collected statistics.
|
||||
To reduce the impact of collecting/logging it uses a toggle
|
||||
with config attribute 'nsslapd-statlog-level' that is a bit mask.
|
||||
So that data are collected and logged only if the appropriate
|
||||
statistic level is set.
|
||||
|
||||
An exemple of statistic level regarding indexes fetching
|
||||
during the evaluation of a search filter.
|
||||
it is implemented in filterindex.c (store) and result.c (retrieve/log).
|
||||
This path uses LDAP_STAT_READ_INDEX=0x1.
|
||||
For LDAP_STAT_READ_INDEX, the collected data are:
|
||||
- for each key (attribute, type, value) the number of
|
||||
IDs
|
||||
- the duration to fetch all the values
|
||||
|
||||
design https://www.port389.org/docs/389ds/design/log-operation-stats.html
|
||||
relates: #3729
|
||||
|
||||
Reviewed by: Pierre Rogier, Mark Reynolds (thanks !)
|
||||
|
||||
(cherry picked from commit a480d2cbfa2b1325f44ab3e1c393c5ee348b388e)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 73 ++++++++++++++++
|
||||
ldap/servers/slapd/back-ldbm/filterindex.c | 49 +++++++++++
|
||||
ldap/servers/slapd/libglobs.c | 48 +++++++++++
|
||||
ldap/servers/slapd/log.c | 26 ++++++
|
||||
ldap/servers/slapd/log.h | 1 +
|
||||
ldap/servers/slapd/main.c | 1 +
|
||||
ldap/servers/slapd/operation.c | 86 +++++++++++++++++++
|
||||
ldap/servers/slapd/proto-slap.h | 8 ++
|
||||
ldap/servers/slapd/result.c | 64 ++++++++++++++
|
||||
ldap/servers/slapd/slap.h | 4 +
|
||||
ldap/servers/slapd/slapi-private.h | 27 ++++++
|
||||
11 files changed, 387 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 84d721756..43288f67f 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -27,6 +27,7 @@ from lib389.idm.group import Groups
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from lib389._constants import DEFAULT_SUFFIX, LOG_ACCESS_LEVEL, PASSWORD
|
||||
from lib389.utils import ds_is_older, ds_is_newer
|
||||
+from lib389.dseldif import DSEldif
|
||||
import ldap
|
||||
import glob
|
||||
import re
|
||||
@@ -1250,6 +1251,78 @@ def test_missing_backend_suffix(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+def test_stat_index(topology_st, request):
|
||||
+ """Testing nsslapd-statlog-level with indexing statistics
|
||||
+
|
||||
+ :id: fcabab05-f000-468c-8eb4-02ce3c39c902
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Check that nsslapd-statlog-level is 0 (default)
|
||||
+ 2. Create 20 users with 'cn' starting with 'user\_'
|
||||
+ 3. Check there is no statistic record in the access log with ADD
|
||||
+ 4. Check there is no statistic record in the access log with SRCH
|
||||
+ 5. Set nsslapd-statlog-level=LDAP_STAT_READ_INDEX (0x1) to get
|
||||
+ statistics when reading indexes
|
||||
+ 6. Check there is statistic records in access log with SRCH
|
||||
+ :expectedresults:
|
||||
+ 1. This should pass
|
||||
+ 2. This should pass
|
||||
+ 3. This should pass
|
||||
+ 4. This should pass
|
||||
+ 5. This should pass
|
||||
+ 6. This should pass
|
||||
+ """
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 1
|
||||
+ log.info("Assert nsslapd-statlog-level is by default 0")
|
||||
+ assert topology_st.standalone.config.get_attr_val_int("nsslapd-statlog-level") == 0
|
||||
+
|
||||
+ # Step 2
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ users_set = []
|
||||
+ log.info('Adding 20 users')
|
||||
+ for i in range(20):
|
||||
+ name = 'user_%d' % i
|
||||
+ last_user = users.create(properties={
|
||||
+ 'uid': name,
|
||||
+ 'sn': name,
|
||||
+ 'cn': name,
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '1000',
|
||||
+ 'homeDirectory': '/home/%s' % name,
|
||||
+ 'mail': '%s@example.com' % name,
|
||||
+ 'userpassword': 'pass%s' % name,
|
||||
+ })
|
||||
+ users_set.append(last_user)
|
||||
+
|
||||
+ # Step 3
|
||||
+ assert not topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+
|
||||
+ # Step 4
|
||||
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "cn=user_*")
|
||||
+ assert not topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+
|
||||
+ # Step 5
|
||||
+ log.info("Set nsslapd-statlog-level: 1 to enable indexing statistics")
|
||||
+ topology_st.standalone.config.set("nsslapd-statlog-level", "1")
|
||||
+
|
||||
+ # Step 6
|
||||
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "cn=user_*")
|
||||
+ topology_st.standalone.stop()
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: attribute.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: duration.*')
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting users')
|
||||
+ for user in users_set:
|
||||
+ user.delete()
|
||||
+ topology_st.standalone.config.set("nsslapd-statlog-level", "0")
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
index 8a79848c3..30550dde7 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
@@ -1040,13 +1040,57 @@ keys2idl(
|
||||
int allidslimit)
|
||||
{
|
||||
IDList *idl = NULL;
|
||||
+ Op_stat *op_stat;
|
||||
+ PRBool collect_stat = PR_FALSE;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "keys2idl", "=> type %s indextype %s\n", type, indextype);
|
||||
+
|
||||
+ /* Before reading the index take the start time */
|
||||
+ if (LDAP_STAT_READ_INDEX & config_get_statlog_level()) {
|
||||
+ op_stat = op_stat_get_operation_extension(pb);
|
||||
+ if (op_stat->search_stat) {
|
||||
+ collect_stat = PR_TRUE;
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_start));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
for (uint32_t i = 0; ivals[i] != NULL; i++) {
|
||||
IDList *idl2 = NULL;
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+ int key_len;
|
||||
|
||||
idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
+ if (collect_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+
|
||||
+ /* indextype e.g. "eq" or "sub" (see index.c) */
|
||||
+ if (indextype) {
|
||||
+ key_stat->index_type = slapi_ch_strdup(indextype);
|
||||
+ }
|
||||
+ /* key value e.g. '^st' or 'smith'*/
|
||||
+ key_len = slapi_value_get_length(ivals[i]);
|
||||
+ if (key_len) {
|
||||
+ key_stat->key = (char *) slapi_ch_calloc(1, key_len + 1);
|
||||
+ memcpy(key_stat->key, slapi_value_get_string(ivals[i]), key_len);
|
||||
+ }
|
||||
|
||||
+ /* attribute name e.g. 'uid' */
|
||||
+ if (type) {
|
||||
+ key_stat->attribute_type = slapi_ch_strdup(type);
|
||||
+ }
|
||||
+
|
||||
+ /* Number of lookup IDs with the key */
|
||||
+ key_stat->id_lookup_cnt = idl2 ? idl2->b_nids : 0;
|
||||
+ if (op_stat->search_stat->keys_lookup) {
|
||||
+ /* it already exist key stat. add key_stat at the head */
|
||||
+ key_stat->next = op_stat->search_stat->keys_lookup;
|
||||
+ } else {
|
||||
+ /* this is the first key stat record */
|
||||
+ key_stat->next = NULL;
|
||||
+ }
|
||||
+ op_stat->search_stat->keys_lookup = key_stat;
|
||||
+ }
|
||||
#ifdef LDAP_ERROR_LOGGING
|
||||
/* XXX if ( slapd_ldap_debug & LDAP_DEBUG_TRACE ) { XXX */
|
||||
{
|
||||
@@ -1080,5 +1124,10 @@ keys2idl(
|
||||
}
|
||||
}
|
||||
|
||||
+ /* All the keys have been fetch, time to take the completion time */
|
||||
+ if (collect_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_end));
|
||||
+ }
|
||||
+
|
||||
return (idl);
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
|
||||
index 2097ab93c..99b2c5d8e 100644
|
||||
--- a/ldap/servers/slapd/libglobs.c
|
||||
+++ b/ldap/servers/slapd/libglobs.c
|
||||
@@ -712,6 +712,10 @@ static struct config_get_and_set
|
||||
NULL, 0,
|
||||
(void **)&global_slapdFrontendConfig.accessloglevel,
|
||||
CONFIG_INT, NULL, SLAPD_DEFAULT_ACCESSLOG_LEVEL_STR, NULL},
|
||||
+ {CONFIG_STATLOGLEVEL_ATTRIBUTE, config_set_statlog_level,
|
||||
+ NULL, 0,
|
||||
+ (void **)&global_slapdFrontendConfig.statloglevel,
|
||||
+ CONFIG_INT, NULL, SLAPD_DEFAULT_STATLOG_LEVEL, NULL},
|
||||
{CONFIG_ERRORLOG_LOGROTATIONTIMEUNIT_ATTRIBUTE, NULL,
|
||||
log_set_rotationtimeunit, SLAPD_ERROR_LOG,
|
||||
(void **)&global_slapdFrontendConfig.errorlog_rotationunit,
|
||||
@@ -1748,6 +1752,7 @@ FrontendConfig_init(void)
|
||||
cfg->accessloglevel = SLAPD_DEFAULT_ACCESSLOG_LEVEL;
|
||||
init_accesslogbuffering = cfg->accesslogbuffering = LDAP_ON;
|
||||
init_csnlogging = cfg->csnlogging = LDAP_ON;
|
||||
+ cfg->statloglevel = SLAPD_DEFAULT_STATLOG_LEVEL;
|
||||
|
||||
init_errorlog_logging_enabled = cfg->errorlog_logging_enabled = LDAP_ON;
|
||||
init_external_libs_debug_enabled = cfg->external_libs_debug_enabled = LDAP_OFF;
|
||||
@@ -5382,6 +5387,38 @@ config_set_accesslog_level(const char *attrname, char *value, char *errorbuf, in
|
||||
return retVal;
|
||||
}
|
||||
|
||||
+int
|
||||
+config_set_statlog_level(const char *attrname, char *value, char *errorbuf, int apply)
|
||||
+{
|
||||
+ int retVal = LDAP_SUCCESS;
|
||||
+ long level = 0;
|
||||
+ char *endp = NULL;
|
||||
+
|
||||
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
|
||||
+
|
||||
+ if (config_value_is_null(attrname, value, errorbuf, 1)) {
|
||||
+ return LDAP_OPERATIONS_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ errno = 0;
|
||||
+ level = strtol(value, &endp, 10);
|
||||
+
|
||||
+ if (*endp != '\0' || errno == ERANGE || level < 0) {
|
||||
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "%s: stat log level \"%s\" is invalid,"
|
||||
+ " access log level must range from 0 to %lld",
|
||||
+ attrname, value, (long long int)LONG_MAX);
|
||||
+ retVal = LDAP_OPERATIONS_ERROR;
|
||||
+ return retVal;
|
||||
+ }
|
||||
+
|
||||
+ if (apply) {
|
||||
+ CFG_LOCK_WRITE(slapdFrontendConfig);
|
||||
+ g_set_statlog_level(level);
|
||||
+ slapdFrontendConfig->statloglevel = level;
|
||||
+ CFG_UNLOCK_WRITE(slapdFrontendConfig);
|
||||
+ }
|
||||
+ return retVal;
|
||||
+}
|
||||
/* set the referral-mode url (which puts us into referral mode) */
|
||||
int
|
||||
config_set_referral_mode(const char *attrname __attribute__((unused)), char *url, char *errorbuf, int apply)
|
||||
@@ -6612,6 +6649,17 @@ config_get_accesslog_level()
|
||||
return retVal;
|
||||
}
|
||||
|
||||
+int
|
||||
+config_get_statlog_level()
|
||||
+{
|
||||
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
|
||||
+ int retVal;
|
||||
+
|
||||
+ retVal = slapdFrontendConfig->statloglevel;
|
||||
+
|
||||
+ return retVal;
|
||||
+}
|
||||
+
|
||||
/* return integer -- don't worry about locking similar to config_check_referral_mode
|
||||
below */
|
||||
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index 8074735e2..837a9c6fd 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -233,6 +233,17 @@ g_set_accesslog_level(int val)
|
||||
LOG_ACCESS_UNLOCK_WRITE();
|
||||
}
|
||||
|
||||
+/******************************************************************************
|
||||
+* Set the stat level
|
||||
+******************************************************************************/
|
||||
+void
|
||||
+g_set_statlog_level(int val)
|
||||
+{
|
||||
+ LOG_ACCESS_LOCK_WRITE();
|
||||
+ loginfo.log_access_stat_level = val;
|
||||
+ LOG_ACCESS_UNLOCK_WRITE();
|
||||
+}
|
||||
+
|
||||
/******************************************************************************
|
||||
* Set whether the process is alive or dead
|
||||
* If it is detached, then we write the error in 'stderr'
|
||||
@@ -283,6 +294,7 @@ g_log_init()
|
||||
if ((loginfo.log_access_buffer->lock = PR_NewLock()) == NULL) {
|
||||
exit(-1);
|
||||
}
|
||||
+ loginfo.log_access_stat_level = cfg->statloglevel;
|
||||
|
||||
/* ERROR LOG */
|
||||
loginfo.log_error_state = cfg->errorlog_logging_enabled;
|
||||
@@ -2640,7 +2652,21 @@ vslapd_log_access(char *fmt, va_list ap)
|
||||
|
||||
return (rc);
|
||||
}
|
||||
+int
|
||||
+slapi_log_stat(int loglevel, const char *fmt, ...)
|
||||
+{
|
||||
+ char buf[2048];
|
||||
+ va_list args;
|
||||
+ int rc = LDAP_SUCCESS;
|
||||
|
||||
+ if (loglevel & loginfo.log_access_stat_level) {
|
||||
+ va_start(args, fmt);
|
||||
+ PR_vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
+ rc = slapi_log_access(LDAP_DEBUG_STATS, "%s", buf);
|
||||
+ va_end(args);
|
||||
+ }
|
||||
+ return rc;
|
||||
+}
|
||||
int
|
||||
slapi_log_access(int level,
|
||||
char *fmt,
|
||||
diff --git a/ldap/servers/slapd/log.h b/ldap/servers/slapd/log.h
|
||||
index 9fb4e7425..6ac37bd29 100644
|
||||
--- a/ldap/servers/slapd/log.h
|
||||
+++ b/ldap/servers/slapd/log.h
|
||||
@@ -120,6 +120,7 @@ struct logging_opts
|
||||
int log_access_exptime; /* time */
|
||||
int log_access_exptimeunit; /* unit time */
|
||||
int log_access_exptime_secs; /* time in secs */
|
||||
+ int log_access_stat_level; /* statistics level in access log file */
|
||||
|
||||
int log_access_level; /* access log level */
|
||||
char *log_access_file; /* access log file path */
|
||||
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
|
||||
index ac45c85d1..9b5b845cb 100644
|
||||
--- a/ldap/servers/slapd/main.c
|
||||
+++ b/ldap/servers/slapd/main.c
|
||||
@@ -1040,6 +1040,7 @@ main(int argc, char **argv)
|
||||
* changes are replicated as soon as the replication plugin is started.
|
||||
*/
|
||||
pw_exp_init();
|
||||
+ op_stat_init();
|
||||
|
||||
plugin_print_lists();
|
||||
plugin_startall(argc, argv, NULL /* specific plugin list */);
|
||||
diff --git a/ldap/servers/slapd/operation.c b/ldap/servers/slapd/operation.c
|
||||
index 4dd3481c7..dacd1838f 100644
|
||||
--- a/ldap/servers/slapd/operation.c
|
||||
+++ b/ldap/servers/slapd/operation.c
|
||||
@@ -652,6 +652,92 @@ slapi_operation_time_expiry(Slapi_Operation *o, time_t timeout, struct timespec
|
||||
slapi_timespec_expire_rel(timeout, &(o->o_hr_time_rel), expiry);
|
||||
}
|
||||
|
||||
+
|
||||
+/*
|
||||
+ * Operation extension for operation statistics
|
||||
+ */
|
||||
+static int op_stat_objtype = -1;
|
||||
+static int op_stat_handle = -1;
|
||||
+
|
||||
+Op_stat *
|
||||
+op_stat_get_operation_extension(Slapi_PBlock *pb)
|
||||
+{
|
||||
+ Slapi_Operation *op;
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ return (Op_stat *)slapi_get_object_extension(op_stat_objtype,
|
||||
+ op, op_stat_handle);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+op_stat_set_operation_extension(Slapi_PBlock *pb, Op_stat *op_stat)
|
||||
+{
|
||||
+ Slapi_Operation *op;
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ slapi_set_object_extension(op_stat_objtype, op,
|
||||
+ op_stat_handle, (void *)op_stat);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * constructor for the operation object extension.
|
||||
+ */
|
||||
+static void *
|
||||
+op_stat_constructor(void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+{
|
||||
+ Op_stat *op_statp = NULL;
|
||||
+ op_statp = (Op_stat *)slapi_ch_calloc(1, sizeof(Op_stat));
|
||||
+ op_statp->search_stat = (Op_search_stat *)slapi_ch_calloc(1, sizeof(Op_search_stat));
|
||||
+
|
||||
+ return op_statp;
|
||||
+}
|
||||
+/*
|
||||
+ * destructor for the operation object extension.
|
||||
+ */
|
||||
+static void
|
||||
+op_stat_destructor(void *extension, void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+{
|
||||
+ Op_stat *op_statp = (Op_stat *)extension;
|
||||
+
|
||||
+ if (NULL == op_statp) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (op_statp->search_stat) {
|
||||
+ struct component_keys_lookup *keys, *next;
|
||||
+
|
||||
+ /* free all the individual key counter */
|
||||
+ keys = op_statp->search_stat->keys_lookup;
|
||||
+ while (keys) {
|
||||
+ next = keys->next;
|
||||
+ slapi_ch_free_string(&keys->attribute_type);
|
||||
+ slapi_ch_free_string(&keys->key);
|
||||
+ slapi_ch_free_string(&keys->index_type);
|
||||
+ slapi_ch_free((void **) &keys);
|
||||
+ keys = next;
|
||||
+ }
|
||||
+ slapi_ch_free((void **) &op_statp->search_stat);
|
||||
+ }
|
||||
+ slapi_ch_free((void **) &op_statp);
|
||||
+}
|
||||
+
|
||||
+#define SLAPI_OP_STAT_MODULE "Module to collect operation stat"
|
||||
+/* Called once from main */
|
||||
+void
|
||||
+op_stat_init(void)
|
||||
+{
|
||||
+ if (slapi_register_object_extension(SLAPI_OP_STAT_MODULE,
|
||||
+ SLAPI_EXT_OPERATION,
|
||||
+ op_stat_constructor,
|
||||
+ op_stat_destructor,
|
||||
+ &op_stat_objtype,
|
||||
+ &op_stat_handle) != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "op_stat_init",
|
||||
+ "slapi_register_object_extension failed; "
|
||||
+ "operation statistics is not enabled\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/* Set the time the operation actually started */
|
||||
void
|
||||
slapi_operation_set_time_started(Slapi_Operation *o)
|
||||
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
|
||||
index 410a3c5fe..3a049ee76 100644
|
||||
--- a/ldap/servers/slapd/proto-slap.h
|
||||
+++ b/ldap/servers/slapd/proto-slap.h
|
||||
@@ -291,6 +291,7 @@ int config_set_defaultreferral(const char *attrname, struct berval **value, char
|
||||
int config_set_timelimit(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_errorlog_level(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_accesslog_level(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
+int config_set_statlog_level(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_auditlog(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_auditfaillog(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_userat(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
@@ -510,6 +511,7 @@ long long config_get_pw_minage(void);
|
||||
long long config_get_pw_warning(void);
|
||||
int config_get_errorlog_level(void);
|
||||
int config_get_accesslog_level(void);
|
||||
+int config_get_statlog_level();
|
||||
int config_get_auditlog_logging_enabled(void);
|
||||
int config_get_auditfaillog_logging_enabled(void);
|
||||
char *config_get_auditlog_display_attrs(void);
|
||||
@@ -815,10 +817,15 @@ int lock_fclose(FILE *fp, FILE *lfp);
|
||||
#define LDAP_DEBUG_INFO 0x08000000 /* 134217728 */
|
||||
#define LDAP_DEBUG_DEBUG 0x10000000 /* 268435456 */
|
||||
#define LDAP_DEBUG_ALL_LEVELS 0xFFFFFF
|
||||
+
|
||||
+#define LDAP_STAT_READ_INDEX 0x00000001 /* 1 */
|
||||
+#define LDAP_STAT_FREE_1 0x00000002 /* 2 */
|
||||
+
|
||||
extern int slapd_ldap_debug;
|
||||
|
||||
int loglevel_is_set(int level);
|
||||
int slapd_log_error_proc(int sev_level, char *subsystem, char *fmt, ...);
|
||||
+int slapi_log_stat(int loglevel, const char *fmt, ...);
|
||||
|
||||
int slapi_log_access(int level, char *fmt, ...)
|
||||
#ifdef __GNUC__
|
||||
@@ -874,6 +881,7 @@ int check_log_max_size(
|
||||
|
||||
|
||||
void g_set_accesslog_level(int val);
|
||||
+void g_set_statlog_level(int val);
|
||||
void log__delete_rotated_logs(void);
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index adcef9539..e94533d72 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -38,6 +38,7 @@ static PRLock *current_conn_count_mutex;
|
||||
|
||||
static int flush_ber(Slapi_PBlock *pb, Connection *conn, Operation *op, BerElement *ber, int type);
|
||||
static char *notes2str(unsigned int notes, char *buf, size_t buflen);
|
||||
+static void log_op_stat(Slapi_PBlock *pb);
|
||||
static void log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries);
|
||||
static void log_entry(Operation *op, Slapi_Entry *e);
|
||||
static void log_referral(Operation *op);
|
||||
@@ -2050,6 +2051,68 @@ notes2str(unsigned int notes, char *buf, size_t buflen)
|
||||
return (buf);
|
||||
}
|
||||
|
||||
+static void
|
||||
+log_op_stat(Slapi_PBlock *pb)
|
||||
+{
|
||||
+
|
||||
+ Connection *conn = NULL;
|
||||
+ Operation *op = NULL;
|
||||
+ Op_stat *op_stat;
|
||||
+ struct timespec duration;
|
||||
+ char stat_etime[ETIME_BUFSIZ] = {0};
|
||||
+
|
||||
+ if (config_get_statlog_level() == 0) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
|
||||
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ op_stat = op_stat_get_operation_extension(pb);
|
||||
+
|
||||
+ if (conn == NULL || op == NULL || op_stat == NULL) {
|
||||
+ return;
|
||||
+ }
|
||||
+ /* process the operation */
|
||||
+ switch (op->o_tag) {
|
||||
+ case LDAP_REQ_BIND:
|
||||
+ case LDAP_REQ_UNBIND:
|
||||
+ case LDAP_REQ_ADD:
|
||||
+ case LDAP_REQ_DELETE:
|
||||
+ case LDAP_REQ_MODRDN:
|
||||
+ case LDAP_REQ_MODIFY:
|
||||
+ case LDAP_REQ_COMPARE:
|
||||
+ break;
|
||||
+ case LDAP_REQ_SEARCH:
|
||||
+ if ((LDAP_STAT_READ_INDEX & config_get_statlog_level()) &&
|
||||
+ op_stat->search_stat) {
|
||||
+ struct component_keys_lookup *key_info;
|
||||
+ for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ op->o_connid, op->o_opid,
|
||||
+ key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
+ key_info->id_lookup_cnt);
|
||||
+ }
|
||||
+
|
||||
+ /* total elapsed time */
|
||||
+ slapi_timespec_diff(&op_stat->search_stat->keys_lookup_end, &op_stat->search_stat->keys_lookup_start, &duration);
|
||||
+ snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: duration %s\n",
|
||||
+ op->o_connid, op->o_opid, stat_etime);
|
||||
+ }
|
||||
+ break;
|
||||
+ case LDAP_REQ_ABANDON_30:
|
||||
+ case LDAP_REQ_ABANDON:
|
||||
+ break;
|
||||
+
|
||||
+ default:
|
||||
+ slapi_log_err(SLAPI_LOG_ERR,
|
||||
+ "log_op_stat", "Ignoring unknown LDAP request (conn=%" PRIu64 ", tag=0x%lx)\n",
|
||||
+ conn->c_connid, op->o_tag);
|
||||
+ break;
|
||||
+ }
|
||||
+}
|
||||
|
||||
static void
|
||||
log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries)
|
||||
@@ -2206,6 +2269,7 @@ log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries
|
||||
} else {
|
||||
ext_str = "";
|
||||
}
|
||||
+ log_op_stat(pb);
|
||||
slapi_log_access(LDAP_DEBUG_STATS,
|
||||
"conn=%" PRIu64 " op=%d RESULT err=%d"
|
||||
" tag=%" BERTAG_T " nentries=%d wtime=%s optime=%s etime=%s%s%s%s\n",
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 927576b70..82550527c 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -348,6 +348,8 @@ typedef void (*VFPV)(); /* takes undefined arguments */
|
||||
#define SLAPD_DEFAULT_FE_ERRORLOG_LEVEL_STR "16384"
|
||||
#define SLAPD_DEFAULT_ACCESSLOG_LEVEL 256
|
||||
#define SLAPD_DEFAULT_ACCESSLOG_LEVEL_STR "256"
|
||||
+#define SLAPD_DEFAULT_STATLOG_LEVEL 0
|
||||
+#define SLAPD_DEFAULT_STATLOG_LEVEL_STR "0"
|
||||
|
||||
#define SLAPD_DEFAULT_DISK_THRESHOLD 2097152
|
||||
#define SLAPD_DEFAULT_DISK_THRESHOLD_STR "2097152"
|
||||
@@ -2082,6 +2084,7 @@ typedef struct _slapdEntryPoints
|
||||
#define CONFIG_SCHEMAREPLACE_ATTRIBUTE "nsslapd-schemareplace"
|
||||
#define CONFIG_LOGLEVEL_ATTRIBUTE "nsslapd-errorlog-level"
|
||||
#define CONFIG_ACCESSLOGLEVEL_ATTRIBUTE "nsslapd-accesslog-level"
|
||||
+#define CONFIG_STATLOGLEVEL_ATTRIBUTE "nsslapd-statlog-level"
|
||||
#define CONFIG_ACCESSLOG_MODE_ATTRIBUTE "nsslapd-accesslog-mode"
|
||||
#define CONFIG_ERRORLOG_MODE_ATTRIBUTE "nsslapd-errorlog-mode"
|
||||
#define CONFIG_AUDITLOG_MODE_ATTRIBUTE "nsslapd-auditlog-mode"
|
||||
@@ -2457,6 +2460,7 @@ typedef struct _slapdFrontendConfig
|
||||
int accessloglevel;
|
||||
slapi_onoff_t accesslogbuffering;
|
||||
slapi_onoff_t csnlogging;
|
||||
+ int statloglevel;
|
||||
|
||||
/* ERROR LOG */
|
||||
slapi_onoff_t errorlog_logging_enabled;
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index 4b6cf29eb..bd7a4b39d 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -449,6 +449,33 @@ int operation_is_flag_set(Slapi_Operation *op, int flag);
|
||||
unsigned long operation_get_type(Slapi_Operation *op);
|
||||
LDAPMod **copy_mods(LDAPMod **orig_mods);
|
||||
|
||||
+/* Structures use to collect statistics per operation */
|
||||
+/* used for LDAP_STAT_READ_INDEX */
|
||||
+struct component_keys_lookup
|
||||
+{
|
||||
+ char *index_type;
|
||||
+ char *attribute_type;
|
||||
+ char *key;
|
||||
+ int id_lookup_cnt;
|
||||
+ struct component_keys_lookup *next;
|
||||
+};
|
||||
+typedef struct op_search_stat
|
||||
+{
|
||||
+ struct component_keys_lookup *keys_lookup;
|
||||
+ struct timespec keys_lookup_start;
|
||||
+ struct timespec keys_lookup_end;
|
||||
+} Op_search_stat;
|
||||
+
|
||||
+/* structure store in the operation extension */
|
||||
+typedef struct op_stat
|
||||
+{
|
||||
+ Op_search_stat *search_stat;
|
||||
+} Op_stat;
|
||||
+
|
||||
+void op_stat_init(void);
|
||||
+Op_stat *op_stat_get_operation_extension(Slapi_PBlock *pb);
|
||||
+void op_stat_set_operation_extension(Slapi_PBlock *pb, Op_stat *op_stat);
|
||||
+
|
||||
/*
|
||||
* From ldap.h
|
||||
* #define LDAP_MOD_ADD 0x00
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,297 +0,0 @@
|
||||
From bc2629db166667cdb01fde2b9e249253d5d868b5 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 21 Nov 2022 11:41:15 +0100
|
||||
Subject: [PATCH] Issue 3729 - (cont) RFE Extend log of operations statistics
|
||||
in access log (#5538)
|
||||
|
||||
Bug description:
|
||||
This is a continuation of the #3729
|
||||
The previous fix did not manage internal SRCH, so
|
||||
statistics of internal SRCH were not logged
|
||||
|
||||
Fix description:
|
||||
For internal operation log_op_stat uses
|
||||
connid/op_id/op_internal_id/op_nested_count that have been
|
||||
computed log_result
|
||||
|
||||
For direct operation log_op_stat uses info from the
|
||||
operation itself (o_connid and o_opid)
|
||||
|
||||
log_op_stat relies on operation_type rather than
|
||||
o_tag that is not available for internal operation
|
||||
|
||||
relates: #3729
|
||||
|
||||
Reviewed by: Pierre Rogier
|
||||
|
||||
(cherry picked from commit 7915e85a55476647ac54330de4f6e89faf6f2934)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 90 ++++++++++++++++++-
|
||||
ldap/servers/slapd/proto-slap.h | 2 +-
|
||||
ldap/servers/slapd/result.c | 74 +++++++++------
|
||||
3 files changed, 136 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 43288f67f..fbb8d7bf1 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -21,7 +21,7 @@ from lib389.idm.domain import Domain
|
||||
from lib389.configurations.sample import create_base_domain
|
||||
from lib389._mapped_object import DSLdapObject
|
||||
from lib389.topologies import topology_st
|
||||
-from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, AutoMembershipDefinitions
|
||||
+from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, AutoMembershipDefinitions, MemberOfPlugin
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
from lib389.idm.group import Groups
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
@@ -1323,6 +1323,94 @@ def test_stat_index(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+def test_stat_internal_op(topology_st, request):
|
||||
+ """Check that statistics can also be collected for internal operations
|
||||
+
|
||||
+ :id: 19f393bd-5866-425a-af7a-4dade06d5c77
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Check that nsslapd-statlog-level is 0 (default)
|
||||
+ 2. Enable memberof plugins
|
||||
+ 3. Create a user
|
||||
+ 4. Remove access log (to only detect new records)
|
||||
+ 5. Enable statistic logging nsslapd-statlog-level=1
|
||||
+ 6. Check that on direct SRCH there is no 'Internal' Stat records
|
||||
+ 7. Remove access log (to only detect new records)
|
||||
+ 8. Add group with the user, so memberof triggers internal search
|
||||
+ and check it exists 'Internal' Stat records
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ # Step 1
|
||||
+ log.info("Assert nsslapd-statlog-level is by default 0")
|
||||
+ assert topology_st.standalone.config.get_attr_val_int("nsslapd-statlog-level") == 0
|
||||
+
|
||||
+ # Step 2
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.enable()
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Step 3 Add setup entries
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX, rdn=None)
|
||||
+ user = users.create(properties={'uid': 'test_1',
|
||||
+ 'cn': 'test_1',
|
||||
+ 'sn': 'test_1',
|
||||
+ 'description': 'member',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/testuser'})
|
||||
+ # Step 4 reset accesslog
|
||||
+ topology_st.standalone.stop()
|
||||
+ lpath = topology_st.standalone.ds_access_log._get_log_path()
|
||||
+ os.unlink(lpath)
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 5 enable statistics
|
||||
+ log.info("Set nsslapd-statlog-level: 1 to enable indexing statistics")
|
||||
+ topology_st.standalone.config.set("nsslapd-statlog-level", "1")
|
||||
+
|
||||
+ # Step 6 for direct SRCH only non internal STAT records
|
||||
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "uid=test_1")
|
||||
+ topology_st.standalone.stop()
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: attribute.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: duration.*')
|
||||
+ assert not topology_st.standalone.ds_access_log.match('.*Internal.*STAT.*')
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 7 reset accesslog
|
||||
+ topology_st.standalone.stop()
|
||||
+ lpath = topology_st.standalone.ds_access_log._get_log_path()
|
||||
+ os.unlink(lpath)
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 8 trigger internal searches and check internal stat records
|
||||
+ groups = Groups(inst, DEFAULT_SUFFIX, rdn=None)
|
||||
+ group = groups.create(properties={'cn': 'mygroup',
|
||||
+ 'member': 'uid=test_1,%s' % DEFAULT_SUFFIX,
|
||||
+ 'description': 'group'})
|
||||
+ topology_st.standalone.restart()
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*Internal.*STAT read index.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*Internal.*STAT read index: attribute.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*Internal.*STAT read index: duration.*')
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting user/group')
|
||||
+ user.delete()
|
||||
+ group.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
|
||||
index 3a049ee76..6e473a08e 100644
|
||||
--- a/ldap/servers/slapd/proto-slap.h
|
||||
+++ b/ldap/servers/slapd/proto-slap.h
|
||||
@@ -511,7 +511,7 @@ long long config_get_pw_minage(void);
|
||||
long long config_get_pw_warning(void);
|
||||
int config_get_errorlog_level(void);
|
||||
int config_get_accesslog_level(void);
|
||||
-int config_get_statlog_level();
|
||||
+int config_get_statlog_level(void);
|
||||
int config_get_auditlog_logging_enabled(void);
|
||||
int config_get_auditfaillog_logging_enabled(void);
|
||||
char *config_get_auditlog_display_attrs(void);
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index e94533d72..87641e92f 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -38,7 +38,7 @@ static PRLock *current_conn_count_mutex;
|
||||
|
||||
static int flush_ber(Slapi_PBlock *pb, Connection *conn, Operation *op, BerElement *ber, int type);
|
||||
static char *notes2str(unsigned int notes, char *buf, size_t buflen);
|
||||
-static void log_op_stat(Slapi_PBlock *pb);
|
||||
+static void log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_internal_id, int32_t op_nested_count);
|
||||
static void log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries);
|
||||
static void log_entry(Operation *op, Slapi_Entry *e);
|
||||
static void log_referral(Operation *op);
|
||||
@@ -2051,65 +2051,82 @@ notes2str(unsigned int notes, char *buf, size_t buflen)
|
||||
return (buf);
|
||||
}
|
||||
|
||||
+#define STAT_LOG_CONN_OP_FMT_INT_INT "conn=Internal(%" PRIu64 ") op=%d(%d)(%d)"
|
||||
+#define STAT_LOG_CONN_OP_FMT_EXT_INT "conn=%" PRIu64 " (Internal) op=%d(%d)(%d)"
|
||||
static void
|
||||
-log_op_stat(Slapi_PBlock *pb)
|
||||
+log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_internal_id, int32_t op_nested_count)
|
||||
{
|
||||
-
|
||||
- Connection *conn = NULL;
|
||||
Operation *op = NULL;
|
||||
Op_stat *op_stat;
|
||||
struct timespec duration;
|
||||
char stat_etime[ETIME_BUFSIZ] = {0};
|
||||
+ int internal_op;
|
||||
|
||||
if (config_get_statlog_level() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
- slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
|
||||
slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ internal_op = operation_is_flag_set(op, OP_FLAG_INTERNAL);
|
||||
op_stat = op_stat_get_operation_extension(pb);
|
||||
|
||||
- if (conn == NULL || op == NULL || op_stat == NULL) {
|
||||
+ if (op == NULL || op_stat == NULL) {
|
||||
return;
|
||||
}
|
||||
/* process the operation */
|
||||
- switch (op->o_tag) {
|
||||
- case LDAP_REQ_BIND:
|
||||
- case LDAP_REQ_UNBIND:
|
||||
- case LDAP_REQ_ADD:
|
||||
- case LDAP_REQ_DELETE:
|
||||
- case LDAP_REQ_MODRDN:
|
||||
- case LDAP_REQ_MODIFY:
|
||||
- case LDAP_REQ_COMPARE:
|
||||
+ switch (operation_get_type(op)) {
|
||||
+ case SLAPI_OPERATION_BIND:
|
||||
+ case SLAPI_OPERATION_UNBIND:
|
||||
+ case SLAPI_OPERATION_ADD:
|
||||
+ case SLAPI_OPERATION_DELETE:
|
||||
+ case SLAPI_OPERATION_MODRDN:
|
||||
+ case SLAPI_OPERATION_MODIFY:
|
||||
+ case SLAPI_OPERATION_COMPARE:
|
||||
+ case SLAPI_OPERATION_EXTENDED:
|
||||
break;
|
||||
- case LDAP_REQ_SEARCH:
|
||||
+ case SLAPI_OPERATION_SEARCH:
|
||||
if ((LDAP_STAT_READ_INDEX & config_get_statlog_level()) &&
|
||||
op_stat->search_stat) {
|
||||
struct component_keys_lookup *key_info;
|
||||
for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
- slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
- op->o_connid, op->o_opid,
|
||||
- key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ if (internal_op) {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid, op_id, op_internal_id, op_nested_count,
|
||||
+ key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
+ key_info->id_lookup_cnt);
|
||||
+ } else {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid, op_id,
|
||||
+ key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
+ key_info->id_lookup_cnt);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* total elapsed time */
|
||||
slapi_timespec_diff(&op_stat->search_stat->keys_lookup_end, &op_stat->search_stat->keys_lookup_start, &duration);
|
||||
snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
- slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: duration %s\n",
|
||||
- op->o_connid, op->o_opid, stat_etime);
|
||||
+ if (internal_op) {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: duration %s\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: duration %s\n",
|
||||
+ connid, op_id, op_internal_id, op_nested_count, stat_etime);
|
||||
+ } else {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: duration %s\n",
|
||||
+ op->o_connid, op->o_opid, stat_etime);
|
||||
+ }
|
||||
}
|
||||
break;
|
||||
- case LDAP_REQ_ABANDON_30:
|
||||
- case LDAP_REQ_ABANDON:
|
||||
+ case SLAPI_OPERATION_ABANDON:
|
||||
break;
|
||||
|
||||
default:
|
||||
slapi_log_err(SLAPI_LOG_ERR,
|
||||
- "log_op_stat", "Ignoring unknown LDAP request (conn=%" PRIu64 ", tag=0x%lx)\n",
|
||||
- conn->c_connid, op->o_tag);
|
||||
+ "log_op_stat", "Ignoring unknown LDAP request (conn=%" PRIu64 ", op_type=0x%lx)\n",
|
||||
+ connid, operation_get_type(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2269,7 +2286,7 @@ log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries
|
||||
} else {
|
||||
ext_str = "";
|
||||
}
|
||||
- log_op_stat(pb);
|
||||
+ log_op_stat(pb, op->o_connid, op->o_opid, 0, 0);
|
||||
slapi_log_access(LDAP_DEBUG_STATS,
|
||||
"conn=%" PRIu64 " op=%d RESULT err=%d"
|
||||
" tag=%" BERTAG_T " nentries=%d wtime=%s optime=%s etime=%s%s%s%s\n",
|
||||
@@ -2284,6 +2301,7 @@ log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries
|
||||
}
|
||||
} else {
|
||||
int optype;
|
||||
+ log_op_stat(pb, connid, op_id, op_internal_id, op_nested_count);
|
||||
#define LOG_MSG_FMT " tag=%" BERTAG_T " nentries=%d wtime=%s optime=%s etime=%s%s%s\n"
|
||||
slapi_log_access(LDAP_DEBUG_ARGS,
|
||||
connid == 0 ? LOG_CONN_OP_FMT_INT_INT LOG_MSG_FMT :
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
From f6eca13762139538d974c1cb285ddf1354fe7837 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 28 Mar 2023 10:27:01 +0200
|
||||
Subject: [PATCH] Issue 5710 - subtree search statistics for index lookup does
|
||||
not report ancestorid/entryrdn lookups (#5711)
|
||||
|
||||
Bug description:
|
||||
The RFE #3729 allows to collect index lookups per search
|
||||
operation. For subtree searches the server lookup ancestorid
|
||||
and those lookup are not recorded
|
||||
|
||||
Fix description:
|
||||
if statistics are enabled, record ancestorid lookup
|
||||
|
||||
relates: #5710
|
||||
|
||||
Reviewed by: Mark Reynolds (thanks)
|
||||
|
||||
(cherry picked from commit fca27c3d0487c9aea9dc7da151a79e3ce0fc7d35)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_search.c | 59 ++++++++++++++++++++++
|
||||
1 file changed, 59 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
index 8c07d1395..5d98e288e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
@@ -35,6 +35,7 @@ static IDList *onelevel_candidates(Slapi_PBlock *pb, backend *be, const char *ba
|
||||
static back_search_result_set *new_search_result_set(IDList *idl, int vlv, int lookthroughlimit);
|
||||
static void delete_search_result_set(Slapi_PBlock *pb, back_search_result_set **sr);
|
||||
static int can_skip_filter_test(Slapi_PBlock *pb, struct slapi_filter *f, int scope, IDList *idl);
|
||||
+static void stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
|
||||
/* This is for performance testing, allows us to disable ACL checking altogether */
|
||||
#if defined(DISABLE_ACL_CHECK)
|
||||
@@ -1167,6 +1168,45 @@ create_subtree_filter(Slapi_Filter *filter, int managedsait, Slapi_Filter **focr
|
||||
return ftop;
|
||||
}
|
||||
|
||||
+static void
|
||||
+stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
+{
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if ((op_stat == NULL) || (op_stat->search_stat == NULL)) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+
|
||||
+ /* indextype is "eq" */
|
||||
+ if (index_type) {
|
||||
+ key_stat->index_type = slapi_ch_strdup(index_type);
|
||||
+ }
|
||||
+
|
||||
+ /* key value e.g. '1234' */
|
||||
+ if (key_value) {
|
||||
+ key_stat->key = (char *) slapi_ch_calloc(1, strlen(key_value) + 1);
|
||||
+ memcpy(key_stat->key, key_value, strlen(key_value));
|
||||
+ }
|
||||
+
|
||||
+ /* attribute name is e.g. 'uid' */
|
||||
+ if (attribute_type) {
|
||||
+ key_stat->attribute_type = slapi_ch_strdup(attribute_type);
|
||||
+ }
|
||||
+
|
||||
+ /* Number of lookup IDs with the key */
|
||||
+ key_stat->id_lookup_cnt = lookup_cnt;
|
||||
+ if (op_stat->search_stat->keys_lookup) {
|
||||
+ /* it already exist key stat. add key_stat at the head */
|
||||
+ key_stat->next = op_stat->search_stat->keys_lookup;
|
||||
+ } else {
|
||||
+ /* this is the first key stat record */
|
||||
+ key_stat->next = NULL;
|
||||
+ }
|
||||
+ op_stat->search_stat->keys_lookup = key_stat;
|
||||
+}
|
||||
|
||||
/*
|
||||
* Build a candidate list for a SUBTREE scope search.
|
||||
@@ -1232,6 +1272,17 @@ subtree_candidates(
|
||||
if (candidates != NULL && (idl_length(candidates) > FILTER_TEST_THRESHOLD) && e) {
|
||||
IDList *tmp = candidates, *descendants = NULL;
|
||||
back_txn txn = {NULL};
|
||||
+ Op_stat *op_stat = NULL;
|
||||
+ char key_value[32] = {0};
|
||||
+
|
||||
+ /* statistics for index lookup is enabled */
|
||||
+ if (LDAP_STAT_READ_INDEX & config_get_statlog_level()) {
|
||||
+ op_stat = op_stat_get_operation_extension(pb);
|
||||
+ if (op_stat) {
|
||||
+ /* easier to just record the entry ID */
|
||||
+ PR_snprintf(key_value, sizeof(key_value), "%lu", (u_long) e->ep_id);
|
||||
+ }
|
||||
+ }
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
|
||||
if (entryrdn_get_noancestorid()) {
|
||||
@@ -1239,12 +1290,20 @@ subtree_candidates(
|
||||
*err = entryrdn_get_subordinates(be,
|
||||
slapi_entry_get_sdn_const(e->ep_entry),
|
||||
e->ep_id, &descendants, &txn, 0);
|
||||
+ if (op_stat) {
|
||||
+ /* record entryrdn lookups */
|
||||
+ stat_add_srch_lookup(op_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ }
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
idl_free(&descendants);
|
||||
} else if (!has_tombstone_filter && !is_bulk_import) {
|
||||
*err = ldbm_ancestorid_read_ext(be, &txn, e->ep_id, &descendants, allidslimit);
|
||||
+ if (op_stat) {
|
||||
+ /* records ancestorid lookups */
|
||||
+ stat_add_srch_lookup(op_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ }
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,249 +0,0 @@
|
||||
From aced6f575f3be70f16756860f8b852d3447df867 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 6 May 2025 16:09:36 +0200
|
||||
Subject: [PATCH] Issue 6764 - statistics about index lookup report a wrong
|
||||
duration (#6765)
|
||||
|
||||
Bug description:
|
||||
During a SRCH statistics about indexes lookup
|
||||
(when nsslapd-statlog-level=1) reports a duration.
|
||||
It is wrong because it should report a duration per filter
|
||||
component.
|
||||
|
||||
Fix description:
|
||||
Record a index lookup duration per key
|
||||
using key_lookup_start/key_lookup_end
|
||||
|
||||
fixes: #6764
|
||||
|
||||
Reviewed by: Pierre Rogier (Thanks !)
|
||||
|
||||
(cherry picked from commit cd8069a76bcbb2d7bb4ac3bb9466019b01cc6db3)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/filterindex.c | 17 +++++++-----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_search.c | 31 +++++++++++++++-------
|
||||
ldap/servers/slapd/result.c | 12 +++++----
|
||||
ldap/servers/slapd/slapi-plugin.h | 9 +++++++
|
||||
ldap/servers/slapd/slapi-private.h | 2 ++
|
||||
ldap/servers/slapd/time.c | 13 +++++++++
|
||||
6 files changed, 62 insertions(+), 22 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
index 30550dde7..abc502b96 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
@@ -1040,8 +1040,7 @@ keys2idl(
|
||||
int allidslimit)
|
||||
{
|
||||
IDList *idl = NULL;
|
||||
- Op_stat *op_stat;
|
||||
- PRBool collect_stat = PR_FALSE;
|
||||
+ Op_stat *op_stat = NULL;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "keys2idl", "=> type %s indextype %s\n", type, indextype);
|
||||
|
||||
@@ -1049,8 +1048,9 @@ keys2idl(
|
||||
if (LDAP_STAT_READ_INDEX & config_get_statlog_level()) {
|
||||
op_stat = op_stat_get_operation_extension(pb);
|
||||
if (op_stat->search_stat) {
|
||||
- collect_stat = PR_TRUE;
|
||||
clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_start));
|
||||
+ } else {
|
||||
+ op_stat = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,11 +1059,14 @@ keys2idl(
|
||||
struct component_keys_lookup *key_stat;
|
||||
int key_len;
|
||||
|
||||
- idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
- if (collect_stat) {
|
||||
+ if (op_stat) {
|
||||
/* gather the index lookup statistics */
|
||||
key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
-
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(key_stat->key_lookup_start));
|
||||
+ }
|
||||
+ idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
+ if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(key_stat->key_lookup_end));
|
||||
/* indextype e.g. "eq" or "sub" (see index.c) */
|
||||
if (indextype) {
|
||||
key_stat->index_type = slapi_ch_strdup(indextype);
|
||||
@@ -1125,7 +1128,7 @@ keys2idl(
|
||||
}
|
||||
|
||||
/* All the keys have been fetch, time to take the completion time */
|
||||
- if (collect_stat) {
|
||||
+ if (op_stat) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_end));
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
index 5d98e288e..27301f453 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
@@ -35,7 +35,7 @@ static IDList *onelevel_candidates(Slapi_PBlock *pb, backend *be, const char *ba
|
||||
static back_search_result_set *new_search_result_set(IDList *idl, int vlv, int lookthroughlimit);
|
||||
static void delete_search_result_set(Slapi_PBlock *pb, back_search_result_set **sr);
|
||||
static int can_skip_filter_test(Slapi_PBlock *pb, struct slapi_filter *f, int scope, IDList *idl);
|
||||
-static void stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
+static void stat_add_srch_lookup(Op_stat *op_stat, struct component_keys_lookup *key_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
|
||||
/* This is for performance testing, allows us to disable ACL checking altogether */
|
||||
#if defined(DISABLE_ACL_CHECK)
|
||||
@@ -1169,17 +1169,12 @@ create_subtree_filter(Slapi_Filter *filter, int managedsait, Slapi_Filter **focr
|
||||
}
|
||||
|
||||
static void
|
||||
-stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
+stat_add_srch_lookup(Op_stat *op_stat, struct component_keys_lookup *key_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
{
|
||||
- struct component_keys_lookup *key_stat;
|
||||
-
|
||||
- if ((op_stat == NULL) || (op_stat->search_stat == NULL)) {
|
||||
+ if ((op_stat == NULL) || (op_stat->search_stat == NULL) || (key_stat == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
- /* gather the index lookup statistics */
|
||||
- key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
-
|
||||
/* indextype is "eq" */
|
||||
if (index_type) {
|
||||
key_stat->index_type = slapi_ch_strdup(index_type);
|
||||
@@ -1286,23 +1281,39 @@ subtree_candidates(
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
|
||||
if (entryrdn_get_noancestorid()) {
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if (op_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_start);
|
||||
+ }
|
||||
/* subtree-rename: on && no ancestorid */
|
||||
*err = entryrdn_get_subordinates(be,
|
||||
slapi_entry_get_sdn_const(e->ep_entry),
|
||||
e->ep_id, &descendants, &txn, 0);
|
||||
if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_end);
|
||||
/* record entryrdn lookups */
|
||||
- stat_add_srch_lookup(op_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ stat_add_srch_lookup(op_stat, key_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
}
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
idl_free(&descendants);
|
||||
} else if (!has_tombstone_filter && !is_bulk_import) {
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if (op_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_start);
|
||||
+ }
|
||||
*err = ldbm_ancestorid_read_ext(be, &txn, e->ep_id, &descendants, allidslimit);
|
||||
if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_end);
|
||||
/* records ancestorid lookups */
|
||||
- stat_add_srch_lookup(op_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ stat_add_srch_lookup(op_stat, key_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
}
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index 87641e92f..f40556de8 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -2089,19 +2089,21 @@ log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_interna
|
||||
op_stat->search_stat) {
|
||||
struct component_keys_lookup *key_info;
|
||||
for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
+ slapi_timespec_diff(&key_info->key_lookup_end, &key_info->key_lookup_start, &duration);
|
||||
+ snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
if (internal_op) {
|
||||
slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n":
|
||||
- STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n",
|
||||
connid, op_id, op_internal_id, op_nested_count,
|
||||
key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ key_info->id_lookup_cnt, stat_etime);
|
||||
} else {
|
||||
slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n",
|
||||
connid, op_id,
|
||||
key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ key_info->id_lookup_cnt, stat_etime);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index a84a60c92..00e9722d2 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -8314,6 +8314,15 @@ void DS_Sleep(PRIntervalTime ticks);
|
||||
* \param struct timespec c the difference.
|
||||
*/
|
||||
void slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *diff);
|
||||
+
|
||||
+/**
|
||||
+ * add 'new' timespect into 'cumul'
|
||||
+ * clock_monotonic to find time taken to perform operations.
|
||||
+ *
|
||||
+ * \param struct timespec cumul to compute total duration.
|
||||
+ * \param struct timespec new is a additional duration
|
||||
+ */
|
||||
+void slapi_timespec_add(struct timespec *cumul, struct timespec *new);
|
||||
/**
|
||||
* Given an operation, determine the time elapsed since the op
|
||||
* began.
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index bd7a4b39d..dfb0e272a 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -457,6 +457,8 @@ struct component_keys_lookup
|
||||
char *attribute_type;
|
||||
char *key;
|
||||
int id_lookup_cnt;
|
||||
+ struct timespec key_lookup_start;
|
||||
+ struct timespec key_lookup_end;
|
||||
struct component_keys_lookup *next;
|
||||
};
|
||||
typedef struct op_search_stat
|
||||
diff --git a/ldap/servers/slapd/time.c b/ldap/servers/slapd/time.c
|
||||
index 0406c3689..0dd457fbe 100644
|
||||
--- a/ldap/servers/slapd/time.c
|
||||
+++ b/ldap/servers/slapd/time.c
|
||||
@@ -272,6 +272,19 @@ slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *dif
|
||||
diff->tv_nsec = nsec;
|
||||
}
|
||||
|
||||
+void
|
||||
+slapi_timespec_add(struct timespec *cumul, struct timespec *new)
|
||||
+{
|
||||
+ /* Now add the two */
|
||||
+ time_t sec = cumul->tv_sec + new->tv_sec;
|
||||
+ long nsec = cumul->tv_nsec + new->tv_nsec;
|
||||
+
|
||||
+ sec += nsec / 1000000000;
|
||||
+ nsec = nsec % 1000000000;
|
||||
+ cumul->tv_sec = sec;
|
||||
+ cumul->tv_nsec = nsec;
|
||||
+}
|
||||
+
|
||||
void
|
||||
slapi_timespec_expire_at(time_t timeout, struct timespec *expire)
|
||||
{
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,82 +0,0 @@
|
||||
From c8a5594efdb2722b6dceaed16219039d8e59c888 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Thu, 5 Jun 2025 10:33:29 +0200
|
||||
Subject: [PATCH] Issue 6470 (Cont) - Some replication status data are reset
|
||||
upon a restart
|
||||
|
||||
(cherry picked from commit a8b419dab31f4fa9fca8c33fe04a79e7a34965e5)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/replication/repl5_agmt.c | 4 ++--
|
||||
ldap/servers/slapd/slapi-plugin.h | 8 --------
|
||||
ldap/servers/slapd/time.c | 13 -------------
|
||||
3 files changed, 2 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
index c3b8d298c..229783763 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
@@ -537,7 +537,7 @@ agmt_new_from_entry(Slapi_Entry *e)
|
||||
if (val) {
|
||||
strcpy(ra->last_init_status, val);
|
||||
}
|
||||
- ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_SUPPLIERS + 1,
|
||||
+ ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_MASTERS + 1,
|
||||
sizeof(struct changecounter *));
|
||||
ra->num_changecounters = 0;
|
||||
ra->max_changecounters = MAX_NUM_OF_MASTERS;
|
||||
@@ -2615,7 +2615,7 @@ agmt_update_init_status(Repl_Agmt *ra)
|
||||
mods[nb_mods] = NULL;
|
||||
|
||||
slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL,
|
||||
- repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
|
||||
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0);
|
||||
slapi_modify_internal_pb(pb);
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 00e9722d2..677be1db0 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -8315,14 +8315,6 @@ void DS_Sleep(PRIntervalTime ticks);
|
||||
*/
|
||||
void slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *diff);
|
||||
|
||||
-/**
|
||||
- * add 'new' timespect into 'cumul'
|
||||
- * clock_monotonic to find time taken to perform operations.
|
||||
- *
|
||||
- * \param struct timespec cumul to compute total duration.
|
||||
- * \param struct timespec new is a additional duration
|
||||
- */
|
||||
-void slapi_timespec_add(struct timespec *cumul, struct timespec *new);
|
||||
/**
|
||||
* Given an operation, determine the time elapsed since the op
|
||||
* began.
|
||||
diff --git a/ldap/servers/slapd/time.c b/ldap/servers/slapd/time.c
|
||||
index 0dd457fbe..0406c3689 100644
|
||||
--- a/ldap/servers/slapd/time.c
|
||||
+++ b/ldap/servers/slapd/time.c
|
||||
@@ -272,19 +272,6 @@ slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *dif
|
||||
diff->tv_nsec = nsec;
|
||||
}
|
||||
|
||||
-void
|
||||
-slapi_timespec_add(struct timespec *cumul, struct timespec *new)
|
||||
-{
|
||||
- /* Now add the two */
|
||||
- time_t sec = cumul->tv_sec + new->tv_sec;
|
||||
- long nsec = cumul->tv_nsec + new->tv_nsec;
|
||||
-
|
||||
- sec += nsec / 1000000000;
|
||||
- nsec = nsec % 1000000000;
|
||||
- cumul->tv_sec = sec;
|
||||
- cumul->tv_nsec = nsec;
|
||||
-}
|
||||
-
|
||||
void
|
||||
slapi_timespec_expire_at(time_t timeout, struct timespec *expire)
|
||||
{
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
From a7231528b5ad7e887eeed4317de48d054cd046cd Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 23 Jul 2025 19:35:32 -0400
|
||||
Subject: [PATCH] Issue 6895 - Crash if repl keep alive entry can not be
|
||||
created
|
||||
|
||||
Description:
|
||||
|
||||
Heap use after free when logging that the replicaton keep-alive entry can not
|
||||
be created. slapi_add_internal_pb() frees the slapi entry, then
|
||||
we try and get the dn from the entry and we get a use-after-free crash.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6895
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
|
||||
(cherry picked from commit 43ab6b1d1de138d6be03b657f27cbb6ba19ddd14)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/chainingdb/cb_config.c | 3 +--
|
||||
ldap/servers/plugins/posix-winsync/posix-winsync.c | 1 -
|
||||
ldap/servers/plugins/replication/repl5_init.c | 3 ---
|
||||
ldap/servers/plugins/replication/repl5_replica.c | 8 ++++----
|
||||
4 files changed, 5 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/chainingdb/cb_config.c b/ldap/servers/plugins/chainingdb/cb_config.c
|
||||
index 40a7088d7..24fa1bcb3 100644
|
||||
--- a/ldap/servers/plugins/chainingdb/cb_config.c
|
||||
+++ b/ldap/servers/plugins/chainingdb/cb_config.c
|
||||
@@ -44,8 +44,7 @@ cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *s
|
||||
slapi_pblock_get(util_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
|
||||
if (LDAP_SUCCESS != res && LDAP_ALREADY_EXISTS != res) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, CB_PLUGIN_SUBSYSTEM,
|
||||
- "cb_config_add_dse_entries - Unable to add config entry (%s) to the DSE: %s\n",
|
||||
- slapi_entry_get_dn(e),
|
||||
+ "cb_config_add_dse_entries - Unable to add config entry to the DSE: %s\n",
|
||||
ldap_err2string(res));
|
||||
rc = res;
|
||||
slapi_pblock_destroy(util_pb);
|
||||
diff --git a/ldap/servers/plugins/posix-winsync/posix-winsync.c b/ldap/servers/plugins/posix-winsync/posix-winsync.c
|
||||
index 56efb2330..ab37497cd 100644
|
||||
--- a/ldap/servers/plugins/posix-winsync/posix-winsync.c
|
||||
+++ b/ldap/servers/plugins/posix-winsync/posix-winsync.c
|
||||
@@ -1625,7 +1625,6 @@ posix_winsync_end_update_cb(void *cbdata __attribute__((unused)),
|
||||
"posix_winsync_end_update_cb: "
|
||||
"add task entry\n");
|
||||
}
|
||||
- /* slapi_entry_free(e_task); */
|
||||
slapi_pblock_destroy(pb);
|
||||
pb = NULL;
|
||||
posix_winsync_config_reset_MOFTaskCreated();
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
|
||||
index 5a748e35a..9b6523a2e 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_init.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_init.c
|
||||
@@ -682,7 +682,6 @@ create_repl_schema_policy(void)
|
||||
repl_schema_top,
|
||||
ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
- slapi_entry_free(e); /* The entry was not consumed */
|
||||
goto done;
|
||||
}
|
||||
slapi_pblock_destroy(pb);
|
||||
@@ -703,7 +702,6 @@ create_repl_schema_policy(void)
|
||||
repl_schema_supplier,
|
||||
ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
- slapi_entry_free(e); /* The entry was not consumed */
|
||||
goto done;
|
||||
}
|
||||
slapi_pblock_destroy(pb);
|
||||
@@ -724,7 +722,6 @@ create_repl_schema_policy(void)
|
||||
repl_schema_consumer,
|
||||
ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
- slapi_entry_free(e); /* The entry was not consumed */
|
||||
goto done;
|
||||
}
|
||||
slapi_pblock_destroy(pb);
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c
|
||||
index d67f1bc71..cec140140 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_replica.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_replica.c
|
||||
@@ -440,10 +440,10 @@ replica_subentry_create(const char *repl_root, ReplicaId rid)
|
||||
if (return_value != LDAP_SUCCESS &&
|
||||
return_value != LDAP_ALREADY_EXISTS &&
|
||||
return_value != LDAP_REFERRAL /* CONSUMER */) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_subentry_create - Unable to "
|
||||
- "create replication keep alive entry %s: error %d - %s\n",
|
||||
- slapi_entry_get_dn_const(e),
|
||||
- return_value, ldap_err2string(return_value));
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_subentry_create - "
|
||||
+ "Unable to create replication keep alive entry 'cn=%s %d,%s': error %d - %s\n",
|
||||
+ KEEP_ALIVE_ENTRY, rid, repl_root,
|
||||
+ return_value, ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,720 +0,0 @@
|
||||
From 18a807e0e23b1160ea61e05e721da9fbd0c560b1 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 15:41:29 -0700
|
||||
Subject: [PATCH] Issue 6884 - Mask password hashes in audit logs (#6885)
|
||||
|
||||
Description: Fix the audit log functionality to mask password hash values for
|
||||
userPassword, nsslapd-rootpw, nsmultiplexorcredentials, nsds5ReplicaCredentials,
|
||||
and nsds5ReplicaBootstrapCredentials attributes in ADD and MODIFY operations.
|
||||
Update auditlog.c to detect password attributes and replace their values with
|
||||
asterisks (**********************) in both LDIF and JSON audit log formats.
|
||||
Add a comprehensive test suite audit_password_masking_test.py to verify
|
||||
password masking works correctly across all log formats and operation types.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6884
|
||||
|
||||
Reviewed by: @mreynolds389, @vashirov (Thanks!!)
|
||||
|
||||
(cherry picked from commit 24f9aea1ae7e29bd885212825dc52d2a5db08a03)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../logging/audit_password_masking_test.py | 457 ++++++++++++++++++
|
||||
ldap/servers/slapd/auditlog.c | 144 +++++-
|
||||
ldap/servers/slapd/slapi-private.h | 1 +
|
||||
src/lib389/lib389/chaining.py | 3 +-
|
||||
4 files changed, 586 insertions(+), 19 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
new file mode 100644
|
||||
index 000000000..ae379cbba
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
@@ -0,0 +1,457 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import logging
|
||||
+import pytest
|
||||
+import os
|
||||
+import re
|
||||
+import time
|
||||
+import ldap
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PW_DM
|
||||
+from lib389.topologies import topology_m2 as topo
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.plugins import ChainingBackendPlugin
|
||||
+from lib389.chaining import ChainingLinks
|
||||
+from lib389.agreement import Agreements
|
||||
+from lib389.replica import ReplicationManager, Replicas
|
||||
+from lib389.idm.directorymanager import DirectoryManager
|
||||
+
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+MASKED_PASSWORD = "**********************"
|
||||
+TEST_PASSWORD = "MySecret123"
|
||||
+TEST_PASSWORD_2 = "NewPassword789"
|
||||
+TEST_PASSWORD_3 = "NewPassword101"
|
||||
+
|
||||
+
|
||||
+def setup_audit_logging(inst, log_format='default', display_attrs=None):
|
||||
+ """Configure audit logging settings"""
|
||||
+ inst.config.replace('nsslapd-auditlog-logging-enabled', 'on')
|
||||
+
|
||||
+ if display_attrs is not None:
|
||||
+ inst.config.replace('nsslapd-auditlog-display-attrs', display_attrs)
|
||||
+
|
||||
+ inst.deleteAuditLogs()
|
||||
+
|
||||
+
|
||||
+def check_password_masked(inst, log_format, expected_password, actual_password):
|
||||
+ """Helper function to check password masking in audit logs"""
|
||||
+
|
||||
+ inst.restart() # Flush the logs
|
||||
+
|
||||
+ # List of all password/credential attributes that should be masked
|
||||
+ password_attributes = [
|
||||
+ 'userPassword',
|
||||
+ 'nsslapd-rootpw',
|
||||
+ 'nsmultiplexorcredentials',
|
||||
+ 'nsDS5ReplicaCredentials',
|
||||
+ 'nsDS5ReplicaBootstrapCredentials'
|
||||
+ ]
|
||||
+
|
||||
+ # Get password schemes to check for hash leakage
|
||||
+ user_password_scheme = inst.config.get_attr_val_utf8('passwordStorageScheme')
|
||||
+ root_password_scheme = inst.config.get_attr_val_utf8('nsslapd-rootpwstoragescheme')
|
||||
+
|
||||
+ # Check LDIF format logs
|
||||
+ found_masked = False
|
||||
+ found_actual = False
|
||||
+ found_hashed = False
|
||||
+
|
||||
+ # Check each password attribute for masked password
|
||||
+ for attr in password_attributes:
|
||||
+ if inst.ds_audit_log.match(f"{attr}: {re.escape(expected_password)}"):
|
||||
+ found_masked = True
|
||||
+ if inst.ds_audit_log.match(f"{attr}: {actual_password}"):
|
||||
+ found_actual = True
|
||||
+
|
||||
+ # Check for hashed passwords in LDIF format
|
||||
+ if user_password_scheme:
|
||||
+ if inst.ds_audit_log.match(f"userPassword: {{{user_password_scheme}}}"):
|
||||
+ found_hashed = True
|
||||
+ if root_password_scheme:
|
||||
+ if inst.ds_audit_log.match(f"nsslapd-rootpw: {{{root_password_scheme}}}"):
|
||||
+ found_hashed = True
|
||||
+
|
||||
+ # Delete audit logs to avoid interference with other tests
|
||||
+ # We need to reset the root password to default as deleteAuditLogs()
|
||||
+ # opens a new connection with the default password
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.change_password(PW_DM)
|
||||
+ inst.deleteAuditLogs()
|
||||
+
|
||||
+ return found_masked, found_actual, found_hashed
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "userPassword"),
|
||||
+])
|
||||
+def test_password_masking_add_operation(topo, log_format, display_attrs):
|
||||
+ """Test password masking in ADD operations
|
||||
+
|
||||
+ :id: 4358bd75-bcc7-401c-b492-d3209b10412d
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Add user with password
|
||||
+ 3. Check that password is masked in audit log
|
||||
+ 4. Verify actual password does not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Password should be masked with asterisks
|
||||
+ 4. Actual password should not be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user = None
|
||||
+
|
||||
+ try:
|
||||
+ user = users.create(properties={
|
||||
+ 'uid': 'test_add_pwd_mask',
|
||||
+ 'cn': 'Test Add User',
|
||||
+ 'sn': 'User',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '1000',
|
||||
+ 'homeDirectory': '/home/test_add',
|
||||
+ 'userPassword': TEST_PASSWORD
|
||||
+ })
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+
|
||||
+ assert found_masked, f"Masked password not found in {log_format} ADD operation"
|
||||
+ assert not found_actual, f"Actual password found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed password found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if user is not None:
|
||||
+ try:
|
||||
+ user.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "userPassword"),
|
||||
+])
|
||||
+def test_password_masking_modify_operation(topo, log_format, display_attrs):
|
||||
+ """Test password masking in MODIFY operations
|
||||
+
|
||||
+ :id: e6963aa9-7609-419c-aae2-1d517aa434bd
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Add user without password
|
||||
+ 3. Add password via MODIFY operation
|
||||
+ 4. Check that password is masked in audit log
|
||||
+ 5. Modify password to new value
|
||||
+ 6. Check that new password is also masked
|
||||
+ 7. Verify actual passwords do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Password should be masked with asterisks
|
||||
+ 5. Success
|
||||
+ 6. New password should be masked with asterisks
|
||||
+ 7. No actual password values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user = None
|
||||
+
|
||||
+ try:
|
||||
+ user = users.create(properties={
|
||||
+ 'uid': 'test_modify_pwd_mask',
|
||||
+ 'cn': 'Test Modify User',
|
||||
+ 'sn': 'User',
|
||||
+ 'uidNumber': '2000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/test_modify'
|
||||
+ })
|
||||
+
|
||||
+ user.replace('userPassword', TEST_PASSWORD)
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked password not found in {log_format} MODIFY operation (first password)"
|
||||
+ assert not found_actual, f"Actual password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ user.replace('userPassword', TEST_PASSWORD_2)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked password not found in {log_format} MODIFY operation (second password)"
|
||||
+ assert not found_actual_2, f"Second actual password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Second hashed password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if user is not None:
|
||||
+ try:
|
||||
+ user.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsslapd-rootpw"),
|
||||
+])
|
||||
+def test_password_masking_rootpw_modify_operation(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsslapd-rootpw MODIFY operations
|
||||
+
|
||||
+ :id: ec8c9fd4-56ba-4663-ab65-58efb3b445e4
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Modify nsslapd-rootpw in configuration
|
||||
+ 3. Check that root password is masked in audit log
|
||||
+ 4. Modify root password to new value
|
||||
+ 5. Check that new root password is also masked
|
||||
+ 6. Verify actual root passwords do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Root password should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. New root password should be masked with asterisks
|
||||
+ 6. No actual root password values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+ dm = DirectoryManager(inst)
|
||||
+
|
||||
+ try:
|
||||
+ dm.change_password(TEST_PASSWORD)
|
||||
+ dm.rebind(TEST_PASSWORD)
|
||||
+ dm.change_password(PW_DM)
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked root password not found in {log_format} MODIFY operation (first root password)"
|
||||
+ assert not found_actual, f"Actual root password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed root password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ dm.change_password(TEST_PASSWORD_2)
|
||||
+ dm.rebind(TEST_PASSWORD_2)
|
||||
+ dm.change_password(PW_DM)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked root password not found in {log_format} MODIFY operation (second root password)"
|
||||
+ assert not found_actual_2, f"Second actual root password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Second hashed root password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ dm.change_password(PW_DM)
|
||||
+ dm.rebind(PW_DM)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsmultiplexorcredentials"),
|
||||
+])
|
||||
+def test_password_masking_multiplexor_credentials(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsmultiplexorcredentials in chaining/multiplexor configurations
|
||||
+
|
||||
+ :id: 161a9498-b248-4926-90be-a696a36ed36e
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Create a chaining backend configuration entry with nsmultiplexorcredentials
|
||||
+ 3. Check that multiplexor credentials are masked in audit log
|
||||
+ 4. Modify the credentials
|
||||
+ 5. Check that updated credentials are also masked
|
||||
+ 6. Verify actual credentials do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Multiplexor credentials should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. Updated credentials should be masked with asterisks
|
||||
+ 6. No actual credential values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+
|
||||
+ # Enable chaining plugin and create chaining link
|
||||
+ chain_plugin = ChainingBackendPlugin(inst)
|
||||
+ chain_plugin.enable()
|
||||
+
|
||||
+ chains = ChainingLinks(inst)
|
||||
+ chain = None
|
||||
+
|
||||
+ try:
|
||||
+ # Create chaining link with multiplexor credentials
|
||||
+ chain = chains.create(properties={
|
||||
+ 'cn': 'testchain',
|
||||
+ 'nsfarmserverurl': 'ldap://localhost:389/',
|
||||
+ 'nsslapd-suffix': 'dc=example,dc=com',
|
||||
+ 'nsmultiplexorbinddn': 'cn=manager',
|
||||
+ 'nsmultiplexorcredentials': TEST_PASSWORD,
|
||||
+ 'nsCheckLocalACI': 'on',
|
||||
+ 'nsConnectionLife': '30',
|
||||
+ })
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked multiplexor credentials not found in {log_format} ADD operation"
|
||||
+ assert not found_actual, f"Actual multiplexor credentials found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed multiplexor credentials found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ # Modify the credentials
|
||||
+ chain.replace('nsmultiplexorcredentials', TEST_PASSWORD_2)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked multiplexor credentials not found in {log_format} MODIFY operation"
|
||||
+ assert not found_actual_2, f"Actual multiplexor credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Hashed multiplexor credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ chain_plugin.disable()
|
||||
+ if chain is not None:
|
||||
+ inst.delete_branch_s(chain.dn, ldap.SCOPE_ONELEVEL)
|
||||
+ chain.delete()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsDS5ReplicaCredentials"),
|
||||
+])
|
||||
+def test_password_masking_replica_credentials(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsDS5ReplicaCredentials in replication agreements
|
||||
+
|
||||
+ :id: 7bf9e612-1b7c-49af-9fc0-de4c7df84b2a
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Create a replication agreement entry with nsDS5ReplicaCredentials
|
||||
+ 3. Check that replica credentials are masked in audit log
|
||||
+ 4. Modify the credentials
|
||||
+ 5. Check that updated credentials are also masked
|
||||
+ 6. Verify actual credentials do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Replica credentials should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. Updated credentials should be masked with asterisks
|
||||
+ 6. No actual credential values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier2']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+ agmt = None
|
||||
+
|
||||
+ try:
|
||||
+ replicas = Replicas(inst)
|
||||
+ replica = replicas.get(DEFAULT_SUFFIX)
|
||||
+ agmts = replica.get_agreements()
|
||||
+ agmt = agmts.create(properties={
|
||||
+ 'cn': 'testagmt',
|
||||
+ 'nsDS5ReplicaHost': 'localhost',
|
||||
+ 'nsDS5ReplicaPort': '389',
|
||||
+ 'nsDS5ReplicaBindDN': 'cn=replication manager,cn=config',
|
||||
+ 'nsDS5ReplicaCredentials': TEST_PASSWORD,
|
||||
+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX
|
||||
+ })
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked replica credentials not found in {log_format} ADD operation"
|
||||
+ assert not found_actual, f"Actual replica credentials found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed replica credentials found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ # Modify the credentials
|
||||
+ agmt.replace('nsDS5ReplicaCredentials', TEST_PASSWORD_2)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked replica credentials not found in {log_format} MODIFY operation"
|
||||
+ assert not found_actual_2, f"Actual replica credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Hashed replica credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if agmt is not None:
|
||||
+ agmt.delete()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsDS5ReplicaBootstrapCredentials"),
|
||||
+])
|
||||
+def test_password_masking_bootstrap_credentials(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsDS5ReplicaCredentials and nsDS5ReplicaBootstrapCredentials in replication agreements
|
||||
+
|
||||
+ :id: 248bd418-ffa4-4733-963d-2314c60b7c5b
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Create a replication agreement entry with both nsDS5ReplicaCredentials and nsDS5ReplicaBootstrapCredentials
|
||||
+ 3. Check that both credentials are masked in audit log
|
||||
+ 4. Modify both credentials
|
||||
+ 5. Check that both updated credentials are also masked
|
||||
+ 6. Verify actual credentials do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Both credentials should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. Both updated credentials should be masked with asterisks
|
||||
+ 6. No actual credential values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier2']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+ agmt = None
|
||||
+
|
||||
+ try:
|
||||
+ replicas = Replicas(inst)
|
||||
+ replica = replicas.get(DEFAULT_SUFFIX)
|
||||
+ agmts = replica.get_agreements()
|
||||
+ agmt = agmts.create(properties={
|
||||
+ 'cn': 'testbootstrapagmt',
|
||||
+ 'nsDS5ReplicaHost': 'localhost',
|
||||
+ 'nsDS5ReplicaPort': '389',
|
||||
+ 'nsDS5ReplicaBindDN': 'cn=replication manager,cn=config',
|
||||
+ 'nsDS5ReplicaCredentials': TEST_PASSWORD,
|
||||
+ 'nsDS5replicabootstrapbinddn': 'cn=bootstrap manager,cn=config',
|
||||
+ 'nsDS5ReplicaBootstrapCredentials': TEST_PASSWORD_2,
|
||||
+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX
|
||||
+ })
|
||||
+
|
||||
+ found_masked_bootstrap, found_actual_bootstrap, found_hashed_bootstrap = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_bootstrap, f"Masked bootstrap credentials not found in {log_format} ADD operation"
|
||||
+ assert not found_actual_bootstrap, f"Actual bootstrap credentials found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed_bootstrap, f"Hashed bootstrap credentials found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ agmt.replace('nsDS5ReplicaBootstrapCredentials', TEST_PASSWORD_3)
|
||||
+
|
||||
+ found_masked_bootstrap_2, found_actual_bootstrap_2, found_hashed_bootstrap_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_3)
|
||||
+ assert found_masked_bootstrap_2, f"Masked bootstrap credentials not found in {log_format} MODIFY operation"
|
||||
+ assert not found_actual_bootstrap_2, f"Actual bootstrap credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_bootstrap_2, f"Hashed bootstrap credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if agmt is not None:
|
||||
+ agmt.delete()
|
||||
+
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
\ No newline at end of file
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 0597ecc6f..c41415725 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -37,6 +37,89 @@ static void write_audit_file(Slapi_Entry *entry, int logtype, int optype, const
|
||||
|
||||
static const char *modrdn_changes[4];
|
||||
|
||||
+/* Helper function to check if an attribute is a password that needs masking */
|
||||
+static int
|
||||
+is_password_attribute(const char *attr_name)
|
||||
+{
|
||||
+ return (strcasecmp(attr_name, SLAPI_USERPWD_ATTR) == 0 ||
|
||||
+ strcasecmp(attr_name, CONFIG_ROOTPW_ATTRIBUTE) == 0 ||
|
||||
+ strcasecmp(attr_name, SLAPI_MB_CREDENTIALS) == 0 ||
|
||||
+ strcasecmp(attr_name, SLAPI_REP_CREDENTIALS) == 0 ||
|
||||
+ strcasecmp(attr_name, SLAPI_REP_BOOTSTRAP_CREDENTIALS) == 0);
|
||||
+}
|
||||
+
|
||||
+/* Helper function to create a masked string representation of an entry */
|
||||
+static char *
|
||||
+create_masked_entry_string(Slapi_Entry *original_entry, int *len)
|
||||
+{
|
||||
+ Slapi_Attr *attr = NULL;
|
||||
+ char *entry_str = NULL;
|
||||
+ char *current_pos = NULL;
|
||||
+ char *line_start = NULL;
|
||||
+ char *next_line = NULL;
|
||||
+ char *colon_pos = NULL;
|
||||
+ int has_password_attrs = 0;
|
||||
+
|
||||
+ if (original_entry == NULL) {
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ /* Single pass through attributes to check for password attributes */
|
||||
+ for (slapi_entry_first_attr(original_entry, &attr); attr != NULL;
|
||||
+ slapi_entry_next_attr(original_entry, attr, &attr)) {
|
||||
+
|
||||
+ char *attr_name = NULL;
|
||||
+ slapi_attr_get_type(attr, &attr_name);
|
||||
+
|
||||
+ if (is_password_attribute(attr_name)) {
|
||||
+ has_password_attrs = 1;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* If no password attributes, return original string - no masking needed */
|
||||
+ entry_str = slapi_entry2str(original_entry, len);
|
||||
+ if (!has_password_attrs) {
|
||||
+ return entry_str;
|
||||
+ }
|
||||
+
|
||||
+ /* Process the string in-place, replacing password values */
|
||||
+ current_pos = entry_str;
|
||||
+ while ((line_start = current_pos) != NULL && *line_start != '\0') {
|
||||
+ /* Find the end of current line */
|
||||
+ next_line = strchr(line_start, '\n');
|
||||
+ if (next_line != NULL) {
|
||||
+ *next_line = '\0'; /* Temporarily terminate line */
|
||||
+ current_pos = next_line + 1;
|
||||
+ } else {
|
||||
+ current_pos = NULL; /* Last line */
|
||||
+ }
|
||||
+
|
||||
+ /* Find the colon that separates attribute name from value */
|
||||
+ colon_pos = strchr(line_start, ':');
|
||||
+ if (colon_pos != NULL) {
|
||||
+ char saved_colon = *colon_pos;
|
||||
+ *colon_pos = '\0'; /* Temporarily null-terminate attribute name */
|
||||
+
|
||||
+ /* Check if this is a password attribute that needs masking */
|
||||
+ if (is_password_attribute(line_start)) {
|
||||
+ strcpy(colon_pos + 1, " **********************");
|
||||
+ }
|
||||
+
|
||||
+ *colon_pos = saved_colon; /* Restore colon */
|
||||
+ }
|
||||
+
|
||||
+ /* Restore newline if it was there */
|
||||
+ if (next_line != NULL) {
|
||||
+ *next_line = '\n';
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Update length since we may have shortened the string */
|
||||
+ *len = strlen(entry_str);
|
||||
+ return entry_str; /* Return the modified original string */
|
||||
+}
|
||||
+
|
||||
void
|
||||
write_audit_log_entry(Slapi_PBlock *pb)
|
||||
{
|
||||
@@ -248,7 +331,21 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
{
|
||||
slapi_entry_attr_find(entry, req_attr, &entry_attr);
|
||||
if (entry_attr) {
|
||||
- log_entry_attr(entry_attr, req_attr, l);
|
||||
+ if (strcmp(req_attr, PSEUDO_ATTR_UNHASHEDUSERPASSWORD) == 0) {
|
||||
+ /* Do not write the unhashed clear-text password */
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Check if this is a password attribute that needs masking */
|
||||
+ if (is_password_attribute(req_attr)) {
|
||||
+ /* userpassword/rootdn password - mask the value */
|
||||
+ addlenstr(l, "#");
|
||||
+ addlenstr(l, req_attr);
|
||||
+ addlenstr(l, ": **********************\n");
|
||||
+ } else {
|
||||
+ /* Regular attribute - log normally */
|
||||
+ log_entry_attr(entry_attr, req_attr, l);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -262,13 +359,11 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
continue;
|
||||
}
|
||||
|
||||
- if (strcasecmp(attr, SLAPI_USERPWD_ATTR) == 0 ||
|
||||
- strcasecmp(attr, CONFIG_ROOTPW_ATTRIBUTE) == 0)
|
||||
- {
|
||||
+ if (is_password_attribute(attr)) {
|
||||
/* userpassword/rootdn password - mask the value */
|
||||
addlenstr(l, "#");
|
||||
addlenstr(l, attr);
|
||||
- addlenstr(l, ": ****************************\n");
|
||||
+ addlenstr(l, ": **********************\n");
|
||||
continue;
|
||||
}
|
||||
log_entry_attr(entry_attr, attr, l);
|
||||
@@ -354,6 +449,10 @@ write_audit_file(
|
||||
break;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ /* Check if this is a password attribute that needs masking */
|
||||
+ int is_password_attr = is_password_attribute(mods[j]->mod_type);
|
||||
+
|
||||
switch (operationtype) {
|
||||
case LDAP_MOD_ADD:
|
||||
addlenstr(l, "add: ");
|
||||
@@ -378,18 +477,27 @@ write_audit_file(
|
||||
break;
|
||||
}
|
||||
if (operationtype != LDAP_MOD_IGNORE) {
|
||||
- for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) {
|
||||
- char *buf, *bufp;
|
||||
- len = strlen(mods[j]->mod_type);
|
||||
- len = LDIF_SIZE_NEEDED(len, mods[j]->mod_bvalues[i]->bv_len) + 1;
|
||||
- buf = slapi_ch_malloc(len);
|
||||
- bufp = buf;
|
||||
- slapi_ldif_put_type_and_value_with_options(&bufp, mods[j]->mod_type,
|
||||
- mods[j]->mod_bvalues[i]->bv_val,
|
||||
- mods[j]->mod_bvalues[i]->bv_len, 0);
|
||||
- *bufp = '\0';
|
||||
- addlenstr(l, buf);
|
||||
- slapi_ch_free((void **)&buf);
|
||||
+ if (is_password_attr) {
|
||||
+ /* Add masked password */
|
||||
+ for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) {
|
||||
+ addlenstr(l, mods[j]->mod_type);
|
||||
+ addlenstr(l, ": **********************\n");
|
||||
+ }
|
||||
+ } else {
|
||||
+ /* Add actual values for non-password attributes */
|
||||
+ for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) {
|
||||
+ char *buf, *bufp;
|
||||
+ len = strlen(mods[j]->mod_type);
|
||||
+ len = LDIF_SIZE_NEEDED(len, mods[j]->mod_bvalues[i]->bv_len) + 1;
|
||||
+ buf = slapi_ch_malloc(len);
|
||||
+ bufp = buf;
|
||||
+ slapi_ldif_put_type_and_value_with_options(&bufp, mods[j]->mod_type,
|
||||
+ mods[j]->mod_bvalues[i]->bv_val,
|
||||
+ mods[j]->mod_bvalues[i]->bv_len, 0);
|
||||
+ *bufp = '\0';
|
||||
+ addlenstr(l, buf);
|
||||
+ slapi_ch_free((void **)&buf);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
addlenstr(l, "-\n");
|
||||
@@ -400,7 +508,7 @@ write_audit_file(
|
||||
e = change;
|
||||
addlenstr(l, attr_changetype);
|
||||
addlenstr(l, ": add\n");
|
||||
- tmp = slapi_entry2str(e, &len);
|
||||
+ tmp = create_masked_entry_string(e, &len);
|
||||
tmpsave = tmp;
|
||||
while ((tmp = strchr(tmp, '\n')) != NULL) {
|
||||
tmp++;
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index dfb0e272a..af2180e55 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -843,6 +843,7 @@ void task_cleanup(void);
|
||||
/* for reversible encyrption */
|
||||
#define SLAPI_MB_CREDENTIALS "nsmultiplexorcredentials"
|
||||
#define SLAPI_REP_CREDENTIALS "nsds5ReplicaCredentials"
|
||||
+#define SLAPI_REP_BOOTSTRAP_CREDENTIALS "nsds5ReplicaBootstrapCredentials"
|
||||
int pw_rever_encode(Slapi_Value **vals, char *attr_name);
|
||||
int pw_rever_decode(char *cipher, char **plain, const char *attr_name);
|
||||
|
||||
diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py
|
||||
index 533b83ebf..33ae78c8b 100644
|
||||
--- a/src/lib389/lib389/chaining.py
|
||||
+++ b/src/lib389/lib389/chaining.py
|
||||
@@ -134,7 +134,7 @@ class ChainingLink(DSLdapObject):
|
||||
"""
|
||||
|
||||
# Create chaining entry
|
||||
- super(ChainingLink, self).create(rdn, properties, basedn)
|
||||
+ link = super(ChainingLink, self).create(rdn, properties, basedn)
|
||||
|
||||
# Create mapping tree entry
|
||||
dn_comps = ldap.explode_dn(properties['nsslapd-suffix'][0])
|
||||
@@ -149,6 +149,7 @@ class ChainingLink(DSLdapObject):
|
||||
self._mts.ensure_state(properties=mt_properties)
|
||||
except ldap.ALREADY_EXISTS:
|
||||
pass
|
||||
+ return link
|
||||
|
||||
|
||||
class ChainingLinks(DSLdapObjects):
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
From 284da99d0cd1ad16c702f4a4f68d2a479ac41576 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 18 Jun 2025 11:12:28 +0200
|
||||
Subject: [PATCH] Issue 6819 - Incorrect pwdpolicysubentry returned for an
|
||||
entry with user password policy
|
||||
|
||||
Bug Description:
|
||||
When both subtree and user password policies exist, pwdpolicysubentry
|
||||
points to the subtree password policy instead of user password policy.
|
||||
|
||||
Fix Description:
|
||||
Update the template for CoS pointer definition to use
|
||||
`operational-default` modifier instead of `operational`.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6819
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit 622c191302879035ef7450a29aa7569ee768c3ab)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../password/pwdPolicy_attribute_test.py | 73 +++++++++++++++++--
|
||||
src/lib389/lib389/pwpolicy.py | 2 +-
|
||||
2 files changed, 66 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
index c2c1e47fb..0dde8d637 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
@@ -59,17 +59,39 @@ def test_user(topology_st, request):
|
||||
return user
|
||||
|
||||
|
||||
-@pytest.fixture(scope="module")
|
||||
-def password_policy(topology_st, test_user):
|
||||
+@pytest.fixture(scope="function")
|
||||
+def password_policy(topology_st, request, test_user):
|
||||
"""Set up password policy for subtree and user"""
|
||||
|
||||
pwp = PwPolicyManager(topology_st.standalone)
|
||||
policy_props = {}
|
||||
- log.info('Create password policy for subtree {}'.format(OU_PEOPLE))
|
||||
- pwp.create_subtree_policy(OU_PEOPLE, policy_props)
|
||||
+ log.info(f"Create password policy for subtree {OU_PEOPLE}")
|
||||
+ try:
|
||||
+ pwp.create_subtree_policy(OU_PEOPLE, policy_props)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"Subtree password policy for {OU_PEOPLE} already exist, skipping")
|
||||
+
|
||||
+ log.info(f"Create password policy for user {TEST_USER_DN}")
|
||||
+ try:
|
||||
+ pwp.create_user_policy(TEST_USER_DN, policy_props)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"User password policy for {TEST_USER_DN} already exist, skipping")
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info(f"Delete password policy for subtree {OU_PEOPLE}")
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(OU_PEOPLE)
|
||||
+ except ValueError:
|
||||
+ log.info(f"Subtree password policy for {OU_PEOPLE} doesn't exist, skipping")
|
||||
+
|
||||
+ log.info(f"Delete password policy for user {TEST_USER_DN}")
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(TEST_USER_DN)
|
||||
+ except ValueError:
|
||||
+ log.info(f"User password policy for {TEST_USER_DN} doesn't exist, skipping")
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
|
||||
- log.info('Create password policy for user {}'.format(TEST_USER_DN))
|
||||
- pwp.create_user_policy(TEST_USER_DN, policy_props)
|
||||
|
||||
@pytest.mark.skipif(ds_is_older('1.4.3.3'), reason="Not implemented")
|
||||
def test_pwd_reset(topology_st, test_user):
|
||||
@@ -257,8 +279,43 @@ def test_pwd_min_age(topology_st, test_user, password_policy):
|
||||
log.info('Bind as DM')
|
||||
topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
user.reset_password(TEST_USER_PWD)
|
||||
- pwp.delete_local_policy(TEST_USER_DN)
|
||||
- pwp.delete_local_policy(OU_PEOPLE)
|
||||
+
|
||||
+
|
||||
+def test_pwdpolicysubentry(topology_st, password_policy):
|
||||
+ """Verify that 'pwdpolicysubentry' attr works as expected
|
||||
+ User should have a priority over a subtree.
|
||||
+
|
||||
+ :id: 4ab0c62a-623b-40b4-af67-99580c77b36c
|
||||
+ :setup: Standalone instance, a test user,
|
||||
+ password policy entries for a user and a subtree
|
||||
+ :steps:
|
||||
+ 1. Create a subtree policy
|
||||
+ 2. Create a user policy
|
||||
+ 3. Search for 'pwdpolicysubentry' in the user entry
|
||||
+ 4. Delete the user policy
|
||||
+ 5. Search for 'pwdpolicysubentry' in the user entry
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should point to the user policy entry
|
||||
+ 4. Success
|
||||
+ 5. Should point to the subtree policy entry
|
||||
+
|
||||
+ """
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, OU_PEOPLE, rdn=None)
|
||||
+ user = users.get(TEST_USER_NAME)
|
||||
+
|
||||
+ pwp_subentry = user.get_attr_vals_utf8('pwdpolicysubentry')[0]
|
||||
+ assert 'nsPwPolicyEntry_subtree' not in pwp_subentry
|
||||
+ assert 'nsPwPolicyEntry_user' in pwp_subentry
|
||||
+
|
||||
+ pwp = PwPolicyManager(topology_st.standalone)
|
||||
+ pwp.delete_local_policy(TEST_USER_DN)
|
||||
+ pwp_subentry = user.get_attr_vals_utf8('pwdpolicysubentry')[0]
|
||||
+ assert 'nsPwPolicyEntry_subtree' in pwp_subentry
|
||||
+ assert 'nsPwPolicyEntry_user' not in pwp_subentry
|
||||
+
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
|
||||
index 7ffe449cc..6a47a44fe 100644
|
||||
--- a/src/lib389/lib389/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/pwpolicy.py
|
||||
@@ -168,7 +168,7 @@ class PwPolicyManager(object):
|
||||
|
||||
# The CoS specification entry at the subtree level
|
||||
cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational',
|
||||
+ cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
'cosTemplateDn': cos_template.dn,
|
||||
'cn': 'nsPwPolicy_CoS'})
|
||||
except ldap.LDAPError as e:
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,572 +0,0 @@
|
||||
From 23e56fd01eaa24a2fa945430f91600dd9c726d34 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 19 Aug 2025 14:30:15 -0700
|
||||
Subject: [PATCH] Issue 6936 - Make user/subtree policy creation idempotent
|
||||
(#6937)
|
||||
|
||||
Description: Correct the CLI mapping typo to use 'nsslapd-pwpolicy-local',
|
||||
rework subtree policy detection to validate CoS templates and add user-policy detection.
|
||||
Make user/subtree policy creation idempotent via ensure_state, and improve deletion
|
||||
logic to distinguish subtree vs user policies and fail if none exist.
|
||||
|
||||
Add a test suite (pwp_history_local_override_test.py) exercising global-only and local-only
|
||||
history enforcement, local overriding global counts, immediate effect of dsconf updates,
|
||||
and fallback to global after removing a user policy, ensuring reliable behavior
|
||||
and preventing regressions.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6936
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit da4eea126cc9019f540b57c1db9dec7988cade10)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../pwp_history_local_override_test.py | 351 ++++++++++++++++++
|
||||
src/lib389/lib389/cli_conf/pwpolicy.py | 4 +-
|
||||
src/lib389/lib389/pwpolicy.py | 107 ++++--
|
||||
3 files changed, 424 insertions(+), 38 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
new file mode 100644
|
||||
index 000000000..6d72725fa
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
@@ -0,0 +1,351 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import os
|
||||
+import time
|
||||
+import ldap
|
||||
+import pytest
|
||||
+import subprocess
|
||||
+import logging
|
||||
+
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD, DN_CONFIG
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.domain import Domain
|
||||
+from lib389.pwpolicy import PwPolicyManager
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+if DEBUGGING:
|
||||
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+else:
|
||||
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+OU_DN = f"ou=People,{DEFAULT_SUFFIX}"
|
||||
+USER_ACI = '(targetattr="userpassword || passwordHistory")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)'
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(autouse=True, scope="function")
|
||||
+def restore_global_policy(topology_st, request):
|
||||
+ """Snapshot and restore global password policy around each test in this file."""
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ attrs = [
|
||||
+ 'nsslapd-pwpolicy-local',
|
||||
+ 'nsslapd-pwpolicy-inherit-global',
|
||||
+ 'passwordHistory',
|
||||
+ 'passwordInHistory',
|
||||
+ 'passwordChange',
|
||||
+ ]
|
||||
+
|
||||
+ entry = inst.getEntry(DN_CONFIG, ldap.SCOPE_BASE, '(objectClass=*)', attrs)
|
||||
+ saved = {attr: entry.getValue(attr) for attr in attrs}
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ for attr, value in saved.items():
|
||||
+ inst.config.replace(attr, value)
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def setup_entries(topology_st, request):
|
||||
+ """Create test OU and user, and install an ACI for self password changes."""
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ suffix = Domain(inst, DEFAULT_SUFFIX)
|
||||
+ suffix.add('aci', USER_ACI)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ try:
|
||||
+ user = users.create_test_user(uid=1)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ user = users.get("test_user_1")
|
||||
+
|
||||
+ def fin():
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(OU_DN)
|
||||
+ except Exception as e:
|
||||
+ if "No password policy" in str(e):
|
||||
+ pass
|
||||
+ else:
|
||||
+ raise e
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(user.dn)
|
||||
+ except Exception as e:
|
||||
+ if "No password policy" in str(e):
|
||||
+ pass
|
||||
+ else:
|
||||
+ raise e
|
||||
+ suffix.remove('aci', USER_ACI)
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return user
|
||||
+
|
||||
+
|
||||
+def set_user_password(inst, user, new_password, bind_as_user_password=None, expect_violation=False):
|
||||
+ if bind_as_user_password is not None:
|
||||
+ user.rebind(bind_as_user_password)
|
||||
+ try:
|
||||
+ user.reset_password(new_password)
|
||||
+ if expect_violation:
|
||||
+ pytest.fail("Password change unexpectedly succeeded")
|
||||
+ except ldap.CONSTRAINT_VIOLATION:
|
||||
+ if not expect_violation:
|
||||
+ pytest.fail("Password change unexpectedly rejected with CONSTRAINT_VIOLATION")
|
||||
+ finally:
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def set_global_history(inst, enabled: bool, count: int, inherit_global: str = 'on'):
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ inst.config.replace('nsslapd-pwpolicy-local', 'on')
|
||||
+ inst.config.replace('nsslapd-pwpolicy-inherit-global', inherit_global)
|
||||
+ inst.config.replace('passwordHistory', 'on' if enabled else 'off')
|
||||
+ inst.config.replace('passwordInHistory', str(count))
|
||||
+ inst.config.replace('passwordChange', 'on')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def ensure_local_subtree_policy(inst, count: int, track_update_time: str = 'on'):
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ pwp.create_subtree_policy(OU_DN, {
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordHistory': 'on',
|
||||
+ 'passwordInHistory': str(count),
|
||||
+ 'passwordTrackUpdateTime': track_update_time,
|
||||
+ })
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def set_local_history_via_cli(inst, count: int):
|
||||
+ sbin_dir = inst.get_sbin_dir()
|
||||
+ inst_name = inst.serverid
|
||||
+ cmd = [f"{sbin_dir}/dsconf", inst_name, "localpwp", "set", f"--pwdhistorycount={count}", OU_DN]
|
||||
+ rc = subprocess.call(cmd)
|
||||
+ assert rc == 0, f"dsconf command failed rc={rc}: {' '.join(cmd)}"
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def test_global_history_only_enforced(topology_st, setup_entries):
|
||||
+ """Global-only history enforcement with count 2
|
||||
+
|
||||
+ :id: 3d8cf35b-4a33-4587-9814-ebe18b7a1f92
|
||||
+ :setup: Standalone instance, test OU and user, ACI for self password changes
|
||||
+ :steps:
|
||||
+ 1. Remove local policies
|
||||
+ 2. Set global policy: passwordHistory=on, passwordInHistory=2
|
||||
+ 3. Set password to Alpha1, then change to Alpha2 and Alpha3 as the user
|
||||
+ 4. Attempt to change to Alpha1 and Alpha2
|
||||
+ 5. Attempt to change to Alpha4
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Changes to Welcome1 and Welcome2 are rejected with CONSTRAINT_VIOLATION
|
||||
+ 5. Change to Welcome4 is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=2)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Alpha1')
|
||||
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha1')
|
||||
+ set_user_password(inst, user, 'Alpha3', bind_as_user_password='Alpha2')
|
||||
+
|
||||
+ # Within last 2
|
||||
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha3', expect_violation=True)
|
||||
+ set_user_password(inst, user, 'Alpha1', bind_as_user_password='Alpha3', expect_violation=True)
|
||||
+
|
||||
+ # New password should be allowed
|
||||
+ set_user_password(inst, user, 'Alpha4', bind_as_user_password='Alpha3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_local_overrides_global_history(topology_st, setup_entries):
|
||||
+ """Local subtree policy (history=3) overrides global (history=1)
|
||||
+
|
||||
+ :id: 97c22f56-5ea6-40c1-8d8c-1cece3bf46fd
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Set global policy passwordInHistory=1
|
||||
+ 2. Create local subtree policy on the OU with passwordInHistory=3
|
||||
+ 3. Set password to Bravo1, then change to Bravo2 and Bravo3 as the user
|
||||
+ 4. Attempt to change to Bravo1
|
||||
+ 5. Attempt to change to Bravo5
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Change to Welcome1 is rejected (local policy wins)
|
||||
+ 5. Change to Welcome5 is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Bravo1')
|
||||
+ set_user_password(inst, user, 'Bravo2', bind_as_user_password='Bravo1')
|
||||
+ set_user_password(inst, user, 'Bravo3', bind_as_user_password='Bravo2')
|
||||
+
|
||||
+ # Third prior should be rejected under local policy count=3
|
||||
+ set_user_password(inst, user, 'Bravo1', bind_as_user_password='Bravo3', expect_violation=True)
|
||||
+
|
||||
+ # New password allowed
|
||||
+ set_user_password(inst, user, 'Bravo5', bind_as_user_password='Bravo3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_change_local_history_via_cli_affects_enforcement(topology_st, setup_entries):
|
||||
+ """Changing local policy via CLI is enforced immediately
|
||||
+
|
||||
+ :id: 5a6d0d14-4009-4bad-86e1-cde5000c43dc
|
||||
+ :setup: Standalone instance, test OU and user, dsconf available
|
||||
+ :steps:
|
||||
+ 1. Ensure local subtree policy passwordInHistory=3
|
||||
+ 2. Set password to Charlie1, then change to Charlie2 and Charlie3 as the user
|
||||
+ 3. Attempt to change to Charlie1 (within last 3)
|
||||
+ 4. Run: dsconf <inst> localpwp set --pwdhistorycount=1 "ou=product testing,<suffix>"
|
||||
+ 5. Attempt to change to Charlie1 again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Change to Welcome1 is rejected
|
||||
+ 4. CLI command succeeds
|
||||
+ 5. Change to Welcome1 now succeeds (only last 1 is disallowed)
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Charlie1')
|
||||
+ set_user_password(inst, user, 'Charlie2', bind_as_user_password='Charlie1', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Charlie3', bind_as_user_password='Charlie2', expect_violation=False)
|
||||
+
|
||||
+ # With count=3, Welcome1 is within history
|
||||
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=True)
|
||||
+
|
||||
+ # Reduce local count to 1 via CLI to exercise CLI mapping and updated code
|
||||
+ set_local_history_via_cli(inst, count=1)
|
||||
+
|
||||
+ # Now Welcome1 should be allowed
|
||||
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_history_local_only_enforced(topology_st, setup_entries):
|
||||
+ """Local-only history enforcement with count 3
|
||||
+
|
||||
+ :id: af6ff34d-ac94-4108-a7b6-2b589c960154
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Disable global password history (passwordHistory=off, passwordInHistory=0, inherit off)
|
||||
+ 2. Ensure local subtree policy with passwordInHistory=3
|
||||
+ 3. Set password to Delta1, then change to Delta2 and Delta3 as the user
|
||||
+ 4. Attempt to change to Delta1
|
||||
+ 5. Attempt to change to Delta5
|
||||
+ 6. Change once more to Delta6, then change to Delta1
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Change to Welcome1 is rejected (within last 3)
|
||||
+ 5. Change to Welcome5 is accepted
|
||||
+ 6. Welcome1 is now older than the last 3 and is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=False, count=0, inherit_global='off')
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Delta1')
|
||||
+ set_user_password(inst, user, 'Delta2', bind_as_user_password='Delta1')
|
||||
+ set_user_password(inst, user, 'Delta3', bind_as_user_password='Delta2')
|
||||
+
|
||||
+ # Within last 2
|
||||
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta3', expect_violation=True)
|
||||
+
|
||||
+ # New password allowed
|
||||
+ set_user_password(inst, user, 'Delta5', bind_as_user_password='Delta3', expect_violation=False)
|
||||
+
|
||||
+ # Now Welcome1 is older than last 2 after one more change
|
||||
+ set_user_password(inst, user, 'Delta6', bind_as_user_password='Delta5', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta6', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_user_policy_detection_and_enforcement(topology_st, setup_entries):
|
||||
+ """User local policy is detected and enforced; removal falls back to global policy
|
||||
+
|
||||
+ :id: 2213126a-1f47-468c-8337-0d2ee5d2d585
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Set global policy passwordInHistory=1
|
||||
+ 2. Create a user local password policy on the user with passwordInHistory=3
|
||||
+ 3. Verify is_user_policy(USER_DN) is True
|
||||
+ 4. Set password to Echo1, then change to Echo2 and Echo3 as the user
|
||||
+ 5. Attempt to change to Echo1 (within last 3)
|
||||
+ 6. Delete the user local policy
|
||||
+ 7. Verify is_user_policy(USER_DN) is False
|
||||
+ 8. Attempt to change to Echo1 again (now only last 1 disallowed by global)
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. is_user_policy returns True
|
||||
+ 4. Success
|
||||
+ 5. Change to Welcome1 is rejected
|
||||
+ 6. Success
|
||||
+ 7. is_user_policy returns False
|
||||
+ 8. Change to Welcome1 succeeds (two back is allowed by global=1)
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
|
||||
+
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ user = setup_entries
|
||||
+ pwp.create_user_policy(user.dn, {
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordHistory': 'on',
|
||||
+ 'passwordInHistory': '3',
|
||||
+ })
|
||||
+
|
||||
+ assert pwp.is_user_policy(user.dn) is True
|
||||
+
|
||||
+ user.reset_password('Echo1')
|
||||
+ set_user_password(inst, user, 'Echo2', bind_as_user_password='Echo1', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Echo3', bind_as_user_password='Echo2', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=True)
|
||||
+
|
||||
+ pwp.delete_local_policy(user.dn)
|
||||
+ assert pwp.is_user_policy(user.dn) is False
|
||||
+
|
||||
+ # With only global=1, Echo1 (two back) is allowed
|
||||
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
index 2d4ba9b21..a3e59a90c 100644
|
||||
--- a/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -43,7 +43,7 @@ def _get_pw_policy(inst, targetdn, log, use_json=None):
|
||||
targetdn = 'cn=config'
|
||||
policydn = targetdn
|
||||
basedn = targetdn
|
||||
- attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy_local'])
|
||||
+ attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy-local'])
|
||||
all_attrs = inst.config.get_attrs_vals_utf8(attr_list)
|
||||
attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
|
||||
else:
|
||||
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
|
||||
index 6a47a44fe..539c230a9 100644
|
||||
--- a/src/lib389/lib389/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/pwpolicy.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2018 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -7,6 +7,7 @@
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
import ldap
|
||||
+from ldap import filter as ldap_filter
|
||||
from lib389._mapped_object import DSLdapObject, DSLdapObjects
|
||||
from lib389.backend import Backends
|
||||
from lib389.config import Config
|
||||
@@ -74,19 +75,56 @@ class PwPolicyManager(object):
|
||||
}
|
||||
|
||||
def is_subtree_policy(self, dn):
|
||||
- """Check if the entry has a subtree password policy. If we can find a
|
||||
- template entry it is subtree policy
|
||||
+ """Check if a subtree password policy exists for a given entry DN.
|
||||
|
||||
- :param dn: Entry DN with PwPolicy set up
|
||||
+ A subtree policy is indicated by the presence of any CoS template
|
||||
+ (under `cn=nsPwPolicyContainer,<dn>`) that has a `pwdpolicysubentry`
|
||||
+ attribute pointing to an existing entry with objectClass `passwordpolicy`.
|
||||
+
|
||||
+ :param dn: Entry DN to check for subtree policy
|
||||
:type dn: str
|
||||
|
||||
- :returns: True if the entry has a subtree policy, False otherwise
|
||||
+ :returns: True if a subtree policy exists, False otherwise
|
||||
+ :rtype: bool
|
||||
"""
|
||||
- cos_templates = CosTemplates(self._instance, 'cn=nsPwPolicyContainer,{}'.format(dn))
|
||||
try:
|
||||
- cos_templates.get('cn=nsPwTemplateEntry,%s' % dn)
|
||||
- return True
|
||||
- except:
|
||||
+ container_basedn = 'cn=nsPwPolicyContainer,{}'.format(dn)
|
||||
+ templates = CosTemplates(self._instance, container_basedn).list()
|
||||
+ for tmpl in templates:
|
||||
+ pwp_dn = tmpl.get_attr_val_utf8('pwdpolicysubentry')
|
||||
+ if not pwp_dn:
|
||||
+ continue
|
||||
+ # Validate that the referenced entry exists and is a passwordpolicy
|
||||
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
|
||||
+ if pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy'):
|
||||
+ return True
|
||||
+ except ldap.LDAPError:
|
||||
+ pass
|
||||
+ return False
|
||||
+
|
||||
+ def is_user_policy(self, dn):
|
||||
+ """Check if the entry has a user password policy.
|
||||
+
|
||||
+ A user policy is indicated by the target entry having a
|
||||
+ `pwdpolicysubentry` attribute that points to an existing
|
||||
+ entry with objectClass `passwordpolicy`.
|
||||
+
|
||||
+ :param dn: Entry DN to check
|
||||
+ :type dn: str
|
||||
+
|
||||
+ :returns: True if the entry has a user policy, False otherwise
|
||||
+ :rtype: bool
|
||||
+ """
|
||||
+ try:
|
||||
+ entry = Account(self._instance, dn)
|
||||
+ if not entry.exists():
|
||||
+ return False
|
||||
+ pwp_dn = entry.get_attr_val_utf8('pwdpolicysubentry')
|
||||
+ if not pwp_dn:
|
||||
+ return False
|
||||
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
|
||||
+ return pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy')
|
||||
+ except ldap.LDAPError:
|
||||
return False
|
||||
|
||||
def create_user_policy(self, dn, properties):
|
||||
@@ -114,10 +152,10 @@ class PwPolicyManager(object):
|
||||
pwp_containers = nsContainers(self._instance, basedn=parentdn)
|
||||
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
|
||||
|
||||
- # Create policy entry
|
||||
+ # Create or update the policy entry
|
||||
properties['cn'] = 'cn=nsPwPolicyEntry_user,%s' % dn
|
||||
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
|
||||
- pwp_entry = pwp_entries.create(properties=properties)
|
||||
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
|
||||
try:
|
||||
# Add policy to the entry
|
||||
user_entry.replace('pwdpolicysubentry', pwp_entry.dn)
|
||||
@@ -152,32 +190,27 @@ class PwPolicyManager(object):
|
||||
pwp_containers = nsContainers(self._instance, basedn=dn)
|
||||
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
|
||||
|
||||
- # Create policy entry
|
||||
- pwp_entry = None
|
||||
+ # Create or update the policy entry
|
||||
properties['cn'] = 'cn=nsPwPolicyEntry_subtree,%s' % dn
|
||||
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
|
||||
- pwp_entry = pwp_entries.create(properties=properties)
|
||||
- try:
|
||||
- # The CoS template entry (nsPwTemplateEntry) that has the pwdpolicysubentry
|
||||
- # value pointing to the above (nsPwPolicyEntry) entry
|
||||
- cos_template = None
|
||||
- cos_templates = CosTemplates(self._instance, pwp_container.dn)
|
||||
- cos_template = cos_templates.create(properties={'cosPriority': '1',
|
||||
- 'pwdpolicysubentry': pwp_entry.dn,
|
||||
- 'cn': 'cn=nsPwTemplateEntry,%s' % dn})
|
||||
-
|
||||
- # The CoS specification entry at the subtree level
|
||||
- cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
- 'cosTemplateDn': cos_template.dn,
|
||||
- 'cn': 'nsPwPolicy_CoS'})
|
||||
- except ldap.LDAPError as e:
|
||||
- # Something went wrong, remove what we have done
|
||||
- if pwp_entry is not None:
|
||||
- pwp_entry.delete()
|
||||
- if cos_template is not None:
|
||||
- cos_template.delete()
|
||||
- raise e
|
||||
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
|
||||
+
|
||||
+ # Ensure the CoS template entry (nsPwTemplateEntry) that points to the
|
||||
+ # password policy entry
|
||||
+ cos_templates = CosTemplates(self._instance, pwp_container.dn)
|
||||
+ cos_template = cos_templates.ensure_state(properties={
|
||||
+ 'cosPriority': '1',
|
||||
+ 'pwdpolicysubentry': pwp_entry.dn,
|
||||
+ 'cn': 'cn=nsPwTemplateEntry,%s' % dn
|
||||
+ })
|
||||
+
|
||||
+ # Ensure the CoS specification entry at the subtree level
|
||||
+ cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
+ cos_pointer_defs.ensure_state(properties={
|
||||
+ 'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
+ 'cosTemplateDn': cos_template.dn,
|
||||
+ 'cn': 'nsPwPolicy_CoS'
|
||||
+ })
|
||||
|
||||
# make sure that local policies are enabled
|
||||
self.set_global_policy({'nsslapd-pwpolicy-local': 'on'})
|
||||
@@ -244,10 +277,12 @@ class PwPolicyManager(object):
|
||||
if self.is_subtree_policy(entry.dn):
|
||||
parentdn = dn
|
||||
subtree = True
|
||||
- else:
|
||||
+ elif self.is_user_policy(entry.dn):
|
||||
dn_comps = ldap.dn.explode_dn(dn)
|
||||
dn_comps.pop(0)
|
||||
parentdn = ",".join(dn_comps)
|
||||
+ else:
|
||||
+ raise ValueError('The target entry dn does not have a password policy')
|
||||
|
||||
# Starting deleting the policy, ignore the parts that might already have been removed
|
||||
pwp_container = nsContainer(self._instance, 'cn=nsPwPolicyContainer,%s' % parentdn)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
From 4a17dc8ef8f226b9d733f3f8fc72bce5e506eb40 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 10 Sep 2025 13:16:26 +0200
|
||||
Subject: [PATCH] Issue 6641 - Fix memory leaks
|
||||
|
||||
Description:
|
||||
Partial backport from 9cede9cdcbfb10e864ba0d91053efdabbe937eca
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6910
|
||||
(cherry picked from commit cec5596acb0fb82ca34ee98b7881312dd7ba602c)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/automember/automember.c | 7 ++++---
|
||||
ldap/servers/plugins/memberof/memberof.c | 13 ++++++++++---
|
||||
2 files changed, 14 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
|
||||
index fde92ee12..1b1da39b3 100644
|
||||
--- a/ldap/servers/plugins/automember/automember.c
|
||||
+++ b/ldap/servers/plugins/automember/automember.c
|
||||
@@ -1755,9 +1755,10 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
|
||||
mod_pb = slapi_pblock_new();
|
||||
/* Do a single mod with error overrides for DEL/ADD */
|
||||
- result = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_dn_byval(group_dn), mods,
|
||||
- automember_get_plugin_id(), 0);
|
||||
-
|
||||
+ Slapi_DN *sdn = slapi_sdn_new_normdn_byref(group_dn);
|
||||
+ result = slapi_single_modify_internal_override(mod_pb, sdn, mods,
|
||||
+ automember_get_plugin_id(), 0);
|
||||
+ slapi_sdn_free(&sdn);
|
||||
if(add){
|
||||
if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index f3dc7cf00..ce1788e35 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1647,6 +1647,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
/* We already did the search for this backend, don't
|
||||
* do it again when we fall through */
|
||||
do_suffix_search = PR_FALSE;
|
||||
+ slapi_pblock_init(search_pb);
|
||||
}
|
||||
}
|
||||
} else if (!all_backends) {
|
||||
@@ -3745,6 +3746,10 @@ memberof_replace_list(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_DN *group_
|
||||
|
||||
pre_index++;
|
||||
} else {
|
||||
+ if (pre_index >= pre_total || post_index >= post_total) {
|
||||
+ /* Don't overrun pre_array/post_array */
|
||||
+ break;
|
||||
+ }
|
||||
/* decide what to do */
|
||||
int cmp = memberof_compare(
|
||||
config,
|
||||
@@ -4438,10 +4443,12 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
|
||||
|
||||
while (1) {
|
||||
slapi_pblock_init(mod_pb);
|
||||
-
|
||||
+ Slapi_DN *sdn = slapi_sdn_new_normdn_byref(dn);
|
||||
/* Internal mod with error overrides for DEL/ADD */
|
||||
- rc = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_normdn_byref(dn), single_mod,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, sdn, single_mod,
|
||||
+ memberof_get_plugin_id(),
|
||||
+ SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
+ slapi_sdn_free(&sdn);
|
||||
if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
if (!add_oc || added_oc) {
|
||||
/*
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,363 +0,0 @@
|
||||
From 16cde9b2e584a75f987c1e5f1151d8703f23263e Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 1 Sep 2025 18:23:33 +0200
|
||||
Subject: [PATCH] Issue 6933 - When deferred memberof update is enabled after
|
||||
the server crashed it should not launch memberof fixup task by default
|
||||
(#6935)
|
||||
|
||||
Bug description:
|
||||
When deferred memberof update is enabled, the updates of the
|
||||
group and the members is done with different TXN.
|
||||
So there is a risk that at the time of a crash the membership
|
||||
('memberof') are invalid.
|
||||
To repair this we should run a memberof fixup task.
|
||||
The problem is that this task is resource intensive and
|
||||
should be, by default, scheduled by the administrator.
|
||||
|
||||
Fix description:
|
||||
The fix introduces a new memberof config parameter 'launchFixup'
|
||||
that is 'off' by default.
|
||||
After a crash, when it is 'on' the server launch the fixup
|
||||
task. If it is 'off' it logs a warning.
|
||||
|
||||
fixes: #6933
|
||||
|
||||
Reviewed by: Simon Pichugin (Thanks !)
|
||||
|
||||
(cherry picked from commit 72f621c56114e1fd3ba3f6c25c731496b881075a)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 109 ++++++++++++------
|
||||
ldap/servers/plugins/memberof/memberof.c | 13 ++-
|
||||
ldap/servers/plugins/memberof/memberof.h | 2 +
|
||||
.../plugins/memberof/memberof_config.c | 11 ++
|
||||
.../lib389/cli_conf/plugins/memberof.py | 9 ++
|
||||
src/lib389/lib389/plugins.py | 30 +++++
|
||||
6 files changed, 136 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 9ba40a0c3..976729c2f 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -1289,15 +1289,19 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
:setup: Standalone Instance
|
||||
:steps:
|
||||
1. Enable memberof plugin to scope SUFFIX
|
||||
- 2. create 1000 users
|
||||
- 3. Create a large groups with 500 members
|
||||
+ 2. create 500 users
|
||||
+ 3. Create a large groups with 250 members
|
||||
4. Restart the instance (using the default 2 minutes timeout)
|
||||
5. Check that users memberof and group members are in sync.
|
||||
- 6. Modify the group to have 10 members.
|
||||
+ 6. Modify the group to have 250 others members.
|
||||
7. Restart the instance with short timeout
|
||||
- 8. Check that fixup task is in progress
|
||||
- 9. Wait until fixup task is completed
|
||||
- 10. Check that users memberof and group members are in sync.
|
||||
+ 8. Check that the instance needs fixup
|
||||
+ 9. Check that deferred thread did not run fixup
|
||||
+ 10. Allow deferred thread to run fixup
|
||||
+ 11. Modify the group to have 250 others members.
|
||||
+ 12. Restart the instance with short timeout
|
||||
+ 13. Check that the instance needs fixup
|
||||
+ 14. Check that deferred thread did run fixup
|
||||
:expectedresults:
|
||||
1. should succeed
|
||||
2. should succeed
|
||||
@@ -1308,14 +1312,18 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
7. should succeed
|
||||
8. should succeed
|
||||
9. should succeed
|
||||
- 10. should succeed
|
||||
"""
|
||||
|
||||
inst = topology_st.standalone
|
||||
+ inst.stop()
|
||||
+ lpath = inst.ds_error_log._get_log_path()
|
||||
+ os.unlink(lpath)
|
||||
+ inst.start()
|
||||
inst.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
||||
errlog = DirsrvErrorLog(inst)
|
||||
test_timeout = 900
|
||||
|
||||
+
|
||||
# Step 1. Enable memberof plugin to scope SUFFIX
|
||||
memberof = MemberOfPlugin(inst)
|
||||
delay=0
|
||||
@@ -1336,8 +1344,8 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
#Creates users and groups
|
||||
users_dn = []
|
||||
|
||||
- # Step 2. create 1000 users
|
||||
- for i in range(1000):
|
||||
+ # Step 2. create 500 users
|
||||
+ for i in range(500):
|
||||
CN = '%s%d' % (USER_CN, i)
|
||||
users = UserAccounts(inst, SUFFIX)
|
||||
user_props = TEST_USER_PROPERTIES.copy()
|
||||
@@ -1347,7 +1355,7 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
|
||||
# Step 3. Create a large groups with 250 members
|
||||
groups = Groups(inst, SUFFIX)
|
||||
- testgroup = groups.create(properties={'cn': 'group500', 'member': users_dn[0:249]})
|
||||
+ testgroup = groups.create(properties={'cn': 'group50', 'member': users_dn[0:249]})
|
||||
|
||||
# Step 4. Restart the instance (using the default 2 minutes timeout)
|
||||
time.sleep(10)
|
||||
@@ -1361,7 +1369,7 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
check_memberof_consistency(inst, testgroup)
|
||||
|
||||
# Step 6. Modify the group to get another big group.
|
||||
- testgroup.replace('member', users_dn[500:999])
|
||||
+ testgroup.replace('member', users_dn[250:499])
|
||||
|
||||
# Step 7. Restart the instance with short timeout
|
||||
pattern = 'deferred_thread_func - thread has stopped'
|
||||
@@ -1374,40 +1382,71 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
nbcleanstop = len(errlog.match(pattern))
|
||||
assert nbcleanstop == original_nbcleanstop
|
||||
|
||||
- original_nbfixupmsg = count_global_fixup_message(errlog)
|
||||
log.info(f'Instance restarted after timeout at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
inst.restart()
|
||||
assert inst.status()
|
||||
log.info(f'Restart completed at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
|
||||
+ # Step 9.
|
||||
# Check that memberofneedfixup is present
|
||||
- dse = DSEldif(inst)
|
||||
- assert dse.get(memberof.dn, 'memberofneedfixup', single=True)
|
||||
-
|
||||
- # Step 8. Check that fixup task is in progress
|
||||
- # Note we have to wait as there may be some delay
|
||||
- elapsed_time = 0
|
||||
- nbfixupmsg = count_global_fixup_message(errlog)
|
||||
- while nbfixupmsg[0] == original_nbfixupmsg[0]:
|
||||
- assert elapsed_time <= test_timeout
|
||||
- assert inst.status()
|
||||
- time.sleep(5)
|
||||
- elapsed_time += 5
|
||||
- nbfixupmsg = count_global_fixup_message(errlog)
|
||||
-
|
||||
- # Step 9. Wait until fixup task is completed
|
||||
- while nbfixupmsg[1] == original_nbfixupmsg[1]:
|
||||
- assert elapsed_time <= test_timeout
|
||||
- assert inst.status()
|
||||
- time.sleep(10)
|
||||
- elapsed_time += 10
|
||||
- nbfixupmsg = count_global_fixup_message(errlog)
|
||||
-
|
||||
- # Step 10. Check that users memberof and group members are in sync.
|
||||
+ # and fixup task was not launched because by default launch_fixup is no
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.set_memberofdeferredupdate("on")
|
||||
+ if (memberof.get_memberofdeferredupdate() and memberof.get_memberofdeferredupdate().lower() != "on"):
|
||||
+ pytest.skip("Memberof deferred update not enabled or not supported.");
|
||||
+ else:
|
||||
+ delay=10
|
||||
+ value = memberof.get_memberofneedfixup()
|
||||
+ assert ((str(value).lower() == "yes") or (str(value).lower() == "true"))
|
||||
+ assert len(errlog.match('.*It is recommended to launch memberof fixup task.*')) == 1
|
||||
+
|
||||
+ # Step 10. allow the server to launch the fixup task
|
||||
+ inst.stop()
|
||||
+ inst.deleteErrorLogs()
|
||||
+ inst.start()
|
||||
+ log.info(f'set memberoflaunchfixup=ON')
|
||||
+ memberof.set_memberoflaunchfixup('on')
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Step 11. Modify the group to get another big group.
|
||||
+ testgroup.replace('member', users_dn[250:499])
|
||||
+
|
||||
+ # Step 12. then kill/reset errorlog/restart
|
||||
+ _kill_instance(inst, sig=signal.SIGKILL, delay=5)
|
||||
+ log.info(f'Instance restarted after timeout at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
+ inst.restart()
|
||||
+ assert inst.status()
|
||||
+ log.info(f'Restart completed at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
+
|
||||
+ # step 13. Check that memberofneedfixup is present
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ value = memberof.get_memberofneedfixup()
|
||||
+ assert ((str(value).lower() == "yes") or (str(value).lower() == "true"))
|
||||
+
|
||||
+ # step 14. fixup task was not launched because by default launch_fixup is no
|
||||
+ assert len(errlog.match('.*It is recommended to launch memberof fixup task.*')) == 0
|
||||
+
|
||||
+ # Check that users memberof and group members are in sync.
|
||||
time.sleep(delay)
|
||||
check_memberof_consistency(inst, testgroup)
|
||||
|
||||
|
||||
+ def fin():
|
||||
+
|
||||
+ for dn in users_dn:
|
||||
+ try:
|
||||
+ inst.delete_s(dn)
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+
|
||||
+ try:
|
||||
+ inst.delete_s(testgroup.dn)
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index ce1788e35..2ee7ee319 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1012,9 +1012,16 @@ deferred_thread_func(void *arg)
|
||||
* keep running this thread until plugin is signaled to close
|
||||
*/
|
||||
g_incr_active_threadcnt();
|
||||
- if (memberof_get_config()->need_fixup && perform_needed_fixup()) {
|
||||
- slapi_log_err(SLAPI_LOG_ALERT, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "Failure occured during global fixup task: memberof values are invalid\n");
|
||||
+ if (memberof_get_config()->need_fixup) {
|
||||
+ if (memberof_get_config()->launch_fixup) {
|
||||
+ if (perform_needed_fixup()) {
|
||||
+ slapi_log_err(SLAPI_LOG_ALERT, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Failure occurred during global fixup task: memberof values are invalid\n");
|
||||
+ }
|
||||
+ } else {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "It is recommended to launch memberof fixup task\n");
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
"deferred_thread_func - thread is starting "
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.h b/ldap/servers/plugins/memberof/memberof.h
|
||||
index c11d901ab..f2bb1d1cf 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.h
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.h
|
||||
@@ -44,6 +44,7 @@
|
||||
#define MEMBEROF_DEFERRED_UPDATE_ATTR "memberOfDeferredUpdate"
|
||||
#define MEMBEROF_AUTO_ADD_OC "memberOfAutoAddOC"
|
||||
#define MEMBEROF_NEED_FIXUP "memberOfNeedFixup"
|
||||
+#define MEMBEROF_LAUNCH_FIXUP "memberOfLaunchFixup"
|
||||
#define NSMEMBEROF "nsMemberOf"
|
||||
#define MEMBEROF_ENTRY_SCOPE_EXCLUDE_SUBTREE "memberOfEntryScopeExcludeSubtree"
|
||||
#define DN_SYNTAX_OID "1.3.6.1.4.1.1466.115.121.1.12"
|
||||
@@ -138,6 +139,7 @@ typedef struct memberofconfig
|
||||
PLHashTable *fixup_cache;
|
||||
Slapi_Task *task;
|
||||
int need_fixup;
|
||||
+ PRBool launch_fixup;
|
||||
} MemberOfConfig;
|
||||
|
||||
/* The key to access the hash table is the normalized DN
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
index 89c44b014..e17c91fb9 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof_config.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
@@ -472,6 +472,7 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
const char *deferred_update = NULL;
|
||||
char *auto_add_oc = NULL;
|
||||
const char *needfixup = NULL;
|
||||
+ const char *launchfixup = NULL;
|
||||
int num_vals = 0;
|
||||
|
||||
*returncode = LDAP_SUCCESS;
|
||||
@@ -508,6 +509,7 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
deferred_update = slapi_entry_attr_get_ref(e, MEMBEROF_DEFERRED_UPDATE_ATTR);
|
||||
auto_add_oc = slapi_entry_attr_get_charptr(e, MEMBEROF_AUTO_ADD_OC);
|
||||
needfixup = slapi_entry_attr_get_ref(e, MEMBEROF_NEED_FIXUP);
|
||||
+ launchfixup = slapi_entry_attr_get_ref(e, MEMBEROF_LAUNCH_FIXUP);
|
||||
|
||||
if (auto_add_oc == NULL) {
|
||||
auto_add_oc = slapi_ch_strdup(NSMEMBEROF);
|
||||
@@ -628,6 +630,15 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
theConfig.deferred_update = PR_FALSE;
|
||||
}
|
||||
}
|
||||
+ theConfig.launch_fixup = PR_FALSE;
|
||||
+ if (theConfig.deferred_update) {
|
||||
+ /* The automatic fixup task is only triggered when
|
||||
+ * deferred update is on
|
||||
+ */
|
||||
+ if (launchfixup && (strcasecmp(launchfixup, "on") == 0)) {
|
||||
+ theConfig.launch_fixup = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
if (allBackends) {
|
||||
if (strcasecmp(allBackends, "on") == 0) {
|
||||
diff --git a/src/lib389/lib389/cli_conf/plugins/memberof.py b/src/lib389/lib389/cli_conf/plugins/memberof.py
|
||||
index 90c1af2c3..598fe0bbc 100644
|
||||
--- a/src/lib389/lib389/cli_conf/plugins/memberof.py
|
||||
+++ b/src/lib389/lib389/cli_conf/plugins/memberof.py
|
||||
@@ -23,6 +23,8 @@ arg_to_attr = {
|
||||
'scope': 'memberOfEntryScope',
|
||||
'exclude': 'memberOfEntryScopeExcludeSubtree',
|
||||
'autoaddoc': 'memberOfAutoAddOC',
|
||||
+ 'deferredupdate': 'memberOfDeferredUpdate',
|
||||
+ 'launchfixup': 'memberOfLaunchFixup',
|
||||
'config_entry': 'nsslapd-pluginConfigArea'
|
||||
}
|
||||
|
||||
@@ -119,6 +121,13 @@ def _add_parser_args(parser):
|
||||
help='If an entry does not have an object class that allows the memberOf attribute '
|
||||
'then the memberOf plugin will automatically add the object class listed '
|
||||
'in the memberOfAutoAddOC parameter')
|
||||
+ parser.add_argument('--deferredupdate', choices=['on', 'off'], type=str.lower,
|
||||
+ help='Specifies that the updates of the members are done after the completion '
|
||||
+ 'of the update of the target group. In addition each update (group/members) '
|
||||
+ 'uses its own transaction')
|
||||
+ parser.add_argument('--launchfixup', choices=['on', 'off'], type=str.lower,
|
||||
+ help='Specify that if the server disorderly shutdown (crash, kill,..) then '
|
||||
+ 'at restart the memberof fixup task is launched automatically')
|
||||
|
||||
|
||||
def create_parser(subparsers):
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 25b49dae4..4f177adef 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -962,6 +962,36 @@ class MemberOfPlugin(Plugin):
|
||||
|
||||
self.remove_all('memberofdeferredupdate')
|
||||
|
||||
+ def get_memberofneedfixup(self):
|
||||
+ """Get memberofneedfixup attribute"""
|
||||
+
|
||||
+ return self.get_attr_val_utf8_l('memberofneedfixup')
|
||||
+
|
||||
+ def get_memberofneedfixup_formatted(self):
|
||||
+ """Display memberofneedfixup attribute"""
|
||||
+
|
||||
+ return self.display_attr('memberofneedfixup')
|
||||
+
|
||||
+ def get_memberoflaunchfixup(self):
|
||||
+ """Get memberoflaunchfixup attribute"""
|
||||
+
|
||||
+ return self.get_attr_val_utf8_l('memberoflaunchfixup')
|
||||
+
|
||||
+ def get_memberoflaunchfixup_formatted(self):
|
||||
+ """Display memberoflaunchfixup attribute"""
|
||||
+
|
||||
+ return self.display_attr('memberoflaunchfixup')
|
||||
+
|
||||
+ def set_memberoflaunchfixup(self, value):
|
||||
+ """Set memberoflaunchfixup attribute"""
|
||||
+
|
||||
+ self.set('memberoflaunchfixup', value)
|
||||
+
|
||||
+ def remove_memberoflaunchfixup(self):
|
||||
+ """Remove all memberoflaunchfixup attributes"""
|
||||
+
|
||||
+ self.remove_all('memberoflaunchfixup')
|
||||
+
|
||||
def get_autoaddoc(self):
|
||||
"""Get memberofautoaddoc attribute"""
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,831 +0,0 @@
|
||||
From 4667e657fe4d3eab1e900cc1f278bc9a9e2fcf0a Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 18 Aug 2025 09:13:12 +0200
|
||||
Subject: [PATCH] Issue 6928 - The parentId attribute is indexed with improper
|
||||
matching rule
|
||||
|
||||
Bug Description:
|
||||
`parentId` attribute contains integer values and needs to be indexed with
|
||||
`integerOrderingMatch` matching rule. This attribute is a system attribute
|
||||
and the configuration entry for this attribute is created when a backend
|
||||
is created. The bug is that the per backend configuration entry does not
|
||||
contain `nsMatchingRule: integerOrderingMatch`.
|
||||
|
||||
Fix Description:
|
||||
* Update `ldbm_instance_create_default_indexes` to support matching rules
|
||||
and update default system index configuration for `parentId` to include
|
||||
`integerOrderingMatch` matching rule.
|
||||
* Add healthcheck linter for default system indexes and indexes created
|
||||
by RetroCL and USN plugins.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6928
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6915
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit fd45579f8111c371852686dafe761fe535a5bef3)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
dirsrvtests/tests/suites/basic/basic_test.py | 2 +-
|
||||
.../healthcheck/health_system_indexes_test.py | 456 ++++++++++++++++++
|
||||
ldap/ldif/template-dse.ldif.in | 8 +
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 32 +-
|
||||
src/lib389/lib389/backend.py | 133 ++++-
|
||||
src/lib389/lib389/lint.py | 29 ++
|
||||
6 files changed, 645 insertions(+), 15 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
index 8bf89cb33..4a45f9dbe 100644
|
||||
--- a/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
@@ -461,7 +461,7 @@ def test_basic_db2index(topology_st):
|
||||
topology_st.standalone.db2index(bename=DEFAULT_BENAME, attrs=indexes)
|
||||
log.info('Checking the server logs for %d backend indexes INFO' % numIndexes)
|
||||
for indexNum, index in enumerate(indexes):
|
||||
- if index in "entryrdn":
|
||||
+ if index in ["entryrdn", "ancestorid"]:
|
||||
assert topology_st.standalone.searchErrorsLog(
|
||||
'INFO - bdb_db2index - ' + DEFAULT_BENAME + ':' + ' Indexing ' + index)
|
||||
else:
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
new file mode 100644
|
||||
index 000000000..61972d60c
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -0,0 +1,456 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+
|
||||
+import pytest
|
||||
+import os
|
||||
+
|
||||
+from lib389.backend import Backends
|
||||
+from lib389.index import Index
|
||||
+from lib389.plugins import (
|
||||
+ USNPlugin,
|
||||
+ RetroChangelogPlugin,
|
||||
+)
|
||||
+from lib389.utils import logging, ds_is_newer
|
||||
+from lib389.cli_base import FakeArgs
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.cli_ctl.health import health_check_run
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+CMD_OUTPUT = "No issues found."
|
||||
+JSON_OUTPUT = "[]"
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def usn_plugin_enabled(topology_st, request):
|
||||
+ """Fixture to enable USN plugin and ensure cleanup after test"""
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Enable USN plugin")
|
||||
+ usn_plugin = USNPlugin(standalone)
|
||||
+ usn_plugin.enable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Disable USN plugin")
|
||||
+ usn_plugin.disable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+ return usn_plugin
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def retrocl_plugin_enabled(topology_st, request):
|
||||
+ """Fixture to enable RetroCL plugin and ensure cleanup after test"""
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Enable RetroCL plugin")
|
||||
+ retrocl_plugin = RetroChangelogPlugin(standalone)
|
||||
+ retrocl_plugin.enable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Disable RetroCL plugin")
|
||||
+ retrocl_plugin.disable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+ return retrocl_plugin
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def log_buffering_enabled(topology_st, request):
|
||||
+ """Fixture to enable log buffering and restore original setting after test"""
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ original_value = standalone.config.get_attr_val_utf8("nsslapd-accesslog-logbuffering")
|
||||
+
|
||||
+ log.info("Enable log buffering")
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Restore original log buffering setting")
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", original_value)
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+ return standalone
|
||||
+
|
||||
+
|
||||
+def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searched_code2=None):
|
||||
+ args = FakeArgs()
|
||||
+ args.instance = instance.serverid
|
||||
+ args.verbose = instance.verbose
|
||||
+ args.list_errors = False
|
||||
+ args.list_checks = False
|
||||
+ args.check = [
|
||||
+ "config",
|
||||
+ "refint",
|
||||
+ "backends",
|
||||
+ "monitor-disk-space",
|
||||
+ "logs",
|
||||
+ "memberof",
|
||||
+ ]
|
||||
+ args.dry_run = False
|
||||
+
|
||||
+ # If we are using BDB as a backend, we will get error DSBLE0006 on new versions
|
||||
+ if (
|
||||
+ ds_is_newer("3.0.0")
|
||||
+ and instance.get_db_lib() == "bdb"
|
||||
+ and (searched_code is CMD_OUTPUT or searched_code is JSON_OUTPUT)
|
||||
+ ):
|
||||
+ searched_code = "DSBLE0006"
|
||||
+
|
||||
+ if json:
|
||||
+ log.info("Use healthcheck with --json option")
|
||||
+ args.json = json
|
||||
+ health_check_run(instance, topology.logcap.log, args)
|
||||
+ assert topology.logcap.contains(searched_code)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code)
|
||||
+
|
||||
+ if searched_code2 is not None:
|
||||
+ assert topology.logcap.contains(searched_code2)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code2)
|
||||
+ else:
|
||||
+ log.info("Use healthcheck without --json option")
|
||||
+ args.json = json
|
||||
+ health_check_run(instance, topology.logcap.log, args)
|
||||
+
|
||||
+ assert topology.logcap.contains(searched_code)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code)
|
||||
+
|
||||
+ if searched_code2 is not None:
|
||||
+ assert topology.logcap.contains(searched_code2)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code2)
|
||||
+
|
||||
+ log.info("Clear the log")
|
||||
+ topology.logcap.flush()
|
||||
+
|
||||
+
|
||||
+def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when parentId system index is missing
|
||||
+
|
||||
+ :id: 2653f16f-cc9c-4fad-9d8c-86a3457c6d0d
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove parentId index
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Re-add the parentId index
|
||||
+ 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
|
||||
+ 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
|
||||
+
|
||||
+ log.info("Remove parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.delete()
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ log.info("Re-add the parentId index")
|
||||
+ backend = Backends(standalone).get("userRoot")
|
||||
+ 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)
|
||||
+
|
||||
+
|
||||
+def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
|
||||
+
|
||||
+ :id: 7ffa71db-8995-430a-bed8-59bce944221c
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove integerOrderingMatch matching rule from parentId index
|
||||
+ 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
|
||||
+ 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
|
||||
+
|
||||
+ log.info("Remove integerOrderingMatch matching rule from parentId index")
|
||||
+ 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)
|
||||
+
|
||||
+ 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
|
||||
+
|
||||
+ :id: 4879dfc8-cd96-43e6-9ebc-053fc8e64ad0
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable USN plugin
|
||||
+ 3. Remove entryusn index
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the entryusn index
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ ENTRYUSN_DN = "cn=entryusn,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove entryusn index")
|
||||
+ entryusn_index = Index(standalone, ENTRYUSN_DN)
|
||||
+ entryusn_index.delete()
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ log.info("Re-add the entryusn index")
|
||||
+ backend = Backends(standalone).get("userRoot")
|
||||
+ 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)
|
||||
+
|
||||
+
|
||||
+def test_usn_plugin_missing_matching_rule(topology_st, usn_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing integerOrderingMatch
|
||||
+
|
||||
+ :id: b00b419f-2ca6-451f-a9b2-f22ad6b10718
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable USN plugin
|
||||
+ 3. Remove integerOrderingMatch matching rule from entryusn index
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the matching rule
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ ENTRYUSN_DN = "cn=entryusn,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Create or modify entryusn index without integerOrderingMatch")
|
||||
+ entryusn_index = Index(standalone, ENTRYUSN_DN)
|
||||
+ entryusn_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)
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule")
|
||||
+ entryusn_index = Index(standalone, ENTRYUSN_DN)
|
||||
+ entryusn_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_retrocl_plugin_missing_changenumber(topology_st, retrocl_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when RetroCL plugin is enabled but changeNumber index is missing from changelog backend
|
||||
+
|
||||
+ :id: 3e1a3625-4e6f-4e23-868d-6f32e018ad7e
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable RetroCL plugin
|
||||
+ 3. Remove changeNumber index from changelog backend
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the changeNumber index
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove changeNumber index from changelog backend")
|
||||
+ changenumber_dn = "cn=changenumber,cn=index,cn=changelog,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ changenumber_index = Index(standalone, changenumber_dn)
|
||||
+ changenumber_index.delete()
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ log.info("Re-add the changeNumber index")
|
||||
+ backends = Backends(standalone)
|
||||
+ changelog_backend = backends.get("changelog")
|
||||
+ changelog_backend.add_index("changenumber", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
+ log.info("Successfully re-added changeNumber index")
|
||||
+
|
||||
+ 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_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when RetroCL plugin is enabled but changeNumber index is missing integerOrderingMatch
|
||||
+
|
||||
+ :id: 1c68b1b2-90a9-4ec0-815a-a626b20744fe
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable RetroCL plugin
|
||||
+ 3. Remove integerOrderingMatch matching rule from changeNumber index
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the matching rule
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove integerOrderingMatch matching rule from changeNumber index")
|
||||
+ changenumber_dn = "cn=changenumber,cn=index,cn=changelog,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ changenumber_index = Index(standalone, changenumber_dn)
|
||||
+ changenumber_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)
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule")
|
||||
+ changenumber_index = Index(standalone, changenumber_dn)
|
||||
+ changenumber_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+ log.info("Successfully re-added integerOrderingMatch to changeNumber index")
|
||||
+
|
||||
+ 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
|
||||
+
|
||||
+ :id: f7cfcd6e-3c47-4ba5-bb2b-1f8e7a29c899
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove multiple system indexes (parentId, nsUniqueId)
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Re-add the missing indexes
|
||||
+ 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
|
||||
+ 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"
|
||||
+ NSUNIQUEID_DN = "cn=nsuniqueid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove multiple system indexes")
|
||||
+ for index_dn in [PARENTID_DN, NSUNIQUEID_DN]:
|
||||
+ index = Index(standalone, index_dn)
|
||||
+ index.delete()
|
||||
+ log.info(f"Successfully removed index: {index_dn}")
|
||||
+
|
||||
+ 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)
|
||||
+
|
||||
+ log.info("Re-add the missing system indexes")
|
||||
+ backend = Backends(standalone).get("userRoot")
|
||||
+ 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)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
|
||||
index 2ddaf5fb3..c2754adf8 100644
|
||||
--- a/ldap/ldif/template-dse.ldif.in
|
||||
+++ b/ldap/ldif/template-dse.ldif.in
|
||||
@@ -973,6 +973,14 @@ 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/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index e82cd17cc..f6a9817a7 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);
|
||||
+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)
|
||||
+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];
|
||||
@@ -162,6 +162,12 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
|
||||
slapi_entry_add_values(e, "nsIndexType", vals);
|
||||
}
|
||||
|
||||
+ if (mr) {
|
||||
+ val.bv_val = mr;
|
||||
+ val.bv_len = strlen(mr);
|
||||
+ slapi_entry_add_values(e, "nsMatchingRule", vals);
|
||||
+ }
|
||||
+
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -184,24 +190,24 @@ 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);
|
||||
+ 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);
|
||||
+ 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);
|
||||
}
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 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("objectclass", "eq", 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("aci", "pres", 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);
|
||||
|
||||
@@ -211,26 +217,26 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
slapi_entry_free(e);
|
||||
#endif
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 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);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 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);
|
||||
+ 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);
|
||||
+ 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);
|
||||
+ 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);
|
||||
|
||||
@@ -239,7 +245,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);
|
||||
+ 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);
|
||||
}
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index cee073ea7..a97def17e 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -34,7 +34,8 @@ from lib389.encrypted_attributes import EncryptedAttr, EncryptedAttrs
|
||||
# This is for sample entry creation.
|
||||
from lib389.configurations import get_sample_entries
|
||||
|
||||
-from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSVIRTLE0001, DSCLLE0001
|
||||
+from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0007, DSVIRTLE0001, DSCLLE0001
|
||||
+from lib389.plugins import USNPlugin
|
||||
|
||||
|
||||
class BackendLegacy(object):
|
||||
@@ -531,6 +532,136 @@ class Backend(DSLdapObject):
|
||||
self._log.debug(f"_lint_cl_trimming - backend ({suffix}) is not replicated")
|
||||
pass
|
||||
|
||||
+ def _lint_system_indexes(self):
|
||||
+ """Check that system indexes are correctly configured"""
|
||||
+ bename = self.lint_uid()
|
||||
+ suffix = self.get_attr_val_utf8('nsslapd-suffix')
|
||||
+ indexes = self.get_indexes()
|
||||
+
|
||||
+ # Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
+ expected_system_indexes = {
|
||||
+ 'entryrdn': {'types': ['subtree'], 'matching_rule': None},
|
||||
+ 'parentId': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ 'ancestorId': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ 'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
+ 'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'nsUniqueId': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'nsds5ReplConflict': {'types': ['eq', 'pres'], 'matching_rule': None}
|
||||
+ }
|
||||
+
|
||||
+ # Default system indexes taken from ldap/ldif/template-dse.ldif.in
|
||||
+ expected_system_indexes.update({
|
||||
+ 'nsCertSubjectDN': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'numsubordinates': {'types': ['pres'], 'matching_rule': None},
|
||||
+ 'nsTombstoneCSN': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'targetuniqueid': {'types': ['eq'], 'matching_rule': None}
|
||||
+ })
|
||||
+
|
||||
+
|
||||
+ # RetroCL plugin creates its own backend with an additonal index for changeNumber
|
||||
+ # See ldap/servers/plugins/retrocl/retrocl_create.c
|
||||
+ if suffix.lower() == 'cn=changelog':
|
||||
+ expected_system_indexes.update({
|
||||
+ 'changeNumber': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'}
|
||||
+ })
|
||||
+
|
||||
+ # USN plugin requires entryusn attribute indexed for equality with integerOrderingMatch rule
|
||||
+ # See ldap/ldif/template-dse.ldif.in
|
||||
+ try:
|
||||
+ usn_plugin = USNPlugin(self._instance)
|
||||
+ if usn_plugin.status():
|
||||
+ expected_system_indexes.update({
|
||||
+ 'entryusn': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'}
|
||||
+ })
|
||||
+ except Exception as e:
|
||||
+ self._log.debug(f"_lint_system_indexes - Error checking USN plugin: {e}")
|
||||
+
|
||||
+ discrepancies = []
|
||||
+ remediation_commands = []
|
||||
+ reindex_attrs = set()
|
||||
+
|
||||
+ for attr_name, expected_config in expected_system_indexes.items():
|
||||
+ try:
|
||||
+ index = indexes.get(attr_name)
|
||||
+ # Check if index exists
|
||||
+ if index is None:
|
||||
+ discrepancies.append(f"Missing system index: {attr_name}")
|
||||
+ # Generate remediation command
|
||||
+ index_types = ' '.join([f"--add-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['matching_rule']:
|
||||
+ cmd += f" --add-mr {expected_config['matching_rule']}"
|
||||
+ 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 []
|
||||
+
|
||||
+ # Normalize to lowercase for comparison
|
||||
+ actual_types = [t.lower() for t in actual_types]
|
||||
+ expected_types = [t.lower() for t in expected_config['types']]
|
||||
+
|
||||
+ # Check index types
|
||||
+ missing_types = set(expected_types) - set(actual_types)
|
||||
+ if missing_types:
|
||||
+ discrepancies.append(f"Index {attr_name} missing types: {', '.join(missing_types)}")
|
||||
+ missing_type_args = ' '.join([f"--add-type {t}" for t in missing_types])
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} {missing_type_args}"
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
+
|
||||
+ # Check matching rules
|
||||
+ expected_mr = expected_config['matching_rule']
|
||||
+ 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)
|
||||
+
|
||||
+ except Exception as e:
|
||||
+ self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
+ discrepancies.append(f"Unable to check index {attr_name}: {str(e)}")
|
||||
+
|
||||
+ if discrepancies:
|
||||
+ report = copy.deepcopy(DSBLE0007)
|
||||
+ report['check'] = f'backends:{bename}:system_indexes'
|
||||
+ report['items'] = [suffix]
|
||||
+
|
||||
+ expected_indexes_list = []
|
||||
+ for attr_name, config in expected_system_indexes.items():
|
||||
+ types_str = "', '".join(config['types'])
|
||||
+ index_desc = f"- {attr_name}: index type{'s' if len(config['types']) > 1 else ''} '{types_str}'"
|
||||
+ if config['matching_rule']:
|
||||
+ index_desc += f" with matching rule '{config['matching_rule']}'"
|
||||
+ expected_indexes_list.append(index_desc)
|
||||
+
|
||||
+ formatted_expected_indexes = '\n'.join(expected_indexes_list)
|
||||
+ report['detail'] = report['detail'].replace('EXPECTED_INDEXES', formatted_expected_indexes)
|
||||
+ report['detail'] = report['detail'].replace('DISCREPANCIES', '\n'.join([f"- {d}" for d in discrepancies]))
|
||||
+
|
||||
+ formatted_commands = '\n'.join([f" # {cmd}" for cmd in remediation_commands])
|
||||
+ report['fix'] = report['fix'].replace('REMEDIATION_COMMANDS', formatted_commands)
|
||||
+
|
||||
+ # Generate specific reindex commands for affected attributes
|
||||
+ if reindex_attrs:
|
||||
+ reindex_commands = []
|
||||
+ for attr in sorted(reindex_attrs):
|
||||
+ reindex_cmd = f"dsconf YOUR_INSTANCE backend index reindex {bename} --attr {attr}"
|
||||
+ reindex_commands.append(f" # {reindex_cmd}")
|
||||
+ formatted_reindex_commands = '\n'.join(reindex_commands)
|
||||
+ else:
|
||||
+ formatted_reindex_commands = " # No reindexing needed"
|
||||
+
|
||||
+ report['fix'] = report['fix'].replace('REINDEX_COMMANDS', formatted_reindex_commands)
|
||||
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
||||
+ report['fix'] = report['fix'].replace('BACKEND_NAME', bename)
|
||||
+ yield report
|
||||
+
|
||||
def create_sample_entries(self, version):
|
||||
"""Creates sample entries under nsslapd-suffix value
|
||||
|
||||
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||||
index 3d3c79ea3..1e48c790d 100644
|
||||
--- a/src/lib389/lib389/lint.py
|
||||
+++ b/src/lib389/lib389/lint.py
|
||||
@@ -57,6 +57,35 @@ DSBLE0003 = {
|
||||
'fix': """You need to import an LDIF file, or create the suffix entry, in order to initialize the database."""
|
||||
}
|
||||
|
||||
+DSBLE0007 = {
|
||||
+ 'dsle': 'DSBLE0007',
|
||||
+ 'severity': 'HIGH',
|
||||
+ 'description': 'Missing or incorrect system indexes.',
|
||||
+ 'items': [],
|
||||
+ 'detail': """System indexes are essential for proper directory server operation. Missing or
|
||||
+incorrectly configured system indexes can lead to poor search performance, replication
|
||||
+issues, and other operational problems.
|
||||
+
|
||||
+The following system indexes should be present with correct configuration:
|
||||
+EXPECTED_INDEXES
|
||||
+
|
||||
+Current discrepancies:
|
||||
+DISCREPANCIES
|
||||
+""",
|
||||
+ 'fix': """Add the missing system indexes or fix the incorrect configurations using dsconf:
|
||||
+
|
||||
+REMEDIATION_COMMANDS
|
||||
+
|
||||
+After adding or modifying indexes, you may need to reindex the affected attributes:
|
||||
+
|
||||
+REINDEX_COMMANDS
|
||||
+
|
||||
+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. For production
|
||||
+systems, you may want to reindex offline or use the --wait option to monitor task completion.
|
||||
+"""
|
||||
+}
|
||||
+
|
||||
# Config checks
|
||||
DSCLE0001 = {
|
||||
'dsle': 'DSCLE0001',
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,606 +0,0 @@
|
||||
From fb28c3a318fa87ff194aeb7f29c0fc1846918d81 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 3 Oct 2025 15:11:12 +0200
|
||||
Subject: [PATCH] Issue 6966 - On large DB, unlimited IDL scan limit reduce the
|
||||
SRCH performance (#6967)
|
||||
|
||||
Bug description:
|
||||
The RFE 2435, removed the limit of the IDList size.
|
||||
A side effect is that for subtree/on-level searches some IDL can be
|
||||
huge. For example a subtree search on the base suffix will build a
|
||||
IDL up to number of entries in the DB.
|
||||
Building such big IDL is accounting for +90% of the etime of the
|
||||
operation.
|
||||
|
||||
Fix description:
|
||||
Using fine grain indexing we can limit IDL for parentid
|
||||
(onelevel) and ancestorid (subtree) index.
|
||||
It support a new backend config parameter nsslapd-systemidlistscanlimit
|
||||
that is the default value limit for parentid/ancestorid limits.
|
||||
Default value is 5000.
|
||||
When creating a new backend it creates parentid/ancestorid
|
||||
indexes with nsIndexIDListScanLimit setting the above limit.
|
||||
At startup the fine grain limit is either taken from nsIndexIDListScanLimit
|
||||
or fallback from nsslapd-systemidlistscanlimit.
|
||||
During a search request it uses the standard fine grain mechanism.
|
||||
On my tests it improves throughput and response time by ~50 times
|
||||
|
||||
fixes: #6966
|
||||
|
||||
Reviewed by: Mark Reynolds, Pierre Rogier, William Brown and Simon
|
||||
Piguchin (Thanks to you all !!!)
|
||||
|
||||
(cherry picked from commit b53181715937135b1c80ff34d56e9e21b53fe889)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/config/config_test.py | 31 +++++--
|
||||
.../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 | 89 ++++++++++++++++---
|
||||
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 | 33 ++++++-
|
||||
src/lib389/lib389/cli_conf/backend.py | 20 +++++
|
||||
10 files changed, 213 insertions(+), 27 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
|
||||
index 19232c87d..430176602 100644
|
||||
--- a/dirsrvtests/tests/suites/config/config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/config_test.py
|
||||
@@ -514,17 +514,19 @@ def test_ndn_cache_enabled(topo):
|
||||
topo.standalone.config.set('nsslapd-ndn-cache-max-size', 'invalid_value')
|
||||
|
||||
|
||||
-def test_require_index(topo):
|
||||
- """Test nsslapd-ignore-virtual-attrs configuration attribute
|
||||
+def test_require_index(topo, request):
|
||||
+ """Validate that unindexed searches are rejected
|
||||
|
||||
:id: fb6e31f2-acc2-4e75-a195-5c356faeb803
|
||||
:setup: Standalone instance
|
||||
:steps:
|
||||
1. Set "nsslapd-require-index" to "on"
|
||||
- 2. Test an unindexed search is rejected
|
||||
+ 2. ancestorid/idlscanlimit to 100
|
||||
+ 3. Test an unindexed search is rejected
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
+ 3. Success
|
||||
"""
|
||||
|
||||
# Set the config
|
||||
@@ -535,6 +537,10 @@ def test_require_index(topo):
|
||||
|
||||
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):
|
||||
@@ -545,11 +551,16 @@ def test_require_index(topo):
|
||||
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):
|
||||
- """Test nsslapd-ignore-virtual-attrs configuration attribute
|
||||
+def test_require_internal_index(topo, request):
|
||||
+ """Ensure internal operations require indexed attributes
|
||||
|
||||
:id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f
|
||||
:setup: Standalone instance
|
||||
@@ -580,6 +591,10 @@ def test_require_internal_index(topo):
|
||||
# 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)
|
||||
@@ -604,6 +619,12 @@ def test_require_internal_index(topo):
|
||||
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/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
index 1ed11c891..8835be8fa 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", [
|
||||
+@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err, restart", [
|
||||
(50, 200, 'cn=config,%s' % DN_LDBM, 'nsslapd-idlistscanlimit', '100',
|
||||
- ldap.UNWILLING_TO_PERFORM),
|
||||
+ ldap.UNWILLING_TO_PERFORM, True),
|
||||
(5, 15, DN_CONFIG, 'nsslapd-timelimit', '20',
|
||||
- ldap.UNAVAILABLE_CRITICAL_EXTENSION),
|
||||
+ ldap.UNAVAILABLE_CRITICAL_EXTENSION, False),
|
||||
(21, 50, DN_CONFIG, 'nsslapd-sizelimit', '20',
|
||||
- ldap.SIZELIMIT_EXCEEDED),
|
||||
+ ldap.SIZELIMIT_EXCEEDED, False),
|
||||
(21, 50, DN_CONFIG, 'nsslapd-pagedsizelimit', '5',
|
||||
- ldap.SIZELIMIT_EXCEEDED),
|
||||
+ ldap.SIZELIMIT_EXCEEDED, False),
|
||||
(5, 50, 'cn=config,%s' % DN_LDBM, 'nsslapd-lookthroughlimit', '20',
|
||||
- ldap.ADMINLIMIT_EXCEEDED)])
|
||||
+ ldap.ADMINLIMIT_EXCEEDED, False)])
|
||||
def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
- suffix, attr_name, attr_value, expected_err):
|
||||
+ suffix, attr_name, attr_value, expected_err, restart):
|
||||
"""Verify that search with a simple paged results control
|
||||
throws expected exceptoins when corresponding limits are
|
||||
exceeded.
|
||||
@@ -351,6 +351,15 @@ 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']
|
||||
@@ -403,6 +412,8 @@ 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 d17ec644b..cde30cedd 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
@@ -554,6 +554,7 @@ 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 30fa09ebb..63f0196c1 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -999,6 +999,8 @@ 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 f6a9817a7..29299b992 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);
|
||||
+Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr, char *scanlimit);
|
||||
|
||||
|
||||
/* 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)
|
||||
+ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr, char *scanlimit)
|
||||
{
|
||||
Slapi_Entry *e = slapi_entry_alloc();
|
||||
struct berval *vals[2];
|
||||
@@ -168,6 +168,11 @@ 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;
|
||||
}
|
||||
|
||||
@@ -180,8 +185,59 @@ 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;
|
||||
+ 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,
|
||||
@@ -190,24 +246,29 @@ 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);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 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);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
+ 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);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
|
||||
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);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
@@ -221,22 +282,22 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 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);
|
||||
+ e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 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);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 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);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0, 0);
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
|
||||
@@ -245,11 +306,15 @@ 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, "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);
|
||||
}
|
||||
|
||||
+ 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 b7bceabf2..f8d8f7474 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
@@ -366,6 +366,35 @@ 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)
|
||||
{
|
||||
@@ -945,6 +974,7 @@ 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 48446193e..004e5ea7e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
@@ -60,6 +60,7 @@ 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 38e7368e1..bae2a64b9 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
@@ -384,6 +384,14 @@ 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 a97def17e..03290ac1c 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -541,8 +541,8 @@ class Backend(DSLdapObject):
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
expected_system_indexes = {
|
||||
'entryrdn': {'types': ['subtree'], 'matching_rule': None},
|
||||
- 'parentId': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
- 'ancestorId': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ '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'},
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
@@ -592,12 +592,15 @@ class Backend(DSLdapObject):
|
||||
cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
|
||||
if expected_config['matching_rule']:
|
||||
cmd += f" --add-mr {expected_config['matching_rule']}"
|
||||
+ if expected_config['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]
|
||||
@@ -623,6 +626,19 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
+ # Check fine grain definitions for parentid ONLY
|
||||
+ expected_scanlimit = expected_config['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)
|
||||
+
|
||||
+
|
||||
except Exception as e:
|
||||
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
discrepancies.append(f"Unable to check index {attr_name}: {str(e)}")
|
||||
@@ -852,12 +868,13 @@ 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, reindex=False):
|
||||
+ def add_index(self, attr_name, types, matching_rules=None, idlistscanlimit=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.
|
||||
"""
|
||||
|
||||
@@ -887,6 +904,15 @@ 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'] = mrs
|
||||
+
|
||||
new_index.create(properties=props, basedn="cn=index," + self._dn)
|
||||
|
||||
if reindex:
|
||||
@@ -1193,6 +1219,7 @@ 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 4dc67d563..d57cb9433 100644
|
||||
--- a/src/lib389/lib389/cli_conf/backend.py
|
||||
+++ b/src/lib389/lib389/cli_conf/backend.py
|
||||
@@ -39,6 +39,7 @@ 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',
|
||||
@@ -587,6 +588,21 @@ 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")
|
||||
@@ -908,6 +924,9 @@ 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')
|
||||
|
||||
@@ -1034,6 +1053,7 @@ 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.51.1
|
||||
|
||||
@ -1,240 +0,0 @@
|
||||
From a5079d745c620393602bc83a9a83c174c2405301 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 14 Oct 2025 15:12:31 +0200
|
||||
Subject: [PATCH] Issue 6979 - Improve the way to detect asynchronous
|
||||
operations in the access logs (#6980)
|
||||
|
||||
Bug description:
|
||||
Asynch operations are prone to make the server unresponsive.
|
||||
The detection of those operations is not easy.
|
||||
Access logs should contain a way to retrieve easilly the
|
||||
operations (an the connections) with async searches
|
||||
|
||||
Fix description:
|
||||
When dispatching a new operation, if the count of
|
||||
uncompleted operations on the connection overpass
|
||||
a threshold (2) then add a note to the 'notes='
|
||||
in the access log
|
||||
between the
|
||||
|
||||
fixes: #6979
|
||||
|
||||
Reviewed by: Pierre Rogier, Simon Pichugin (Thanks !)
|
||||
|
||||
(cherry picked from commit 1f0210264545d4e674507e8962c81b2e9d3b28b6)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
dirsrvtests/tests/suites/basic/basic_test.py | 69 +++++++++++++++++++-
|
||||
ldap/servers/slapd/connection.c | 23 ++++++-
|
||||
ldap/servers/slapd/daemon.c | 2 +
|
||||
ldap/servers/slapd/result.c | 2 +
|
||||
ldap/servers/slapd/slap.h | 1 +
|
||||
ldap/servers/slapd/slapi-plugin.h | 2 +
|
||||
6 files changed, 95 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
index 4a45f9dbe..7f13fac1a 100644
|
||||
--- a/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
@@ -245,6 +245,72 @@ def test_basic_ops(topology_st, import_example_ldif):
|
||||
assert False
|
||||
log.info('test_basic_ops: PASSED')
|
||||
|
||||
+def test_basic_search_asynch(topology_st, request):
|
||||
+ """
|
||||
+ Tests asynchronous searches generate string 'notes=B'
|
||||
+ and 'notes=N' in access logs
|
||||
+
|
||||
+ :id: 1b761421-d2bb-487b-813e-2278123fd13c
|
||||
+ :parametrized: no
|
||||
+ :setup: Standalone instance, create test user to search with filter (uid=*).
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. trigger async searches
|
||||
+ 3. Verify access logs contains 'notes=B' up to 10 attempts
|
||||
+ 4. Verify access logs contains 'notes=N' up to 10 attempts
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+
|
||||
+ """
|
||||
+
|
||||
+ log.info('Running test_basic_search_asynch...')
|
||||
+
|
||||
+ search_filter = "(uid=*)"
|
||||
+ topology_st.standalone.restart()
|
||||
+ topology_st.standalone.config.set("nsslapd-accesslog-logbuffering", "off")
|
||||
+ topology_st.standalone.config.set("nsslapd-maxthreadsperconn", "3")
|
||||
+
|
||||
+ try:
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, rdn=None)
|
||||
+ user = users.create_test_user()
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('Failed to create test user: error ' + e.args[0]['desc'])
|
||||
+ assert False
|
||||
+
|
||||
+ for attempt in range(10):
|
||||
+ msgids = []
|
||||
+ for i in range(5):
|
||||
+ searchid = topology_st.standalone.search(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, search_filter)
|
||||
+ msgids.append(searchid)
|
||||
+
|
||||
+ for msgid in msgids:
|
||||
+ rtype, rdata = topology_st.standalone.result(msgid)
|
||||
+
|
||||
+ # verify if some operations got blocked
|
||||
+ error_lines = topology_st.standalone.ds_access_log.match('.*notes=.*B.* details.*')
|
||||
+ if len(error_lines) > 0:
|
||||
+ log.info('test_basic_search_asynch: found "notes=B" after %d attempt(s)' % (attempt + 1))
|
||||
+ break
|
||||
+
|
||||
+ assert attempt < 10
|
||||
+
|
||||
+ # verify if some operations got flagged Not synchronous
|
||||
+ error_lines = topology_st.standalone.ds_access_log.match('.*notes=.*N.* details.*')
|
||||
+ assert len(error_lines) > 0
|
||||
+
|
||||
+ def fin():
|
||||
+ user.delete()
|
||||
+ topology_st.standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
+ topology_st.standalone.config.set("nsslapd-maxthreadsperconn", "5")
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ log.info('test_basic_search_asynch: PASSED')
|
||||
|
||||
def test_basic_import_export(topology_st, import_example_ldif):
|
||||
"""Test online and offline LDIF import & export
|
||||
@@ -1771,9 +1837,6 @@ def test_dscreate_with_different_rdn(dscreate_test_rdn_value):
|
||||
else:
|
||||
assert True
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
|
||||
index 10a8cc577..6c5ef5291 100644
|
||||
--- a/ldap/servers/slapd/connection.c
|
||||
+++ b/ldap/servers/slapd/connection.c
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "prlog.h" /* for PR_ASSERT */
|
||||
#include "fe.h"
|
||||
#include <sasl/sasl.h>
|
||||
+#include <stdbool.h>
|
||||
#if defined(LINUX)
|
||||
#include <netinet/tcp.h> /* for TCP_CORK */
|
||||
#endif
|
||||
@@ -568,6 +569,19 @@ connection_dispatch_operation(Connection *conn, Operation *op, Slapi_PBlock *pb)
|
||||
/* Set the start time */
|
||||
slapi_operation_set_time_started(op);
|
||||
|
||||
+ /* difficult to detect false asynch operations
|
||||
+ * Indeed because of scheduling of threads a previous
|
||||
+ * operation may have sent its result but not yet updated
|
||||
+ * the completed count.
|
||||
+ * To avoid false positive lets set a limit of 2.
|
||||
+ */
|
||||
+ if ((conn->c_opsinitiated - conn->c_opscompleted) > 2) {
|
||||
+ unsigned int opnote;
|
||||
+ opnote = slapi_pblock_get_operation_notes(pb);
|
||||
+ opnote |= SLAPI_OP_NOTE_ASYNCH_OP; /* the operation is dispatch while others are running */
|
||||
+ slapi_pblock_set_operation_notes(pb, opnote);
|
||||
+ }
|
||||
+
|
||||
/* If the minimum SSF requirements are not met, only allow
|
||||
* bind and extended operations through. The bind and extop
|
||||
* code will ensure that only SASL binds and startTLS are
|
||||
@@ -1006,10 +1020,16 @@ connection_wait_for_new_work(Slapi_PBlock *pb, int32_t interval)
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "connection_wait_for_new_work", "no work to do\n");
|
||||
ret = CONN_NOWORK;
|
||||
} else {
|
||||
+ Connection *conn = wqitem;
|
||||
/* make new pb */
|
||||
- slapi_pblock_set(pb, SLAPI_CONNECTION, wqitem);
|
||||
+ slapi_pblock_set(pb, SLAPI_CONNECTION, conn);
|
||||
slapi_pblock_set_op_stack_elem(pb, op_stack_obj);
|
||||
slapi_pblock_set(pb, SLAPI_OPERATION, op_stack_obj->op);
|
||||
+ if (conn->c_flagblocked) {
|
||||
+ /* flag this new operation that it was blocked by maxthreadperconn */
|
||||
+ slapi_pblock_set_operation_notes(pb, SLAPI_OP_NOTE_ASYNCH_BLOCKED);
|
||||
+ conn->c_flagblocked = false;
|
||||
+ }
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&work_q_lock);
|
||||
@@ -1869,6 +1889,7 @@ connection_threadmain(void *arg)
|
||||
} else {
|
||||
/* keep count of how many times maxthreads has blocked an operation */
|
||||
conn->c_maxthreadsblocked++;
|
||||
+ conn->c_flagblocked = true;
|
||||
if (conn->c_maxthreadsblocked == 1 && connection_has_psearch(conn)) {
|
||||
slapi_log_err(SLAPI_LOG_NOTICE, "connection_threadmain",
|
||||
"Connection (conn=%" PRIu64 ") has a running persistent search "
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index bef75e4a3..2534483c1 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
+#include <stdbool.h>
|
||||
#if defined(HAVE_MNTENT_H)
|
||||
#include <mntent.h>
|
||||
#endif
|
||||
@@ -1673,6 +1674,7 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
} else {
|
||||
if (c->c_threadnumber >= c->c_max_threads_per_conn) {
|
||||
c->c_maxthreadsblocked++;
|
||||
+ c->c_flagblocked = true;
|
||||
if (c->c_maxthreadsblocked == 1 && connection_has_psearch(c)) {
|
||||
slapi_log_err(SLAPI_LOG_NOTICE, "connection_threadmain",
|
||||
"Connection (conn=%" PRIu64 ") has a running persistent search "
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index f40556de8..f000e32f1 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -1945,6 +1945,8 @@ static struct slapi_note_map notemap[] = {
|
||||
{SLAPI_OP_NOTE_SIMPLEPAGED, "P", "Paged Search"},
|
||||
{SLAPI_OP_NOTE_FULL_UNINDEXED, "A", "Fully Unindexed Filter"},
|
||||
{SLAPI_OP_NOTE_FILTER_INVALID, "F", "Filter Element Missing From Schema"},
|
||||
+ {SLAPI_OP_NOTE_ASYNCH_OP, "N", "Not synchronous operation"},
|
||||
+ {SLAPI_OP_NOTE_ASYNCH_BLOCKED, "B", "Blocked because too many operations"},
|
||||
};
|
||||
|
||||
#define SLAPI_NOTEMAP_COUNT (sizeof(notemap) / sizeof(struct slapi_note_map))
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 82550527c..36d26bf4a 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -1720,6 +1720,7 @@ typedef struct conn
|
||||
int32_t c_anon_access;
|
||||
int32_t c_max_threads_per_conn;
|
||||
int32_t c_bind_auth_token;
|
||||
+ bool c_flagblocked; /* Flag the next read operation as blocked */
|
||||
} Connection;
|
||||
#define CONN_FLAG_SSL 1 /* Is this connection an SSL connection or not ? \
|
||||
* Used to direct I/O code when SSL is handled differently \
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 677be1db0..6517665a9 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -7336,6 +7336,8 @@ typedef enum _slapi_op_note_t {
|
||||
SLAPI_OP_NOTE_SIMPLEPAGED = 0x02,
|
||||
SLAPI_OP_NOTE_FULL_UNINDEXED = 0x04,
|
||||
SLAPI_OP_NOTE_FILTER_INVALID = 0x08,
|
||||
+ SLAPI_OP_NOTE_ASYNCH_OP = 0x10,
|
||||
+ SLAPI_OP_NOTE_ASYNCH_BLOCKED = 0x20,
|
||||
} slapi_op_note_t;
|
||||
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
From f4992f3038078ff96a7982d7a6fcced1c3870e16 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Thu, 16 Oct 2025 22:00:13 -0700
|
||||
Subject: [PATCH] Issue 7047 - MemberOf plugin logs null attribute name on
|
||||
fixup task completion (#7048)
|
||||
|
||||
Description: The MemberOf plugin logged "(null)" instead of the attribute
|
||||
name when the global fixup task completed. This occurred because the config
|
||||
structure containing the attribute name was freed before the completion log
|
||||
message was written.
|
||||
|
||||
This fix moves the memberof_free_config() call to after the log statement,
|
||||
ensuring the attribute name is available for logging.
|
||||
|
||||
Additionally, the test_shutdown_on_deferred_memberof test has been improved
|
||||
to properly verify the fixup task behavior by checking that both the "started"
|
||||
and "finished" log messages contain the correct attribute name.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7047
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit 777187a89f13bc00dc03b0e9370333cdfc299da9)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 21 +++++++++++++++++--
|
||||
ldap/servers/plugins/memberof/memberof.c | 1 +
|
||||
2 files changed, 20 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 976729c2f..7b5410b67 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -1423,8 +1423,25 @@ def test_shutdown_on_deferred_memberof(topology_st):
|
||||
value = memberof.get_memberofneedfixup()
|
||||
assert ((str(value).lower() == "yes") or (str(value).lower() == "true"))
|
||||
|
||||
- # step 14. fixup task was not launched because by default launch_fixup is no
|
||||
- assert len(errlog.match('.*It is recommended to launch memberof fixup task.*')) == 0
|
||||
+ # step 14. Verify the global fixup started/finished messages
|
||||
+ attribute_name = 'memberOf'
|
||||
+ started_lines = errlog.match('.*Memberof plugin started the global fixup task for attribute .*')
|
||||
+ assert len(started_lines) >= 1
|
||||
+ for line in started_lines:
|
||||
+ log.info(f'Started line: {line}')
|
||||
+ assert f'attribute {attribute_name}' in line
|
||||
+
|
||||
+ # Wait for finished messages to appear, then verify no nulls are present
|
||||
+ finished_lines = []
|
||||
+ for _ in range(60):
|
||||
+ finished_lines = errlog.match('.*Memberof plugin finished the global fixup task.*')
|
||||
+ if finished_lines:
|
||||
+ break
|
||||
+ time.sleep(1)
|
||||
+ assert len(finished_lines) >= 1
|
||||
+ for line in finished_lines:
|
||||
+ log.info(f'Finished line: {line}')
|
||||
+ assert '(null)' not in line
|
||||
|
||||
# Check that users memberof and group members are in sync.
|
||||
time.sleep(delay)
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 2ee7ee319..b52bc0331 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -926,6 +926,7 @@ perform_needed_fixup()
|
||||
slapi_ch_free_string(&td.filter_str);
|
||||
slapi_log_err(SLAPI_LOG_INFO, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
"Memberof plugin finished the global fixup task for attribute %s\n", config.memberof_attr);
|
||||
+ memberof_free_config(&config);
|
||||
return rc;
|
||||
}
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
From 496277d9a69a559f690530145ffa5bb2f7b0e837 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 20 Oct 2025 14:30:52 +0200
|
||||
Subject: [PATCH] Issue 7032 - The new ipahealthcheck test
|
||||
ipahealthcheck.ds.backends.BackendsCheck raises CRITICAL issue (#7036)
|
||||
|
||||
Bug description:
|
||||
The bug fix #6966 adds a 'scanlimit' to one of the system
|
||||
index ('parentid'). So not all of them have such attribute.
|
||||
In healthcheck such attribute (i.e. key) can miss but
|
||||
the code assumes it is present
|
||||
|
||||
Fix description:
|
||||
Get 'parentid' from the dict with the proper routine
|
||||
(Thanks Florence Renaud for the debug/fix)
|
||||
|
||||
fixes: #7032
|
||||
|
||||
Reviewed by: Pierre Rogier and Simon Pichugin (thank you !)
|
||||
|
||||
(cherry picked from commit ea8d4c8c2261861118cf8ae20dffb0e5a466e9d2)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
src/lib389/lib389/backend.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 03290ac1c..e74d1fbf9 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -627,7 +627,7 @@ class Backend(DSLdapObject):
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
# Check fine grain definitions for parentid ONLY
|
||||
- expected_scanlimit = expected_config['scanlimit']
|
||||
+ 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
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,320 +0,0 @@
|
||||
From 747eed3acc8ec05c8a0740080d058a295631b807 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 20 Aug 2025 13:44:47 -0400
|
||||
Subject: [PATCH] Issue 6947 - Revise time skew check in healthcheck tool and
|
||||
add option to exclude checks
|
||||
|
||||
Description:
|
||||
|
||||
The current check reports a critical warning if time skew is greater than
|
||||
1 day - even if "nsslapd-ignore-time-skew" is set to "on". If we are ignoring
|
||||
time skew we should still report a warning if it's very significant like
|
||||
30 days.
|
||||
|
||||
Also added an option to exclude checks
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6947
|
||||
|
||||
Reviewed by: progier, spichugi, viktor(Thanks!!!)
|
||||
|
||||
(cherry picked from commit 7ac6d61df5d696b2e0c7911379448daecf10e652)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../suites/healthcheck/health_config_test.py | 3 +-
|
||||
.../healthcheck/health_security_test.py | 1 +
|
||||
.../healthcheck/health_tunables_test.py | 1 +
|
||||
.../suites/healthcheck/healthcheck_test.py | 37 +++++++++++++++-
|
||||
src/lib389/lib389/cli_ctl/health.py | 34 +++++++++++----
|
||||
src/lib389/lib389/dseldif.py | 42 +++++++++++++++----
|
||||
src/lib389/lib389/lint.py | 11 +++++
|
||||
7 files changed, 109 insertions(+), 20 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_config_test.py b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
index 747699486..f6fe220b8 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
@@ -10,7 +10,7 @@
|
||||
import pytest
|
||||
import os
|
||||
import subprocess
|
||||
-
|
||||
+import time
|
||||
from lib389.backend import Backends, DatabaseConfig
|
||||
from lib389.cos import CosTemplates, CosPointerDefinitions
|
||||
from lib389.dbgen import dbgen_users
|
||||
@@ -46,6 +46,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searc
|
||||
args.list_checks = False
|
||||
args.check = ['config', 'refint', 'backends', 'monitor-disk-space', 'logs', 'memberof']
|
||||
args.dry_run = False
|
||||
+ args.exclude_check = []
|
||||
|
||||
if json:
|
||||
log.info('Use healthcheck with --json option')
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_security_test.py b/dirsrvtests/tests/suites/healthcheck/health_security_test.py
|
||||
index ebd330d95..753658037 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_security_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_security_test.py
|
||||
@@ -46,6 +46,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searc
|
||||
args.list_checks = False
|
||||
args.check = None
|
||||
args.dry_run = False
|
||||
+ args.exclude_check = []
|
||||
|
||||
if json:
|
||||
log.info('Use healthcheck with --json option')
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py b/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py
|
||||
index 5e80c8038..2d9ae90da 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py
|
||||
@@ -32,6 +32,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code=None, json=F
|
||||
args.verbose = instance.verbose
|
||||
args.list_errors = list_errors
|
||||
args.list_checks = list_checks
|
||||
+ args.exclude_check = []
|
||||
args.check = check
|
||||
args.dry_run = False
|
||||
args.json = json
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py b/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
|
||||
index f45688dbb..ef49240f7 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
|
||||
@@ -41,6 +41,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code=None, json=F
|
||||
args.list_errors = list_errors
|
||||
args.list_checks = list_checks
|
||||
args.check = check
|
||||
+ args.exclude_check = []
|
||||
args.dry_run = False
|
||||
args.json = json
|
||||
|
||||
@@ -265,8 +266,40 @@ def test_healthcheck_check_option(topology_st):
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, searched_code=JSON_OUTPUT, json=True, check=[item])
|
||||
|
||||
|
||||
-@pytest.mark.ds50873
|
||||
-@pytest.mark.bz1685160
|
||||
+def test_healthcheck_exclude_option(topology_st):
|
||||
+ """Check functionality of HealthCheck Tool with --exclude-check option
|
||||
+
|
||||
+ :id: a4e2103c-67b8-4359-a8ba-67a8650cd3b7
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Set check to exclude from list
|
||||
+ 2. Run HealthCheck
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ exclude_list = [
|
||||
+ ('config:passwordscheme', 'config:passwordscheme',
|
||||
+ 'config:securitylog_buffering'),
|
||||
+ ('config', 'config:', 'backends:userroot:mappingtree')
|
||||
+ ]
|
||||
+
|
||||
+ for exclude, unwanted, wanted in exclude_list:
|
||||
+ unwanted_pattern = 'Checking ' + unwanted
|
||||
+ wanted_pattern = 'Checking ' + wanted
|
||||
+
|
||||
+ log.info('Exclude check: %s unwanted: %s wanted: %s',
|
||||
+ exclude, unwanted, wanted)
|
||||
+
|
||||
+ run_healthcheck_exclude(topology_st.logcap, inst,
|
||||
+ unwanted=unwanted_pattern,
|
||||
+ wanted=wanted_pattern,
|
||||
+ exclude_check=exclude)
|
||||
+
|
||||
+
|
||||
@pytest.mark.skipif(ds_is_older("1.4.1"), reason="Not implemented")
|
||||
def test_healthcheck_standalone_tls(topology_st):
|
||||
"""Check functionality of HealthCheck Tool on TLS enabled standalone instance with no errors
|
||||
diff --git a/src/lib389/lib389/cli_ctl/health.py b/src/lib389/lib389/cli_ctl/health.py
|
||||
index d85e3906a..38540e0df 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/health.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/health.py
|
||||
@@ -75,6 +75,9 @@ def _list_errors(log):
|
||||
|
||||
|
||||
def _list_checks(inst, specs: Iterable[str]):
|
||||
+ if specs is None:
|
||||
+ yield []
|
||||
+ return
|
||||
o_uids = dict(_list_targets(inst))
|
||||
for s in specs:
|
||||
wanted, rest = DSLint._dslint_parse_spec(s)
|
||||
@@ -85,19 +88,27 @@ def _list_checks(inst, specs: Iterable[str]):
|
||||
for l in o_uids[wanted].lint_list(rest):
|
||||
yield o_uids[wanted], l
|
||||
else:
|
||||
- raise ValueError('No such object specifier')
|
||||
+ raise ValueError('No such object specifier: ' + wanted)
|
||||
|
||||
|
||||
def _print_checks(inst, log, specs: Iterable[str]) -> None:
|
||||
for o, s in _list_checks(inst, specs):
|
||||
log.info(f'{o.lint_uid()}:{s[0]}')
|
||||
|
||||
-def _run(inst, log, args, checks):
|
||||
+
|
||||
+def _run(inst, log, args, checks, exclude_checks):
|
||||
if not args.json:
|
||||
log.info("Beginning lint report, this could take a while ...")
|
||||
|
||||
report = []
|
||||
+ excludes = []
|
||||
+ for _, skip in exclude_checks:
|
||||
+ excludes.append(skip[0])
|
||||
+
|
||||
for o, s in checks:
|
||||
+ if s[0] in excludes:
|
||||
+ continue
|
||||
+
|
||||
if not args.json:
|
||||
log.info(f"Checking {o.lint_uid()}:{s[0]} ...")
|
||||
try:
|
||||
@@ -119,12 +130,12 @@ def _run(inst, log, args, checks):
|
||||
if count > 1:
|
||||
plural = "s"
|
||||
if not args.json:
|
||||
- log.info("{} Issue{} found! Generating report ...".format(count, plural))
|
||||
+ log.info(f"{count} Issue{plural} found! Generating report ...")
|
||||
idx = 1
|
||||
for item in report:
|
||||
_format_check_output(log, item, idx)
|
||||
idx += 1
|
||||
- log.info('\n\n===== End Of Report ({} Issue{} found) ====='.format(count, plural))
|
||||
+ log.info(f'\n\n===== End Of Report ({count} Issue{plural} found) =====')
|
||||
else:
|
||||
log.info(json.dumps(report, indent=4))
|
||||
|
||||
@@ -147,17 +158,21 @@ def health_check_run(inst, log, args):
|
||||
dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
|
||||
dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
|
||||
try:
|
||||
- inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
|
||||
+ inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose,
|
||||
+ args=args)
|
||||
except Exception as e:
|
||||
- raise ValueError('Failed to connect to Directory Server instance: ' + str(e))
|
||||
+ raise ValueError('Failed to connect to Directory Server instance: ' +
|
||||
+ str(e)) from e
|
||||
|
||||
checks = args.check or dict(_list_targets(inst)).keys()
|
||||
-
|
||||
+ exclude_checks = args.exclude_check
|
||||
+ print("MARK excl: " + str(exclude_checks))
|
||||
if args.list_checks or args.dry_run:
|
||||
_print_checks(inst, log, checks)
|
||||
return
|
||||
|
||||
- _run(inst, log, args, _list_checks(inst, checks))
|
||||
+ _run(inst, log, args, _list_checks(inst, checks),
|
||||
+ _list_checks(inst, exclude_checks))
|
||||
|
||||
disconnect_instance(inst)
|
||||
|
||||
@@ -175,3 +190,6 @@ def create_parser(subparsers):
|
||||
run_healthcheck_parser.add_argument('--check', nargs='+', default=None,
|
||||
help='Areas to check. These can be obtained by --list-checks. Every element on the left of the colon (:)'
|
||||
' may be replaced by an asterisk if multiple options on the right are available.')
|
||||
+ run_healthcheck_parser.add_argument('--exclude-check', nargs='+', default=[],
|
||||
+ help='Areas to skip. These can be obtained by --list-checks. Every element on the left of the colon (:)'
|
||||
+ ' may be replaced by an asterisk if multiple options on the right are available.')
|
||||
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
|
||||
index 31577c9fa..3104a7b6f 100644
|
||||
--- a/src/lib389/lib389/dseldif.py
|
||||
+++ b/src/lib389/lib389/dseldif.py
|
||||
@@ -23,7 +23,8 @@ from lib389.lint import (
|
||||
DSPERMLE0002,
|
||||
DSSKEWLE0001,
|
||||
DSSKEWLE0002,
|
||||
- DSSKEWLE0003
|
||||
+ DSSKEWLE0003,
|
||||
+ DSSKEWLE0004
|
||||
)
|
||||
|
||||
|
||||
@@ -66,26 +67,49 @@ class DSEldif(DSLint):
|
||||
return 'dseldif'
|
||||
|
||||
def _lint_nsstate(self):
|
||||
+ """
|
||||
+ Check the nsState attribute, which contains the CSN generator time
|
||||
+ diffs, for excessive replication time skew
|
||||
+ """
|
||||
+ ignoring_skew = False
|
||||
+ skew_high = 86400 # 1 day
|
||||
+ skew_medium = 43200 # 12 hours
|
||||
+ skew_low = 21600 # 6 hours
|
||||
+
|
||||
+ ignore_skew = self.get("cn=config", "nsslapd-ignore-time-skew")
|
||||
+ if ignore_skew is not None and ignore_skew[0].lower() == "on":
|
||||
+ # If we are ignoring time skew only report a warning if the skew
|
||||
+ # is significant
|
||||
+ ignoring_skew = True
|
||||
+ skew_high = 86400 * 365 # Report a warning for skew over a year
|
||||
+ skew_medium = 99999999999
|
||||
+ skew_low = 99999999999
|
||||
+
|
||||
suffixes = self.readNsState()
|
||||
for suffix in suffixes:
|
||||
# Check the local offset first
|
||||
report = None
|
||||
- skew = int(suffix['time_skew'])
|
||||
- if skew >= 86400:
|
||||
- # 24 hours - replication will break
|
||||
- report = copy.deepcopy(DSSKEWLE0003)
|
||||
- elif skew >= 43200:
|
||||
+ skew = abs(int(suffix['time_skew']))
|
||||
+ if skew >= skew_high:
|
||||
+ if ignoring_skew:
|
||||
+ # Ignoring skew, but it's too excessive not to report it
|
||||
+ report = copy.deepcopy(DSSKEWLE0004)
|
||||
+ else:
|
||||
+ # 24 hours of skew - replication will break
|
||||
+ report = copy.deepcopy(DSSKEWLE0003)
|
||||
+ elif skew >= skew_medium:
|
||||
# 12 hours
|
||||
report = copy.deepcopy(DSSKEWLE0002)
|
||||
- elif skew >= 21600:
|
||||
+ elif skew >= skew_low:
|
||||
# 6 hours
|
||||
report = copy.deepcopy(DSSKEWLE0001)
|
||||
if report is not None:
|
||||
report['items'].append(suffix['suffix'])
|
||||
report['items'].append('Time Skew')
|
||||
report['items'].append('Skew: ' + suffix['time_skew_str'])
|
||||
- report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
||||
- report['check'] = f'dseldif:nsstate'
|
||||
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE',
|
||||
+ self._instance.serverid)
|
||||
+ report['check'] = 'dseldif:nsstate'
|
||||
yield report
|
||||
|
||||
def _update(self):
|
||||
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||||
index 1e48c790d..fe39a5d59 100644
|
||||
--- a/src/lib389/lib389/lint.py
|
||||
+++ b/src/lib389/lib389/lint.py
|
||||
@@ -518,6 +518,17 @@ Also look at https://access.redhat.com/documentation/en-us/red_hat_directory_ser
|
||||
and find the paragraph "Too much time skew"."""
|
||||
}
|
||||
|
||||
+DSSKEWLE0004 = {
|
||||
+ 'dsle': 'DSSKEWLE0004',
|
||||
+ 'severity': 'Low',
|
||||
+ 'description': 'Extensive time skew.',
|
||||
+ 'items': ['Replication'],
|
||||
+ 'detail': """The time skew is over 365 days. If the time skew continues to
|
||||
+increase eventually serious replication problems can occur.""",
|
||||
+ 'fix': """Avoid making changes to the system time, and make sure the clocks
|
||||
+on all the replicas are correct."""
|
||||
+}
|
||||
+
|
||||
DSLOGNOTES0001 = {
|
||||
'dsle': 'DSLOGNOTES0001',
|
||||
'severity': 'Medium',
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
From 3ed914e5b7a668fbf90c4a2f425ce166901018b6 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 18 Nov 2025 14:17:09 +0100
|
||||
Subject: [PATCH] Issue 6901 - Update changelog trimming logging (#7102)
|
||||
|
||||
Description:
|
||||
* Set SLAPI_LOG_ERR for message in `_cl5DispatchTrimThread`
|
||||
* Add number of scanned entries to the log.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6901
|
||||
|
||||
Reviewed by: @mreynolds389, @progier389, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit 375d317cbe39c7792cdc608f236846e18252d6b1)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/replication/cl5_api.c | 11 +++++++----
|
||||
1 file changed, 7 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
|
||||
index 5d4edea92..21d2f5b8b 100644
|
||||
--- a/ldap/servers/plugins/replication/cl5_api.c
|
||||
+++ b/ldap/servers/plugins/replication/cl5_api.c
|
||||
@@ -2082,7 +2082,7 @@ _cl5DispatchDBThreads(void)
|
||||
NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
|
||||
PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE);
|
||||
if (NULL == pth) {
|
||||
- slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl,
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
|
||||
"_cl5DispatchDBThreads - Failed to create trimming thread"
|
||||
"; NSPR error - %d\n",
|
||||
PR_GetError());
|
||||
@@ -3687,7 +3687,7 @@ _cl5TrimFile(Object *obj, long *numToTrim)
|
||||
slapi_operation_parameters op = {0};
|
||||
ReplicaId csn_rid;
|
||||
void *it;
|
||||
- int finished = 0, totalTrimmed = 0, count;
|
||||
+ int finished = 0, totalTrimmed = 0, totalScanned = 0, count, scanned;
|
||||
PRBool abort;
|
||||
char strCSN[CSN_STRSIZE];
|
||||
int rc;
|
||||
@@ -3704,6 +3704,7 @@ _cl5TrimFile(Object *obj, long *numToTrim)
|
||||
while (!finished && !slapi_is_shutting_down()) {
|
||||
it = NULL;
|
||||
count = 0;
|
||||
+ scanned = 0;
|
||||
txnid = NULL;
|
||||
abort = PR_FALSE;
|
||||
|
||||
@@ -3720,6 +3721,7 @@ _cl5TrimFile(Object *obj, long *numToTrim)
|
||||
|
||||
finished = _cl5GetFirstEntry(obj, &entry, &it, txnid);
|
||||
while (!finished && !slapi_is_shutting_down()) {
|
||||
+ scanned++;
|
||||
/*
|
||||
* This change can be trimmed if it exceeds purge
|
||||
* parameters and has been seen by all consumers.
|
||||
@@ -3809,6 +3811,7 @@ _cl5TrimFile(Object *obj, long *numToTrim)
|
||||
rc, db_strerror(rc));
|
||||
} else {
|
||||
totalTrimmed += count;
|
||||
+ totalScanned += scanned;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3818,8 +3821,8 @@ _cl5TrimFile(Object *obj, long *numToTrim)
|
||||
ruv_destroy(&ruv);
|
||||
|
||||
if (totalTrimmed) {
|
||||
- slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5TrimFile - Trimmed %d changes from the changelog\n",
|
||||
- totalTrimmed);
|
||||
+ slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5TrimFile - Scanned %d records, and trimmed %d changes from the changelog\n",
|
||||
+ totalScanned, totalTrimmed);
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
From 902805365a07ccb8210ec5a8867431c17999ae4a Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 18 Nov 2025 15:04:45 -0500
|
||||
Subject: [PATCH] Issue 7007 - Improve paged result search locking
|
||||
|
||||
Description:
|
||||
|
||||
Hold the paged result connection hash mutex while acquiring the global
|
||||
connection paged result lock. Otherwise there is a window where the
|
||||
mutex could be rmoved and lead to a crash
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7007
|
||||
|
||||
Reviewed by: progier, spichugi, and tbordaz(Thanks!!!)
|
||||
|
||||
(cherry picked from commit 17968b55bc481aaef775c51131cc93a70b86793d)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/pagedresults.c | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
|
||||
index 4aa1fa3e5..a18081c63 100644
|
||||
--- a/ldap/servers/slapd/pagedresults.c
|
||||
+++ b/ldap/servers/slapd/pagedresults.c
|
||||
@@ -801,6 +801,7 @@ pagedresults_cleanup(Connection *conn, int needlock)
|
||||
prp->pr_current_be = NULL;
|
||||
if (prp->pr_mutex) {
|
||||
PR_DestroyLock(prp->pr_mutex);
|
||||
+ prp->pr_mutex = NULL;
|
||||
}
|
||||
memset(prp, '\0', sizeof(PagedResults));
|
||||
}
|
||||
@@ -841,6 +842,7 @@ pagedresults_cleanup_all(Connection *conn, int needlock)
|
||||
prp = conn->c_pagedresults.prl_list + i;
|
||||
if (prp->pr_mutex) {
|
||||
PR_DestroyLock(prp->pr_mutex);
|
||||
+ prp->pr_mutex = NULL;
|
||||
}
|
||||
if (prp->pr_current_be && prp->pr_search_result_set &&
|
||||
prp->pr_current_be->be_search_results_release) {
|
||||
@@ -1022,11 +1024,10 @@ pagedresults_lock(Connection *conn, int index)
|
||||
}
|
||||
pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
prp = conn->c_pagedresults.prl_list + index;
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
if (prp->pr_mutex) {
|
||||
PR_Lock(prp->pr_mutex);
|
||||
}
|
||||
- return;
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1038,11 +1039,10 @@ pagedresults_unlock(Connection *conn, int index)
|
||||
}
|
||||
pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
prp = conn->c_pagedresults.prl_list + index;
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
if (prp->pr_mutex) {
|
||||
PR_Unlock(prp->pr_mutex);
|
||||
}
|
||||
- return;
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
}
|
||||
|
||||
int
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
From 85c82d8e7c95eeb76788311b33544f8086cec31b Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 26 Nov 2025 10:38:40 +0100
|
||||
Subject: [PATCH] Issue 6966 - (2nd) On large DB, unlimited IDL scan limit
|
||||
reduce the SRCH performance
|
||||
|
||||
(cherry picked from commit e098ee776d94c2d4dac6f2a01473d63d8db54954)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 4 ----
|
||||
1 file changed, 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 29299b992..65084c61c 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -278,10 +278,6 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
slapi_entry_free(e);
|
||||
#endif
|
||||
|
||||
- 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);
|
||||
-
|
||||
e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,143 +0,0 @@
|
||||
From 106cd8af10368dac8f1c3897436f9ca32bc13685 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 4 Nov 2025 12:05:51 +0100
|
||||
Subject: [PATCH] Issue 7056 - DSBLE0007 doesn't generate remediation steps for
|
||||
missing indexes
|
||||
|
||||
Bug Description:
|
||||
dsctl healthcheck doesn't generate remediation steps for missing
|
||||
indexes, instead it prints an error message:
|
||||
|
||||
```
|
||||
- Unable to check index ancestorId: No object exists given the filter criteria: ancestorId (&(&(objectclass=nsIndex))(|(cn=ancestorId)))
|
||||
```
|
||||
|
||||
Fix Description:
|
||||
Catch `ldap.NO_SUCH_OBJECT` when index is missing and generate
|
||||
remediation instructions.
|
||||
Update remediation instructions for missing index.
|
||||
Fix failing tests due to missing idlistscanlimit.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7056
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thank you!)
|
||||
|
||||
(cherry picked from commit 0a85d7bcca0422ff1a8e20b219727410333c1a4f)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 9 ++++--
|
||||
src/lib389/lib389/backend.py | 28 ++++++++++++-------
|
||||
2 files changed, 24 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 61972d60c..6293340ca 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -171,7 +171,8 @@ 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"])
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
+ idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
|
||||
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)
|
||||
@@ -259,7 +260,8 @@ 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"])
|
||||
+ backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
+ idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
|
||||
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)
|
||||
@@ -443,7 +445,8 @@ 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"])
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
+ idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
backend.add_index("nsuniqueid", ["eq"])
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index e74d1fbf9..14b64d1d3 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -541,8 +541,8 @@ class Backend(DSLdapObject):
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
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', 'scanlimit': 'limit=5000 type=eq flags=AND'},
|
||||
+ 'ancestorid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
@@ -584,15 +584,24 @@ class Backend(DSLdapObject):
|
||||
for attr_name, expected_config in expected_system_indexes.items():
|
||||
try:
|
||||
index = indexes.get(attr_name)
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ # Index is missing
|
||||
+ index = None
|
||||
+ except Exception as e:
|
||||
+ self._log.debug(f"_lint_system_indexes - Error getting index {attr_name}: {e}")
|
||||
+ discrepancies.append(f"Unable to check index {attr_name}: {str(e)}")
|
||||
+ continue
|
||||
+
|
||||
+ try:
|
||||
# Check if index exists
|
||||
if index is None:
|
||||
discrepancies.append(f"Missing system index: {attr_name}")
|
||||
# Generate remediation command
|
||||
- index_types = ' '.join([f"--add-type {t}" for t in expected_config['types']])
|
||||
+ 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['matching_rule']:
|
||||
- cmd += f" --add-mr {expected_config['matching_rule']}"
|
||||
- if expected_config['scanlimit']:
|
||||
+ 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']}"
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
@@ -616,7 +625,7 @@ class Backend(DSLdapObject):
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
# Check matching rules
|
||||
- expected_mr = expected_config['matching_rule']
|
||||
+ expected_mr = expected_config.get('matching_rule')
|
||||
if expected_mr:
|
||||
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
|
||||
if expected_mr.lower() not in actual_mrs_lower:
|
||||
@@ -638,7 +647,6 @@ class Backend(DSLdapObject):
|
||||
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}")
|
||||
discrepancies.append(f"Unable to check index {attr_name}: {str(e)}")
|
||||
@@ -907,11 +915,11 @@ class Backend(DSLdapObject):
|
||||
|
||||
if idlistscanlimit is not None:
|
||||
scanlimits = []
|
||||
- for scanlimit in idlistscanlimit:
|
||||
+ for scanlimit in idlistscanlimit:
|
||||
scanlimits.append(scanlimit)
|
||||
# Only add if there are actually limits in the list.
|
||||
if len(scanlimits) > 0:
|
||||
- props['nsIndexIDListScanLimit'] = mrs
|
||||
+ props['nsIndexIDListScanLimit'] = scanlimits
|
||||
|
||||
new_index.create(properties=props, basedn="cn=index," + self._dn)
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
From aa0240c73c000f1d8a009a7394b04afc806504b3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 9 Jan 2026 11:39:50 +0100
|
||||
Subject: [PATCH 1/2] Issue 7172 - Index ordering mismatch after upgrade
|
||||
(#7173)
|
||||
|
||||
Bug Description:
|
||||
Commit daf731f55071d45eaf403a52b63d35f4e699ff28 introduced a regression.
|
||||
After upgrading to a version that adds `integerOrderingMatch` matching
|
||||
rule to `parentid` and `ancestorid` indexes, searches may return empty
|
||||
or incorrect results.
|
||||
|
||||
This happens because the existing index data was created with
|
||||
lexicographic ordering, but the new compare function expects integer
|
||||
ordering. Index lookups fail because the compare function doesn't match
|
||||
the data ordering.
|
||||
The root cause is that `ldbm_instance_create_default_indexes()` calls
|
||||
`attr_index_config()` unconditionally for `parentid` and `ancestorid`
|
||||
indexes, which triggers `ainfo_dup()` to overwrite `ai_key_cmp_fn` on
|
||||
existing indexes. This breaks indexes that were created without the
|
||||
`integerOrderingMatch` matching rule.
|
||||
|
||||
Fix Description:
|
||||
* Call `attr_index_config()` for `parentid` and `ancestorid` indexes
|
||||
only if index config doesn't exist.
|
||||
|
||||
* Add `upgrade_check_id_index_matching_rule()` that logs an error on
|
||||
server startup if `parentid` or `ancestorid` indexes are missing the
|
||||
integerOrderingMatch matching rule, advising administrators to reindex.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7172
|
||||
|
||||
Reviewed by: @tbordaz, @progier389, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 25 +++++++++++++++++--------
|
||||
1 file changed, 17 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 65084c61c..04340a992 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -191,6 +191,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
char *ancestorid_indexes_limit = NULL;
|
||||
char *parentid_indexes_limit = NULL;
|
||||
struct attrinfo *ai = NULL;
|
||||
+ struct attrinfo *index_already_configured = NULL;
|
||||
struct index_idlistsizeinfo *iter;
|
||||
int cookie;
|
||||
int limit;
|
||||
@@ -255,10 +256,14 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
|
||||
- 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);
|
||||
+ ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
+ index_already_configured = ai;
|
||||
+ 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);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
@@ -302,10 +307,14 @@ 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", 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);
|
||||
+ ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
+ index_already_configured = ai;
|
||||
+ 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);
|
||||
+ }
|
||||
}
|
||||
|
||||
slapi_ch_free_string(&ancestorid_indexes_limit);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
From 2d9618da04161d7aecf2a19f5c06cd0b5749921c Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 12 Jan 2026 10:58:02 +0100
|
||||
Subject: [PATCH 2/2] Issue 7172 - (2nd) Index ordering mismatch after upgrade
|
||||
(#7180)
|
||||
|
||||
Commit 742c12e0247ab64e87da000a4de2f3e5c99044ab introduced a regression
|
||||
where the check to skip creating parentid/ancestorid indexes if they
|
||||
already exist was incorrect.
|
||||
The `ainfo_get()` function falls back to returning
|
||||
LDBM_PSEUDO_ATTR_DEFAULT attrinfo when the requested attribute is not
|
||||
found.
|
||||
Since LDBM_PSEUDO_ATTR_DEFAULT is created before the ancestorid check,
|
||||
`ainfo_get()` returns LDBM_PSEUDO_ATTR_DEFAULT instead of NULL, causing
|
||||
the ancestorid index creation to be skipped entirely.
|
||||
|
||||
When operations later try to use the ancestorid index, they fall back to
|
||||
LDBM_PSEUDO_ATTR_DEFAULT, and attempting to open the .default dbi
|
||||
mid-transaction fails with MDB_NOTFOUND (-30798).
|
||||
|
||||
Fix Description:
|
||||
Instead of just checking if `ainfo_get()` returns non-NULL, verify that
|
||||
the returned attrinfo is actually for the requested attribute.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7172
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 8 +++++---
|
||||
1 file changed, 5 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 04340a992..29643f7e4 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -191,7 +191,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
char *ancestorid_indexes_limit = NULL;
|
||||
char *parentid_indexes_limit = NULL;
|
||||
struct attrinfo *ai = NULL;
|
||||
- struct attrinfo *index_already_configured = NULL;
|
||||
+ int index_already_configured = 0;
|
||||
struct index_idlistsizeinfo *iter;
|
||||
int cookie;
|
||||
int limit;
|
||||
@@ -257,7 +257,8 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
}
|
||||
|
||||
ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
- index_already_configured = 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);
|
||||
@@ -308,7 +309,8 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
- index_already_configured = 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);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,235 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,789 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,312 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,313 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,307 +0,0 @@
|
||||
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
@ -1,34 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,537 +0,0 @@
|
||||
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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user