Resolves: RHEL-61347 - Directory Server is unavailable after a restart with nsslapd-readonly=on and consumes 100% CPU
This commit is contained in:
parent
55b0293847
commit
ecd5366aec
351
0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch
Normal file
351
0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch
Normal file
@ -0,0 +1,351 @@
|
||||
From 4eef34cec551582d1de23266bc6cde84a7e38b5d Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 24 Mar 2025 10:43:21 +0100
|
||||
Subject: [PATCH] Issue 6680 - instance read-only mode is broken (#6681)
|
||||
|
||||
Read only mode is broken because some plugins fails to starts as they are not able to create/updates some entries in the dse backend.
|
||||
Solution is to allow interrnal operations to write in dse.backend but not modify the dse.ldif (except for the special case when trying to modify nsslapd-readonly flags (to be allowed to set/unset the readonly mode)
|
||||
|
||||
Issue: #6680
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (thanks!)
|
||||
---
|
||||
.../tests/suites/config/regression_test.py | 60 ++++++++++
|
||||
ldap/servers/slapd/dse.c | 110 +++++++++++++++++-
|
||||
ldap/servers/slapd/mapping_tree.c | 90 ++++++++++++--
|
||||
3 files changed, 247 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/regression_test.py b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
index 8dbba8cd2f..6e313ac8ab 100644
|
||||
--- a/dirsrvtests/tests/suites/config/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
@@ -28,6 +28,8 @@ CUSTOM_MEM = '9100100100'
|
||||
IDLETIMEOUT = 5
|
||||
DN_TEST_USER = f'uid={TEST_USER_PROPERTIES["uid"]},ou=People,{DEFAULT_SUFFIX}'
|
||||
|
||||
+RO_ATTR = 'nsslapd-readonly'
|
||||
+
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def idletimeout_topo(topo, request):
|
||||
@@ -190,3 +192,61 @@ def test_idletimeout(idletimeout_topo, dn, expected_result):
|
||||
except ldap.SERVER_DOWN:
|
||||
result = True
|
||||
assert expected_result == result
|
||||
+
|
||||
+
|
||||
+def test_instance_readonly_mode(topo):
|
||||
+ """Check that readonly mode is supported
|
||||
+
|
||||
+ :id: 34d2e28e-04d7-11f0-b0cf-482ae39447e5
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Set readonly mode
|
||||
+ 2. Stop the instance
|
||||
+ 3. Get dse.ldif modification time
|
||||
+ 4. Start the instance
|
||||
+ 5. Get dse.ldif modification time
|
||||
+ 6. Check that modification time has not changed
|
||||
+ 7. Check that readonly mode is set
|
||||
+ 8. Try to modify another config attribute
|
||||
+ 9. Unset readonly mode
|
||||
+ 10. Restart the instance
|
||||
+ 11. Check that modification time has not changed
|
||||
+ 12. Check that modification time has changed
|
||||
+ 13. Check that readonly mode is unset
|
||||
+ 14. Try to modify another config attribute
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Should get ldap.UNWILLING_TO_PERFORM exception
|
||||
+ 9. Success
|
||||
+ 10. Success
|
||||
+ 11. Success
|
||||
+ 12. Success
|
||||
+ 13. Success
|
||||
+ 14. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ dse_path = f'{topo.standalone.get_config_dir()}/dse.ldif'
|
||||
+ inst.config.replace(RO_ATTR, 'on')
|
||||
+ inst.stop()
|
||||
+ dse_mtime = os.stat(dse_path).st_mtime
|
||||
+ inst.start()
|
||||
+ new_dse_mtime = os.stat(dse_path).st_mtime
|
||||
+ assert dse_mtime == new_dse_mtime
|
||||
+ assert inst.config.get_attr_val_utf8(RO_ATTR) == "on"
|
||||
+ attr = 'nsslapd-errorlog-maxlogsize'
|
||||
+ val = inst.config.get_attr_val_utf8(attr)
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ inst.config.replace(attr, val)
|
||||
+ inst.config.replace(RO_ATTR, 'off')
|
||||
+ inst.restart()
|
||||
+ new_dse_mtime = os.stat(dse_path).st_mtime
|
||||
+ assert dse_mtime != new_dse_mtime
|
||||
+ assert inst.config.get_attr_val_utf8(RO_ATTR) == "off"
|
||||
+ inst.config.replace(attr, val)
|
||||
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
|
||||
index e3157c1ce5..0f266f0d70 100644
|
||||
--- a/ldap/servers/slapd/dse.c
|
||||
+++ b/ldap/servers/slapd/dse.c
|
||||
@@ -1031,6 +1031,114 @@ dse_check_for_readonly_error(Slapi_PBlock *pb, struct dse *pdse)
|
||||
return rc; /* no error */
|
||||
}
|
||||
|
||||
+/* Trivial wrapper around slapi_re_comp to handle errors */
|
||||
+static Slapi_Regex *
|
||||
+recomp(const char *regexp)
|
||||
+{
|
||||
+ char *error = "";
|
||||
+ Slapi_Regex *re = slapi_re_comp(regexp, &error);
|
||||
+ if (re == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "is_readonly_set_in_dse",
|
||||
+ "Failed to compile '%s' regular expression. Error is %s\n",
|
||||
+ regexp, error);
|
||||
+ }
|
||||
+ slapi_ch_free_string(&error);
|
||||
+ return re;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check if "nsslapd-readonly: on" is in cn-config in dse.ldif file
|
||||
+ * ( If the flag is set in memory but on in the file, the file should
|
||||
+ * be written (to let dsconf able to modify the nsslapd-readonly flag)
|
||||
+ */
|
||||
+static bool
|
||||
+is_readonly_set_in_dse(const char *dsename)
|
||||
+{
|
||||
+ Slapi_Regex *re_config = recomp("^dn:\\s+cn=config\\s*$");
|
||||
+ Slapi_Regex *re_isro = recomp("^" CONFIG_READONLY_ATTRIBUTE ":\\s+on\\s*$");
|
||||
+ Slapi_Regex *re_eoe = recomp("^$");
|
||||
+ bool isconfigentry = false;
|
||||
+ bool isro = false;
|
||||
+ FILE *fdse = NULL;
|
||||
+ char line[128];
|
||||
+ char *error = NULL;
|
||||
+ const char *regexp = "";
|
||||
+
|
||||
+ if (!dsename) {
|
||||
+ goto done;
|
||||
+ }
|
||||
+ if (re_config == NULL || re_isro == NULL || re_eoe == NULL) {
|
||||
+ goto done;
|
||||
+ }
|
||||
+ fdse = fopen(dsename, "r");
|
||||
+ if (fdse == NULL) {
|
||||
+ /* No dse file, we need to write it */
|
||||
+ goto done;
|
||||
+ }
|
||||
+ while (fgets(line, (sizeof line), fdse)) {
|
||||
+ /* Convert the read line to lowercase */
|
||||
+ for (char *pt=line; *pt; pt++) {
|
||||
+ if (isalpha(*pt)) {
|
||||
+ *pt = tolower(*pt);
|
||||
+ }
|
||||
+ }
|
||||
+ if (slapi_re_exec_nt(re_config, line)) {
|
||||
+ isconfigentry = true;
|
||||
+ }
|
||||
+ if (slapi_re_exec_nt(re_eoe, line)) {
|
||||
+ if (isconfigentry) {
|
||||
+ /* End of config entry ==> readonly flag is not set */
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ if (isconfigentry && slapi_re_exec_nt(re_isro, line)) {
|
||||
+ /* Found readonly flag */
|
||||
+ isro = true;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+done:
|
||||
+ if (fdse) {
|
||||
+ (void) fclose(fdse);
|
||||
+ }
|
||||
+ slapi_re_free(re_config);
|
||||
+ slapi_re_free(re_isro);
|
||||
+ slapi_re_free(re_eoe);
|
||||
+ return isro;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check if dse.ldif can be written
|
||||
+ * Beware that even in read-only mode dse.ldif file
|
||||
+ * should still be written to change the nsslapd-readonly value
|
||||
+ */
|
||||
+static bool
|
||||
+check_if_readonly(struct dse *pdse)
|
||||
+{
|
||||
+ static bool ro = false;
|
||||
+
|
||||
+ if (pdse->dse_filename == NULL) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (!slapi_config_get_readonly()) {
|
||||
+ ro = false;
|
||||
+ return ro;
|
||||
+ }
|
||||
+ if (ro) {
|
||||
+ /* read-only mode and dse is up to date ==> Do not modify it. */
|
||||
+ return ro;
|
||||
+ }
|
||||
+ /* First attempt to write the dse.ldif since readonly mode is enabled.
|
||||
+ * Lets check if "nsslapd-readonly: on" is in cn=config entry
|
||||
+ * and allow to write the dse.ldif if it is the case
|
||||
+ */
|
||||
+ if (is_readonly_set_in_dse(pdse->dse_filename)) {
|
||||
+ /* read-only mode and dse is up to date ==> Do not modify it. */
|
||||
+ ro = true;
|
||||
+ }
|
||||
+ /* Read only mode but nsslapd-readonly value is not up to date. */
|
||||
+ return ro;
|
||||
+}
|
||||
|
||||
/*
|
||||
* Write the AVL tree of entries back to the LDIF file.
|
||||
@@ -1041,7 +1149,7 @@ dse_write_file_nolock(struct dse *pdse)
|
||||
FPWrapper fpw;
|
||||
int rc = 0;
|
||||
|
||||
- if (dont_ever_write_dse_files) {
|
||||
+ if (dont_ever_write_dse_files || check_if_readonly(pdse)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/mapping_tree.c b/ldap/servers/slapd/mapping_tree.c
|
||||
index dd7b1af37c..e51b3b9484 100644
|
||||
--- a/ldap/servers/slapd/mapping_tree.c
|
||||
+++ b/ldap/servers/slapd/mapping_tree.c
|
||||
@@ -2058,6 +2058,82 @@ slapi_dn_write_needs_referral(Slapi_DN *target_sdn, Slapi_Entry **referral)
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
+
|
||||
+/*
|
||||
+ * This function dermines if an operation should be rejected
|
||||
+ * when readonly mode is enabled.
|
||||
+ * All operations are rejected except:
|
||||
+ * - if they target a private backend that is not the DSE backend
|
||||
+ * - if they are read operations (SEARCH, COMPARE, BIND, UNBIND)
|
||||
+ * - if they are tombstone fixup operation (i.e: tombstone purging)
|
||||
+ * - if they are internal operation that targets the DSE backend.
|
||||
+ * (change will then be done in memory but not written in dse.ldif)
|
||||
+ * - single modify modify operation on cn=config changing nsslapd-readonly
|
||||
+ * (to allow "dsconf instance config replace nsslapd-readonly=xxx",
|
||||
+ change will then be done both in memory and in dse.ldif)
|
||||
+ */
|
||||
+static bool
|
||||
+is_rejected_op(Slapi_Operation *op, Slapi_Backend *be)
|
||||
+{
|
||||
+ const char *betype = slapi_be_gettype(be);
|
||||
+ unsigned long be_op_type = operation_get_type(op);
|
||||
+ int isdse = (betype && strcmp(betype, "DSE") == 0);
|
||||
+
|
||||
+ /* Private backend operations are not rejected */
|
||||
+
|
||||
+ /* Read operations are not rejected */
|
||||
+ if ((be_op_type == SLAPI_OPERATION_SEARCH) ||
|
||||
+ (be_op_type == SLAPI_OPERATION_COMPARE) ||
|
||||
+ (be_op_type == SLAPI_OPERATION_BIND) ||
|
||||
+ (be_op_type == SLAPI_OPERATION_UNBIND)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ /* Tombstone fixup are not rejected. */
|
||||
+ if (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_FIXUP)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (!isdse) {
|
||||
+ /* write operation on readonly backends are rejected */
|
||||
+ if (be->be_readonly) {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ /* private backends (DSE excepted) are not backed on files
|
||||
+ * so write operations are accepted.
|
||||
+ * but other operations (not on DSE) are rejected.
|
||||
+ */
|
||||
+ if (slapi_be_private(be)) {
|
||||
+ return false;
|
||||
+ } else {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Allowed operations in dse backend are:
|
||||
+ * - the internal operations and
|
||||
+ * - modify of nsslapd-readonly flag in cn=config
|
||||
+ */
|
||||
+
|
||||
+ if (operation_is_flag_set(op, OP_FLAG_INTERNAL)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (be_op_type == SLAPI_OPERATION_MODIFY) {
|
||||
+ Slapi_DN *sdn = operation_get_target_spec(op);
|
||||
+ Slapi_DN config = {0};
|
||||
+ LDAPMod **mods = op->o_params.p.p_modify.modify_mods;
|
||||
+ slapi_sdn_init_ndn_byref(&config, SLAPD_CONFIG_DN);
|
||||
+ if (mods && mods[0] && !mods[1] &&
|
||||
+ slapi_sdn_compare(sdn, &config) == 0 &&
|
||||
+ strcasecmp(mods[0]->mod_type, CONFIG_READONLY_ATTRIBUTE) == 0) {
|
||||
+ /* Single modifier impacting nsslapd-readonly */
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Description:
|
||||
* The reason we have a mapping tree. This function selects a backend or
|
||||
@@ -2095,7 +2171,6 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re
|
||||
int ret;
|
||||
int scope = LDAP_SCOPE_BASE;
|
||||
int op_type;
|
||||
- int fixup = 0;
|
||||
|
||||
if (slapi_atomic_load_32(&mapping_tree_freed, __ATOMIC_RELAXED)) {
|
||||
/* shutdown detected */
|
||||
@@ -2112,7 +2187,6 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re
|
||||
|
||||
/* Get the target for this op */
|
||||
target_sdn = operation_get_target_spec(op);
|
||||
- fixup = operation_is_flag_set(op, OP_FLAG_TOMBSTONE_FIXUP);
|
||||
|
||||
PR_ASSERT(mapping_tree_inited == 1);
|
||||
|
||||
@@ -2161,22 +2235,14 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re
|
||||
* or if the whole server is readonly AND backend is public (!private)
|
||||
*/
|
||||
if ((ret == LDAP_SUCCESS) && *be && !be_isdeleted(*be) &&
|
||||
- (((*be)->be_readonly && !fixup) ||
|
||||
- ((slapi_config_get_readonly() && !fixup) &&
|
||||
- !slapi_be_private(*be)))) {
|
||||
- unsigned long be_op_type = operation_get_type(op);
|
||||
-
|
||||
- if ((be_op_type != SLAPI_OPERATION_SEARCH) &&
|
||||
- (be_op_type != SLAPI_OPERATION_COMPARE) &&
|
||||
- (be_op_type != SLAPI_OPERATION_BIND) &&
|
||||
- (be_op_type != SLAPI_OPERATION_UNBIND)) {
|
||||
+ ((*be)->be_readonly || slapi_config_get_readonly()) &&
|
||||
+ is_rejected_op(op, *be)) {
|
||||
if (errorbuf) {
|
||||
PL_strncpyz(errorbuf, slapi_config_get_readonly() ? "Server is read-only" : "database is read-only", ebuflen);
|
||||
}
|
||||
ret = LDAP_UNWILLING_TO_PERFORM;
|
||||
slapi_be_Unlock(*be);
|
||||
*be = NULL;
|
||||
- }
|
||||
}
|
||||
|
||||
return ret;
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -47,7 +47,7 @@ ExcludeArch: i686
|
||||
Summary: 389 Directory Server (base)
|
||||
Name: 389-ds-base
|
||||
Version: 2.7.0
|
||||
Release: 3%{?dist}
|
||||
Release: 4%{?dist}
|
||||
License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (0BSD OR Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT OR Zlib) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND MIT AND MPL-2.0 AND Zlib
|
||||
URL: https://www.port389.org
|
||||
Conflicts: selinux-policy-base < 3.9.8
|
||||
@ -284,6 +284,7 @@ Source4: 389-ds-base.sysusers
|
||||
|
||||
Patch: 0001-Issue-6377-syntax-error-in-setup.py-6378.patch
|
||||
Patch: 0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch
|
||||
Patch: 0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch
|
||||
|
||||
%description
|
||||
389 Directory Server is an LDAPv3 compliant server. The base package includes
|
||||
@ -726,8 +727,10 @@ exit 0
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Tue Jul 01 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-3
|
||||
* Mon Jul 21 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-4
|
||||
- Resolves: RHEL-61347 - Directory Server is unavailable after a restart with nsslapd-readonly=on and consumes 100% CPU
|
||||
|
||||
* Tue Jul 01 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-3
|
||||
- Resolves: RHEL-77983 - Defects found by OpenScanHub
|
||||
- Resolves: RHEL-79673 - Improve the "result" field of ipa-healthcheck if replicas are busy
|
||||
- Resolves: RHEL-80496 - Can't rename users member of automember rule [rhel-9]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user