import OL 389-ds-base-2.6.1-8.el9_6

This commit is contained in:
eabdullin 2025-05-23 12:04:18 +00:00
parent 949299fbd4
commit 4ea200d003
56 changed files with 64278 additions and 6814 deletions

View File

@ -1,3 +1,3 @@
274dec37976c1efde9cbeb458d50bbcd6b244974 SOURCES/389-ds-base-2.5.2.tar.bz2
25969f6e65d79aa29671eff7185e4307ff3c08a0 SOURCES/389-ds-base-2.6.1.tar.bz2
1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2
c9dfe857929ddade41096e6d387f593f79d05ea0 SOURCES/vendor-2.5.2-1.tar.gz
110a41a9286255a008309620c3be3b3965e579dc SOURCES/vendor-2.6.1-1.tar.gz

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
SOURCES/389-ds-base-2.5.2.tar.bz2
SOURCES/389-ds-base-2.6.1.tar.bz2
SOURCES/jemalloc-5.3.0.tar.bz2
SOURCES/vendor-2.5.2-1.tar.gz
SOURCES/vendor-2.6.1-1.tar.gz

View File

@ -1,60 +0,0 @@
From 0ff5aa641d619bdcc154c2c94f8f8180bcaec776 Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Thu, 29 Aug 2024 10:49:57 +0200
Subject: [PATCH] Issue 6312 - In branch 2.5, healthcheck report an invalid
warning regarding BDB deprecation (#6313)
Bug description:
during healthcheck, _lint_backend_implementation checks that
the instance is not running a BDB backend.
This check only applies for instance after 3.0.0
Fix description:
If the instance is newer than 3.0.0 the health check
just returns
relates: #6312
Reviewed by:
---
dirsrvtests/tests/suites/healthcheck/healthcheck_test.py | 1 +
src/lib389/lib389/backend.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py b/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
index 29cca187e..66cf3c7d3 100644
--- a/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
@@ -556,6 +556,7 @@ def test_lint_backend_implementation_wrong_files(topology_st):
@pytest.mark.skipif(get_default_db_lib() == "mdb", reason="Not needed for mdb")
+@pytest.mark.skipif(ds_is_older("3.0.0"), reason="mdb and bdb are both supported")
def test_lint_backend_implementation(topology_st):
"""Test the lint for backend implementation mismatch
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index caee88e6a..0ed00a4a7 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -14,7 +14,7 @@ from lib389._constants import DN_LDBM, DN_CHAIN, DN_PLUGIN, DEFAULT_BENAME
from lib389.properties import BACKEND_OBJECTCLASS_VALUE, BACKEND_PROPNAME_TO_ATTRNAME, BACKEND_CHAIN_BIND_DN, \
BACKEND_CHAIN_BIND_PW, BACKEND_CHAIN_URLS, BACKEND_PROPNAME_TO_ATTRNAME, BACKEND_NAME, \
BACKEND_SUFFIX, BACKEND_SAMPLE_ENTRIES, TASK_WAIT
-from lib389.utils import normalizeDN, ensure_str, assert_c
+from lib389.utils import normalizeDN, ensure_str, assert_c, ds_is_newer
from lib389 import Entry
# Need to fix this ....
@@ -513,7 +513,7 @@ class Backend(DSLdapObject):
def _lint_backend_implementation(self):
backend_impl = self._instance.get_db_lib()
- if backend_impl == 'bdb':
+ if backend_impl == 'bdb' and ds_is_newer('3.0.0', instance=self._instance):
result = DSBLE0006
result['items'] = [self.lint_uid()]
yield result
--
2.46.0

View File

@ -0,0 +1,60 @@
From 0921400a39b61687db2bc55ebd5021eef507e960 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 28 Jan 2025 21:05:49 +0100
Subject: [PATCH] Issue 6468 - Fix building for older versions of Python
Bug Description:
Structural Pattern Matching has been added in Python 3.10, older version
do not support it.
Fix Description:
Replace `match` and `case` statements with `if-elif`.
Relates: https://github.com/389ds/389-ds-base/issues/6468
Reviewed by: @droideck (Thanks!)
---
src/lib389/lib389/cli_conf/logging.py | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/src/lib389/lib389/cli_conf/logging.py b/src/lib389/lib389/cli_conf/logging.py
index 2e86f2de8..d1e32822c 100644
--- a/src/lib389/lib389/cli_conf/logging.py
+++ b/src/lib389/lib389/cli_conf/logging.py
@@ -234,19 +234,20 @@ def get_log_config(inst, basedn, log, args):
attr_map = {}
levels = {}
- match args.logtype:
- case "access":
- attr_map = ACCESS_ATTR_MAP
- levels = ACCESS_LEVELS
- case "error":
- attr_map = ERROR_ATTR_MAP
- levels = ERROR_LEVELS
- case "security":
- attr_map = SECURITY_ATTR_MAP
- case "audit":
- attr_map = AUDIT_ATTR_MAP
- case "auditfail":
- attr_map = AUDITFAIL_ATTR_MAP
+ if args.logtype == "access":
+ attr_map = ACCESS_ATTR_MAP
+ levels = ACCESS_LEVELS
+ elif args.logtype == "error":
+ attr_map = ERROR_ATTR_MAP
+ levels = ERROR_LEVELS
+ elif args.logtype == "security":
+ attr_map = SECURITY_ATTR_MAP
+ elif args.logtype == "audit":
+ attr_map = AUDIT_ATTR_MAP
+ elif args.logtype == "auditfail":
+ attr_map = AUDITFAIL_ATTR_MAP
+ else:
+ raise ValueError(f"Unknown logtype: {args.logtype}")
sorted_results = []
for attr, value in attrs.items():
--
2.48.0

View File

@ -1,237 +0,0 @@
From af27f433ec14bcaf070108ab0b6af64ad1153a11 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 6 Sep 2024 18:07:17 +0200
Subject: [PATCH] Issue 6316 - lmdb reindex is broken if index type is
specified (#6318)
While reindexing using task or offline reindex, if the attribute name contains the index type (for example :eq,pres)
Then the attribute is not reindexed. Problem occurs when lmdb is used, things are working fine with bdb.
Solution: strip the index type in reindex as it is done in bdb case.
Anyway the reindex design requires that for a given attribute all the configured index types must be rebuild.
Issue: #6316
Reviewed by: @tbordaz, @droideck (Thanks!)
---
.../tests/suites/indexes/regression_test.py | 141 +++++++++++++++++-
.../slapd/back-ldbm/db-mdb/mdb_import.c | 10 +-
2 files changed, 147 insertions(+), 4 deletions(-)
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
index c385f5ca4..b077b529a 100644
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
@@ -10,6 +10,9 @@ import time
import os
import pytest
import ldap
+import logging
+import glob
+import re
from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX
from lib389.backend import Backend, Backends, DatabaseConfig
from lib389.cos import CosClassicDefinition, CosClassicDefinitions, CosTemplate
@@ -31,6 +34,8 @@ SUFFIX2 = 'dc=example2,dc=com'
BENAME2 = 'be2'
DEBUGGING = os.getenv("DEBUGGING", default=False)
+logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
@pytest.fixture(scope="function")
@@ -83,6 +88,7 @@ def add_a_group_with_users(request, topo):
'cn': USER_NAME,
'uidNumber': f'{num}',
'gidNumber': f'{num}',
+ 'description': f'Description for {USER_NAME}',
'homeDirectory': f'/home/{USER_NAME}'
})
users_list.append(user)
@@ -95,9 +101,10 @@ def add_a_group_with_users(request, topo):
# If the server crashed, start it again to do the cleanup
if not topo.standalone.status():
topo.standalone.start()
- for user in users_list:
- user.delete()
- group.delete()
+ if not DEBUGGING:
+ for user in users_list:
+ user.delete()
+ group.delete()
request.addfinalizer(fin)
@@ -124,6 +131,38 @@ def set_small_idlistscanlimit(request, topo):
request.addfinalizer(fin)
+
+@pytest.fixture(scope="function")
+def set_description_index(request, topo, add_a_group_with_users):
+ """
+ Set some description values and description index without reindexing.
+ """
+ inst = topo.standalone
+ backends = Backends(inst)
+ backend = backends.get(DEFAULT_BENAME)
+ indexes = backend.get_indexes()
+ attr = 'description'
+
+ def fin(always=False):
+ if always or not DEBUGGING:
+ try:
+ idx = indexes.get(attr)
+ idx.delete()
+ except ldap.NO_SUCH_OBJECT:
+ pass
+
+ request.addfinalizer(fin)
+ fin(always=True)
+ index = indexes.create(properties={
+ 'cn': attr,
+ 'nsSystemIndex': 'false',
+ 'nsIndexType': ['eq', 'pres', 'sub']
+ })
+ # Restart needed with lmdb (to open the dbi handle)
+ inst.restart()
+ return (indexes, attr)
+
+
#unstable or unstatus tests, skipped for now
@pytest.mark.flaky(max_runs=2, min_passes=1)
@pytest.mark.skipif(ds_is_older("1.4.4.4"), reason="Not implemented")
@@ -347,6 +386,102 @@ def test_task_status(topo):
assert reindex_task.get_exit_code() == 0
+def count_keys(inst, bename, attr, prefix=''):
+ indexfile = os.path.join(inst.dbdir, bename, attr + '.db')
+ # (bdb - we should also accept a version number for .db suffix)
+ for f in glob.glob(f'{indexfile}*'):
+ indexfile = f
+
+ inst.stop()
+ output = inst.dbscan(None, None, args=['-f', indexfile, '-A'], stopping=False).decode()
+ inst.start()
+ count = 0
+ regexp = f'^KEY: {re.escape(prefix)}'
+ for match in re.finditer(regexp, output, flags=re.MULTILINE):
+ count += 1
+ log.info(f"count_keys found {count} keys starting with '{prefix}' in {indexfile}")
+ return count
+
+
+def test_reindex_task_with_type(topo, set_description_index):
+ """Check that reindex task works as expected when index type is specified.
+
+ :id: 0c7f2fda-69f6-11ef-9eb8-083a88554478
+ :setup: Standalone instance
+ - with 100 users having description attribute
+ - with description:eq,pres,sub index entry but not yet reindexed
+ :steps:
+ 1. Set description in suffix entry
+ 2. Count number of equality keys in description index
+ 3. Start a Reindex task on description:eq,pres and wait for completion
+ 4. Check the task status and exit code
+ 5. Count the equality, presence and substring keys in description index
+ 6. Start a Reindex task on description and wait for completion
+ 7. Check the task status and exit code
+ 8. Count the equality, presence and substring keys in description index
+
+ :expectedresults:
+ 1. Success
+ 2. Should be either no key (bdb) or a single one (lmdb)
+ 3. Success
+ 4. Success
+ 5. Should have: more equality keys than in step 2
+ one presence key
+ some substrings keys
+ 6. Success
+ 7. Success
+ 8. Should have same counts than in step 5
+ """
+ (indexes, attr) = set_description_index
+ inst = topo.standalone
+ if not inst.is_dbi_supported():
+ pytest.skip('This test requires that dbscan supports -A option')
+ # modify indexed value
+ Domain(inst, DEFAULT_SUFFIX).replace(attr, f'test_before_reindex')
+
+ keys1 = count_keys(inst, DEFAULT_BENAME, attr, prefix='=')
+ assert keys1 <= 1
+
+ tasks = Tasks(topo.standalone)
+ # completed reindex tasks MUST have a status because freeipa check it.
+
+ # Reindex attr with eq,pres types
+ log.info(f'Reindex {attr} with eq,pres types')
+ tasks.reindex(
+ suffix=DEFAULT_SUFFIX,
+ attrname=f'{attr}:eq,pres',
+ args={TASK_WAIT: True}
+ )
+ reindex_task = Task(topo.standalone, tasks.dn)
+ assert reindex_task.status()
+ assert reindex_task.get_exit_code() == 0
+
+ keys2e = count_keys(inst, DEFAULT_BENAME, attr, prefix='=')
+ keys2p = count_keys(inst, DEFAULT_BENAME, attr, prefix='+')
+ keys2s = count_keys(inst, DEFAULT_BENAME, attr, prefix='*')
+ assert keys2e > keys1
+ assert keys2p > 0
+ assert keys2s > 0
+
+ # Reindex attr without types
+ log.info(f'Reindex {attr} without types')
+ tasks.reindex(
+ suffix=DEFAULT_SUFFIX,
+ attrname=attr,
+ args={TASK_WAIT: True}
+ )
+ reindex_task = Task(topo.standalone, tasks.dn)
+ assert reindex_task.status()
+ assert reindex_task.get_exit_code() == 0
+
+ keys3e = count_keys(inst, DEFAULT_BENAME, attr, prefix='=')
+ keys3p = count_keys(inst, DEFAULT_BENAME, attr, prefix='+')
+ keys3s = count_keys(inst, DEFAULT_BENAME, attr, prefix='*')
+ assert keys3e == keys2e
+ assert keys3p == keys2p
+ assert keys3s == keys2s
+
+
def test_task_and_be(topo, add_backend_and_ldif_50K_users):
"""Check that backend is writable after finishing a tasks
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 d57146953..ce2151174 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
@@ -1150,6 +1150,8 @@ process_db2index_attrs(Slapi_PBlock *pb, ImportCtx_t *ctx)
* TBD
*/
char **attrs = NULL;
+ char *attrname = NULL;
+ char *pt = NULL;
int i;
slapi_pblock_get(pb, SLAPI_DB2INDEX_ATTRS, &attrs);
@@ -1157,7 +1159,13 @@ process_db2index_attrs(Slapi_PBlock *pb, ImportCtx_t *ctx)
for (i = 0; attrs && attrs[i]; i++) {
switch (attrs[i][0]) {
case 't': /* attribute type to index */
- slapi_ch_array_add(&ctx->indexAttrs, slapi_ch_strdup(attrs[i] + 1));
+ attrname = slapi_ch_strdup(attrs[i] + 1);
+ /* Strip index type */
+ pt = strchr(attrname, ':');
+ if (pt != NULL) {
+ *pt = '\0';
+ }
+ slapi_ch_array_add(&ctx->indexAttrs, attrname);
break;
case 'T': /* VLV Search to index */
slapi_ch_array_add(&ctx->indexVlvs, get_vlv_dbname(attrs[i] + 1));
--
2.46.0

View File

@ -0,0 +1,146 @@
From 12f9bf81e834549db02b1243ecf769b511c9f69f Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 31 Jan 2025 08:54:27 -0500
Subject: [PATCH] Issue 6489 - After log rotation refresh the FD pointer
Description:
When flushing a log buffer we get a FD for log prior to checking if the
log should be rotated. If the log is rotated that FD reference is now
invalid, and it needs to be refrehed before proceeding
Relates: https://github.com/389ds/389-ds-base/issues/6489
Reviewed by: tbordaz(Thanks!)
---
.../suites/logging/log_flush_rotation_test.py | 81 +++++++++++++++++++
ldap/servers/slapd/log.c | 18 +++++
2 files changed, 99 insertions(+)
create mode 100644 dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
diff --git a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
new file mode 100644
index 000000000..b33a622e1
--- /dev/null
+++ b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
@@ -0,0 +1,81 @@
+# --- 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 logging
+import time
+import pytest
+from lib389._constants import DEFAULT_SUFFIX, PW_DM
+from lib389.tasks import ImportTask
+from lib389.idm.user import UserAccounts
+from lib389.topologies import topology_st as topo
+
+
+log = logging.getLogger(__name__)
+
+
+def test_log_flush_and_rotation_crash(topo):
+ """Make sure server does not crash whening flushing a buffer and rotating
+ the log at the same time
+
+ :id: d4b0af2f-48b2-45f5-ae8b-f06f692c3133
+ :setup: Standalone Instance
+ :steps:
+ 1. Enable all logs
+ 2. Enable log buffering for all logs
+ 3. Set rotation time unit to 1 minute
+ 4. Make sure server is still running after 1 minute
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ """
+
+ inst = topo.standalone
+
+ # Enable logging and buffering
+ inst.config.set("nsslapd-auditlog-logging-enabled", "on")
+ inst.config.set("nsslapd-accesslog-logbuffering", "on")
+ inst.config.set("nsslapd-auditlog-logbuffering", "on")
+ inst.config.set("nsslapd-errorlog-logbuffering", "on")
+ inst.config.set("nsslapd-securitylog-logbuffering", "on")
+
+ # Set rotation policy to trigger rotation asap
+ inst.config.set("nsslapd-accesslog-logrotationtimeunit", "minute")
+ inst.config.set("nsslapd-auditlog-logrotationtimeunit", "minute")
+ inst.config.set("nsslapd-errorlog-logrotationtimeunit", "minute")
+ inst.config.set("nsslapd-securitylog-logrotationtimeunit", "minute")
+
+ #
+ # Performs ops to populate all the logs
+ #
+ # Access & audit log
+ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
+ user = users.create_test_user()
+ user.set("userPassword", PW_DM)
+ # Security log
+ user.bind(PW_DM)
+ # Error log
+ import_task = ImportTask(inst)
+ import_task.import_suffix_from_ldif(ldiffile="/not/here",
+ suffix=DEFAULT_SUFFIX)
+
+ # Wait a minute and make sure the server did not crash
+ log.info("Sleep until logs are flushed and rotated")
+ time.sleep(61)
+
+ assert inst.status()
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", CURRENT_FILE])
+
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 8352f4abd..c1260a203 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -6746,6 +6746,23 @@ log_refresh_state(int32_t log_type)
return 0;
}
}
+static LOGFD
+log_refresh_fd(int32_t log_type)
+{
+ switch (log_type) {
+ case SLAPD_ACCESS_LOG:
+ return loginfo.log_access_fdes;
+ case SLAPD_SECURITY_LOG:
+ return loginfo.log_security_fdes;
+ case SLAPD_AUDIT_LOG:
+ return loginfo.log_audit_fdes;
+ case SLAPD_AUDITFAIL_LOG:
+ return loginfo.log_auditfail_fdes;
+ case SLAPD_ERROR_LOG:
+ return loginfo.log_error_fdes;
+ }
+ return NULL;
+}
/* this function assumes the lock is already acquired */
/* if sync_now is non-zero, data is flushed to physical storage */
@@ -6857,6 +6874,7 @@ log_flush_buffer(LogBufferInfo *lbi, int log_type, int sync_now, int locked)
rotationtime_secs);
}
log_state = log_refresh_state(log_type);
+ fd = log_refresh_fd(log_type);
}
if (log_state & LOGGING_NEED_TITLE) {
--
2.48.0

View File

@ -1,53 +0,0 @@
From c9a1628de9f666a43911392220c0e83d051a51c6 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 6 Jun 2024 21:05:09 +0200
Subject: [PATCH] Issue 6192 - Test failure: test_match_large_valueset
Description:
When BDB backend is used, nsslapd-cache-autosize needs to be set to 0
first in order to change nsslapd-cachememsize.
Also increase the expected etime slightly, as it fails on slower VMs
both with BDB and MDB backends.
Fixes: https://github.com/389ds/389-ds-base/issues/6192
Reviewed by: @droideck, @tbordaz (Thanks!)
---
dirsrvtests/tests/suites/filter/filter_test.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/filter/filter_test.py b/dirsrvtests/tests/suites/filter/filter_test.py
index 4baaf04a7..b17846c01 100644
--- a/dirsrvtests/tests/suites/filter/filter_test.py
+++ b/dirsrvtests/tests/suites/filter/filter_test.py
@@ -15,7 +15,7 @@ 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._constants import PASSWORD, DEFAULT_SUFFIX, DN_DM, SUFFIX, DN_CONFIG_LDBM
from lib389.utils import *
pytestmark = pytest.mark.tier1
@@ -372,6 +372,9 @@ def test_match_large_valueset(topology_st):
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
+ if get_default_db_lib() == "bdb":
+ config_ldbm = DSLdapObject(inst, DN_CONFIG_LDBM)
+ config_ldbm.set('nsslapd-cache-autosize', '0')
be_groups.replace('nsslapd-cachememsize', groups_entrycache)
except:
raise
@@ -427,7 +430,7 @@ def test_match_large_valueset(topology_st):
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)
+ assert (etime < 5)
if __name__ == '__main__':
# Run isolated
--
2.47.1

View File

@ -0,0 +1,311 @@
From f077f9692d1625a1bc2dc6ee02a4fca71ee30b03 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Wed, 13 Nov 2024 15:31:35 +0100
Subject: [PATCH] Issue 6374 - nsslapd-mdb-max-dbs autotuning doesn't work
properly (#6400)
* Issue 6374 - nsslapd-mdb-max-dbs autotuning doesn't work properly
Several issues:
After restarting the server nsslapd-mdb-max-dbs may not be high enough to add a new backend
because the value computation is wrong.
dbscan fails to open the database if nsslapd-mdb-max-dbs has been increased.
dbscan crashes when closing the database (typically when using -S)
When starting the instance the nsslapd-mdb-max-dbs parameter is increased to ensure that a new backend may be added.
When dse.ldif path is not specified, the db environment is now open using the INFO.mdb data instead of using the default values.
synchronization between thread closure and database context destruction is hardened
Issue: #6374
Reviewed by: @tbordaz , @vashirov (Thanks!)
(cherry picked from commit 56cd3389da608a3f6eeee58d20dffbcd286a8033)
---
.../tests/suites/config/config_test.py | 86 +++++++++++++++++++
ldap/servers/slapd/back-ldbm/back-ldbm.h | 2 +
.../slapd/back-ldbm/db-mdb/mdb_config.c | 17 ++--
.../back-ldbm/db-mdb/mdb_import_threads.c | 9 +-
.../slapd/back-ldbm/db-mdb/mdb_instance.c | 8 ++
ldap/servers/slapd/back-ldbm/dbimpl.c | 2 +-
ldap/servers/slapd/back-ldbm/import.c | 14 ++-
7 files changed, 128 insertions(+), 10 deletions(-)
diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
index 57b155af7..34dac36b6 100644
--- a/dirsrvtests/tests/suites/config/config_test.py
+++ b/dirsrvtests/tests/suites/config/config_test.py
@@ -17,6 +17,7 @@ from lib389.topologies import topology_m2, topology_st as topo
from lib389.utils import *
from lib389._constants import DN_CONFIG, DEFAULT_SUFFIX, DEFAULT_BENAME
from lib389._mapped_object import DSLdapObjects
+from lib389.agreement import Agreements
from lib389.cli_base import FakeArgs
from lib389.cli_conf.backend import db_config_set
from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
@@ -27,6 +28,8 @@ from lib389.cos import CosPointerDefinitions, CosTemplates
from lib389.backend import Backends, DatabaseConfig
from lib389.monitor import MonitorLDBM, Monitor
from lib389.plugins import ReferentialIntegrityPlugin
+from lib389.replica import BootstrapReplicationManager, Replicas
+from lib389.passwd import password_generate
pytestmark = pytest.mark.tier0
@@ -36,6 +39,8 @@ PSTACK_CMD = '/usr/bin/pstack'
logging.getLogger(__name__).setLevel(logging.INFO)
log = logging.getLogger(__name__)
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+
@pytest.fixture(scope="module")
def big_file():
TEMP_BIG_FILE = ''
@@ -811,6 +816,87 @@ def test_numlisteners_limit(topo):
assert numlisteners[0] == '4'
+def bootstrap_replication(inst_from, inst_to, creds):
+ manager = BootstrapReplicationManager(inst_to)
+ rdn_val = 'replication manager'
+ if manager.exists():
+ manager.delete()
+ manager.create(properties={
+ 'cn': rdn_val,
+ 'uid': rdn_val,
+ 'userPassword': creds
+ })
+ for replica in Replicas(inst_to).list():
+ replica.remove_all('nsDS5ReplicaBindDNGroup')
+ replica.replace('nsDS5ReplicaBindDN', manager.dn)
+ for agmt in Agreements(inst_from).list():
+ agmt.replace('nsDS5ReplicaBindDN', manager.dn)
+ agmt.replace('nsDS5ReplicaCredentials', creds)
+
+
+@pytest.mark.skipif(get_default_db_lib() != "mdb", reason="This test requires lmdb")
+def test_lmdb_autotuned_maxdbs(topology_m2, request):
+ """Verify that after restart, nsslapd-mdb-max-dbs is large enough to add a new backend.
+
+ :id: 0272d432-9080-11ef-8f40-482ae39447e5
+ :setup: Two suppliers configuration
+ :steps:
+ 1. loop 20 times
+ 3. In 1 loop: restart instance
+ 3. In 1 loop: add a new backend
+ 4. In 1 loop: check that instance is still alive
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ """
+
+ s1 = topology_m2.ms["supplier1"]
+ s2 = topology_m2.ms["supplier2"]
+
+ backends = Backends(s1)
+ db_config = DatabaseConfig(s1)
+ # Generate the teardown finalizer
+ belist = []
+ creds=password_generate()
+ bootstrap_replication(s2, s1, creds)
+ bootstrap_replication(s1, s2, creds)
+
+ def fin():
+ s1.start()
+ for be in belist:
+ be.delete()
+
+ if not DEBUGGING:
+ request.addfinalizer(fin)
+
+ # 1. Set autotuning (off-line to be able to decrease the value)
+ s1.stop()
+ dse_ldif = DSEldif(s1)
+ dse_ldif.replace(db_config.dn, 'nsslapd-mdb-max-dbs', '0')
+ os.remove(f'{s1.dbdir}/data.mdb')
+ s1.start()
+
+ # 2. Reinitialize the db:
+ log.info("Bulk import...")
+ agmt = Agreements(s2).list()[0]
+ agmt.begin_reinit()
+ (done, error) = agmt.wait_reinit()
+ log.info(f'Bulk importresult is ({done}, {error})')
+ assert done is True
+ assert error is False
+
+ # 3. loop 20 times
+ for idx in range(20):
+ s1.restart()
+ log.info(f'Adding backend test{idx}')
+ belist.append(backends.create(properties={'cn': f'test{idx}',
+ 'nsslapd-suffix': f'dc=test{idx}'}))
+ assert s1.status()
+
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
index 8fea63e35..35d0ece04 100644
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
@@ -896,4 +896,6 @@ typedef struct _back_search_result_set
((L)->size == (R)->size && !memcmp((L)->data, (R)->data, (L)->size))
typedef int backend_implement_init_fn(struct ldbminfo *li, config_info *config_array);
+
+pthread_mutex_t *get_import_ctx_mutex();
#endif /* _back_ldbm_h_ */
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
index 351f54037..1f7b71442 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
@@ -83,7 +83,7 @@ dbmdb_compute_limits(struct ldbminfo *li)
uint64_t total_space = 0;
uint64_t avail_space = 0;
uint64_t cur_dbsize = 0;
- int nbchangelogs = 0;
+ int nbvlvs = 0;
int nbsuffixes = 0;
int nbindexes = 0;
int nbagmt = 0;
@@ -99,8 +99,8 @@ dbmdb_compute_limits(struct ldbminfo *li)
* But some tunable may be autotuned.
*/
if (dbmdb_count_config_entries("(objectClass=nsMappingTree)", &nbsuffixes) ||
- dbmdb_count_config_entries("(objectClass=nsIndex)", &nbsuffixes) ||
- dbmdb_count_config_entries("(&(objectClass=nsds5Replica)(nsDS5Flags=1))", &nbchangelogs) ||
+ dbmdb_count_config_entries("(objectClass=nsIndex)", &nbindexes) ||
+ dbmdb_count_config_entries("(objectClass=vlvIndex)", &nbvlvs) ||
dbmdb_count_config_entries("(objectClass=nsds5replicationagreement)", &nbagmt)) {
/* error message is already logged */
return 1;
@@ -120,8 +120,15 @@ dbmdb_compute_limits(struct ldbminfo *li)
info->pagesize = sysconf(_SC_PAGE_SIZE);
limits->min_readers = config_get_threadnumber() + nbagmt + DBMDB_READERS_MARGIN;
- /* Default indexes are counted in "nbindexes" so we should always have enough resource to add 1 new suffix */
- limits->min_dbs = nbsuffixes + nbindexes + nbchangelogs + DBMDB_DBS_MARGIN;
+ /*
+ * For each suffix there are 4 databases instances:
+ * long-entryrdn, replication_changelog, id2entry and ancestorid
+ * then the indexes and the vlv and vlv cache
+ *
+ * Default indexes are counted in "nbindexes" so we should always have enough
+ * resource to add 1 new suffix
+ */
+ limits->min_dbs = 4*nbsuffixes + nbindexes + 2*nbvlvs + DBMDB_DBS_MARGIN;
total_space = ((uint64_t)(buf.f_blocks)) * ((uint64_t)(buf.f_bsize));
avail_space = ((uint64_t)(buf.f_bavail)) * ((uint64_t)(buf.f_bsize));
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 8c879da31..707a110c5 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
@@ -4312,9 +4312,12 @@ dbmdb_import_init_writer(ImportJob *job, ImportRole_t role)
void
dbmdb_free_import_ctx(ImportJob *job)
{
- if (job->writer_ctx) {
- ImportCtx_t *ctx = job->writer_ctx;
- job->writer_ctx = NULL;
+ ImportCtx_t *ctx = NULL;
+ pthread_mutex_lock(get_import_ctx_mutex());
+ ctx = job->writer_ctx;
+ job->writer_ctx = NULL;
+ pthread_mutex_unlock(get_import_ctx_mutex());
+ if (ctx) {
pthread_mutex_destroy(&ctx->workerq.mutex);
pthread_cond_destroy(&ctx->workerq.cv);
slapi_ch_free((void**)&ctx->workerq.slots);
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
index 6386ecf06..05f1e348d 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
@@ -287,6 +287,13 @@ int add_dbi(dbi_open_ctx_t *octx, backend *be, const char *fname, int flags)
slapi_ch_free((void**)&treekey.dbname);
return octx->rc;
}
+ if (treekey.dbi >= ctx->dsecfg.max_dbs) {
+ octx->rc = MDB_DBS_FULL;
+ slapi_log_err(SLAPI_LOG_ERR, "add_dbi", "Failed to open database instance %s slots: %d/%d. Error is %d: %s.\n",
+ treekey.dbname, treekey.dbi, ctx->dsecfg.max_dbs, octx->rc, mdb_strerror(octx->rc));
+ slapi_ch_free((void**)&treekey.dbname);
+ return octx->rc;
+ }
if (octx->ai && octx->ai->ai_key_cmp_fn) {
octx->rc = dbmdb_update_dbi_cmp_fn(ctx, &treekey, octx->ai->ai_key_cmp_fn, octx->txn);
if (octx->rc) {
@@ -689,6 +696,7 @@ int dbmdb_make_env(dbmdb_ctx_t *ctx, int readOnly, mdb_mode_t mode)
rc = dbmdb_write_infofile(ctx);
} else {
/* No Config ==> read it from info file */
+ ctx->dsecfg = ctx->startcfg;
}
if (rc) {
return rc;
diff --git a/ldap/servers/slapd/back-ldbm/dbimpl.c b/ldap/servers/slapd/back-ldbm/dbimpl.c
index da4a4548e..42f4a0718 100644
--- a/ldap/servers/slapd/back-ldbm/dbimpl.c
+++ b/ldap/servers/slapd/back-ldbm/dbimpl.c
@@ -463,7 +463,7 @@ int dblayer_show_statistics(const char *dbimpl_name, const char *dbhome, FILE *f
li->li_plugin = be->be_database;
li->li_plugin->plg_name = (char*) "back-ldbm-dbimpl";
li->li_plugin->plg_libpath = (char*) "libback-ldbm";
- li->li_directory = (char*)dbhome;
+ li->li_directory = get_li_directory(dbhome);
/* Initialize database plugin */
rc = dbimpl_setup(li, dbimpl_name);
diff --git a/ldap/servers/slapd/back-ldbm/import.c b/ldap/servers/slapd/back-ldbm/import.c
index 2bb8cb581..30ec462fa 100644
--- a/ldap/servers/slapd/back-ldbm/import.c
+++ b/ldap/servers/slapd/back-ldbm/import.c
@@ -27,6 +27,9 @@
#define NEED_DN_NORM_SP -25
#define NEED_DN_NORM_BT -26
+/* Protect against import context destruction */
+static pthread_mutex_t import_ctx_mutex = PTHREAD_MUTEX_INITIALIZER;
+
/********** routines to manipulate the entry fifo **********/
@@ -143,6 +146,14 @@ ldbm_back_wire_import(Slapi_PBlock *pb)
/* Threads management */
+/* Return the mutex that protects against import context destruction */
+pthread_mutex_t *
+get_import_ctx_mutex()
+{
+ return &import_ctx_mutex;
+}
+
+
/* tell all the threads to abort */
void
import_abort_all(ImportJob *job, int wait_for_them)
@@ -151,7 +162,7 @@ import_abort_all(ImportJob *job, int wait_for_them)
/* tell all the worker threads to abort */
job->flags |= FLAG_ABORT;
-
+ pthread_mutex_lock(&import_ctx_mutex);
for (worker = job->worker_list; worker; worker = worker->next)
worker->command = ABORT;
@@ -167,6 +178,7 @@ import_abort_all(ImportJob *job, int wait_for_them)
}
}
}
+ pthread_mutex_unlock(&import_ctx_mutex);
}
--
2.48.0

View File

@ -0,0 +1,894 @@
From b53faa9e7289383bbc02fc260b1b34958a317fdd Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 6 Sep 2024 14:45:06 +0200
Subject: [PATCH] Issue 6090 - Fix dbscan options and man pages (#6315)
* Issue 6090 - Fix dbscan options and man pages
dbscan -d option is dangerously confusing as it removes a database instance while in db_stat it identify the database
(cf issue #5609 ).
This fix implements long options in dbscan, rename -d in --remove, and requires a new --do-it option for action that change the database content.
The fix should also align both the usage and the dbscan man page with the new set of options
Issue: #6090
Reviewed by: @tbordaz, @droideck (Thanks!)
(cherry picked from commit 25e1d16887ebd299dfe0088080b9ee0deec1e41f)
---
dirsrvtests/tests/suites/clu/dbscan_test.py | 253 ++++++++++++++++++
.../tests/suites/clu/repl_monitor_test.py | 4 +-
.../slapd/back-ldbm/db-bdb/bdb_layer.c | 12 +-
ldap/servers/slapd/back-ldbm/dbimpl.c | 50 +++-
ldap/servers/slapd/tools/dbscan.c | 182 ++++++++++---
man/man1/dbscan.1 | 74 +++--
src/lib389/lib389/__init__.py | 9 +-
src/lib389/lib389/cli_ctl/dblib.py | 13 +-
8 files changed, 531 insertions(+), 66 deletions(-)
create mode 100644 dirsrvtests/tests/suites/clu/dbscan_test.py
diff --git a/dirsrvtests/tests/suites/clu/dbscan_test.py b/dirsrvtests/tests/suites/clu/dbscan_test.py
new file mode 100644
index 000000000..2c9a9651a
--- /dev/null
+++ b/dirsrvtests/tests/suites/clu/dbscan_test.py
@@ -0,0 +1,253 @@
+# --- 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 logging
+import os
+import pytest
+import re
+import subprocess
+import sys
+
+from lib389 import DirSrv
+from lib389._constants import DBSCAN
+from lib389.topologies import topology_m2 as topo_m2
+from difflib import context_diff
+
+pytestmark = pytest.mark.tier0
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+
+
+class CalledProcessUnexpectedReturnCode(subprocess.CalledProcessError):
+ def __init__(self, result, expected_rc):
+ super().__init__(cmd=result.args, returncode=result.returncode, output=result.stdout, stderr=result.stderr)
+ self.expected_rc = expected_rc
+ self.result = result
+
+ def __str__(self):
+ return f'Command {self.result.args} returned {self.result.returncode} instead of {self.expected_rc}'
+
+
+class DbscanPaths:
+ @staticmethod
+ def list_instances(inst, dblib, dbhome):
+ # compute db instance pathnames
+ instances = dbscan(['-D', dblib, '-L', dbhome], inst=inst).stdout
+ dbis = []
+ if dblib == 'bdb':
+ pattern = r'^ (.*) $'
+ prefix = f'{dbhome}/'
+ else:
+ pattern = r'^ (.*) flags:'
+ prefix = f''
+ for match in re.finditer(pattern, instances, flags=re.MULTILINE):
+ dbis.append(prefix+match.group(1))
+ return dbis
+
+ @staticmethod
+ def list_options(inst):
+ # compute supported options
+ options = []
+ usage = dbscan(['-h'], inst=inst, expected_rc=None).stdout
+ pattern = r'^\s+(?:(-[^-,]+), +)?(--[^ ]+).*$'
+ for match in re.finditer(pattern, usage, flags=re.MULTILINE):
+ for idx in range(1,3):
+ if match.group(idx) is not None:
+ options.append(match.group(idx))
+ return options
+
+ def __init__(self, inst):
+ dblib = inst.get_db_lib()
+ dbhome = inst.ds_paths.db_home_dir
+ self.inst = inst
+ self.dblib = dblib
+ self.dbhome = dbhome
+ self.options = DbscanPaths.list_options(inst)
+ self.dbis = DbscanPaths.list_instances(inst, dblib, dbhome)
+ self.ldif_dir = inst.ds_paths.ldif_dir
+
+ def get_dbi(self, attr, backend='userroot'):
+ for dbi in self.dbis:
+ if f'{backend}/{attr}.'.lower() in dbi.lower():
+ return dbi
+ raise KeyError(f'Unknown dbi {backend}/{attr}')
+
+ def __repr__(self):
+ attrs = ['inst', 'dblib', 'dbhome', 'ldif_dir', 'options', 'dbis' ]
+ res = ", ".join(map(lambda x: f'{x}={self.__dict__[x]}', attrs))
+ return f'DbscanPaths({res})'
+
+
+def dbscan(args, inst=None, expected_rc=0):
+ if inst is None:
+ prefix = os.environ.get('PREFIX', "")
+ prog = f'{prefix}/bin/dbscan'
+ else:
+ prog = os.path.join(inst.ds_paths.bin_dir, DBSCAN)
+ args.insert(0, prog)
+ output = subprocess.run(args, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ log.debug(f'{args} result is {output.returncode} output is {output.stdout}')
+ if expected_rc is not None and expected_rc != output.returncode:
+ raise CalledProcessUnexpectedReturnCode(output, expected_rc)
+ return output
+
+
+def log_export_file(filename):
+ with open(filename, 'r') as file:
+ log.debug(f'=========== Dump of {filename} ================')
+ for line in file:
+ log.debug(line.rstrip('\n'))
+ log.debug(f'=========== Enf of {filename} =================')
+
+
+@pytest.fixture(scope='module')
+def paths(topo_m2, request):
+ inst = topo_m2.ms["supplier1"]
+ if sys.version_info < (3,5):
+ pytest.skip('requires python version >= 3.5')
+ paths = DbscanPaths(inst)
+ if '--do-it' not in paths.options:
+ pytest.skip('Not supported with this dbscan version')
+ inst.stop()
+ return paths
+
+
+def test_dbscan_destructive_actions(paths, request):
+ """Test that dbscan remove/import actions
+
+ :id: f40b0c42-660a-11ef-9544-083a88554478
+ :setup: Stopped standalone instance
+ :steps:
+ 1. Export cn instance with dbscan
+ 2. Run dbscan --remove ...
+ 3. Check the error message about missing --do-it
+ 4. Check that cn instance is still present
+ 5. Run dbscan -I import_file ...
+ 6. Check it was properly imported
+ 7. Check that cn instance is still present
+ 8. Run dbscan --remove ... --doit
+ 9. Check the error message about missing --do-it
+ 10. Check that cn instance is still present
+ 11. Run dbscan -I import_file ... --do-it
+ 12. Check it was properly imported
+ 13. Check that cn instance is still present
+ 14. Export again the database
+ 15. Check that content of export files are the same
+ :expectedresults:
+ 1. Success
+ 2. dbscan return code should be 1 (error)
+ 3. Error message should be present
+ 4. cn instance should be present
+ 5. dbscan return code should be 1 (error)
+ 6. Error message should be present
+ 7. cn instance should be present
+ 8. dbscan return code should be 0 (success)
+ 9. Error message should not be present
+ 10. cn instance should not be present
+ 11. dbscan return code should be 0 (success)
+ 12. Error message should not be present
+ 13. cn instance should be present
+ 14. Success
+ 15. Export files content should be the same
+ """
+
+ # Export cn instance with dbscan
+ export_cn = f'{paths.ldif_dir}/dbscan_cn.data'
+ export_cn2 = f'{paths.ldif_dir}/dbscan_cn2.data'
+ cndbi = paths.get_dbi('replication_changelog')
+ inst = paths.inst
+ dblib = paths.dblib
+ exportok = False
+ def fin():
+ if os.path.exists(export_cn):
+ # Restore cn if it was exported successfully but does not exists any more
+ if exportok and cndbi not in DbscanPaths.list_instances(inst, dblib, paths.dbhome):
+ dbscan(['-D', dblib, '-f', cndbi, '-I', export_cn, '--do-it'], inst=inst)
+ if not DEBUGGING:
+ os.remove(export_cn)
+ if os.path.exists(export_cn) and not DEBUGGING:
+ os.remove(export_cn2)
+
+ fin()
+ request.addfinalizer(fin)
+ dbscan(['-D', dblib, '-f', cndbi, '-X', export_cn], inst=inst)
+ exportok = True
+
+ expected_msg = "without specifying '--do-it' parameter."
+
+ # Run dbscan --remove ...
+ result = dbscan(['-D', paths.dblib, '--remove', '-f', cndbi],
+ inst=paths.inst, expected_rc=1)
+
+ # Check the error message about missing --do-it
+ assert expected_msg in result.stdout
+
+ # Check that cn instance is still present
+ curdbis = DbscanPaths.list_instances(paths.inst, paths.dblib, paths.dbhome)
+ assert cndbi in curdbis
+
+ # Run dbscan -I import_file ...
+ result = dbscan(['-D', paths.dblib, '-f', cndbi, '-I', export_cn],
+ inst=paths.inst, expected_rc=1)
+
+ # Check the error message about missing --do-it
+ assert expected_msg in result.stdout
+
+ # Check that cn instance is still present
+ curdbis = DbscanPaths.list_instances(paths.inst, paths.dblib, paths.dbhome)
+ assert cndbi in curdbis
+
+ # Run dbscan --remove ... --doit
+ result = dbscan(['-D', paths.dblib, '--remove', '-f', cndbi, '--do-it'],
+ inst=paths.inst, expected_rc=0)
+
+ # Check the error message about missing --do-it
+ assert expected_msg not in result.stdout
+
+ # Check that cn instance is still present
+ curdbis = DbscanPaths.list_instances(paths.inst, paths.dblib, paths.dbhome)
+ assert cndbi not in curdbis
+
+ # Run dbscan -I import_file ... --do-it
+ result = dbscan(['-D', paths.dblib, '-f', cndbi,
+ '-I', export_cn, '--do-it'],
+ inst=paths.inst, expected_rc=0)
+
+ # Check the error message about missing --do-it
+ assert expected_msg not in result.stdout
+
+ # Check that cn instance is still present
+ curdbis = DbscanPaths.list_instances(paths.inst, paths.dblib, paths.dbhome)
+ assert cndbi in curdbis
+
+ # Export again the database
+ dbscan(['-D', dblib, '-f', cndbi, '-X', export_cn2], inst=inst)
+
+ # Check that content of export files are the same
+ with open(export_cn) as f1:
+ f1lines = f1.readlines()
+ with open(export_cn2) as f2:
+ f2lines = f2.readlines()
+ diffs = list(context_diff(f1lines, f2lines))
+ if len(diffs) > 0:
+ log.debug("Export file differences are:")
+ for d in diffs:
+ log.debug(d)
+ log_export_file(export_cn)
+ log_export_file(export_cn2)
+ assert diffs is None
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main("-s %s" % CURRENT_FILE)
diff --git a/dirsrvtests/tests/suites/clu/repl_monitor_test.py b/dirsrvtests/tests/suites/clu/repl_monitor_test.py
index d83416847..842dd96fd 100644
--- a/dirsrvtests/tests/suites/clu/repl_monitor_test.py
+++ b/dirsrvtests/tests/suites/clu/repl_monitor_test.py
@@ -77,13 +77,13 @@ def get_hostnames_from_log(port1, port2):
# search for Supplier :hostname:port
# and use \D to insure there is no more number is after
# the matched port (i.e that 10 is not matching 101)
- regexp = '(Supplier: )([^:]*)(:' + str(port1) + '\D)'
+ regexp = '(Supplier: )([^:]*)(:' + str(port1) + r'\D)'
match=re.search(regexp, logtext)
host_m1 = 'localhost.localdomain'
if (match is not None):
host_m1 = match.group(2)
# Same for supplier 2
- regexp = '(Supplier: )([^:]*)(:' + str(port2) + '\D)'
+ regexp = '(Supplier: )([^:]*)(:' + str(port2) + r'\D)'
match=re.search(regexp, logtext)
host_m2 = 'localhost.localdomain'
if (match is not None):
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
index de6be0f42..4b30e8e87 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
@@ -5820,8 +5820,16 @@ bdb_import_file_name(ldbm_instance *inst)
static char *
bdb_restore_file_name(struct ldbminfo *li)
{
- char *fname = slapi_ch_smprintf("%s/../.restore", li->li_directory);
-
+ char *pt = strrchr(li->li_directory, '/');
+ char *fname = NULL;
+ if (pt == NULL) {
+ fname = slapi_ch_strdup(".restore");
+ } else {
+ size_t len = pt-li->li_directory;
+ fname = slapi_ch_malloc(len+10);
+ strncpy(fname, li->li_directory, len);
+ strcpy(fname+len, "/.restore");
+ }
return fname;
}
diff --git a/ldap/servers/slapd/back-ldbm/dbimpl.c b/ldap/servers/slapd/back-ldbm/dbimpl.c
index 42f4a0718..134d06480 100644
--- a/ldap/servers/slapd/back-ldbm/dbimpl.c
+++ b/ldap/servers/slapd/back-ldbm/dbimpl.c
@@ -397,7 +397,48 @@ const char *dblayer_op2str(dbi_op_t op)
return str[idx];
}
-/* Open db env, db and db file privately */
+/* Get the li_directory directory from the database instance name -
+ * Caller should free the returned value
+ */
+static char *
+get_li_directory(const char *fname)
+{
+ /*
+ * li_directory is an existing directory.
+ * it can be fname or its parent or its greatparent
+ * in case of problem returns the provided name
+ */
+ char *lid = slapi_ch_strdup(fname);
+ struct stat sbuf = {0};
+ char *pt = NULL;
+ for (int count=0; count<3; count++) {
+ if (stat(lid, &sbuf) == 0) {
+ if (S_ISDIR(sbuf.st_mode)) {
+ return lid;
+ }
+ /* Non directory existing file could be regular
+ * at the first iteration otherwise it is an error.
+ */
+ if (count>0 || !S_ISREG(sbuf.st_mode)) {
+ break;
+ }
+ }
+ pt = strrchr(lid, '/');
+ if (pt == NULL) {
+ slapi_ch_free_string(&lid);
+ return slapi_ch_strdup(".");
+ }
+ *pt = '\0';
+ }
+ /*
+ * Error case. Returns a copy of the original string:
+ * and let dblayer_private_open_fn fail to open the database
+ */
+ slapi_ch_free_string(&lid);
+ return slapi_ch_strdup(fname);
+}
+
+/* Open db env, db and db file privately (for dbscan) */
int dblayer_private_open(const char *plgname, const char *dbfilename, int rw, Slapi_Backend **be, dbi_env_t **env, dbi_db_t **db)
{
struct ldbminfo *li;
@@ -412,7 +453,7 @@ int dblayer_private_open(const char *plgname, const char *dbfilename, int rw, Sl
li->li_plugin = (*be)->be_database;
li->li_plugin->plg_name = (char*) "back-ldbm-dbimpl";
li->li_plugin->plg_libpath = (char*) "libback-ldbm";
- li->li_directory = slapi_ch_strdup(dbfilename);
+ li->li_directory = get_li_directory(dbfilename);
/* Initialize database plugin */
rc = dbimpl_setup(li, plgname);
@@ -439,7 +480,10 @@ int dblayer_private_close(Slapi_Backend **be, dbi_env_t **env, dbi_db_t **db)
}
slapi_ch_free((void**)&li->li_dblayer_private);
slapi_ch_free((void**)&li->li_dblayer_config);
- ldbm_config_destroy(li);
+ if (dblayer_is_lmdb(*be)) {
+ /* Generate use after free and double free in bdb case */
+ ldbm_config_destroy(li);
+ }
slapi_ch_free((void**)&(*be)->be_database);
slapi_ch_free((void**)&(*be)->be_instance_info);
slapi_ch_free((void**)be);
diff --git a/ldap/servers/slapd/tools/dbscan.c b/ldap/servers/slapd/tools/dbscan.c
index 2d28dd951..12edf7c5b 100644
--- a/ldap/servers/slapd/tools/dbscan.c
+++ b/ldap/servers/slapd/tools/dbscan.c
@@ -26,6 +26,7 @@
#include <string.h>
#include <ctype.h>
#include <errno.h>
+#include <getopt.h>
#include "../back-ldbm/dbimpl.h"
#include "../slapi-plugin.h"
#include "nspr.h"
@@ -85,6 +86,8 @@
#define DB_BUFFER_SMALL ENOMEM
#endif
+#define COUNTOF(array) ((sizeof(array))/sizeof(*(array)))
+
#if defined(linux)
#include <getopt.h>
#endif
@@ -130,9 +133,43 @@ long ind_cnt = 0;
long allids_cnt = 0;
long other_cnt = 0;
char *dump_filename = NULL;
+int do_it = 0;
static Slapi_Backend *be = NULL; /* Pseudo backend used to interact with db */
+/* For Long options without shortcuts */
+enum {
+ OPT_FIRST = 0x1000,
+ OPT_DO_IT,
+ OPT_REMOVE,
+};
+
+static const struct option options[] = {
+ /* Options without shortcut */
+ { "do-it", no_argument, 0, OPT_DO_IT },
+ { "remove", no_argument, 0, OPT_REMOVE },
+ /* Options with shortcut */
+ { "import", required_argument, 0, 'I' },
+ { "export", required_argument, 0, 'X' },
+ { "db-type", required_argument, 0, 'D' },
+ { "dbi", required_argument, 0, 'f' },
+ { "ascii", no_argument, 0, 'A' },
+ { "raw", no_argument, 0, 'R' },
+ { "truncate-entry", required_argument, 0, 't' },
+ { "entry-id", required_argument, 0, 'K' },
+ { "key", required_argument, 0, 'k' },
+ { "list", required_argument, 0, 'L' },
+ { "stats", required_argument, 0, 'S' },
+ { "id-list-max-size", required_argument, 0, 'l' },
+ { "id-list-min-size", required_argument, 0, 'G' },
+ { "show-id-list-lenghts", no_argument, 0, 'n' },
+ { "show-id-list", no_argument, 0, 'r' },
+ { "summary", no_argument, 0, 's' },
+ { "help", no_argument, 0, 'h' },
+ { 0, 0, 0, 0 }
+};
+
+
/** db_printf - functioning same as printf but a place for manipluating output.
*/
void
@@ -899,7 +936,7 @@ is_changelog(char *filename)
}
static void
-usage(char *argv0)
+usage(char *argv0, int error)
{
char *copy = strdup(argv0);
char *p0 = NULL, *p1 = NULL;
@@ -922,42 +959,52 @@ usage(char *argv0)
}
printf("\n%s - scan a db file and dump the contents\n", p0);
printf(" common options:\n");
- printf(" -D <dbimpl> specify db implementaion (may be: bdb or mdb)\n");
- printf(" -f <filename> specify db file\n");
- printf(" -A dump as ascii data\n");
- printf(" -R dump as raw data\n");
- printf(" -t <size> entry truncate size (bytes)\n");
+ printf(" -A, --ascii dump as ascii data\n");
+ printf(" -D, --db-type <dbimpl> specify db implementaion (may be: bdb or mdb)\n");
+ printf(" -f, --dbi <filename> specify db instance\n");
+ printf(" -R, --raw dump as raw data\n");
+ printf(" -t, --truncate-entry <size> entry truncate size (bytes)\n");
+
printf(" entry file options:\n");
- printf(" -K <entry_id> lookup only a specific entry id\n");
+ printf(" -K, --entry-id <entry_id> lookup only a specific entry id\n");
+
printf(" index file options:\n");
- printf(" -k <key> lookup only a specific key\n");
- printf(" -L <dbhome> list all db files\n");
- printf(" -S <dbhome> show statistics\n");
- printf(" -l <size> max length of dumped id list\n");
- printf(" (default %" PRIu32 "; 40 bytes <= size <= 1048576 bytes)\n", MAX_BUFFER);
- printf(" -G <n> only display index entries with more than <n> ids\n");
- printf(" -n display ID list lengths\n");
- printf(" -r display the conents of ID list\n");
- printf(" -s Summary of index counts\n");
- printf(" -I file Import database content from file\n");
- printf(" -X file Export database content in file\n");
+ printf(" -G, --id-list-min-size <n> only display index entries with more than <n> ids\n");
+ printf(" -I, --import file Import database instance from file.\n");
+ printf(" -k, --key <key> lookup only a specific key\n");
+ printf(" -l, --id-list-max-size <size> max length of dumped id list\n");
+ printf(" (default %" PRIu32 "; 40 bytes <= size <= 1048576 bytes)\n", MAX_BUFFER);
+ printf(" -n, --show-id-list-lenghts display ID list lengths\n");
+ printf(" --remove remove database instance\n");
+ printf(" -r, --show-id-list display the conents of ID list\n");
+ printf(" -S, --stats <dbhome> show statistics\n");
+ printf(" -X, --export file export database instance in file\n");
+
+ printf(" other options:\n");
+ printf(" -s, --summary summary of index counts\n");
+ printf(" -L, --list <dbhome> list all db files\n");
+ printf(" --do-it confirmation flags for destructive actions like --remove or --import\n");
+ printf(" -h, --help display this usage\n");
+
printf(" sample usages:\n");
- printf(" # list the db files\n");
- printf(" %s -D mdb -L /var/lib/dirsrv/slapd-i/db/\n", p0);
- printf(" %s -f id2entry.db\n", p0);
+ printf(" # list the database instances\n");
+ printf(" %s -L /var/lib/dirsrv/slapd-supplier1/db/\n", p0);
printf(" # dump the entry file\n");
printf(" %s -f id2entry.db\n", p0);
printf(" # display index keys in cn.db4\n");
printf(" %s -f cn.db4\n", p0);
+ printf(" # display index keys in cn on lmdb\n");
+ printf(" %s -f /var/lib/dirsrv/slapd-supplier1/db/userroot/cn.db\n", p0);
+ printf(" (Note: Use 'dbscan -L db_home_dir' to get the db instance path)\n");
printf(" # display index keys and the count of entries having the key in mail.db4\n");
printf(" %s -r -f mail.db4\n", p0);
printf(" # display index keys and the IDs having more than 20 IDs in sn.db4\n");
printf(" %s -r -G 20 -f sn.db4\n", p0);
printf(" # display summary of objectclass.db4\n");
- printf(" %s -f objectclass.db4\n", p0);
+ printf(" %s -s -f objectclass.db4\n", p0);
printf("\n");
free(copy);
- exit(1);
+ exit(error?1:0);
}
void dump_ascii_val(const char *str, dbi_val_t *val)
@@ -1126,13 +1173,12 @@ importdb(const char *dbimpl_name, const char *filename, const char *dump_name)
dblayer_init_pvt_txn();
if (!dump) {
- printf("Failed to open dump file %s. Error %d: %s\n", dump_name, errno, strerror(errno));
- fclose(dump);
+ printf("Error: Failed to open dump file %s. Error %d: %s\n", dump_name, errno, strerror(errno));
return 1;
}
if (dblayer_private_open(dbimpl_name, filename, 1, &be, &env, &db)) {
- printf("Can't initialize db plugin: %s\n", dbimpl_name);
+ printf("Error: Can't initialize db plugin: %s\n", dbimpl_name);
fclose(dump);
return 1;
}
@@ -1142,11 +1188,16 @@ importdb(const char *dbimpl_name, const char *filename, const char *dump_name)
!_read_line(dump, &keyword, &data) && keyword == 'v') {
ret = dblayer_db_op(be, db, txn.txn, DBI_OP_PUT, &key, &data);
}
+ if (ret !=0) {
+ printf("Error: failed to write record in database. Error %d: %s\n", ret, dblayer_strerror(ret));
+ dump_ascii_val("Failing record key", &key);
+ dump_ascii_val("Failing record value", &data);
+ }
fclose(dump);
dblayer_value_free(be, &key);
dblayer_value_free(be, &data);
if (dblayer_private_close(&be, &env, &db)) {
- printf("Unable to shutdown the db plugin: %s\n", dblayer_strerror(1));
+ printf("Error: Unable to shutdown the db plugin: %s\n", dblayer_strerror(1));
return 1;
}
return ret;
@@ -1243,6 +1294,7 @@ removedb(const char *dbimpl_name, const char *filename)
return 1;
}
+ db = NULL; /* Database is already closed by dblayer_db_remove */
if (dblayer_private_close(&be, &env, &db)) {
printf("Unable to shutdown the db plugin: %s\n", dblayer_strerror(1));
return 1;
@@ -1250,7 +1302,6 @@ removedb(const char *dbimpl_name, const char *filename)
return 0;
}
-
int
main(int argc, char **argv)
{
@@ -1262,11 +1313,46 @@ main(int argc, char **argv)
int ret = 0;
char *find_key = NULL;
uint32_t entry_id = 0xffffffff;
- char *dbimpl_name = (char*) "bdb";
- int c;
+ char *defdbimpl = getenv("NSSLAPD_DB_LIB");
+ char *dbimpl_name = (char*) "mdb";
+ int longopt_idx = 0;
+ int c = 0;
+ char optstring[2*COUNTOF(options)+1] = {0};
+
+ if (defdbimpl) {
+ if (strcasecmp(defdbimpl, "bdb") == 0) {
+ dbimpl_name = (char*) "bdb";
+ }
+ if (strcasecmp(defdbimpl, "mdb") == 0) {
+ dbimpl_name = (char*) "mdb";
+ }
+ }
+
+ /* Compute getopt short option string */
+ {
+ char *pt = optstring;
+ for (const struct option *opt = options; opt->name; opt++) {
+ if (opt->val>0 && opt->val<OPT_FIRST) {
+ *pt++ = (char)(opt->val);
+ if (opt->has_arg == required_argument) {
+ *pt++ = ':';
+ }
+ }
+ }
+ *pt = '\0';
+ }
- while ((c = getopt(argc, argv, "Af:RL:S:l:nG:srk:K:hvt:D:X:I:d")) != EOF) {
+ while ((c = getopt_long(argc, argv, optstring, options, &longopt_idx)) != EOF) {
+ if (c == 0) {
+ c = longopt_idx;
+ }
switch (c) {
+ case OPT_DO_IT:
+ do_it = 1;
+ break;
+ case OPT_REMOVE:
+ display_mode |= REMOVE;
+ break;
case 'A':
display_mode |= ASCIIDATA;
break;
@@ -1332,32 +1418,48 @@ main(int argc, char **argv)
display_mode |= IMPORT;
dump_filename = optarg;
break;
- case 'd':
- display_mode |= REMOVE;
- break;
case 'h':
default:
- usage(argv[0]);
+ usage(argv[0], 1);
}
}
+ if (filename == NULL) {
+ fprintf(stderr, "PARAMETER ERROR! 'filename' parameter is missing.\n");
+ usage(argv[0], 1);
+ }
+
if (display_mode & EXPORT) {
return exportdb(dbimpl_name, filename, dump_filename);
}
if (display_mode & IMPORT) {
+ if (!strstr(filename, "/id2entry") && !strstr(filename, "/replication_changelog")) {
+ /* schema is unknown in dbscan ==> duplicate keys sort order is unknown
+ * ==> cannot create dbi with duplicate keys
+ * ==> only id2entry and repl changelog is importable.
+ */
+ fprintf(stderr, "ERROR: The only database instances that may be imported with dbscan are id2entry and replication_changelog.\n");
+ exit(1);
+ }
+
+ if (do_it == 0) {
+ fprintf(stderr, "PARAMETER ERROR! Trying to perform a destructive action (import)\n"
+ " without specifying '--do-it' parameter.\n");
+ exit(1);
+ }
return importdb(dbimpl_name, filename, dump_filename);
}
if (display_mode & REMOVE) {
+ if (do_it == 0) {
+ fprintf(stderr, "PARAMETER ERROR! Trying to perform a destructive action (remove)\n"
+ " without specifying '--do-it' parameter.\n");
+ exit(1);
+ }
return removedb(dbimpl_name, filename);
}
- if (filename == NULL) {
- fprintf(stderr, "PARAMETER ERROR! 'filename' parameter is missing.\n");
- usage(argv[0]);
- }
-
if (display_mode & LISTDBS) {
dbi_dbslist_t *dbs = dblayer_list_dbs(dbimpl_name, filename);
if (dbs) {
diff --git a/man/man1/dbscan.1 b/man/man1/dbscan.1
index 810608371..dfb6e8351 100644
--- a/man/man1/dbscan.1
+++ b/man/man1/dbscan.1
@@ -31,50 +31,94 @@ Scans a Directory Server database index file and dumps the contents.
.\" respectively.
.SH OPTIONS
A summary of options is included below:
+.IP
+common options:
+.TP
+.B \fB\-A, \-\-ascii\fR
+dump as ascii data
+.TP
+.B \fB\-D, \-\-db\-type\fR <filename>
+specify db type: bdb or mdb
.TP
-.B \fB\-f\fR <filename>
-specify db file
+.B \fB\-f, \-\-dbi\fR <filename>
+specify db instance
.TP
-.B \fB\-R\fR
+.B \fB\-R, \-\-raw\fR
dump as raw data
.TP
-.B \fB\-t\fR <size>
+.B \fB\-t, \-\-truncate\-entry\fR <size>
entry truncate size (bytes)
.IP
entry file options:
.TP
-.B \fB\-K\fR <entry_id>
+.B \fB\-K, \-\-entry\-id\fR <entry_id>
lookup only a specific entry id
+.IP
index file options:
.TP
-.B \fB\-k\fR <key>
+.B \fB\-G, \-\-id\-list\-min\-size\fR <n>
+only display index entries with more than <n> ids
+.TP
+.B \fB\-I, \-\-import\fR <file>
+Import database instance from file. Requires \-\-do\-it parameter
+WARNING! Only the id2entry and replication_changelog database instances
+may be imported by dbscan.
+.TP
+.B \fB\-k, \-\-key\fR <key>
lookup only a specific key
.TP
-.B \fB\-l\fR <size>
+.B \fB\-l, \-\-id\-list\-max\-size\fR <size>
max length of dumped id list
(default 4096; 40 bytes <= size <= 1048576 bytes)
.TP
-.B \fB\-G\fR <n>
-only display index entries with more than <n> ids
-.TP
-.B \fB\-n\fR
+.B \fB\-n, \-\-show\-id\-list\-lenghts\fR
display ID list lengths
.TP
-.B \fB\-r\fR
+.B \fB\-\-remove\fR
+remove a db instance. Requires \-\-do\-it parameter
+.TP
+.B \fB\-r, \-\-show\-id\-list\fR
display the contents of ID list
.TP
-.B \fB\-s\fR
+.B \fB\-S, \-\-stats\fR
+display statistics
+.TP
+.B \fB\-X, \-\-export\fR <file>
+Export database instance to file
+.IP
+other options:
+.TP
+.B \fB\-s, \-\-summary\fR
Summary of index counts
+.TP
+.B \fB\-L, \-\-list\fR
+List od database instances
+.TP
+.B \fB\-\-do\-it\fR
+confirmation required for actions that change the database contents
+.TP
+.B \fB\-h, \-\-help\-it\fR
+display the usage
.IP
.SH USAGE
Sample usages:
.TP
+List the database instances
+.B
+dbscan -L /var/lib/dirsrv/slapd-supplier1/db
+.TP
Dump the entry file:
.B
dbscan \fB\-f\fR id2entry.db4
.TP
Display index keys in cn.db4:
-.B dbscan \fB\-f\fR cn.db4
+.B
+dbscan \fB\-f\fR cn.db4
+.TP
+Display index keys in cn on lmdb:
+.B
+dbscan \fB\-f\fR /var/lib/dirsrv/slapd\-supplier1/db/userroot/cn.db
+ (Note: Use \fBdbscan \-L db_home_dir\R to get the db instance path)
.TP
Display index keys and the count of entries having the key in mail.db4:
.B
@@ -86,7 +130,7 @@ dbscan \fB\-r\fR \fB\-G\fR 20 \fB\-f\fR sn.db4
.TP
Display summary of objectclass.db4:
.B
-dbscan \fB\-f\fR objectclass.db4
+dbscan \fB\-s \-f\fR objectclass.db4
.br
.SH AUTHOR
dbscan was written by the 389 Project.
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index e87582d9e..368741a66 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -3039,14 +3039,17 @@ class DirSrv(SimpleLDAPObject, object):
return self._dbisupport
# check if -D and -L options are supported
try:
- cmd = ["%s/dbscan" % self.get_bin_dir(), "--help"]
+ cmd = ["%s/dbscan" % self.get_bin_dir(), "-h"]
self.log.debug("DEBUG: checking dbscan supported options %s" % cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
except subprocess.CalledProcessError:
pass
output, stderr = p.communicate()
- self.log.debug("is_dbi_supported output " + output.decode())
- if "-D <dbimpl>" in output.decode() and "-L <dbhome>" in output.decode():
+ output = output.decode()
+ self.log.debug("is_dbi_supported output " + output)
+ if "-D <dbimpl>" in output and "-L <dbhome>" in output:
+ self._dbisupport = True
+ elif "--db-type" in output and "--list" in output:
self._dbisupport = True
else:
self._dbisupport = False
diff --git a/src/lib389/lib389/cli_ctl/dblib.py b/src/lib389/lib389/cli_ctl/dblib.py
index e9269e340..82f09c70c 100644
--- a/src/lib389/lib389/cli_ctl/dblib.py
+++ b/src/lib389/lib389/cli_ctl/dblib.py
@@ -158,6 +158,14 @@ def run_dbscan(args):
return output
+def does_dbscan_need_do_it():
+ prefix = os.environ.get('PREFIX', "")
+ prog = f'{prefix}/bin/dbscan'
+ args = [ prog, '-h' ]
+ output = subprocess.run(args, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ return '--do-it' in output.stdout
+
+
def export_changelog(be, dblib):
# Export backend changelog
try:
@@ -172,7 +180,10 @@ def import_changelog(be, dblib):
# import backend changelog
try:
cl5dbname = be['eccl5dbname'] if dblib == "bdb" else be['cl5dbname']
- run_dbscan(['-D', dblib, '-f', cl5dbname, '-I', be['cl5name']])
+ if does_dbscan_need_do_it():
+ run_dbscan(['-D', dblib, '-f', cl5dbname, '-I', be['cl5name'], '--do-it'])
+ else:
+ run_dbscan(['-D', dblib, '-f', cl5dbname, '-I', be['cl5name']])
return True
except subprocess.CalledProcessError as e:
return False
--
2.48.0

View File

@ -1,217 +0,0 @@
From 58efd22cdefed368c97ef235f2abb66ac4aec234 Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Thu, 5 Sep 2024 10:19:59 +0200
Subject: [PATCH] Issue 6307 - Wrong set of entries returned for some search
filters (#6308)
Bug description:
When the server returns an entry to a search it
checks both access and matching of the filter.
When evaluating a '!' (NOT) logical expression the server,
in a first phase evaluates ONLY the right to access the
related component (and its subcomponents).
Then in a second phase verifies the matching.
If the related component is a OR, in the first phase it
evaluates access AND matching, this even if the call was
to evaluate only access.
This result in incoherent results.
Fix description:
Make sure that when the function vattr_test_filter_list_or
is called to only check access, it does not evaluate the matching.
fixes: #6307
Reviewed by: Pierre Rogier (Thanks !!)
---
.../tests/suites/filter/filter_test.py | 116 ++++++++++++++++++
ldap/servers/slapd/filterentry.c | 11 ++
2 files changed, 127 insertions(+)
diff --git a/dirsrvtests/tests/suites/filter/filter_test.py b/dirsrvtests/tests/suites/filter/filter_test.py
index b17846c01..e1d02f133 100644
--- a/dirsrvtests/tests/suites/filter/filter_test.py
+++ b/dirsrvtests/tests/suites/filter/filter_test.py
@@ -10,12 +10,14 @@ import logging
import pytest
import time
+from ldap import SCOPE_SUBTREE
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, DN_CONFIG_LDBM
+from lib389.idm.user import UserAccount, UserAccounts
from lib389.utils import *
pytestmark = pytest.mark.tier1
@@ -432,6 +434,120 @@ def test_match_large_valueset(topology_st):
assert len(entries) == groups_number
assert (etime < 5)
+def test_filter_not_operator(topology_st, request):
+ """Test ldapsearch with scope one gives only single entry
+
+ :id: b3711e02-7e76-444d-82f3-495c6dadd97f
+ :setup: Standalone instance
+ :steps:
+ 1. Creating user1..user9
+ 2. Adding specific 'telephonenumber' to the users
+ 3. Check returned set (5 users) with the first filter
+ 4. Check returned set (4 users) with the second filter
+ :expectedresults:
+ 1. This should pass
+ 2. This should pass
+ 3. This should pass
+ 4. This should pass
+ """
+
+ topology_st.standalone.start()
+ # Creating Users
+ log.info('Create users from user1 to user9')
+ users = UserAccounts(topology_st.standalone, "ou=people,%s" % DEFAULT_SUFFIX, rdn=None)
+
+ for user in ['user1',
+ 'user2',
+ 'user3',
+ 'user4',
+ 'user5',
+ 'user6',
+ 'user7',
+ 'user8',
+ 'user9']:
+ users.create(properties={
+ 'mail': f'{user}@redhat.com',
+ 'uid': user,
+ 'givenName': user.title(),
+ 'cn': f'bit {user}',
+ 'sn': user.title(),
+ 'manager': f'uid={user},{SUFFIX}',
+ 'userpassword': PW_DM,
+ 'homeDirectory': '/home/' + user,
+ 'uidNumber': '1000',
+ 'gidNumber': '2000',
+ })
+ # Adding specific values to the users
+ log.info('Adding telephonenumber values')
+ user = UserAccount(topology_st.standalone, 'uid=user1, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['1234', '2345'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user2, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['1234', '4567'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user3, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['1234', '4567'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user4, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['1234'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user5, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['2345'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user6, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['3456'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user7, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['4567'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user8, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['1234'])
+
+ user = UserAccount(topology_st.standalone, 'uid=user9, ou=people, %s' % DEFAULT_SUFFIX)
+ user.add('telephonenumber', ['1234', '4567'])
+
+ # Do a first test of filter containing a NOT
+ # and check the expected values are retrieved
+ log.info('Search with filter containing NOT')
+ log.info('expect user2, user3, user6, user8 and user9')
+ filter1 = "(|(telephoneNumber=3456)(&(telephoneNumber=1234)(!(|(uid=user1)(uid=user4)))))"
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, filter1)
+ uids = []
+ for entry in entries:
+ assert entry.hasAttr('uid')
+ uids.append(entry.getValue('uid'))
+
+ assert len(uids) == 5
+ for uid in [b'user2', b'user3', b'user6', b'user8', b'user9']:
+ assert uid in uids
+
+ # Do a second test of filter containing a NOT
+ # and check the expected values are retrieved
+ log.info('Search with a second filter containing NOT')
+ log.info('expect user2, user3, user8 and user9')
+ filter1 = "(|(&(telephoneNumber=1234)(!(|(uid=user1)(uid=user4)))))"
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, filter1)
+ uids = []
+ for entry in entries:
+ assert entry.hasAttr('uid')
+ uids.append(entry.getValue('uid'))
+
+ assert len(uids) == 4
+ for uid in [b'user2', b'user3', b'user8', b'user9']:
+ assert uid in uids
+
+ def fin():
+ """
+ Deletes entries after the test.
+ """
+ for user in users.list():
+ pass
+ user.delete()
+
+
+ request.addfinalizer(fin)
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
index d2c7e3082..f5604161d 100644
--- a/ldap/servers/slapd/filterentry.c
+++ b/ldap/servers/slapd/filterentry.c
@@ -948,14 +948,17 @@ slapi_vattr_filter_test_ext_internal(
break;
case LDAP_FILTER_NOT:
+ slapi_log_err(SLAPI_LOG_FILTER, "vattr_test_filter_list_NOT", "=>\n");
rc = slapi_vattr_filter_test_ext_internal(pb, e, f->f_not, verify_access, only_check_access, access_check_done);
if (verify_access && only_check_access) {
/* dont play with access control return codes
* do not negate return code */
+ slapi_log_err(SLAPI_LOG_FILTER, "vattr_test_filter_list_NOT only check access", "<= %d\n", rc);
break;
}
if (rc > 0) {
/* an error occurred or access denied, don't negate */
+ slapi_log_err(SLAPI_LOG_FILTER, "vattr_test_filter_list_NOT slapi_vattr_filter_test_ext_internal fails", "<= %d\n", rc);
break;
}
if (verify_access) {
@@ -980,6 +983,7 @@ slapi_vattr_filter_test_ext_internal(
/* filter verification only, no error */
rc = (rc == 0) ? -1 : 0;
}
+ slapi_log_err(SLAPI_LOG_FILTER, "vattr_test_filter_list_NOT", "<= %d\n", rc);
break;
default:
@@ -1084,6 +1088,13 @@ vattr_test_filter_list_or(
continue;
}
}
+ /* we are not evaluating if the entry matches
+ * but only that we have access to ALL components
+ * so check the next one
+ */
+ if (only_check_access) {
+ continue;
+ }
/* now check if filter matches */
/*
* We can NOT skip this because we need to know if the item we matched on
--
2.47.1

View File

@ -1,254 +0,0 @@
From cd13c0e33d2f5c63e50af90c6841f6104c4dcdb9 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 24 Oct 2024 19:18:03 -0400
Subject: [PATCH] Issue 6381 - CleanAllRUV - move changelog purging to the very
end of the task
Description:
There are deadlock situations that can occur when cleanAllRUV is removing the
clean task attribute (nsds5ReplicaCleanRUV) from the replica config, while
the change log purging is occurring. Instead do the the changelog purge after
everything else is done and have the changelog purging code remove the rid
from the cleaned list once it finishes.
Also improved the task logging.
Fixes: https://github.com/389ds/389-ds-base/issues/6381
Reviewed by: progier389(Thanks!)
---
ldap/servers/plugins/replication/cl5_api.c | 55 +++++++++++--------
ldap/servers/plugins/replication/repl5.h | 2 +-
.../plugins/replication/repl_cleanallruv.c | 43 ++++++++-------
3 files changed, 57 insertions(+), 43 deletions(-)
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
index 413e78a30..a944d3b29 100644
--- a/ldap/servers/plugins/replication/cl5_api.c
+++ b/ldap/servers/plugins/replication/cl5_api.c
@@ -246,7 +246,7 @@ static int _cl5CheckMissingCSN(const CSN *minCsn, const RUV *supplierRUV, cldb_H
static int cldb_IsTrimmingEnabled(cldb_Handle *cldb);
static int _cl5TrimMain(void *param);
void _cl5TrimReplica(Replica *r);
-void _cl5PurgeRID(cldb_Handle *cldb, ReplicaId cleaned_rid);
+void _cl5PurgeRID(cleanruv_purge_data *data, cldb_Handle *cldb);
static PRBool _cl5CanTrim(time_t time, long *numToTrim, Replica *replica, CL5Config *dbTrim);
int _cl5ConstructRUVs (cldb_Handle *cldb);
int _cl5ReadRUVs(cldb_Handle *cldb);
@@ -984,7 +984,7 @@ cl5CreateReplayIteratorEx(Private_Repl_Protocol *prp, const RUV *consumerRuv, CL
pthread_mutex_unlock(&(cldb->stLock));
/* iterate through the ruv in csn order to find first supplier for which
- we can replay changes */
+ we can replay changes */
rc = _cl5PositionCursorForReplay (consumerRID, consumerRuv, replica, iterator, NULL);
if (rc != CL5_SUCCESS) {
@@ -1874,8 +1874,8 @@ _cl5Iterate(cldb_Handle *cldb, dbi_iterate_cb_t *action_cb, DBLCI_CTX *dblcictx,
continue;
}
} else {
- /* read-only opertion on bdb are transactionless, so no reason to abort txn
- * after having seen some number of records
+ /* read-only opertion on bdb are transactionless, so no reason to abort txn
+ * after having seen some number of records
*/
dblcictx->seen.nbmax = 0;
}
@@ -2552,21 +2552,19 @@ _cl5TrimMain(void *param)
static void
_cl5DoPurging(cleanruv_purge_data *purge_data)
{
- ReplicaId rid = purge_data->cleaned_rid;
- const Slapi_DN *suffix_sdn = purge_data->suffix_sdn;
cldb_Handle *cldb = replica_get_cl_info(purge_data->replica);
-
if (cldb == NULL) {
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
"_cl5DoPurging - Changelog info was NULL - is your replication configuration valid?\n");
return;
}
+
pthread_mutex_lock(&(cldb->clLock));
- _cl5PurgeRID (cldb, rid);
- slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl,
- "_cl5DoPurging - Purged rid (%d) from suffix (%s)\n",
- rid, slapi_sdn_get_dn(suffix_sdn));
+
+ _cl5PurgeRID(purge_data, cldb);
+
pthread_mutex_unlock(&(cldb->clLock));
+
return;
}
@@ -2653,7 +2651,7 @@ _cl5PurgeRidOnEntry(dbi_val_t *key, dbi_val_t *data, void *ctx)
}
/*
- * _cl5PurgeRID(Object *obj, ReplicaId cleaned_rid)
+ * _cl5PurgeRID(cleanruv_purge_data, cleaned_rid)
*
* Clean the entire changelog of updates from the "cleaned rid" via CLEANALLRUV
* Delete entries in batches so we don't consume too many db locks, and we don't
@@ -2662,18 +2660,30 @@ _cl5PurgeRidOnEntry(dbi_val_t *key, dbi_val_t *data, void *ctx)
* beginning for each new iteration.
*/
void
-_cl5PurgeRID(cldb_Handle *cldb, ReplicaId cleaned_rid)
+_cl5PurgeRID(cleanruv_purge_data *data, cldb_Handle *cldb)
{
DBLCI_CTX dblcictx = {0};
+ int32_t rc = 0;
dblcictx.seen.nbmax = CL5_PURGE_MAX_LOOKUP_PER_TRANSACTION;
dblcictx.changed.nbmax = CL5_PURGE_MAX_PER_TRANSACTION;
- dblcictx.rid2purge = cleaned_rid;
- _cl5Iterate(cldb, _cl5PurgeRidOnEntry, &dblcictx, PR_FALSE);
-
- slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl,
- "_cl5PurgeRID - Removed (%ld entries) that originated from rid (%d)\n",
- dblcictx.changed.tot, cleaned_rid);
+ dblcictx.rid2purge = data->cleaned_rid;
+
+ rc = _cl5Iterate(cldb, _cl5PurgeRidOnEntry, &dblcictx, PR_FALSE);
+ if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) {
+ cleanruv_log(data->task, data->cleaned_rid, CLEANALLRUV_ID,
+ SLAPI_LOG_ERR,
+ "Purging failed to iterate through the entire changelog "
+ "(error %d). There is a chance the rid was not fully "
+ "removed, and you may have to run the cleanAllRUV task "
+ "again.",
+ rc);
+ } else {
+ cleanruv_log(data->task, data->cleaned_rid, CLEANALLRUV_ID,
+ SLAPI_LOG_INFO,
+ "Purged %ld records from the changelog",
+ dblcictx.changed.tot);
+ }
}
/*
@@ -4459,11 +4469,10 @@ trigger_cl_purging_thread(void *arg)
/* Purge the changelog */
_cl5DoPurging(purge_data);
- slapi_counter_decrement(cldb->clThreads);
+ /* Remove the rid from the internal list */
+ remove_cleaned_rid(purge_data->cleaned_rid);
- slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl,
- "trigger_cl_purging_thread - purged changelog for (%s) rid (%d)\n",
- slapi_sdn_get_dn(purge_data->suffix_sdn), purge_data->cleaned_rid);
+ slapi_counter_decrement(cldb->clThreads);
free_and_return:
pthread_mutex_unlock(&(cldb->stLock));
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
index f7fc74e82..45b42be0f 100644
--- a/ldap/servers/plugins/replication/repl5.h
+++ b/ldap/servers/plugins/replication/repl5.h
@@ -830,8 +830,8 @@ typedef struct _cleanruv_data
typedef struct _cleanruv_purge_data
{
int cleaned_rid;
- const Slapi_DN *suffix_sdn;
Replica *replica;
+ Slapi_Task *task;
} cleanruv_purge_data;
typedef struct _csngen_test_data
diff --git a/ldap/servers/plugins/replication/repl_cleanallruv.c b/ldap/servers/plugins/replication/repl_cleanallruv.c
index 42877add5..a985e691f 100644
--- a/ldap/servers/plugins/replication/repl_cleanallruv.c
+++ b/ldap/servers/plugins/replication/repl_cleanallruv.c
@@ -1777,7 +1777,6 @@ replica_execute_cleanruv_task(Replica *replica, ReplicaId rid, char *returntext
{
Object *RUVObj;
RUV *local_ruv = NULL;
- cleanruv_purge_data *purge_data;
int rc = 0;
PR_ASSERT(replica);
@@ -1794,10 +1793,14 @@ replica_execute_cleanruv_task(Replica *replica, ReplicaId rid, char *returntext
(ruv_replica_count(local_ruv) <= 1)) {
return LDAP_UNWILLING_TO_PERFORM;
}
- rc = ruv_delete_replica(local_ruv, rid);
- if (replica_write_ruv(replica)) {
+ if ((rc = ruv_delete_replica(local_ruv, rid))) {
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "cleanAllRUV_task - "
+ "Failed to remove rid from RUV (%d)\n", rc);
+ return LDAP_OPERATIONS_ERROR;
+ }
+ if ((rc = replica_write_ruv(replica))) {
slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name,
- "cleanAllRUV_task - Could not write RUV\n");
+ "cleanAllRUV_task - Could not write RUV (%d)\n", rc);
}
object_release(RUVObj);
@@ -1809,19 +1812,6 @@ replica_execute_cleanruv_task(Replica *replica, ReplicaId rid, char *returntext
*/
cl5CleanRUV(rid, replica);
- /*
- * Now purge the changelog. The purging thread will free the purge_data
- */
- purge_data = (cleanruv_purge_data *)slapi_ch_calloc(1, sizeof(cleanruv_purge_data));
- purge_data->cleaned_rid = rid;
- purge_data->suffix_sdn = replica_get_root(replica);
- purge_data->replica = replica;
- trigger_cl_purging(purge_data);
-
- if (rc != RUV_SUCCESS) {
- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "cleanAllRUV_task - Task failed(%d)\n", rc);
- return LDAP_OPERATIONS_ERROR;
- }
slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, "cleanAllRUV_task - Finished successfully\n");
return LDAP_SUCCESS;
}
@@ -2097,6 +2087,7 @@ static void
replica_cleanallruv_thread(void *arg)
{
cleanruv_data *data = arg;
+ cleanruv_purge_data *purge_data = NULL;
Object *agmt_obj = NULL;
Object *ruv_obj = NULL;
Repl_Agmt *agmt = NULL;
@@ -2377,7 +2368,20 @@ done:
"Propagated task does not delete Keep alive entry (%d).", data->rid);
}
clean_agmts(data);
- remove_cleaned_rid(data->rid);
+
+ /*
+ * Now purge the changelog. The purging thread will free the
+ * purge_data and update the cleaned rid list
+ */
+ purge_data = (cleanruv_purge_data *)slapi_ch_calloc(1, sizeof(cleanruv_purge_data));
+ purge_data->cleaned_rid = data->rid;
+ purge_data->replica = data->replica;
+ purge_data->task = data->task;
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
+ "Triggering changelog purge thread. This might complete "
+ "after the cleaning task finishes.");
+ trigger_cl_purging(purge_data);
+
cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
"Successfully cleaned rid(%d)", data->rid);
} else {
@@ -2436,7 +2440,8 @@ clean_agmts(cleanruv_data *data)
agmt_obj = agmtlist_get_next_agreement_for_replica(data->replica, agmt_obj);
continue;
}
- cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Cleaning agmt...");
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
+ "Cleaning agmt (%s) ...", agmt_get_long_name(agmt));
agmt_stop(agmt);
agmt_update_consumer_ruv(agmt);
agmt_start(agmt);
--
2.47.1

View File

@ -0,0 +1,70 @@
From de52853a3551f1d1876ea21b33a5242ad669fec1 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Tue, 4 Feb 2025 15:40:16 +0000
Subject: [PATCH] Issue 6566 - RI plugin failure to handle a modrdn for rename
of member of multiple groups (#6567)
Bug description:
With AM and RI plugins enabled, the rename of a user that is part of multiple groups
fails with a "value exists" error.
Fix description:
For a modrdn the RI plugin creates a new DN, before a modify is attempted check
if the new DN already exists in the attr being updated.
Fixes: https://github.com/389ds/389-ds-base/issues/6566
Reviewed by: @progier389 , @tbordaz (Thank you)
---
ldap/servers/plugins/referint/referint.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
index 468fdc239..218863ea5 100644
--- a/ldap/servers/plugins/referint/referint.c
+++ b/ldap/servers/plugins/referint/referint.c
@@ -924,6 +924,7 @@ _update_all_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
{
Slapi_Mods *smods = NULL;
char *newDN = NULL;
+ struct berval bv = {0};
char **dnParts = NULL;
char *sval = NULL;
char *newvalue = NULL;
@@ -1026,22 +1027,30 @@ _update_all_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
}
/* else: normalize_rc < 0) Ignore the DN normalization error for now. */
+ bv.bv_val = newDN;
+ bv.bv_len = strlen(newDN);
p = PL_strstr(sval, slapi_sdn_get_ndn(origDN));
if (p == sval) {
/* (case 1) */
slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
- slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newDN);
-
+ /* Add only if the attr value does not exist */
+ if (VALUE_PRESENT != attr_value_find_wsi(attr, &bv, &v)) {
+ 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);
*p = '\0';
newvalue = slapi_ch_smprintf("%s%s", sval, newDN);
- slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newvalue);
+ /* Add only if the attr value does not exist */
+ if (VALUE_PRESENT != attr_value_find_wsi(attr, &bv, &v)) {
+ slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newvalue);
+ }
slapi_ch_free_string(&newvalue);
}
/* else: value does not include the modified DN. Ignore it. */
slapi_ch_free_string(&sval);
+ bv = (struct berval){0};
}
rc = _do_modify(mod_pb, entrySDN, slapi_mods_get_ldapmods_byref(smods));
if (rc) {
--
2.48.0

View File

@ -0,0 +1,43 @@
From a634756784056270773d67747061e26152d85469 Mon Sep 17 00:00:00 2001
From: Masahiro Matsuya <mmatsuya@redhat.com>
Date: Wed, 5 Feb 2025 11:38:04 +0900
Subject: [PATCH] Issue 6258 - Mitigate race condition in paged_results_test.py
(#6433)
The regression test dirsrvtests/tests/suites/paged_results/paged_results_test.py::test_multi_suffix_search has a race condition causing it to fail due to multiple queries potentially writing their logs out of chronological order.
This failure is mitigated by sorting the retrieved access_log_lines by their "op" value. This ensures the log lines are in chronological order, as expected by the assertions at the end of test_multi_suffix_search().
Helps fix: #6258
Reviewed by: @droideck , @progier389 (Thanks!)
Co-authored-by: Anuar Beisembayev <111912342+abeisemb@users.noreply.github.com>
---
dirsrvtests/tests/suites/paged_results/paged_results_test.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
index eaf0e0da9..fca48db0f 100644
--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py
+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
@@ -7,6 +7,7 @@
# --- END COPYRIGHT BLOCK ---
#
import socket
+import re
from random import sample, randrange
import pytest
@@ -1126,6 +1127,8 @@ def test_multi_suffix_search(topology_st, create_user, new_suffixes):
topology_st.standalone.restart(timeout=10)
access_log_lines = topology_st.standalone.ds_access_log.match('.*pr_cookie=.*')
+ # Sort access_log_lines by op number to mitigate race condition effects.
+ access_log_lines.sort(key=lambda x: int(re.search(r"op=(\d+) RESULT", x).group(1)))
pr_cookie_list = ([line.rsplit('=', 1)[-1] for line in access_log_lines])
pr_cookie_list = [int(pr_cookie) for pr_cookie in pr_cookie_list]
log.info('Assert that last pr_cookie == -1 and others pr_cookie == 0')
--
2.48.0

View File

@ -1,460 +0,0 @@
From 1ffc8c7c97b158e8cefd22bd0402aa0285b2baba Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 31 Oct 2024 08:09:09 -0400
Subject: [PATCH] Issue 6390 - Adjust cleanAllRUV max per txn and interval
limits
The cleanAllRUV changelog purging and the "normal" changelog trimming are
using different limits. The cleanAllRUV "purging" holds the transaction 10
times longer than the "trimming" does. When we use the same/smaller limits
the CPU still gets high but it's spread out and it should not block other
threads from running.
Improved overall work flow of cleanAllRUV - changed changelog purging to
be a function and not a thread which simplifies other parts of the code.
Also fixed typo in repl logs (issue 6369), and deadlock with lmdb during
purging (issue 6396)
relates: https://github.com/389ds/389-ds-base/issues/6390
fixes: https://github.com/389ds/389-ds-base/issues/6396
fixes: https://github.com/389ds/389-ds-base/issues/6369
Reviewed by: jchapman & progier (Thanks!!)
---
ldap/servers/plugins/replication/cl5_api.c | 127 ++++++++----------
ldap/servers/plugins/replication/cl5_api.h | 41 +++---
ldap/servers/plugins/replication/repl5.h | 8 +-
.../plugins/replication/repl5_protocol_util.c | 2 +-
.../plugins/replication/repl_cleanallruv.c | 30 ++---
5 files changed, 91 insertions(+), 117 deletions(-)
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
index a944d3b29..92fb776dc 100644
--- a/ldap/servers/plugins/replication/cl5_api.c
+++ b/ldap/servers/plugins/replication/cl5_api.c
@@ -90,8 +90,6 @@
#define MAX_RETRIES 10 /* Maximum number of retry in case of db retryable error */
#define CL5_TRIM_MAX_PER_TRANSACTION 100
#define CL5_TRIM_MAX_LOOKUP_PER_TRANSACTION 10000
-#define CL5_PURGE_MAX_PER_TRANSACTION 1000
-#define CL5_PURGE_MAX_LOOKUP_PER_TRANSACTION 10000
/***** Data Definitions *****/
@@ -246,14 +244,13 @@ static int _cl5CheckMissingCSN(const CSN *minCsn, const RUV *supplierRUV, cldb_H
static int cldb_IsTrimmingEnabled(cldb_Handle *cldb);
static int _cl5TrimMain(void *param);
void _cl5TrimReplica(Replica *r);
-void _cl5PurgeRID(cleanruv_purge_data *data, cldb_Handle *cldb);
+int32_t _cl5PurgeRID(cleanruv_data *data, cldb_Handle *cldb);
static PRBool _cl5CanTrim(time_t time, long *numToTrim, Replica *replica, CL5Config *dbTrim);
int _cl5ConstructRUVs (cldb_Handle *cldb);
int _cl5ReadRUVs(cldb_Handle *cldb);
static int _cl5WriteRUV(cldb_Handle *cldb, PRBool purge);
static int _cl5UpdateRUV (cldb_Handle *cldb, CSN *csn, PRBool newReplica, PRBool purge);
static int _cl5GetRUV2Purge2(Replica *r, RUV **ruv);
-void trigger_cl_purging_thread(void *rid);
/* bakup/recovery, import/export */
static int _cl5LDIF2Operation(char *ldifEntry, slapi_operation_parameters *op, char **replGen);
@@ -1844,9 +1841,13 @@ _cl5Iterate(cldb_Handle *cldb, dbi_iterate_cb_t *action_cb, DBLCI_CTX *dblcictx,
dblcictx->finished = PR_FALSE;
dblcictx->cldb = cldb;
- while ( !slapi_is_shutting_down() &&
- ((rc == CL5_SUCCESS && dblcictx->finished == PR_FALSE) ||
- (rc == CL5_DB_RETRY && nbtries < MAX_RETRIES))) {
+ while ((rc == CL5_SUCCESS && dblcictx->finished == PR_FALSE) ||
+ (rc == CL5_DB_RETRY && nbtries < MAX_RETRIES))
+ {
+ if (slapi_is_shutting_down()) {
+ return CL5_SHUTDOWN;
+ }
+
nbtries++;
dblcictx->changed.nb = 0;
dblcictx->seen.nb = 0;
@@ -2502,7 +2503,7 @@ _cl5TrimMain(void *param)
cldb->trimmingOnGoing = 1;
slapi_counter_increment(cldb->clThreads);
- while (cldb->dbState == CL5_STATE_OPEN)
+ while (cldb->dbState == CL5_STATE_OPEN && !slapi_is_shutting_down())
{
pthread_mutex_unlock(&(cldb->stLock));
@@ -2548,24 +2549,28 @@ _cl5TrimMain(void *param)
* We are purging a changelog after a cleanAllRUV task. Find the specific
* changelog for the backend that is being cleaned, and purge all the records
* with the cleaned rid.
+ *
+ * If we encounter a shutdown _cl5PurgeRID will return 1
*/
-static void
-_cl5DoPurging(cleanruv_purge_data *purge_data)
+static int32_t
+_cl5DoPurging(cleanruv_data *purge_data)
{
cldb_Handle *cldb = replica_get_cl_info(purge_data->replica);
+ int32_t rc = 0;
+
if (cldb == NULL) {
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
"_cl5DoPurging - Changelog info was NULL - is your replication configuration valid?\n");
- return;
+ return rc;
}
pthread_mutex_lock(&(cldb->clLock));
- _cl5PurgeRID(purge_data, cldb);
+ rc = _cl5PurgeRID(purge_data, cldb);
pthread_mutex_unlock(&(cldb->clLock));
- return;
+ return rc;
}
static inline int
@@ -2651,7 +2656,7 @@ _cl5PurgeRidOnEntry(dbi_val_t *key, dbi_val_t *data, void *ctx)
}
/*
- * _cl5PurgeRID(cleanruv_purge_data, cleaned_rid)
+ * _cl5PurgeRID(cleanruv_data, cleaned_rid)
*
* Clean the entire changelog of updates from the "cleaned rid" via CLEANALLRUV
* Delete entries in batches so we don't consume too many db locks, and we don't
@@ -2659,19 +2664,28 @@ _cl5PurgeRidOnEntry(dbi_val_t *key, dbi_val_t *data, void *ctx)
* We save the key from the last iteration so we don't have to start from the
* beginning for each new iteration.
*/
-void
-_cl5PurgeRID(cleanruv_purge_data *data, cldb_Handle *cldb)
+int32_t
+_cl5PurgeRID(cleanruv_data *data, cldb_Handle *cldb)
{
DBLCI_CTX dblcictx = {0};
int32_t rc = 0;
- dblcictx.seen.nbmax = CL5_PURGE_MAX_LOOKUP_PER_TRANSACTION;
- dblcictx.changed.nbmax = CL5_PURGE_MAX_PER_TRANSACTION;
- dblcictx.rid2purge = data->cleaned_rid;
+ if (dblayer_is_lmdb(cldb->be)) {
+ dblcictx.seen.nbmax = 5000;
+ dblcictx.changed.nbmax = 50;
+ } else {
+ dblcictx.seen.nbmax = 10000;
+ dblcictx.changed.nbmax = 50;
+ }
+ dblcictx.rid2purge = data->rid;
rc = _cl5Iterate(cldb, _cl5PurgeRidOnEntry, &dblcictx, PR_FALSE);
- if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) {
- cleanruv_log(data->task, data->cleaned_rid, CLEANALLRUV_ID,
+ if (rc == CL5_SHUTDOWN) {
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_NOTICE,
+ "Server shutting down. Process will resume at server "
+ "startup");
+ } else if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) {
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID,
SLAPI_LOG_ERR,
"Purging failed to iterate through the entire changelog "
"(error %d). There is a chance the rid was not fully "
@@ -2679,11 +2693,14 @@ _cl5PurgeRID(cleanruv_purge_data *data, cldb_Handle *cldb)
"again.",
rc);
} else {
- cleanruv_log(data->task, data->cleaned_rid, CLEANALLRUV_ID,
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID,
SLAPI_LOG_INFO,
- "Purged %ld records from the changelog",
- dblcictx.changed.tot);
+ "Scanned %ld records, and purged %ld records from the "
+ "changelog",
+ dblcictx.seen.tot, dblcictx.changed.tot);
}
+
+ return rc;
}
/*
@@ -4292,7 +4309,7 @@ _cl5ExportFile(PRFileDesc *prFile, cldb_Handle *cldb)
}
slapi_write_buffer(prFile, "\n", strlen("\n"));
- dblcictx.seen.nbmax = CL5_PURGE_MAX_LOOKUP_PER_TRANSACTION;
+ dblcictx.seen.nbmax = CL5_TRIM_MAX_LOOKUP_PER_TRANSACTION;
dblcictx.exportFile = prFile;
rc = _cl5Iterate(cldb, _cl5ExportEntry2File, &dblcictx, PR_TRUE);
@@ -4415,68 +4432,40 @@ cl5CleanRUV(ReplicaId rid, Replica *replica)
ruv_delete_replica(cldb->maxRUV, rid);
}
-static void
-free_purge_data(cleanruv_purge_data *purge_data)
-{
- slapi_ch_free((void **)&purge_data);
-}
-
-/*
- * Create a thread to purge a changelog of cleaned RIDs
- */
-void
-trigger_cl_purging(cleanruv_purge_data *purge_data)
-{
- PRThread *trim_tid = NULL;
-
- trim_tid = PR_CreateThread(PR_USER_THREAD, (VFP)(void *)trigger_cl_purging_thread,
- (void *)purge_data, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
- PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE);
- if (NULL == trim_tid) {
- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
- "trigger_cl_purging - Failed to create cl purging "
- "thread; NSPR error - %d\n",
- PR_GetError());
- free_purge_data(purge_data);
- } else {
- /* need a little time for the thread to get started */
- DS_Sleep(PR_SecondsToInterval(1));
- }
-}
-
/*
* Purge a changelog of entries that originated from a particular replica(rid)
*/
-void
-trigger_cl_purging_thread(void *arg)
+int32_t
+cldb_purge_rid(cleanruv_data *purge_data)
{
- cleanruv_purge_data *purge_data = (cleanruv_purge_data *)arg;
- Replica *replica = purge_data->replica;
- cldb_Handle *cldb = replica_get_cl_info(replica);
+ cldb_Handle *cldb = replica_get_cl_info(purge_data->replica);
+ int32_t rc = -1;
if (cldb == NULL) {
- return;
+ return rc;
}
pthread_mutex_lock(&(cldb->stLock));
+
/* Make sure we have a change log, and we aren't closing it */
if (cldb->dbState != CL5_STATE_OPEN) {
- goto free_and_return;
+ pthread_mutex_unlock(&(cldb->stLock));
+ return rc;
}
-
slapi_counter_increment(cldb->clThreads);
+ pthread_mutex_unlock(&(cldb->stLock));
/* Purge the changelog */
- _cl5DoPurging(purge_data);
-
- /* Remove the rid from the internal list */
- remove_cleaned_rid(purge_data->cleaned_rid);
+ rc = _cl5DoPurging(purge_data);
slapi_counter_decrement(cldb->clThreads);
-free_and_return:
- pthread_mutex_unlock(&(cldb->stLock));
- free_purge_data(purge_data);
+ /* Handle result code */
+ if (rc == CL5_SUCCESS || rc == CL5_NOTFOUND) {
+ return LDAP_SUCCESS;
+ } else {
+ return -1;
+ }
}
char *
diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h
index a690eee37..e4b4f11ee 100644
--- a/ldap/servers/plugins/replication/cl5_api.h
+++ b/ldap/servers/plugins/replication/cl5_api.h
@@ -81,26 +81,27 @@ typedef enum {
/* error codes */
enum
{
- CL5_SUCCESS, /* successful operation */
- CL5_BAD_DATA, /* invalid parameter passed to the function */
- CL5_BAD_FORMAT, /* db data has unexpected format */
- CL5_BAD_STATE, /* changelog is in an incorrect state for attempted operation */
- CL5_BAD_DBVERSION, /* changelog has invalid dbversion */
- CL5_DB_ERROR, /* database error */
- CL5_NOTFOUND, /* requested entry or value was not found */
- CL5_MEMORY_ERROR, /* memory allocation failed */
- CL5_SYSTEM_ERROR, /* NSPR error occured, use PR_Error for furhter info */
- CL5_CSN_ERROR, /* CSN API failed */
- CL5_RUV_ERROR, /* RUV API failed */
- CL5_OBJSET_ERROR, /* namedobjset api failed */
- CL5_DB_LOCK_ERROR, /* bdb returns error 12 when the db runs out of locks,
+ CL5_SUCCESS, /* successful operation */
+ CL5_BAD_DATA, /* invalid parameter passed to the function */
+ CL5_BAD_FORMAT, /* db data has unexpected format */
+ CL5_BAD_STATE, /* changelog is in an incorrect state for attempted operation */
+ CL5_BAD_DBVERSION, /* changelog has invalid dbversion */
+ CL5_DB_ERROR, /* database error */
+ CL5_NOTFOUND, /* requested entry or value was not found */
+ CL5_MEMORY_ERROR, /* memory allocation failed */
+ CL5_SYSTEM_ERROR, /* NSPR error occured, use PR_Error for furhter info */
+ CL5_CSN_ERROR, /* CSN API failed */
+ CL5_RUV_ERROR, /* RUV API failed */
+ CL5_OBJSET_ERROR, /* namedobjset api failed */
+ CL5_DB_LOCK_ERROR, /* bdb returns error 12 when the db runs out of locks,
this var needs to be in slot 12 of the list.
Do not re-order enum above! */
- CL5_PURGED_DATA, /* requested data has been purged */
- CL5_MISSING_DATA, /* data should be in the changelog, but is missing */
- CL5_UNKNOWN_ERROR, /* unclassified error */
- CL5_IGNORE_OP, /* ignore this updated - used by CLEANALLRUV task */
- CL5_DB_RETRY, /* Retryable database error */
+ CL5_PURGED_DATA, /* requested data has been purged */
+ CL5_MISSING_DATA, /* data should be in the changelog, but is missing */
+ CL5_UNKNOWN_ERROR, /* unclassified error */
+ CL5_IGNORE_OP, /* ignore this updated - used by CLEANALLRUV task */
+ CL5_DB_RETRY, /* Retryable database error */
+ CL5_SHUTDOWN, /* server shutdown during changelog iteration */
CL5_LAST_ERROR_CODE /* Should always be last in this enum */
};
@@ -140,7 +141,7 @@ int cl5Open(void);
int cl5Close(void);
/* Name: cldb_RemoveReplicaDB
- Description: Clear the cldb information from the replica
+ Description: Clear the cldb information from the replica
and delete the database file
*/
int cldb_RemoveReplicaDB(Replica *replica);
@@ -328,7 +329,7 @@ int cl5NotifyRUVChange(Replica *replica);
void cl5CleanRUV(ReplicaId rid, Replica *replica);
void cl5NotifyCleanup(int rid);
-void trigger_cl_purging(cleanruv_purge_data *purge_data);
+int32_t cldb_purge_rid(cleanruv_data *purge_data);
int cldb_SetReplicaDB(Replica *replica, void *arg);
int cldb_UnSetReplicaDB(Replica *replica, void *arg);
int cldb_StartTrimming(Replica *replica);
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
index 45b42be0f..2ba2cfaa7 100644
--- a/ldap/servers/plugins/replication/repl5.h
+++ b/ldap/servers/plugins/replication/repl5.h
@@ -827,13 +827,6 @@ typedef struct _cleanruv_data
PRBool original_task;
} cleanruv_data;
-typedef struct _cleanruv_purge_data
-{
- int cleaned_rid;
- Replica *replica;
- Slapi_Task *task;
-} cleanruv_purge_data;
-
typedef struct _csngen_test_data
{
Slapi_Task *task;
@@ -927,5 +920,6 @@ void cleanruv_log(Slapi_Task *task, int rid, char *task_type, int sev_level, cha
char *replica_cleanallruv_get_local_maxcsn(ReplicaId rid, char *base_dn);
int replica_execute_cleanruv_task(Replica *r, ReplicaId rid, char *returntext);
int replica_execute_cleanall_ruv_task(Replica *r, ReplicaId rid, Slapi_Task *task, const char *force_cleaning, PRBool original_task, char *returntext);
+void delete_cleaned_rid_config(cleanruv_data *data);
#endif /* _REPL5_H_ */
diff --git a/ldap/servers/plugins/replication/repl5_protocol_util.c b/ldap/servers/plugins/replication/repl5_protocol_util.c
index 78fa863bb..25173e5cd 100644
--- a/ldap/servers/plugins/replication/repl5_protocol_util.c
+++ b/ldap/servers/plugins/replication/repl5_protocol_util.c
@@ -361,7 +361,7 @@ acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv)
/* remote replica detected a duplicate ReplicaID */
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name,
"acquire_replica - "
- "%s: Unable to aquire replica: the replica "
+ "%s: Unable to acquire replica: the replica "
"has the same Replica ID as this one. "
"Replication is aborting.\n",
agmt_get_long_name(prp->agmt));
diff --git a/ldap/servers/plugins/replication/repl_cleanallruv.c b/ldap/servers/plugins/replication/repl_cleanallruv.c
index a985e691f..b62c2fae0 100644
--- a/ldap/servers/plugins/replication/repl_cleanallruv.c
+++ b/ldap/servers/plugins/replication/repl_cleanallruv.c
@@ -41,7 +41,7 @@ static int replica_cleanallruv_send_abort_extop(Repl_Agmt *ra, Slapi_Task *task,
static int replica_cleanallruv_check_maxcsn(Repl_Agmt *agmt, char *basedn, char *rid_text, char *maxcsn, Slapi_Task *task);
static int replica_cleanallruv_replica_alive(Repl_Agmt *agmt);
static int replica_cleanallruv_check_ruv(char *repl_root, Repl_Agmt *ra, char *rid_text, Slapi_Task *task, char *force);
-static void delete_cleaned_rid_config(cleanruv_data *data);
+
static int replica_cleanallruv_is_finished(Repl_Agmt *agmt, char *filter, Slapi_Task *task);
static void check_replicas_are_done_cleaning(cleanruv_data *data);
static void check_replicas_are_done_aborting(cleanruv_data *data);
@@ -791,7 +791,7 @@ delete_aborted_rid(Replica *r, ReplicaId rid, char *repl_root, char *certify_all
/*
* Just remove the dse.ldif config, but we need to keep the cleaned rids in memory until we know we are done
*/
-static void
+void
delete_cleaned_rid_config(cleanruv_data *clean_data)
{
Slapi_PBlock *pb, *modpb;
@@ -2087,7 +2087,6 @@ static void
replica_cleanallruv_thread(void *arg)
{
cleanruv_data *data = arg;
- cleanruv_purge_data *purge_data = NULL;
Object *agmt_obj = NULL;
Object *ruv_obj = NULL;
Repl_Agmt *agmt = NULL;
@@ -2351,13 +2350,11 @@ done:
/*
* Success - the rid has been cleaned!
*
- * Delete the cleaned rid config.
* Make sure all the replicas have been "pre_cleaned"
* Remove the keep alive entry if present
* Clean the agreements' RUV
- * Remove the rid from the internal clean list
+ * Purge the changelog
*/
- delete_cleaned_rid_config(data);
check_replicas_are_done_cleaning(data);
if (data->original_task) {
cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
@@ -2369,21 +2366,14 @@ done:
}
clean_agmts(data);
- /*
- * Now purge the changelog. The purging thread will free the
- * purge_data and update the cleaned rid list
- */
- purge_data = (cleanruv_purge_data *)slapi_ch_calloc(1, sizeof(cleanruv_purge_data));
- purge_data->cleaned_rid = data->rid;
- purge_data->replica = data->replica;
- purge_data->task = data->task;
cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
- "Triggering changelog purge thread. This might complete "
- "after the cleaning task finishes.");
- trigger_cl_purging(purge_data);
-
- cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
- "Successfully cleaned rid(%d)", data->rid);
+ "Purging changelog...");
+ if (cldb_purge_rid(data) == LDAP_SUCCESS) {
+ delete_cleaned_rid_config(data);
+ remove_cleaned_rid(data->rid);
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO,
+ "Successfully cleaned rid(%d)", data->rid);
+ }
} else {
/*
* Shutdown or abort
--
2.47.1

View File

@ -1,4 +1,4 @@
From 2e5a470ab5195b5e5f31fa8fb7b03d0c20b6a90f Mon Sep 17 00:00:00 2001
From 769e71499880a0820424bf925c0f0fe793e11cc8 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 28 Jun 2024 18:56:49 +0200
Subject: [PATCH] Issue 6229 - After an initial failure, subsequent online
@ -134,10 +134,10 @@ index 0460a42f6..6658cc80a 100644
slapi_ch_free_string(&backup_config_dir);
slapi_ch_free_string(&dse_file);
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 457c5ed60..35f8173a7 100644
index 70a289bdb..de4161b0c 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
@@ -982,6 +982,9 @@ dbmdb_backup(struct ldbminfo *li, char *dest_dir, Slapi_Task *task)
@@ -983,6 +983,9 @@ dbmdb_backup(struct ldbminfo *li, char *dest_dir, Slapi_Task *task)
if (ldbm_archive_config(dest_dir, task) != 0) {
slapi_log_err(SLAPI_LOG_ERR, "dbmdb_backup",
"Backup of config files failed or is incomplete\n");
@ -148,7 +148,7 @@ index 457c5ed60..35f8173a7 100644
goto bail;
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 4edea63b7..95926a5d6 100644
index 368741a66..cb372c138 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -69,7 +69,7 @@ from lib389.utils import (
@ -160,7 +160,7 @@ index 4edea63b7..95926a5d6 100644
from lib389.dseldif import DSEldif
# mixin
@@ -1420,7 +1420,7 @@ class DirSrv(SimpleLDAPObject, object):
@@ -1424,7 +1424,7 @@ class DirSrv(SimpleLDAPObject, object):
name, self.ds_paths.prefix)
# create the archive
@ -169,7 +169,7 @@ index 4edea63b7..95926a5d6 100644
backup_file = os.path.join(backup_dir, name)
tar = tarfile.open(backup_file, "w:gz")
tar.extraction_filter = (lambda member, path: member)
@@ -2806,7 +2806,7 @@ class DirSrv(SimpleLDAPObject, object):
@@ -2810,7 +2810,7 @@ class DirSrv(SimpleLDAPObject, object):
else:
# No output file specified. Use the default ldif location/name
cmd.append('-a')
@ -178,7 +178,7 @@ index 4edea63b7..95926a5d6 100644
if bename:
ldifname = os.path.join(self.ds_paths.ldif_dir, "%s-%s-%s.ldif" % (self.serverid, bename, tnow))
else:
@@ -2877,7 +2877,7 @@ class DirSrv(SimpleLDAPObject, object):
@@ -2881,7 +2881,7 @@ class DirSrv(SimpleLDAPObject, object):
if archive_dir is None:
# Use the instance name and date/time as the default backup name
@ -187,7 +187,7 @@ index 4edea63b7..95926a5d6 100644
archive_dir = os.path.join(self.ds_paths.backup_dir, "%s-%s" % (self.serverid, tnow))
elif not archive_dir.startswith("/"):
# Relative path, append it to the bak directory
@@ -3499,7 +3499,7 @@ class DirSrv(SimpleLDAPObject, object):
@@ -3506,7 +3506,7 @@ class DirSrv(SimpleLDAPObject, object):
if archive is None:
# Use the instance name and date/time as the default backup name
@ -197,7 +197,7 @@ index 4edea63b7..95926a5d6 100644
backup_dir_name = "%s-%s" % (self.serverid, tnow)
else:
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
index 193805780..c1a2e7aaa 100644
index 6c2adb5b2..6bf302862 100644
--- a/src/lib389/lib389/tasks.py
+++ b/src/lib389/lib389/tasks.py
@@ -118,7 +118,7 @@ class Task(DSLdapObject):
@ -562,5 +562,5 @@ index 193805780..c1a2e7aaa 100644
dn = f'cn={self.cn},cn=reload ldapi mappings,cn=tasks,cn=config'
super(LDAPIMappingReloadTask, self).__init__(instance, dn)
--
2.48.1
2.48.0

View File

@ -1,112 +0,0 @@
From 1217d4a2a4bd43ff572d91a6379501feb5d4c517 Mon Sep 17 00:00:00 2001
From: Firstyear <william@blackhats.net.au>
Date: Mon, 5 Aug 2024 08:57:35 +1000
Subject: [PATCH] Issue 6284 - BUG - freelist ordering causes high wtime
(#6285)
Bug Description: Multithread listeners were added to
support having multiple threads able to perform IO
for connections to reduce wtime. However, when the server
is first started if the number of clients is less than
the size of a single listeners conntable this causes
extremely high wtimes.
This is because the initial connection freelist
construction adds every connection in order of *listener*
so new connections always fill the first listener thread,
then only once that listener is full it spills over to the
next.
If the conntable size is small, this isn't noticeable, but
an example is if your contable is 16384 elements with 4
listeners, then the first 4096 connections will always
go to the first listener.
Fix Description: When the freelist is constructed rather
than iterate over each listener *then* each slot, we
iterate over each slot then each listenr. This causes
the initial freelist to be interleaved between all
listeners so that even with a small number of connections
the work is spread fairly without lasering a single
listener.
A supplied test from a user shows a significant drop in
wtime and an increase in throughput of operations with
this change.
fixes: https://github.com/389ds/389-ds-base/issues/6284
Author: William Brown <william@blackhats.net.au>
Review by: @tbordaz and @jchapma (Thanks!)
---
ldap/servers/slapd/conntable.c | 33 +++++++++++++++++++++++++++------
1 file changed, 27 insertions(+), 6 deletions(-)
diff --git a/ldap/servers/slapd/conntable.c b/ldap/servers/slapd/conntable.c
index 90f08abd4..b8e2ce3b5 100644
--- a/ldap/servers/slapd/conntable.c
+++ b/ldap/servers/slapd/conntable.c
@@ -115,20 +115,26 @@ connection_table_new(int table_size)
{
Connection_Table *ct;
size_t i = 0;
- int ct_list = 0;
- int free_idx = 0;
+ size_t ct_list = 0;
+ size_t free_idx = 0;
ber_len_t maxbersize = config_get_maxbersize();
ct = (Connection_Table *)slapi_ch_calloc(1, sizeof(Connection_Table));
ct->list_num = config_get_num_listeners();
ct->num_active = (int *)slapi_ch_calloc(1, ct->list_num * sizeof(int));
+ /* Connection table size must be divisible by the number of listeners */
ct->size = table_size - (table_size % ct->list_num);
/* Account for first slot of each list being used as head (c_next). */
ct->list_size = (ct->size/ct->list_num)+1;
+ /*
+ * Since there are extra list heads, in the case ct->size was a multiple of list_num, we
+ * need to expand ct->size to reflect the true allocation amount.
+ */
+ ct->size = ct->list_size * ct->list_num;
ct->c = (Connection **)slapi_ch_calloc(1, ct->size * sizeof(Connection *));
ct->fd = (struct POLL_STRUCT **)slapi_ch_calloc(1, ct->list_num * sizeof(struct POLL_STRUCT*));
ct->table_mutex = PR_NewLock();
/* Allocate the freelist (a slot for each connection plus another slot for the final NULL pointer) */
- ct->c_freelist = (Connection **)slapi_ch_calloc(1, (ct->size+1) * sizeof(Connection *));
+ ct->c_freelist = (Connection **)slapi_ch_calloc(1, (ct->size + 1) * sizeof(Connection *));
ct->conn_next_offset = 0;
slapi_log_err(SLAPI_LOG_INFO, "connection_table_new", "Number of connection sub-tables %d, each containing %d slots.\n",
@@ -184,11 +190,26 @@ connection_table_new(int table_size)
/* Ready to rock, mark as such. */
ct->c[ct_list][i].c_state = CONN_STATE_INIT;
+ }
+ }
+ /*
+ * The freelist is how new connections are inserted into our ct to be used. We need to ensure
+ * these are *spread* initially over the set of listeners so that we distributed between handlers
+ * from the start. Over time as things are re-added to the freelist it will "shuffle" and we
+ * will effectively have randomised listener assignment.
+ *
+ * Without this it means that a single listener will be "focused on" until it's conntable
+ * is full at which point we spill out into the next listener.
+ *
+ * This is why in the subsequent loops we INVERT the loop to iterate over list_size
+ * first, and then the ct_list as the inner component so that the freelist initially
+ * alternates between the listeners first to help distribute requests.
+ */
+ for (i = 1; i < ct->list_size; i++) {
+ for (ct_list = 0; ct_list < ct->list_num; ct_list++) {
/* Map multiple ct lists to a single freelist, but skip slot 0 of each list. */
- if (i != 0) {
- ct->c_freelist[free_idx++] = &(ct->c[ct_list][i]);
- }
+ ct->c_freelist[free_idx++] = &(ct->c[ct_list][i]);
}
}
--
2.47.1

View File

@ -1,54 +0,0 @@
From 47709ccde40a50eb908bf43e11cce3dc68fa0ef5 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Wed, 21 Aug 2024 23:00:37 +0100
Subject: [PATCH] Issue 6296 - basic_test.py::test_conn_limits fails in main
branch (#6300)
Description:
A CI test to validate connection management functionality by
configuring an instance with a very small connection table fails,
with the instance failing to start.
Fix description:
In a multi list connection table configuration, the connection table
code has been updated to expand the connection table size to include
a head for each list. This expanded size needs to be accounted for
when we determine if we can accept new connections and on final check
at server starup time.
Relates: https://github.com/389ds/389-ds-base/issues/6296
Reviewed by: @droideck (Thank you)
---
ldap/servers/slapd/daemon.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
index 9e2f32c43..56e20bd13 100644
--- a/ldap/servers/slapd/daemon.c
+++ b/ldap/servers/slapd/daemon.c
@@ -829,8 +829,8 @@ accept_thread(void *vports)
num_poll = setup_pr_accept_pds(n_tcps, s_tcps, i_unix, &fds);
while (!g_get_shutdown()) {
- /* Do we need to accept new connections? */
- int accept_new_connections = (ct->size > ct->conn_next_offset);
+ /* Do we need to accept new connections, account for ct->size including list heads. */
+ int accept_new_connections = ((ct->size - ct->list_num) > ct->conn_next_offset);
if (!accept_new_connections) {
if (last_accept_new_connections) {
slapi_log_err(SLAPI_LOG_ERR, "accept_thread",
@@ -2081,8 +2081,8 @@ unfurl_banners(Connection_Table *ct, daemon_ports_t *ports, PRFileDesc **n_tcps,
slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
char addrbuf[256];
int isfirsttime = 1;
-
- if (ct->size > (slapdFrontendConfig->maxdescriptors - slapdFrontendConfig->reservedescriptors)) {
+ /* Take into account that ct->size includes a list head for each listener. */
+ if ((ct->size - ct->list_num) > (slapdFrontendConfig->maxdescriptors - slapdFrontendConfig->reservedescriptors)) {
slapi_log_err(SLAPI_LOG_ERR, "slapd_daemon",
"Not enough descriptors to accept any connections. "
"This may be because the maxdescriptors configuration "
--
2.47.1

View File

@ -1,4 +1,4 @@
From 39138128783d3e3cd337a6d6b01b19abbc7bb6de Mon Sep 17 00:00:00 2001
From b2511553590f0d9b41856d8baff5f3cd103dd46f Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Thu, 6 Feb 2025 18:25:36 +0100
Subject: [PATCH] Issue 6554 - During import of entries without nsUniqueId, a
@ -27,7 +27,7 @@ Reviewed by: Pierre Rogier
2 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py
index 832a27cf6..6b6630133 100644
index b7cba32fd..18caec633 100644
--- a/dirsrvtests/tests/suites/import/import_test.py
+++ b/dirsrvtests/tests/suites/import/import_test.py
@@ -14,11 +14,13 @@ import os
@ -45,8 +45,8 @@ index 832a27cf6..6b6630133 100644
from lib389.dbgen import dbgen_users
from lib389.tasks import ImportTask
from lib389.index import Indexes
@@ -627,6 +629,81 @@ def test_crash_on_ldif2db_with_lmdb(topo, _import_clean):
__check_for_core(now)
@@ -690,6 +692,81 @@ def test_online_import_under_load(topo):
assert import_task.get_exit_code() == 0
+def test_duplicate_nsuniqueid(topo_m2, request):
@ -128,10 +128,10 @@ index 832a27cf6..6b6630133 100644
# Run isolated
# -s for DEBUG mode
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 65f564aff..716e3c0f3 100644
index 707a110c5..0f445bb56 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
@@ -606,10 +606,20 @@ dbmdb_import_generate_uniqueid(ImportJob *job, Slapi_Entry *e)
@@ -610,10 +610,20 @@ dbmdb_import_generate_uniqueid(ImportJob *job, Slapi_Entry *e)
{
const char *uniqueid = slapi_entry_get_uniqueid(e);
int rc = UID_SUCCESS;
@ -152,7 +152,7 @@ index 65f564aff..716e3c0f3 100644
/* generate id based on dn */
if (job->uuid_gen_type == SLAPI_UNIQUEID_GENERATE_NAME_BASED) {
char *dn = slapi_entry_get_dn(e);
@@ -620,6 +630,7 @@ dbmdb_import_generate_uniqueid(ImportJob *job, Slapi_Entry *e)
@@ -624,6 +634,7 @@ dbmdb_import_generate_uniqueid(ImportJob *job, Slapi_Entry *e)
/* time based */
rc = slapi_uniqueIDGenerateString(&newuniqueid);
}
@ -161,5 +161,5 @@ index 65f564aff..716e3c0f3 100644
if (rc == UID_SUCCESS) {
slapi_entry_set_uniqueid(e, newuniqueid);
--
2.48.1
2.48.0

View File

@ -1,686 +0,0 @@
From 569628ce405f214a7e35deb96b4e6c22519a4268 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Wed, 4 Dec 2024 22:29:11 -0500
Subject: [PATCH] Issue 5798 - Fix dsconf config multi-valued attr operations
(#6426)
Description:
Fix add operation to properly handle multi-valued attributes
so they persist after server restart.
Add support for multiple values at once via dsconf config.
Handle delete operation in a more flexible way.
Refactor attribute handling code for better error handling and consistency.
*Add CI test suite
Fixes: https://github.com/389ds/389-ds-base/issues/5798
Reviewed by: @progier389 (Thanks!)
---
.../tests/suites/clu/dsconf_config_test.py | 347 ++++++++++++++++++
src/lib389/lib389/_mapped_object.py | 55 +--
src/lib389/lib389/cli_conf/config.py | 172 +++++----
3 files changed, 481 insertions(+), 93 deletions(-)
create mode 100644 dirsrvtests/tests/suites/clu/dsconf_config_test.py
diff --git a/dirsrvtests/tests/suites/clu/dsconf_config_test.py b/dirsrvtests/tests/suites/clu/dsconf_config_test.py
new file mode 100644
index 000000000..d67679adf
--- /dev/null
+++ b/dirsrvtests/tests/suites/clu/dsconf_config_test.py
@@ -0,0 +1,347 @@
+# --- 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 subprocess
+import logging
+import pytest
+from lib389._constants import DN_DM
+from lib389.topologies import topology_st
+
+pytestmark = pytest.mark.tier1
+
+log = logging.getLogger(__name__)
+
+HAPROXY_IPS = {
+ 'single': '192.168.1.1',
+ 'multiple': ['10.0.0.1', '172.16.0.1', '192.168.2.1']
+}
+
+REFERRALS = {
+ 'single': 'ldap://primary.example.com',
+ 'multiple': [
+ 'ldap://server1.example.com',
+ 'ldap://server2.example.com',
+ 'ldap://server3.example.com'
+ ]
+}
+
+TEST_ATTRS = [
+ ('nsslapd-haproxy-trusted-ip', HAPROXY_IPS),
+ ('nsslapd-referral', REFERRALS)
+]
+
+
+def execute_dsconf_command(dsconf_cmd, subcommands):
+ """Execute dsconf command and return output and return code"""
+
+ cmdline = dsconf_cmd + subcommands
+ proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
+ out, _ = proc.communicate()
+ return out.decode('utf-8'), proc.returncode
+
+
+def get_dsconf_base_cmd(topology):
+ """Return base dsconf command list"""
+
+ return ['/usr/sbin/dsconf', topology.standalone.serverid,
+ '-D', DN_DM, '-w', 'password']
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_single_value_add(topology_st, attr_name, values_dict):
+ """Test adding a single value to an attribute
+
+ :id: ffc912a6-c188-413d-9c35-7f4b3774d946
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add a single value to the specified attribute
+ 2. Verify the value was added correctly
+ :expectedresults:
+ 1. Command should execute successfully
+ 2. Added value should be present in the configuration
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ value = values_dict['single']
+ test_attr = f"{attr_name}={value}"
+
+ # Add single value
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'add', test_attr])
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_single_value_replace(topology_st, attr_name, values_dict):
+ """Test replacing a single value in configuration attributes
+
+ :id: 112e3e5e-8db8-4974-9ea4-ed789c2d02f2
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add initial value to the specified attribute
+ 2. Replace the value with a new one
+ 3. Verify the replacement was successful
+ :expectedresults:
+ 1. Initial value should be added successfully
+ 2. Replace command should execute successfully
+ 3. New value should be present and old value should be absent
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add initial value
+ value = values_dict['single']
+ test_attr = f"{attr_name}={value}"
+ execute_dsconf_command(dsconf_cmd, ['config', 'add', test_attr])
+
+ # Replace with new value
+ new_value = values_dict['multiple'][0]
+ replace_attr = f"{attr_name}={new_value}"
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'replace', replace_attr])
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert new_value in output
+ assert value not in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_add(topology_st, attr_name, values_dict):
+ """Test adding multiple values in a single batch command
+
+ :id: 4c34c7f8-16cc-4ab6-938a-967537be5470
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values to the attribute in a single command
+ 2. Verify all values were added correctly
+ :expectedresults:
+ 1. Batch add command should execute successfully
+ 2. All added values should be present in the configuration
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add multiple values in one command
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+ assert rc == 0
+
+ # Verify all values
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ for value in values_dict['multiple']:
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_replace(topology_st, attr_name, values_dict):
+ """Test replacing with multiple values in a single batch command
+
+ :id: 05cf28b8-000e-4856-a10b-7e1df012737d
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add initial single value
+ 2. Replace with multiple values in a single command
+ 3. Verify the replacement was successful
+ :expectedresults:
+ 1. Initial value should be added successfully
+ 2. Batch replace command should execute successfully
+ 3. All new values should be present and initial value should be absent
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add initial value
+ initial_value = values_dict['single']
+ execute_dsconf_command(dsconf_cmd, ['config', 'add', f"{attr_name}={initial_value}"])
+
+ # Replace with multiple values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'replace'] + attr_values)
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert initial_value not in output
+ for value in values_dict['multiple']:
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_specific_delete(topology_st, attr_name, values_dict):
+ """Test deleting specific values from multi-valued attribute
+
+ :id: bb325c9a-eae8-438a-b577-bd63540b91cb
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values to the attribute
+ 2. Delete a specific value
+ 3. Verify the deletion was successful
+ :expectedresults:
+ 1. Multiple values should be added successfully
+ 2. Specific delete command should execute successfully
+ 3. Deleted value should be absent while others remain present
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add multiple values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+
+ # Delete middle value
+ delete_value = values_dict['multiple'][1]
+ output, rc = execute_dsconf_command(dsconf_cmd,
+ ['config', 'delete', f"{attr_name}={delete_value}"])
+ assert rc == 0
+
+ # Verify remaining values
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert delete_value not in output
+ assert values_dict['multiple'][0] in output
+ assert values_dict['multiple'][2] in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_delete(topology_st, attr_name, values_dict):
+ """Test deleting multiple values in a single batch command
+
+ :id: 4b105824-b060-4f83-97d7-001a01dba1a5
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values to the attribute
+ 2. Delete multiple values in a single command
+ 3. Verify the batch deletion was successful
+ :expectedresults:
+ 1. Multiple values should be added successfully
+ 2. Batch delete command should execute successfully
+ 3. Deleted values should be absent while others remain present
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add all values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+
+ # Delete multiple values in one command
+ delete_values = [f"{attr_name}={val}" for val in values_dict['multiple'][:2]]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'delete'] + delete_values)
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert values_dict['multiple'][2] in output
+ assert values_dict['multiple'][0] not in output
+ assert values_dict['multiple'][1] not in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_single_value_persists_after_restart(topology_st, attr_name, values_dict):
+ """Test single value persists after server restart
+
+ :id: be1a7e3d-a9ca-48a1-a3bc-062990d4f3e9
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add single value to the attribute
+ 2. Verify the value is present
+ 3. Restart the server
+ 4. Verify the value persists after restart
+ :expectedresults:
+ 1. Value should be added successfully
+ 2. Value should be present before restart
+ 3. Server should restart successfully
+ 4. Value should still be present after restart
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add single value
+ value = values_dict['single']
+ output, rc = execute_dsconf_command(dsconf_cmd,
+ ['config', 'add', f"{attr_name}={value}"])
+ assert rc == 0
+
+ # Verify before restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert value in output
+
+ # Restart the server
+ topology_st.standalone.restart(timeout=10)
+
+ # Verify after restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_persists_after_restart(topology_st, attr_name, values_dict):
+ """Test multiple values added in batch persist after server restart
+
+ :id: fd0435e2-90b1-465a-8968-d3a375c8fb22
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values in a single batch command
+ 2. Verify all values are present
+ 3. Restart the server
+ 4. Verify all values persist after restart
+ :expectedresults:
+ 1. Batch add command should execute successfully
+ 2. All values should be present before restart
+ 3. Server should restart successfully
+ 4. All values should still be present after restart
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add multiple values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+ assert rc == 0
+
+ # Verify before restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ for value in values_dict['multiple']:
+ assert value in output
+
+ # Restart the server
+ topology_st.standalone.restart(timeout=10)
+
+ # Verify after restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ for value in values_dict['multiple']:
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
index 9799b8839..af5aab71e 100644
--- a/src/lib389/lib389/_mapped_object.py
+++ b/src/lib389/lib389/_mapped_object.py
@@ -321,25 +321,31 @@ class DSLdapObject(DSLogging, DSLint):
This is useful for configuration changes that require
atomic operation, and ease of use.
- An example of usage is add_many((key, value), (key, value))
+ An example of usage is add_many((key, value), (key, [value1, value2]))
No wrapping list is needed for the arguments.
- :param *args: tuples of key,value to replace.
- :type *args: (str, str)
+ :param *args: tuples of key,value to add. Value can be a single value
+ or a collection (list, tuple, set) of values.
+ :type *args: (str, str) or (str, list/tuple/set)
"""
-
mods = []
for arg in args:
- if isinstance(arg[1], list) or isinstance(arg[1], tuple):
- value = ensure_list_bytes(arg[1])
+ key, value = arg
+ if isinstance(value, (list, tuple, set)):
+ value = ensure_list_bytes(list(value))
else:
- value = [ensure_bytes(arg[1])]
- mods.append((ldap.MOD_ADD, ensure_str(arg[0]), value))
- return _modify_ext_s(self._instance,self._dn, mods, serverctrls=self._server_controls,
- clientctrls=self._client_controls, escapehatch='i am sure')
+ value = [ensure_bytes(value)]
+
+ mods.append((ldap.MOD_ADD, ensure_str(key), value))
+
+ return _modify_ext_s(self._instance,
+ self._dn,
+ mods,
+ serverctrls=self._server_controls,
+ clientctrls=self._client_controls,
+ escapehatch='i am sure')
- # Basically what it means;
def replace(self, key, value):
"""Replace an attribute with a value
@@ -355,23 +361,30 @@ class DSLdapObject(DSLogging, DSLint):
This is useful for configuration changes that require
atomic operation, and ease of use.
- An example of usage is replace_many((key, value), (key, value))
+ An example of usage is replace_many((key, value), (key, [value1, value2]))
No wrapping list is needed for the arguments.
- :param *args: tuples of key,value to replace.
- :type *args: (str, str)
+ :param *args: tuples of key,value to replace. Value can be a single value
+ or a collection (list, tuple, set) of values.
+ :type *args: (str, str) or (str, list/tuple/set)
"""
-
mods = []
for arg in args:
- if isinstance(arg[1], list) or isinstance(arg[1], tuple):
- value = ensure_list_bytes(arg[1])
+ key, value = arg
+ if isinstance(value, (list, tuple, set)):
+ value = ensure_list_bytes(list(value))
else:
- value = [ensure_bytes(arg[1])]
- mods.append((ldap.MOD_REPLACE, ensure_str(arg[0]), value))
- return _modify_ext_s(self._instance,self._dn, mods, serverctrls=self._server_controls,
- clientctrls=self._client_controls, escapehatch='i am sure')
+ value = [ensure_bytes(value)]
+
+ mods.append((ldap.MOD_REPLACE, ensure_str(key), value))
+
+ return _modify_ext_s(self._instance,
+ self._dn,
+ mods,
+ serverctrls=self._server_controls,
+ clientctrls=self._client_controls,
+ escapehatch='i am sure')
# This needs to work on key + val, and key
def remove(self, key, value):
diff --git a/src/lib389/lib389/cli_conf/config.py b/src/lib389/lib389/cli_conf/config.py
index fb4c68f8b..6f4eddc8c 100644
--- a/src/lib389/lib389/cli_conf/config.py
+++ b/src/lib389/lib389/cli_conf/config.py
@@ -1,5 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2023 Red Hat, Inc.
+# Copyright (C) 2024 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -12,12 +12,9 @@ from lib389.config import Config
from lib389.cli_base import (
_generic_get_entry,
_generic_get_attr,
- _generic_replace_attr,
CustomHelpFormatter
)
-OpType = Enum("OpType", "add delete")
-
def _config_display_ldapimaprootdn_warning(log, args):
"""If we update the rootdn we need to update the ldapi settings too"""
@@ -28,44 +25,48 @@ def _config_display_ldapimaprootdn_warning(log, args):
"For LDAPI configuration, \"nsslapd-rootdn\" is used instead.")
-def _config_get_existing_attrs(conf, args, op_type):
- """Get the existing attribute from the server and return them in a dict
- so we can add them back after the operation is done.
-
- For op_type == OpType.delete, we delete them from the server so we can add
- back only those that are not specified in the command line.
- (i.e delete nsslapd-haproxy-trusted-ip="192.168.0.1", but
- nsslapd-haproxy-trusted-ip has 192.168.0.1 and 192.168.0.2 values.
- So we want only 192.168.0.1 to be deleted in the end)
- """
-
- existing_attrs = {}
- if args and args.attr:
- for attr in args.attr:
- if "=" in attr:
- [attr_name, val] = attr.split("=", 1)
- # We should process only multi-valued attributes this way
- if attr_name.lower() == "nsslapd-haproxy-trusted-ip" or \
- attr_name.lower() == "nsslapd-referral":
- if attr_name not in existing_attrs.keys():
- existing_attrs[attr_name] = conf.get_attr_vals_utf8(attr_name)
- existing_attrs[attr_name] = [x for x in existing_attrs[attr_name] if x != val]
-
- if op_type == OpType.add:
- if existing_attrs[attr_name] == []:
- del existing_attrs[attr_name]
-
- if op_type == OpType.delete:
- conf.remove_all(attr_name)
- else:
- if op_type == OpType.delete:
- conf.remove_all(attr)
- else:
- raise ValueError(f"You must specify a value to add for the attribute ({attr_name})")
- return existing_attrs
- else:
- # Missing value
- raise ValueError(f"Missing attribute to {op_type.name}")
+def _format_values_for_display(values):
+ """Format a set of values for display"""
+
+ if not values:
+ return ""
+ if len(values) == 1:
+ return f"'{next(iter(values))}'"
+ return ', '.join(f"'{value}'" for value in sorted(values))
+
+
+def _parse_attr_value_pairs(attrs, allow_no_value=False):
+ """Parse attribute value pairs from a list of strings in the format 'attr=value'"""
+
+ attr_values = {}
+ attrs_without_values = set()
+
+ for attr in attrs:
+ if "=" in attr:
+ attr_name, val = attr.split("=", 1)
+ if attr_name not in attr_values:
+ attr_values[attr_name] = set()
+ attr_values[attr_name].add(val)
+ elif allow_no_value:
+ attrs_without_values.add(attr)
+ else:
+ raise ValueError(f"Invalid attribute format: {attr}. Must be in format 'attr=value'")
+
+ return attr_values, attrs_without_values
+
+
+def _handle_attribute_operation(conf, operation_type, attr_values, log):
+ """Handle attribute operations (add, replace) with consistent error handling"""
+
+ if operation_type == "add":
+ conf.add_many(*[(k, v) for k, v in attr_values.items()])
+ elif operation_type == "replace":
+ conf.replace_many(*[(k, v) for k, v in attr_values.items()])
+
+ for attr_name, values in attr_values.items():
+ formatted_values = _format_values_for_display(values)
+ operation_past = "added" if operation_type == "add" else f"{operation_type}d"
+ log.info(f"Successfully {operation_past} value(s) for '{attr_name}': {formatted_values}")
def config_get(inst, basedn, log, args):
@@ -77,49 +78,76 @@ def config_get(inst, basedn, log, args):
def config_replace_attr(inst, basedn, log, args):
- _generic_replace_attr(inst, basedn, log.getChild('config_replace_attr'), Config, args)
+ if not args or not args.attr:
+ raise ValueError("Missing attribute to replace")
+ conf = Config(inst, basedn)
+ attr_values, _ = _parse_attr_value_pairs(args.attr)
+ _handle_attribute_operation(conf, "replace", attr_values, log)
_config_display_ldapimaprootdn_warning(log, args)
def config_add_attr(inst, basedn, log, args):
+ if not args or not args.attr:
+ raise ValueError("Missing attribute to add")
+
conf = Config(inst, basedn)
- final_mods = []
-
- existing_attrs = _config_get_existing_attrs(conf, args, OpType.add)
-
- if args and args.attr:
- for attr in args.attr:
- if "=" in attr:
- [attr_name, val] = attr.split("=", 1)
- if attr_name in existing_attrs:
- for v in existing_attrs[attr_name]:
- final_mods.append((attr_name, v))
- final_mods.append((attr_name, val))
- try:
- conf.add_many(*set(final_mods))
- except ldap.TYPE_OR_VALUE_EXISTS:
- pass
- else:
- raise ValueError(f"You must specify a value to add for the attribute ({attr_name})")
- else:
- # Missing value
- raise ValueError("Missing attribute to add")
+ attr_values, _ = _parse_attr_value_pairs(args.attr)
+ # Validate no values already exist
+ for attr_name, values in attr_values.items():
+ existing_vals = set(conf.get_attr_vals_utf8(attr_name) or [])
+ duplicate_vals = values & existing_vals
+ if duplicate_vals:
+ raise ldap.ALREADY_EXISTS(
+ f"Values {duplicate_vals} already exist for attribute '{attr_name}'")
+
+ _handle_attribute_operation(conf, "add", attr_values, log)
_config_display_ldapimaprootdn_warning(log, args)
def config_del_attr(inst, basedn, log, args):
- conf = Config(inst, basedn)
- final_mods = []
+ if not args or not args.attr:
+ raise ValueError("Missing attribute to delete")
- existing_attrs = _config_get_existing_attrs(conf, args, OpType.delete)
+ conf = Config(inst, basedn)
+ attr_values, attrs_to_remove = _parse_attr_value_pairs(args.attr, allow_no_value=True)
+
+ # Handle complete attribute removal
+ for attr in attrs_to_remove:
+ try:
+ if conf.get_attr_vals_utf8(attr):
+ conf.remove_all(attr)
+ log.info(f"Removed attribute '{attr}' completely")
+ else:
+ log.warning(f"Attribute '{attr}' does not exist - skipping")
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.warning(f"Attribute '{attr}' does not exist - skipping")
+
+ # Validate and handle value-specific deletion
+ if attr_values:
+ for attr_name, values in attr_values.items():
+ try:
+ existing_values = set(conf.get_attr_vals_utf8(attr_name) or [])
+ values_to_delete = values & existing_values
+ values_not_found = values - existing_values
+
+ if values_not_found:
+ formatted_values = _format_values_for_display(values_not_found)
+ log.warning(f"Values {formatted_values} do not exist for attribute '{attr_name}' - skipping")
+
+ if values_to_delete:
+ remaining_values = existing_values - values_to_delete
+ if not remaining_values:
+ conf.remove_all(attr_name)
+ else:
+ conf.replace_many((attr_name, remaining_values))
+ formatted_values = _format_values_for_display(values_to_delete)
+ log.info(f"Successfully deleted values {formatted_values} from '{attr_name}'")
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.warning(f"Attribute '{attr_name}' does not exist - skipping")
- # Then add the attributes back all except the one we need to remove
- for attr_name in existing_attrs.keys():
- for val in existing_attrs[attr_name]:
- final_mods.append((attr_name, val))
- conf.add_many(*set(final_mods))
+ _config_display_ldapimaprootdn_warning(log, args)
def create_parser(subparsers):
--
2.48.0

View File

@ -1,4 +1,4 @@
From a71c12881604b5e37d64093aedd158aa112b39cc Mon Sep 17 00:00:00 2001
From 116b7cf21618ad7e717ae7f535709508a824f7d9 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 13 Feb 2025 16:37:43 +0100
Subject: [PATCH] Issue 6561 - TLS 1.2 stickiness in FIPS mode
@ -15,10 +15,10 @@ Reviewed by: @mreynolds389 (Thanks!)
1 file changed, 8 deletions(-)
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
index ef9e97fb9..329bd5c90 100644
index 94259efe7..84a7fb004 100644
--- a/ldap/servers/slapd/ssl.c
+++ b/ldap/servers/slapd/ssl.c
@@ -1914,14 +1914,6 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
@@ -1929,14 +1929,6 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
*/
sslStatus = SSL_VersionRangeGet(pr_sock, &slapdNSSVersions);
if (sslStatus == SECSuccess) {

View File

@ -0,0 +1,44 @@
From 39d91c4b86fc2ad7e35f8bebd510dff984e8ba56 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Wed, 5 Mar 2025 23:46:02 +0100
Subject: [PATCH] Issue 6090 - dbscan: use bdb by default
Bug Description:
dbscan started to use mdb by default on versions where it's not the
default.
Fix Description:
Use bdb by default on 2.x versions.
Relates: https://github.com/389ds/389-ds-base/issues/6090
Reviewed by: @mreynolds389 (Thanks!)
---
ldap/servers/slapd/tools/dbscan.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ldap/servers/slapd/tools/dbscan.c b/ldap/servers/slapd/tools/dbscan.c
index 12edf7c5b..9260c1532 100644
--- a/ldap/servers/slapd/tools/dbscan.c
+++ b/ldap/servers/slapd/tools/dbscan.c
@@ -1280,7 +1280,7 @@ removedb(const char *dbimpl_name, const char *filename)
if (!filename) {
printf("Error: -f option is missing.\n"
- "Usage: dbscan -D mdb -d -f <db_home_dir>/<backend_name>/<db_name>\n");
+ "Usage: dbscan -D bdb -d -f <db_home_dir>/<backend_name>/<db_name>\n");
return 1;
}
@@ -1314,7 +1314,7 @@ main(int argc, char **argv)
char *find_key = NULL;
uint32_t entry_id = 0xffffffff;
char *defdbimpl = getenv("NSSLAPD_DB_LIB");
- char *dbimpl_name = (char*) "mdb";
+ char *dbimpl_name = (char*) "bdb";
int longopt_idx = 0;
int c = 0;
char optstring[2*COUNTOF(options)+1] = {0};
--
2.48.1

View File

@ -1,200 +0,0 @@
From 7ed791beda5d507e15c970a88289f533aa06b2d3 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 !!!)
---
.../tests/suites/indexes/entryrdn_test.py | 108 +++++++++++++++++-
.../back-ldbm/db-mdb/mdb_import_threads.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 11 +-
3 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/dirsrvtests/tests/suites/indexes/entryrdn_test.py b/dirsrvtests/tests/suites/indexes/entryrdn_test.py
index 345955d4d..7669292ab 100644
--- a/dirsrvtests/tests/suites/indexes/entryrdn_test.py
+++ b/dirsrvtests/tests/suites/indexes/entryrdn_test.py
@@ -11,11 +11,16 @@ import os
import pytest
import ldap
import logging
+from lib389 import Entry
from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX
-from lib389.backend import Backends
+from lib389.backend import Backends, Backend
+from lib389.mappingTree import MappingTrees
+from lib389.configurations.sample import create_base_domain
+from lib389.idm.domain import Domain
from lib389.idm.user import UserAccounts, UserAccount
from lib389.idm.organizationalunit import OrganizationalUnits
from lib389.topologies import topology_m2 as topo_m2
+from lib389.topologies import topology_st
from lib389.agreement import Agreements
from lib389.utils import ds_is_older, ensure_bytes
from lib389.tasks import Tasks,ExportTask, ImportTask
@@ -283,6 +288,107 @@ def test_long_rdn(topo_m2):
ou.delete()
assert not ou.exists()
+def test_entry_rdn_same_as_suffix(topology_st, request):
+ """
+ Test that a reindex is successful even if an entry
+ has a RDN that is identical to the suffix
+
+ :id: 7f5a38e9-b979-4664-b132-81df0e60f38a
+ :setup: standalone
+ :steps:
+ 1. Create a new backend with suffix 'dc=dup_rdn' (ID 1)
+ 2. Create a dummy entry 'ou=my_org,dc=dup_rdn' (ID 2)
+ 3. Create the problematic entry 'dc=dup_rdn,dc=dup_rdn' (ID 3)
+ 4. Create a dummy entry 'ou=my_org,dc=dup_rdn,dc=dup_rdn' (ID 4)
+ 5. Do an offline reindex
+ 6. Check that entryrdn contains the key P3 (parent of ID 3)
+ 7. Check that error log does not contain 'entryrdn_insert_key - Same DN'
+ :expectedresults:
+ 1. Should succeed
+ 2. Should succeed
+ 3. Should succeed
+ 4. Should succeed
+ 5. Should succeed
+ 6. Should succeed
+ 7. Should succeed
+ """
+ inst = topology_st.standalone
+
+ # Create a suffix 'dc=dup_rdn'
+ be_name = 'domain'
+ dc_value = 'dup_rdn'
+ suffix = 'dc=' + dc_value
+ rdn = 'my_org'
+ be1 = Backend(inst)
+ be1.create(properties={
+ 'cn': be_name,
+ 'nsslapd-suffix': suffix,
+ },
+ create_mapping_tree=False
+ )
+
+ mts = MappingTrees(inst)
+ mt = mts.create(properties={
+ 'cn': suffix,
+ 'nsslapd-state': 'backend',
+ 'nsslapd-backend': be_name,
+ })
+
+ # Create the domain entry 'dc=dup_rdn'
+ create_base_domain(inst, suffix)
+
+ # Create the org ou=my_org,dc=dup_rdn
+ ous = OrganizationalUnits(inst, suffix)
+ ou = ous.create(properties={ 'ou': rdn })
+
+ # when reindexing entryrdn the following entry
+ # (dc=dup_rdn,dc=dup_rdn) Triggers
+ # this message.
+ # This is because its RDN (dc=dup_rdn) is also
+ # the suffix DN
+ info_message = 'entryrdn_insert_key - Same DN (dn: %s) is already in the entryrdn file with different' % (ou.dn)
+ log.info("In case if the bug still exist this line should be in the error log")
+ log.info(" --> " + info_message)
+
+ # Create the domain entry 'dc=dup_rdn,dc=dup_rdn'
+ trigger_entry = suffix + "," + suffix
+ domain = Domain(inst, dn=trigger_entry)
+ domain.create(properties={
+ 'dc': dc_value,
+ 'description': 'Entry with RDN identical to suffix'
+ })
+
+ # Create the org ou=my_org,dc=dup_rdn,dc=dup_rdn
+ ous = OrganizationalUnits(inst, trigger_entry)
+ ou = ous.create(properties={ 'ou': rdn })
+
+
+ # Trigger an offline reindex
+ log.info('Offline reindex, stopping the server')
+ topology_st.standalone.stop()
+
+ log.info('Reindex all the suffix')
+ topology_st.standalone.db2index(bename=be_name)
+
+ # make sure the key 'P3' (parent of 'dc=dup_rdn,dc=dup_rdn') exists
+ dbscanout = topology_st.standalone.dbscan(bename=be_name, index='entryrdn')
+ log.info(dbscanout)
+ assert(ensure_bytes('P3') in ensure_bytes(dbscanout))
+
+ # make sure there is no failure detected/logged in error logs
+ if topology_st.standalone.get_db_lib() == "mdb":
+ pattern_str = ".*Inconsistent id2entry database.*"
+ else:
+ pattern_str = ".*entryrdn_insert_key - Same DN.*is already in the entryrdn file with different.*$"
+ assert not topology_st.standalone.ds_error_log.match(pattern_str)
+
+
+ def fin():
+ topology_st.standalone.restart()
+ mt.delete()
+ be1.delete()
+
+ request.addfinalizer(fin)
if __name__ == "__main__":
# Run isolated
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
index 143f2c9df..65f564aff 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
@@ -731,7 +731,7 @@ get_entry_type(WorkerQueueData_t *wqelmt, Slapi_DN *sdn)
int len = SLAPI_ATTR_UNIQUEID_LENGTH;
const char *ndn = slapi_sdn_get_ndn(sdn);
- if (slapi_be_issuffix(be, sdn)) {
+ if (slapi_be_issuffix(be, sdn) && (wqelmt->wait_id == 1)) {
return DNRC_SUFFIX;
}
if (PL_strncasecmp(ndn, SLAPI_ATTR_UNIQUEID, len) || ndn[len] != '=') {
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
index 267957a36..8901b790a 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
@@ -1077,7 +1077,16 @@ entryrdn_lookup_dn(backend *be,
_ENTRYRDN_DEBUG_GOTO_BAIL();
goto bail;
}
- maybesuffix = 1;
+ if (workid == 1) {
+ /* The loop (workid) iterates from the starting 'id'
+ * up to the suffix ID (i.e. '1').
+ * A corner case (#6417) is if an entry, on the path
+ * 'id' -> suffix, has the same RDN than the suffix.
+ * In order to erroneously believe the loop hits the suffix
+ * we need to check that 'workid' is '1' (suffix)
+ */
+ maybesuffix = 1;
+ }
} else {
_entryrdn_cursor_print_error("entryrdn_lookup_dn",
key.data, data.size, data.ulen, rc);
--
2.48.1

File diff suppressed because it is too large Load Diff

View File

@ -1,90 +0,0 @@
From f8f390dcc32cefccf762be49c1625a8d123a5d7f 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 456ed8b99..f8d8b1640 100644
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
@@ -1171,6 +1171,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 8901b790a..2713240c1 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
@@ -992,6 +992,7 @@ entryrdn_lookup_dn(backend *be,
ID workid = id; /* starting from the given id */
rdn_elem *elem = NULL;
int maybesuffix = 0;
+ ID suffix_id = 1;
slapi_log_err(SLAPI_LOG_TRACE, "entryrdn_lookup_dn",
"--> entryrdn_lookup_dn\n");
@@ -1032,6 +1033,22 @@ entryrdn_lookup_dn(backend *be,
dblayer_value_free(be, &data);
dblayer_value_init(be, &data);
+ /* 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 */
keybuf = slapi_ch_smprintf("%c%u", RDN_INDEX_PARENT, workid);
@@ -1077,7 +1094,7 @@ entryrdn_lookup_dn(backend *be,
_ENTRYRDN_DEBUG_GOTO_BAIL();
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.1

File diff suppressed because it is too large Load Diff

View File

@ -1,71 +0,0 @@
From 20c20ff195272e0a2ccea6d6b081311798f03eb8 Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Mon, 13 Jan 2025 15:52:07 +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 | 32 +++++++++++---------
1 file changed, 18 insertions(+), 14 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
index 2713240c1..e3d765893 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
@@ -1033,21 +1033,25 @@ entryrdn_lookup_dn(backend *be,
dblayer_value_free(be, &data);
dblayer_value_init(be, &data);
- /* 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);
+ /* 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));
+ 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);
}
- dblayer_value_free(be, &data);
- dblayer_value_free(be, &key);
do {
/* Setting up a key for the node to get its parent */
--
2.48.1

View File

@ -1,137 +0,0 @@
From 5d2afc232c6f98addb7810e6573f652a02a00f34 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 4 Dec 2024 15:39:58 -0500
Subject: [PATCH] Issue 6432 - Crash during bind when acct policy plugin does
not have "alwaysrecordlogin" set
Description:
If alwaysrecordlogin is off then we dereference NULL ptr cfg->login_history_attr
when trying to write the history/time value. Instead we should skip
over this code if it is not set.
Relates: https://github.com/389ds/389-ds-base/issues/6432
Reviewed by: tbordaz(Thanks!)
---
.../accpol_check_all_state_attrs_test.py | 77 ++++++++++++++++++-
ldap/servers/plugins/acctpolicy/acct_plugin.c | 4 +-
2 files changed, 77 insertions(+), 4 deletions(-)
diff --git a/dirsrvtests/tests/suites/plugins/accpol_check_all_state_attrs_test.py b/dirsrvtests/tests/suites/plugins/accpol_check_all_state_attrs_test.py
index ec518ca7f..96c7a0324 100644
--- a/dirsrvtests/tests/suites/plugins/accpol_check_all_state_attrs_test.py
+++ b/dirsrvtests/tests/suites/plugins/accpol_check_all_state_attrs_test.py
@@ -21,10 +21,11 @@ from lib389._constants import (
PASSWORD,
PLUGIN_ACCT_POLICY,
)
-from lib389.idm.user import (UserAccount, UserAccounts)
+from lib389.idm.user import (UserAccount)
from lib389.plugins import (AccountPolicyPlugin, AccountPolicyConfig)
+from lib389.cos import (CosTemplate, CosPointerDefinition)
from lib389.idm.domain import Domain
-from datetime import datetime, timedelta
+
log = logging.getLogger(__name__)
@@ -131,9 +132,79 @@ def test_inactivty_and_expiration(topo):
test_user.bind(NEW_PASSWORD)
+def test_alwaysrecordlogin_off(topo):
+ """Test the server does not crash when alwaysrecordlogin is "off"
+
+ :id: 49eb0993-ee59-48a9-8324-fb965b202ba9
+ :setup: Standalone Instance
+ :steps:
+ 1. Create test user
+ 2. Configure account policy, COS, and restart
+ 3. Bind as test user
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ """
+
+ LOCAL_POLICY = 'cn=Account Inactivation Policy,dc=example,dc=com'
+ TEMPL_COS = 'cn=TempltCoS,ou=people,dc=example,dc=com'
+ DEFIN_COS = 'cn=DefnCoS,ou=people,dc=example,dc=com'
+ TEST_USER_NAME = 'crash'
+ TEST_USER_DN = f'uid={TEST_USER_NAME},ou=people,{DEFAULT_SUFFIX}'
+
+ inst = topo.standalone
+
+ # Create the test user
+ test_user = UserAccount(inst, TEST_USER_DN)
+ test_user.create(properties={
+ 'uid': TEST_USER_NAME,
+ 'cn': TEST_USER_NAME,
+ 'sn': TEST_USER_NAME,
+ 'userPassword': PASSWORD,
+ 'uidNumber': '1000',
+ 'gidNumber': '2000',
+ 'homeDirectory': '/home/crash',
+ })
+
+ # Configure account policy plugin
+ plugin = AccountPolicyPlugin(inst)
+ plugin.enable()
+ plugin.set('nsslapd-pluginarg0', ACCP_CONF)
+ accp = AccountPolicyConfig(inst, dn=ACCP_CONF)
+ accp.set('alwaysrecordlogin', 'no')
+ accp.set('stateattrname', 'lastLoginTime')
+ accp.set('altstateattrname', 'passwordexpirationtime')
+ accp.set('specattrname', 'acctPolicySubentry')
+ accp.set('limitattrname', 'accountInactivityLimit')
+ accp.set('accountInactivityLimit', '123456')
+ accp.set('checkAllStateAttrs', 'on')
+ inst.restart()
+ # Local policy
+ laccp = AccountPolicyConfig(inst, dn=LOCAL_POLICY)
+ laccp.create(properties={
+ 'cn': 'Account Inactivation Policy',
+ 'accountInactivityLimit': '12312321'
+ })
+ # COS
+ cos_template = CosTemplate(inst, dn=TEMPL_COS)
+ cos_template.create(properties={'cn': 'TempltCoS',
+ 'acctPolicySubentry': LOCAL_POLICY})
+ cos_def = CosPointerDefinition(inst, dn=DEFIN_COS)
+ cos_def.create(properties={
+ 'cn': 'DefnCoS',
+ 'cosTemplateDn': TEMPL_COS,
+ 'cosAttribute': 'acctPolicySubentry default operational-default'})
+ inst.restart()
+
+ # Bind as test user to make sure the server does not crash
+ conn = test_user.bind(PASSWORD)
+ test_user = UserAccount(conn, TEST_USER_DN)
+ test_user.bind(PASSWORD)
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
CURRENT_FILE = os.path.realpath(__file__)
pytest.main(["-s", CURRENT_FILE])
-
diff --git a/ldap/servers/plugins/acctpolicy/acct_plugin.c b/ldap/servers/plugins/acctpolicy/acct_plugin.c
index ba9705f74..220d0f6b2 100644
--- a/ldap/servers/plugins/acctpolicy/acct_plugin.c
+++ b/ldap/servers/plugins/acctpolicy/acct_plugin.c
@@ -372,7 +372,9 @@ acct_record_login(const char *dn)
"acct_record_login - Recorded %s=%s on \"%s\"\n", cfg->always_record_login_attr, timestr, dn);
/* update login history */
- acct_update_login_history(dn, timestr);
+ if (cfg->login_history_attr) {
+ acct_update_login_history(dn, timestr);
+ }
}
done:
--
2.48.1

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
From deb3b14a69a31e3c61dc65ba2045637da8f52cf7 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 15 Nov 2024 12:43:30 +0100
Subject: [PATCH] Issue 6386 - backup/restore broken after db log rotation
(#6406)
Restore does fail if the db log file have rotated since the backup. The reason is that the current log files are not removed mixing old and new log file which confuse the database.
Solution is to remove all existing log files before copying the backuped files.
Note: the number of db region should not change and having an extra db file should not be a problem so only the log files must be removed.
Issue: #6386
Reviewed by: @mreynolds389, @tbordaz (Thanks!)
(cherry picked from commit a48be7b2b389a336e3f48a775e466c71f2eb2b31)
(cherry picked from commit 0c8829bf43a6b3c413618377e1fbd574cf818f32)
---
.../slapd/back-ldbm/db-bdb/bdb_layer.c | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
index d2e1331b2..44f25098f 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
@@ -5591,6 +5591,28 @@ bdb_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task)
/* Otherwise use the src_dir from the caller */
real_src_dir = src_dir;
+ /* Lets remove existing log files before copying the new ones (See issue #6386) */
+ prefix = BDB_CONFIG(li)->bdb_log_directory;
+ if (prefix == NULL) {
+ prefix = home_dir;
+ }
+ dirhandle = PR_OpenDir(prefix);
+ if (NULL != dirhandle) {
+ while (NULL !=
+ (direntry = PR_ReadDir(dirhandle, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) {
+ if (NULL == direntry->name) {
+ /* NSPR doesn't behave like the docs say it should */
+ break;
+ }
+ if (bdb_is_logfilename(direntry->name)) {
+ PR_snprintf(filename1, sizeof(filename2), "%s/%s",
+ prefix, direntry->name);
+ unlink(filename1);
+ }
+ }
+ }
+ PR_CloseDir(dirhandle);
+
/* We copy the files over from the staging area */
/* We want to treat the logfiles specially: if there's
* a log file directory configured, copy the logfiles there
--
2.48.1

File diff suppressed because it is too large Load Diff

View File

@ -1,182 +0,0 @@
From fc1d1b8c5438f29c3408adca26423fc6226f47ff Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Thu, 12 Dec 2024 16:30:50 +0100
Subject: [PATCH] Issue 6446 - on replica consumer, account policy plugin fails
to manage the last login history (#6448)
Bug description:
An internal update needs the flag SLAPI_OP_FLAG_BYPASS_REFERRALS
on a RO consumer. Else the server does not select the backend
and returns LDAP_REFERRAL to the internal operation.
In acct_update_login_history the flag is missing.
Fix description:
Use the same flags in acct_update_login_history as it is
used in acct_record_login
fixes: #6446
Reviewed by: Mark Reynolds (Thanks!)
---
.../tests/suites/plugins/accpol_test.py | 123 +++++++++++++++++-
ldap/servers/plugins/acctpolicy/acct_plugin.c | 3 +-
2 files changed, 124 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/plugins/accpol_test.py b/dirsrvtests/tests/suites/plugins/accpol_test.py
index 964d98ee0..f5db8e461 100644
--- a/dirsrvtests/tests/suites/plugins/accpol_test.py
+++ b/dirsrvtests/tests/suites/plugins/accpol_test.py
@@ -10,7 +10,7 @@ import pytest
import subprocess
from lib389.tasks import *
from lib389.utils import *
-from lib389.topologies import topology_st
+from lib389.topologies import topology_st, topology_m1c1
from lib389.idm.user import (UserAccount, UserAccounts)
from lib389.plugins import (AccountPolicyPlugin, AccountPolicyConfig, AccountPolicyConfigs)
from lib389.cos import (CosTemplate, CosPointerDefinition)
@@ -1298,6 +1298,127 @@ def test_locact_modrdn(topology_st, accpol_local):
account_status(topology_st, suffix, subtree, userid, 1, 0, "Enabled")
del_users(topology_st, suffix, subtree, userid, nousrs)
+def test_acct_policy_consumer(topology_m1c1, request):
+ """Test the lastLoginHistory is updated on consumer without
+ referral error
+
+ :id: 53a9a2c7-6b10-41c9-b9f9-bde412d04acb
+ :setup: Supplier Instance, Consumer Instance
+ :steps:
+ 1. Create a test entry on the supplier
+ 2. Configure lastLoginTime on supplier and consumer
+ 3. On supplier 3 binds of the test entry
+ 4. On supplier check there is 3 lastLoginHistory values
+ 5. On supplier check there is no error in error logs
+ 6. On consumer 3 binds of the test entry
+ 7. On consumer check there is 3 lastLoginHistory values
+ 8. On consumer check there is no error in error logs
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. Success
+ 7. Success
+ 8. Success
+ """
+
+ supplier = topology_m1c1.ms['supplier1']
+ consumer = topology_m1c1.cs['consumer1']
+ USER_PW = 'password'
+
+ # Add on the supplier a test user entry and wait it is on the consumer
+ users_supplier = UserAccounts(supplier, DEFAULT_SUFFIX, rdn=None)
+ user_supplier = users_supplier.create_test_user(uid=1000, gid=2000)
+ user_supplier.replace('userPassword', USER_PW)
+
+ users_consumer = UserAccounts(consumer, DEFAULT_SUFFIX, rdn=None)
+ for i in range(0, 10):
+ try:
+ user_consumer = users_consumer.get("test_user_1000")
+ break
+ except ldap.NO_SUCH_OBJECT:
+ time.sleep(1)
+
+ # Configure lastLoginTime on supplier and consumer
+ plugin = AccountPolicyPlugin(supplier)
+ plugin.enable()
+ ACCPOL_DN = "cn={},{}".format(PLUGIN_ACCT_POLICY, DN_PLUGIN)
+ ACCP_CONF = "{},{}".format(DN_CONFIG, ACCPOL_DN)
+ plugin.set('nsslapd-pluginarg0', ACCP_CONF)
+ accp = AccountPolicyConfig(supplier, dn=ACCP_CONF)
+ accp.set('alwaysrecordlogin', 'yes')
+ accp.set('stateattrname', 'lastLoginTime')
+ supplier.restart(timeout=10)
+
+ plugin = AccountPolicyPlugin(consumer)
+ plugin.enable()
+ ACCPOL_DN = "cn={},{}".format(PLUGIN_ACCT_POLICY, DN_PLUGIN)
+ ACCP_CONF = "{},{}".format(DN_CONFIG, ACCPOL_DN)
+ plugin.set('nsslapd-pluginarg0', ACCP_CONF)
+ accp = AccountPolicyConfig(consumer, dn=ACCP_CONF)
+ accp.set('alwaysrecordlogin', 'yes')
+ accp.set('stateattrname', 'lastLoginTime')
+ consumer.restart(timeout=10)
+
+ # On supplier
+ # Do 3 binds with a delay to
+ # allow several values lastLoginHistory
+ user_supplier.bind(USER_PW)
+ time.sleep(2)
+ user_supplier = users_supplier.get("test_user_1000")
+ user_supplier.bind(USER_PW)
+ time.sleep(2)
+ user_supplier = users_supplier.get("test_user_1000")
+ user_supplier.bind(USER_PW)
+
+ # Verify there is no referral error
+ results = supplier.ds_error_log.match('.*.acct_update_login_history - Modify error 10 on entry*')
+ assert not results
+
+ # Verify that we got 3 values for lastLoginHistory
+ assert len(user_supplier.get_attr_vals_utf8_l('lastLoginHistory')) == 3
+
+ # On Consumer
+ # Do 3 binds with a delay to
+ # allow several values lastLoginHistory
+ user_supplier.bind(USER_PW)
+ user_consumer.bind(USER_PW)
+ time.sleep(2)
+ user_consumer = users_supplier.get("test_user_1000")
+ user_consumer.bind(USER_PW)
+ time.sleep(2)
+ user_consumer = users_supplier.get("test_user_1000")
+ user_consumer.bind(USER_PW)
+
+ # Verify there is no referral error
+ results = consumer.ds_error_log.match('.*.acct_update_login_history - Modify error 10 on entry*')
+ assert not results
+
+ # Verify that we got at least 3 values for lastLoginHistory
+ assert len(user_consumer.get_attr_vals_utf8_l('lastLoginHistory')) >= 3
+
+
+ def fin():
+ user_supplier.delete()
+ log.info('Disabling Global accpolicy plugin and removing pwpolicy attrs')
+ try:
+ plugin = AccountPolicyPlugin(supplier)
+ plugin.disable()
+ except ldap.LDAPError as e:
+ log.error('Failed to disable Global accpolicy plugin, {}'.format(e.message['desc']))
+ assert False
+ supplier.restart(timeout=10)
+ try:
+ plugin = AccountPolicyPlugin(consumer)
+ plugin.disable()
+ except ldap.LDAPError as e:
+ log.error('Failed to disable Global accpolicy plugin, {}'.format(e.message['desc']))
+ assert False
+ consumer.restart(timeout=10)
+
+ request.addfinalizer(fin)
if __name__ == '__main__':
# Run isolated
diff --git a/ldap/servers/plugins/acctpolicy/acct_plugin.c b/ldap/servers/plugins/acctpolicy/acct_plugin.c
index 220d0f6b2..af566b934 100644
--- a/ldap/servers/plugins/acctpolicy/acct_plugin.c
+++ b/ldap/servers/plugins/acctpolicy/acct_plugin.c
@@ -290,7 +290,8 @@ acct_update_login_history(const char *dn, char *timestr)
list_of_mods[1] = NULL;
mod_pb = slapi_pblock_new();
- slapi_modify_internal_set_pb(mod_pb, dn, list_of_mods, NULL, NULL, plugin_id, 0);
+ slapi_modify_internal_set_pb(mod_pb, dn, list_of_mods, NULL, NULL, plugin_id,
+ SLAPI_OP_FLAG_NO_ACCESS_CHECK |SLAPI_OP_FLAG_BYPASS_REFERRALS);
slapi_modify_internal_pb(mod_pb);
slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
if (rc != LDAP_SUCCESS) {
--
2.48.1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,975 @@
From b172a9b9239f165894fa16fd4f5596925b60f997 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Thu, 6 Mar 2025 13:26:37 +0000
Subject: [PATCH] Issue 6429 - UI - clicking on a database suffix under the
Monitor tab crashes UI (#6610)
Bug description:
Clicking on a db suffix under the Monitor tab causes the UI to crash when
the instance is configured with the mdb db engine.
Fix description:
Introduced separate database and suffix monitor classes tailored for mdb. Parent
class detects the configured db engine and calls the appropriate monitor class.
Fixes: https://github.com/389ds/389-ds-base/issues/6429
Reviewed by: @mreynolds389, @droideck (Thank you)
---
.../src/lib/database/databaseConfig.jsx | 1 -
.../389-console/src/lib/monitor/dbMonitor.jsx | 385 +++++++++++++++++-
.../src/lib/monitor/suffixMonitor.jsx | 363 ++++++++++++++++-
src/cockpit/389-console/src/monitor.jsx | 97 ++++-
4 files changed, 823 insertions(+), 23 deletions(-)
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
index 112625be9..52a2cf2df 100644
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
@@ -1253,7 +1253,6 @@ export class GlobalDatabaseConfigMDB extends React.Component {
// Check if a setting was changed, if so enable the save button
for (const config_attr of check_attrs) {
if (this.state[config_attr] !== this.state['_' + config_attr]) {
- // jc console.log(config_attr);
saveBtnDisabled = false;
break;
}
diff --git a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
index f3f51733b..08aa1aaea 100644
--- a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
@@ -326,7 +326,6 @@ export class DatabaseMonitor extends React.Component {
</GridItem>
</Grid>
</Tab>
-
<Tab eventKey={1} title={<TabTitleText>{_("Normalized DN Cache")}</TabTitleText>}>
<div className="ds-margin-top-lg">
<Grid hasGutter>
@@ -511,12 +510,394 @@ export class DatabaseMonitor extends React.Component {
// Prop types and defaults
DatabaseMonitor.propTypes = {
+ data: PropTypes.object,
serverId: PropTypes.string,
enableTree: PropTypes.func,
};
DatabaseMonitor.defaultProps = {
+ data: {},
serverId: "",
};
-export default DatabaseMonitor;
+export class DatabaseMonitorMDB extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ activeTabKey: 0,
+ data: {},
+ loading: true,
+ // refresh chart
+ cache_refresh: "",
+ count: 10,
+ ndnCount: 5,
+ dbCacheList: [],
+ ndnCacheList: [],
+ ndnCacheUtilList: []
+ };
+
+ // Toggle currently active tab
+ this.handleNavSelect = (event, tabIndex) => {
+ this.setState({
+ activeTabKey: tabIndex
+ });
+ };
+
+ this.startCacheRefresh = this.startCacheRefresh.bind(this);
+ this.refreshCache = this.refreshCache.bind(this);
+ }
+
+ componentDidMount() {
+ this.resetChartData();
+ this.refreshCache();
+ this.startCacheRefresh();
+ this.props.enableTree();
+ }
+
+ componentWillUnmount() {
+ this.stopCacheRefresh();
+ }
+
+ resetChartData() {
+ this.setState({
+ data: {
+ normalizeddncachehitratio: [0],
+ maxnormalizeddncachesize: [0],
+ currentnormalizeddncachesize: [0],
+ normalizeddncachetries: [0],
+ normalizeddncachehits: [0],
+ normalizeddncacheevictions: [0],
+ currentnormalizeddncachecount: [0],
+ normalizeddncachethreadsize: [0],
+ normalizeddncachethreadslots: [0],
+ },
+ ndnCacheList: [
+ { name: "", x: "1", y: 0 },
+ { name: "", x: "2", y: 0 },
+ { name: "", x: "3", y: 0 },
+ { name: "", x: "4", y: 0 },
+ { name: "", x: "5", y: 0 },
+ ],
+ ndnCacheUtilList: [
+ { name: "", x: "1", y: 0 },
+ { name: "", x: "2", y: 0 },
+ { name: "", x: "3", y: 0 },
+ { name: "", x: "4", y: 0 },
+ { name: "", x: "5", y: 0 },
+ ],
+ });
+ }
+
+ refreshCache() {
+ // Search for db cache stat and update state
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "monitor", "ldbm"
+ ];
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let count = this.state.count + 1;
+ const ndnCount = this.state.ndnCount + 1;
+ if (count > 100) {
+ // Keep progress count in check
+ count = 1;
+ }
+
+ // Build up the DB Cache chart data
+ const dbratio = config.attrs.dbcachehitratio[0];
+ const chart_data = this.state.dbCacheList;
+ chart_data.shift();
+ chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(dbratio) });
+
+ // Build up the NDN Cache chart data
+ const ndnratio = config.attrs.normalizeddncachehitratio[0];
+ const ndn_chart_data = this.state.ndnCacheList;
+ ndn_chart_data.shift();
+ ndn_chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(ndnratio) });
+
+ // Build up the DB Cache Util chart data
+ const ndn_util_chart_data = this.state.ndnCacheUtilList;
+ const currNDNSize = parseInt(config.attrs.currentnormalizeddncachesize[0]);
+ const maxNDNSize = parseInt(config.attrs.maxnormalizeddncachesize[0]);
+ const ndn_utilization = (currNDNSize / maxNDNSize) * 100;
+ ndn_util_chart_data.shift();
+ ndn_util_chart_data.push({ name: _("Cache Utilization"), x: ndnCount.toString(), y: parseInt(ndn_utilization) });
+
+ this.setState({
+ data: config.attrs,
+ loading: false,
+ dbCacheList: chart_data,
+ ndnCacheList: ndn_chart_data,
+ ndnCacheUtilList: ndn_util_chart_data,
+ count,
+ ndnCount
+ });
+ })
+ .fail(() => {
+ this.resetChartData();
+ });
+ }
+
+ startCacheRefresh() {
+ this.setState({
+ cache_refresh: setInterval(this.refreshCache, 2000),
+ });
+ }
+
+ stopCacheRefresh() {
+ clearInterval(this.state.cache_refresh);
+ }
+
+ render() {
+ let chartColor = ChartThemeColor.green;
+ let ndnChartColor = ChartThemeColor.green;
+ let ndnUtilColor = ChartThemeColor.green;
+ let dbcachehit = 0;
+ let ndncachehit = 0;
+ let ndncachemax = 0;
+ let ndncachecurr = 0;
+ let utilratio = 0;
+ let content = (
+ <div className="ds-margin-top-xlg ds-center">
+ <TextContent>
+ <Text component={TextVariants.h3}>
+ {_("Loading database monitor information ...")}
+ </Text>
+ </TextContent>
+ <Spinner className="ds-margin-top-lg" size="xl" />
+ </div>
+ );
+
+ if (!this.state.loading) {
+ dbcachehit = parseInt(this.state.data.dbcachehitratio[0]);
+ ndncachehit = parseInt(this.state.data.normalizeddncachehitratio[0]);
+ ndncachemax = parseInt(this.state.data.maxnormalizeddncachesize[0]);
+ ndncachecurr = parseInt(this.state.data.currentnormalizeddncachesize[0]);
+ utilratio = Math.round((ndncachecurr / ndncachemax) * 100);
+ if (utilratio === 0) {
+ // Just round up to 1
+ utilratio = 1;
+ }
+
+ // Database cache
+ if (dbcachehit > 89) {
+ chartColor = ChartThemeColor.green;
+ } else if (dbcachehit > 74) {
+ chartColor = ChartThemeColor.orange;
+ } else {
+ chartColor = ChartThemeColor.purple;
+ }
+ // NDN cache ratio
+ if (ndncachehit > 89) {
+ ndnChartColor = ChartThemeColor.green;
+ } else if (ndncachehit > 74) {
+ ndnChartColor = ChartThemeColor.orange;
+ } else {
+ ndnChartColor = ChartThemeColor.purple;
+ }
+ // NDN cache utilization
+ if (utilratio > 95) {
+ ndnUtilColor = ChartThemeColor.purple;
+ } else if (utilratio > 90) {
+ ndnUtilColor = ChartThemeColor.orange;
+ } else {
+ ndnUtilColor = ChartThemeColor.green;
+ }
+
+ content = (
+ <Tabs activeKey={this.state.activeTabKey} onSelect={this.handleNavSelect}>
+ <Tab eventKey={0} title={<TabTitleText>{_("Normalized DN Cache")}</TabTitleText>}>
+ <div className="ds-margin-top-lg">
+ <Grid hasGutter>
+ <GridItem span={6}>
+ <Card isSelectable>
+ <CardBody>
+ <div className="ds-container">
+ <div className="ds-center">
+ <TextContent className="ds-margin-top-xlg" title={_("The normalized DN cache hit ratio (normalizeddncachehitratio).")}>
+ <Text component={TextVariants.h3}>
+ {_("Cache Hit Ratio")}
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text component={TextVariants.h2}>
+ <b>{ndncachehit}%</b>
+ </Text>
+ </TextContent>
+ </div>
+ <div className="ds-margin-left" style={{ height: '200px', width: '350px' }}>
+ <Chart
+ ariaDesc="NDN Cache"
+ ariaTitle={_("Live Normalized DN Cache Statistics")}
+ containerComponent={<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}`} constrainToVisibleArea />}
+ height={200}
+ maxDomain={{ y: 100 }}
+ minDomain={{ y: 0 }}
+ padding={{
+ bottom: 40,
+ left: 60,
+ top: 10,
+ right: 15,
+ }}
+ width={350}
+ themeColor={ndnChartColor}
+ >
+ <ChartAxis />
+ <ChartAxis dependentAxis showGrid tickValues={[25, 50, 75, 100]} />
+ <ChartGroup>
+ <ChartArea
+ data={this.state.ndnCacheList}
+ />
+ </ChartGroup>
+ </Chart>
+ </div>
+ </div>
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem span={6}>
+ <Card isSelectable>
+ <CardBody>
+ <div className="ds-container">
+ <div className="ds-center">
+ <TextContent className="ds-margin-top-lg" title={_("The amount of the cache that is being used: max size (maxnormalizeddncachesize) vs current size (currentnormalizeddncachesize)")}>
+ <Text component={TextVariants.h2}>
+ {_("Cache Utilization")}
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text component={TextVariants.h3}>
+ <b>{utilratio}%</b>
+ </Text>
+ </TextContent>
+ <TextContent className="ds-margin-top-xlg">
+ <Text component={TextVariants.h5}>
+ {_("Cached DN's")}
+ </Text>
+ </TextContent>
+ <b>{numToCommas(this.state.data.currentnormalizeddncachecount[0])}</b>
+ </div>
+ <div className="ds-margin-left" style={{ height: '200px', width: '350px' }}>
+ <Chart
+ ariaDesc="NDN Cache Utilization"
+ ariaTitle={_("Live Normalized DN Cache Utilization Statistics")}
+ containerComponent={<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}`} constrainToVisibleArea />}
+ height={200}
+ maxDomain={{ y: 100 }}
+ minDomain={{ y: 0 }}
+ padding={{
+ bottom: 40,
+ left: 60,
+ top: 10,
+ right: 15,
+ }}
+ width={350}
+ themeColor={ndnUtilColor}
+ >
+ <ChartAxis />
+ <ChartAxis dependentAxis showGrid tickValues={[25, 50, 75, 100]} />
+ <ChartGroup>
+ <ChartArea
+ data={this.state.ndnCacheUtilList}
+ />
+ </ChartGroup>
+ </Chart>
+ </div>
+ </div>
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+
+ <Grid hasGutter className="ds-margin-top-xlg">
+ <GridItem span={3}>
+ {_("NDN Cache Hit Ratio:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{this.state.data.normalizeddncachehitratio}%</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache Max Size:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{displayBytes(this.state.data.maxnormalizeddncachesize)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache Tries:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.normalizeddncachetries)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Current Cache Size:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{displayBytes(this.state.data.currentnormalizeddncachesize)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache Hits:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.normalizeddncachehits)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache DN Count:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.currentnormalizeddncachecount)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache Evictions:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.normalizeddncacheevictions)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache Thread Size:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.normalizeddncachethreadsize)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("NDN Cache Thread Slots:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.normalizeddncachethreadslots)}</b>
+ </GridItem>
+ </Grid>
+ </div>
+ </Tab>
+ </Tabs>
+ );
+ }
+
+ return (
+ <div id="db-content">
+ <TextContent>
+ <Text className="ds-sub-header" component={TextVariants.h2}>
+ {_("Database Performance Statistics")}
+ </Text>
+ </TextContent>
+ <div className="ds-margin-top-lg">
+ {content}
+ </div>
+
+ </div>
+ );
+ }
+}
+
+// Prop types and defaults
+
+DatabaseMonitorMDB.propTypes = {
+ data: PropTypes.object,
+ serverId: PropTypes.string,
+ enableTree: PropTypes.func,
+};
+
+DatabaseMonitorMDB.defaultProps = {
+ data: {},
+ serverId: "",
+};
diff --git a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
index 464137731..ec78dbdc2 100644
--- a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
@@ -626,4 +626,365 @@ SuffixMonitor.defaultProps = {
bename: "",
};
-export default SuffixMonitor;
+export class SuffixMonitorMDB extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ activeTabKey: 0,
+ data: {},
+ loading: true,
+ // refresh charts
+ cache_refresh: "",
+ count: 10,
+ utilCount: 5,
+ entryCacheList: [],
+ entryUtilCacheList: [],
+ };
+
+ // Toggle currently active tab
+ this.handleNavSelect = (event, tabIndex) => {
+ this.setState({
+ activeTabKey: tabIndex
+ });
+ };
+
+ this.startCacheRefresh = this.startCacheRefresh.bind(this);
+ this.refreshSuffixCache = this.refreshSuffixCache.bind(this);
+ }
+
+ componentDidMount() {
+ this.resetChartData();
+ this.refreshSuffixCache();
+ this.startCacheRefresh();
+ this.props.enableTree();
+ }
+
+ componentWillUnmount() {
+ this.stopCacheRefresh();
+ }
+
+ resetChartData() {
+ this.setState({
+ data: {
+ // Entry cache
+ entrycachehitratio: [0],
+ entrycachetries: [0],
+ entrycachehits: [0],
+ maxentrycachesize: [0],
+ currententrycachesize: [0],
+ maxentrycachecount: [0],
+ currententrycachecount: [0],
+ },
+ entryCacheList: [
+ { name: "", x: "1", y: 0 },
+ { name: "", x: "2", y: 0 },
+ { name: "", x: "3", y: 0 },
+ { name: "", x: "4", y: 0 },
+ { name: "", x: "5", y: 0 },
+ { name: "", x: "6", y: 0 },
+ { name: "", x: "7", y: 0 },
+ { name: "", x: "8", y: 0 },
+ { name: "", x: "9", y: 0 },
+ { name: "", x: "10", y: 0 },
+ ],
+ });
+ }
+
+ refreshSuffixCache() {
+ // Search for db cache stat and update state
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "monitor", "backend", this.props.suffix
+ ];
+ log_cmd("refreshSuffixCache", "Get suffix monitor", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let count = this.state.count + 1;
+ const utilCount = this.state.utilCount + 1;
+ if (count > 100) {
+ // Keep progress count in check
+ count = 1;
+ }
+
+ // Build up the Entry Cache chart data
+ const entryRatio = config.attrs.entrycachehitratio[0];
+ const entry_data = this.state.entryCacheList;
+ entry_data.shift();
+ entry_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(entryRatio) });
+
+ // Build up the Entry Util chart data
+ const entry_util_data = this.state.entryUtilCacheList;
+ let maxsize = config.attrs.maxentrycachesize[0];
+ let currsize = config.attrs.currententrycachesize[0];
+ let utilratio = Math.round((currsize / maxsize) * 100);
+ if (utilratio === 0) {
+ utilratio = 1;
+ }
+ entry_util_data.shift();
+ entry_util_data.push({ name: _("Cache Utilization"), x: utilCount.toString(), y: parseInt(utilratio) });
+
+ this.setState({
+ data: config.attrs,
+ loading: false,
+ entryCacheList: entry_data,
+ entryUtilCacheList: entry_util_data,
+ count,
+ utilCount
+ });
+ })
+ .fail(() => {
+ this.resetChartData();
+ });
+ }
+
+ startCacheRefresh() {
+ this.setState({
+ cache_refresh: setInterval(this.refreshSuffixCache, 2000)
+ });
+ }
+
+ stopCacheRefresh() {
+ clearInterval(this.state.cache_refresh);
+ }
+
+ render() {
+ let entryChartColor = ChartThemeColor.green;
+ let entryUtilChartColor = ChartThemeColor.green;
+ let cachehit = 1;
+ let cachemax = 0;
+ let cachecurr = 0;
+ let cachecount = 0;
+ let utilratio = 1;
+ let SuffixIcon = TreeIcon;
+
+ if (this.props.dbtype === "subsuffix") {
+ SuffixIcon = LeafIcon;
+ }
+
+ let content = (
+ <div className="ds-margin-top-xlg ds-center">
+ <TextContent>
+ <Text component={TextVariants.h3}>
+ {_("Loading Suffix Monitor Information ...")}
+ </Text>
+ </TextContent>
+ <Spinner className="ds-margin-top-lg" size="xl" />
+ </div>
+ );
+
+ if (!this.state.loading) {
+ // Entry cache
+ cachehit = parseInt(this.state.data.entrycachehitratio[0]);
+ cachemax = parseInt(this.state.data.maxentrycachesize[0]);
+ cachecurr = parseInt(this.state.data.currententrycachesize[0]);
+ cachecount = parseInt(this.state.data.currententrycachecount[0]);
+ utilratio = Math.round((cachecurr / cachemax) * 100);
+
+ // Adjust ratios if needed
+ if (utilratio === 0) {
+ utilratio = 1;
+ }
+
+ // Entry cache chart color
+ if (cachehit > 89) {
+ entryChartColor = ChartThemeColor.green;
+ } else if (cachehit > 74) {
+ entryChartColor = ChartThemeColor.orange;
+ } else {
+ entryChartColor = ChartThemeColor.purple;
+ }
+ // Entry cache utilization
+ if (utilratio > 95) {
+ entryUtilChartColor = ChartThemeColor.purple;
+ } else if (utilratio > 90) {
+ entryUtilChartColor = ChartThemeColor.orange;
+ } else {
+ entryUtilChartColor = ChartThemeColor.green;
+ }
+
+ content = (
+ <div id="monitor-suffix-page">
+ <Tabs activeKey={this.state.activeTabKey} onSelect={this.handleNavSelect}>
+ <Tab eventKey={0} title={<TabTitleText>{_("Entry Cache")}</TabTitleText>}>
+ <div className="ds-margin-top">
+ <Grid hasGutter>
+ <GridItem span={6}>
+ <Card isSelectable>
+ <CardBody>
+ <div className="ds-container">
+ <div className="ds-center">
+ <TextContent title={_("The entry cache hit ratio (entrycachehitratio)")}>
+ <Text className="ds-margin-top" component={TextVariants.h3}>
+ {_("Cache Hit Ratio")}
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text className="ds-margin-top" component={TextVariants.h2}>
+ <b>{cachehit}%</b>
+ </Text>
+ </TextContent>
+ </div>
+ <div className="ds-margin-left" style={{ height: '200px', width: '350px' }}>
+ <Chart
+ ariaDesc="Entry Cache"
+ ariaTitle={_("Live Entry Cache Statistics")}
+ containerComponent={<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}`} constrainToVisibleArea />}
+ height={200}
+ maxDomain={{ y: 100 }}
+ minDomain={{ y: 0 }}
+ padding={{
+ bottom: 40,
+ left: 60,
+ top: 10,
+ right: 15,
+ }}
+ width={350}
+ themeColor={entryChartColor}
+ >
+ <ChartAxis />
+ <ChartAxis dependentAxis showGrid tickValues={[25, 50, 75, 100]} />
+ <ChartGroup>
+ <ChartArea
+ data={this.state.entryCacheList}
+ />
+ </ChartGroup>
+ </Chart>
+ </div>
+ </div>
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem span={6}>
+ <Card isSelectable>
+ <CardBody>
+ <div className="ds-container">
+ <div className="ds-center">
+ <TextContent title={_("The amount of the cache that is being used: max size (maxentrycachesize) vs current size (currententrycachesize)")}>
+ <Text className="ds-margin-top" component={TextVariants.h3}>
+ {_("Cache Utilization")}
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text component={TextVariants.h2}>
+ <b>{utilratio}%</b>
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text className="ds-margin-top-lg" component={TextVariants.h5}>
+ {_("Cached Entries")}
+ </Text>
+ </TextContent>
+ <b>{cachecount}</b>
+ </div>
+ <div className="ds-margin-left" style={{ height: '200px', width: '350px' }}>
+ <Chart
+ ariaDesc="Entry Cache Utilization"
+ ariaTitle={_("Live Entry Cache Utilization Statistics")}
+ containerComponent={<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}`} constrainToVisibleArea />}
+ height={200}
+ maxDomain={{ y: 100 }}
+ minDomain={{ y: 0 }}
+ padding={{
+ bottom: 40,
+ left: 60,
+ top: 10,
+ right: 15,
+ }}
+ width={350}
+ themeColor={entryUtilChartColor}
+ >
+ <ChartAxis />
+ <ChartAxis dependentAxis showGrid tickValues={[25, 50, 75, 100]} />
+ <ChartGroup>
+ <ChartArea
+ data={this.state.entryUtilCacheList}
+ />
+ </ChartGroup>
+ </Chart>
+ </div>
+ </div>
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ </div>
+ <Grid hasGutter className="ds-margin-top-xlg">
+ <GridItem span={3}>
+ {_("Entry Cache Hit Ratio:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{this.state.data.entrycachehitratio[0]}%</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("Entry Cache Max Size:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{displayBytes(cachemax)} </b>
+ </GridItem>
+
+ <GridItem span={3}>
+ {_("Entry Cache Hits:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.entrycachehits[0])}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("Entry Cache Current Size:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{displayBytes(cachecurr)}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("Entry Cache Tries:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.entrycachetries[0])}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("Entry Cache Max Entries:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.maxentrycachecount[0])}</b>
+ </GridItem>
+ <GridItem span={3}>
+ {_("Entry Cache Count:")}
+ </GridItem>
+ <GridItem span={2}>
+ <b>{numToCommas(this.state.data.currententrycachecount[0])}</b>
+ </GridItem>
+ </Grid>
+ </Tab>
+ </Tabs>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <TextContent>
+ <Text component={TextVariants.h2}>
+ <SuffixIcon /> {this.props.suffix} (<b>{this.props.bename}</b>)
+ </Text>
+ </TextContent>
+ <div className="ds-margin-top-lg">
+ {content}
+ </div>
+ </div>
+ );
+ }
+}
+
+SuffixMonitorMDB.propTypes = {
+ serverId: PropTypes.string,
+ suffix: PropTypes.string,
+ bename: PropTypes.string,
+ enableTree: PropTypes.func,
+};
+
+SuffixMonitorMDB.defaultProps = {
+ serverId: "",
+ suffix: "",
+ bename: "",
+};
diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx
index da959be07..7e0e0c5d4 100644
--- a/src/cockpit/389-console/src/monitor.jsx
+++ b/src/cockpit/389-console/src/monitor.jsx
@@ -3,8 +3,8 @@ import React from "react";
import { log_cmd } from "./lib/tools.jsx";
import PropTypes from "prop-types";
import ServerMonitor from "./lib/monitor/serverMonitor.jsx";
-import DatabaseMonitor from "./lib/monitor/dbMonitor.jsx";
-import SuffixMonitor from "./lib/monitor/suffixMonitor.jsx";
+import { DatabaseMonitor, DatabaseMonitorMDB } from "./lib/monitor/dbMonitor.jsx";
+import { SuffixMonitor, SuffixMonitorMDB } from "./lib/monitor/suffixMonitor.jsx";
import ChainingMonitor from "./lib/monitor/chainingMonitor.jsx";
import AccessLogMonitor from "./lib/monitor/accesslog.jsx";
import AuditLogMonitor from "./lib/monitor/auditlog.jsx";
@@ -35,6 +35,8 @@ import {
const _ = cockpit.gettext;
+const BE_IMPL_MDB = "mdb";
+
export class Monitor extends React.Component {
constructor(props) {
super(props);
@@ -82,6 +84,8 @@ export class Monitor extends React.Component {
auditlogLocation: "",
auditfaillogLocation: "",
securitylogLocation: "",
+ // DB engine, bdb or mdb (default)
+ dbEngine: BE_IMPL_MDB,
};
// Bindings
@@ -98,6 +102,7 @@ export class Monitor extends React.Component {
this.loadMonitorChaining = this.loadMonitorChaining.bind(this);
this.loadDiskSpace = this.loadDiskSpace.bind(this);
this.reloadDisks = this.reloadDisks.bind(this);
+ this.getDBEngine = this.getDBEngine.bind(this);
// Replication
this.onHandleLoadMonitorReplication = this.onHandleLoadMonitorReplication.bind(this);
this.loadCleanTasks = this.loadCleanTasks.bind(this);
@@ -114,6 +119,10 @@ export class Monitor extends React.Component {
this.loadMonitor = this.loadMonitor.bind(this);
}
+ componentDidMount() {
+ this.getDBEngine();
+ }
+
componentDidUpdate(prevProps) {
if (this.props.wasActiveList.includes(6)) {
if (this.state.firstLoad) {
@@ -580,6 +589,32 @@ export class Monitor extends React.Component {
});
}
+ getDBEngine () {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "config", "get"
+ ];
+ log_cmd("getDBEngine", "Get DB Implementation", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ const attrs = config.attrs;
+ if ('nsslapd-backend-implement' in attrs) {
+ this.setState({
+ dbEngine: attrs['nsslapd-backend-implement'][0],
+ });
+ }
+ })
+ .fail(err => {
+ const errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ cockpit.format("Error detecting DB implementation type - $0", errMsg.desc)
+ );
+ });
+ }
+
reloadSNMP() {
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
@@ -955,13 +990,24 @@ export class Monitor extends React.Component {
</div>
);
} else {
- monitor_element = (
- <DatabaseMonitor
- data={this.state.ldbmData}
- enableTree={this.enableTree}
- serverId={this.props.serverId}
- />
- );
+ if (this.state.dbEngine === BE_IMPL_MDB) {
+ monitor_element = (
+ <DatabaseMonitorMDB
+ data={this.state.ldbmData}
+ enableTree={this.enableTree}
+ serverId={this.props.serverId}
+ />
+ );
+ } else {
+ monitor_element = (
+ <DatabaseMonitor
+ data={this.state.ldbmData}
+ enableTree={this.enableTree}
+ serverId={this.props.serverId}
+ />
+ );
+ }
+
}
} else if (this.state.node_name === "server-monitor") {
if (this.state.serverLoading) {
@@ -1142,16 +1188,29 @@ export class Monitor extends React.Component {
);
} else {
// Suffix
- monitor_element = (
- <SuffixMonitor
- serverId={this.props.serverId}
- suffix={this.state.node_text}
- bename={this.state.bename}
- enableTree={this.enableTree}
- key={this.state.node_text}
- addNotification={this.props.addNotification}
- />
- );
+ if (this.state.dbEngine === BE_IMPL_MDB) {
+ monitor_element = (
+ <SuffixMonitorMDB
+ serverId={this.props.serverId}
+ suffix={this.state.node_text}
+ bename={this.state.bename}
+ enableTree={this.enableTree}
+ key={this.state.node_text}
+ addNotification={this.props.addNotification}
+ />
+ );
+ } else {
+ monitor_element = (
+ <SuffixMonitor
+ serverId={this.props.serverId}
+ suffix={this.state.node_text}
+ bename={this.state.bename}
+ enableTree={this.enableTree}
+ key={this.state.node_text}
+ addNotification={this.props.addNotification}
+ />
+ );
+ }
}
}
}
--
2.48.1

View File

@ -1,41 +0,0 @@
From d43600284728d677ea6a98d9bd3d2e92bfbaecdb Mon Sep 17 00:00:00 2001
From: Masahiro Matsuya <mmatsuya@redhat.com>
Date: Fri, 24 Jan 2025 15:08:22 +0900
Subject: [PATCH] Issue 6446 - Fix test_acct_policy_consumer test to wait
enough for lastLoginHistory update (#6530)
Adding a small sleep to wait for lastLoginHistory update after the bind
operation.
Relates: #6446
Author: Masahiro Matsuya
Reviewed by: tbordaz
---
dirsrvtests/tests/suites/plugins/accpol_test.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dirsrvtests/tests/suites/plugins/accpol_test.py b/dirsrvtests/tests/suites/plugins/accpol_test.py
index f5db8e461..1354f0559 100644
--- a/dirsrvtests/tests/suites/plugins/accpol_test.py
+++ b/dirsrvtests/tests/suites/plugins/accpol_test.py
@@ -1372,6 +1372,7 @@ def test_acct_policy_consumer(topology_m1c1, request):
time.sleep(2)
user_supplier = users_supplier.get("test_user_1000")
user_supplier.bind(USER_PW)
+ time.sleep(2)
# Verify there is no referral error
results = supplier.ds_error_log.match('.*.acct_update_login_history - Modify error 10 on entry*')
@@ -1391,6 +1392,7 @@ def test_acct_policy_consumer(topology_m1c1, request):
time.sleep(2)
user_consumer = users_supplier.get("test_user_1000")
user_consumer.bind(USER_PW)
+ time.sleep(2)
# Verify there is no referral error
results = consumer.ds_error_log.match('.*.acct_update_login_history - Modify error 10 on entry*')
--
2.48.1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,255 @@
From 7b5743f65730334e5e9afa0ba8cff2368b417526 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Tue, 11 Mar 2025 10:23:10 -0400
Subject: [PATCH] Issue 6665 - UI - Need to refresh log settings after saving
Description:
While do we reload the config from the parent component it is not resetting the
child component as expected.
Relates: https://github.com/389ds/389-ds-base/issues/6665
Reviewed by: spichugi(Thanks!)
---
.../389-console/src/lib/server/accessLog.jsx | 20 ++++++++----------
.../389-console/src/lib/server/auditLog.jsx | 21 ++++++++-----------
.../src/lib/server/auditfailLog.jsx | 20 ++++++++----------
.../389-console/src/lib/server/errorLog.jsx | 20 ++++++++----------
.../src/lib/server/securityLog.jsx | 20 ++++++++----------
5 files changed, 45 insertions(+), 56 deletions(-)
diff --git a/src/cockpit/389-console/src/lib/server/accessLog.jsx b/src/cockpit/389-console/src/lib/server/accessLog.jsx
index c1e4bc4c4..803fa8795 100644
--- a/src/cockpit/389-console/src/lib/server/accessLog.jsx
+++ b/src/cockpit/389-console/src/lib/server/accessLog.jsx
@@ -301,32 +301,30 @@ export class ServerAccessLog extends React.Component {
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"success",
_("Successfully updated Access Log settings")
);
- this.setState({
- loading: false
- });
})
.fail(err => {
const errMsg = JSON.parse(err);
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"error",
cockpit.format(_("Error saving Access Log settings - $0"), errMsg.desc)
);
- this.setState({
- loading: false
- });
});
}
- refreshConfig(refesh) {
- this.setState({
- loading: true,
- loaded: false,
- });
+ refreshConfig(loading) {
+ if (!loading) {
+ this.setState({
+ loading: true,
+ loaded: false,
+ });
+ }
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
diff --git a/src/cockpit/389-console/src/lib/server/auditLog.jsx b/src/cockpit/389-console/src/lib/server/auditLog.jsx
index c4a55008c..40f681e93 100644
--- a/src/cockpit/389-console/src/lib/server/auditLog.jsx
+++ b/src/cockpit/389-console/src/lib/server/auditLog.jsx
@@ -308,33 +308,30 @@ export class ServerAuditLog extends React.Component {
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"success",
_("Successfully updated Audit Log settings")
);
- this.setState({
- loading: false
- });
-
})
.fail(err => {
const errMsg = JSON.parse(err);
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"error",
cockpit.format(_("Error saving Audit Log settings - $0"), errMsg.desc)
);
- this.setState({
- loading: false,
- });
});
}
- refreshConfig() {
- this.setState({
- loading: true,
- loaded: false,
- });
+ refreshConfig(loading) {
+ if (!loading) {
+ this.setState({
+ loading: true,
+ loaded: false,
+ });
+ }
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
diff --git a/src/cockpit/389-console/src/lib/server/auditfailLog.jsx b/src/cockpit/389-console/src/lib/server/auditfailLog.jsx
index 0e5ae0a88..19785276a 100644
--- a/src/cockpit/389-console/src/lib/server/auditfailLog.jsx
+++ b/src/cockpit/389-console/src/lib/server/auditfailLog.jsx
@@ -244,32 +244,30 @@ export class ServerAuditFailLog extends React.Component {
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"success",
_("Successfully updated Audit Fail Log settings")
);
- this.setState({
- loading: false
- });
})
.fail(err => {
const errMsg = JSON.parse(err);
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"error",
cockpit.format(_("Error saving Audit Fail Log settings - $0"), errMsg.desc)
);
- this.setState({
- loading: false
- });
});
}
- refreshConfig() {
- this.setState({
- loading: true,
- loaded: false,
- });
+ refreshConfig(loading) {
+ if (!loading) {
+ this.setState({
+ loading: true,
+ loaded: false,
+ });
+ }
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
diff --git a/src/cockpit/389-console/src/lib/server/errorLog.jsx b/src/cockpit/389-console/src/lib/server/errorLog.jsx
index 0922a80ff..0ad36e594 100644
--- a/src/cockpit/389-console/src/lib/server/errorLog.jsx
+++ b/src/cockpit/389-console/src/lib/server/errorLog.jsx
@@ -326,32 +326,30 @@ export class ServerErrorLog extends React.Component {
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
this.props.reload();
+ this.handleRefreshConfig(1);
this.props.addNotification(
"success",
_("Successfully updated Error Log settings")
);
- this.setState({
- loading: false
- });
})
.fail(err => {
const errMsg = JSON.parse(err);
this.props.reload();
+ this.handleRefreshConfig(1);
this.props.addNotification(
"error",
cockpit.format(_("Error saving Error Log settings - $0"), errMsg.desc)
);
- this.setState({
- loading: false
- });
});
}
- handleRefreshConfig() {
- this.setState({
- loading: true,
- loaded: false,
- });
+ handleRefreshConfig(loading) {
+ if (!loading) {
+ this.setState({
+ loading: true,
+ loaded: false,
+ });
+ };
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
diff --git a/src/cockpit/389-console/src/lib/server/securityLog.jsx b/src/cockpit/389-console/src/lib/server/securityLog.jsx
index 01adcca0c..77000a873 100644
--- a/src/cockpit/389-console/src/lib/server/securityLog.jsx
+++ b/src/cockpit/389-console/src/lib/server/securityLog.jsx
@@ -245,32 +245,30 @@ export class ServerSecurityLog extends React.Component {
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"success",
_("Successfully updated Security Log settings")
);
- this.setState({
- loading: false
- });
})
.fail(err => {
const errMsg = JSON.parse(err);
this.props.reload();
+ this.refreshConfig(1);
this.props.addNotification(
"error",
cockpit.format(_("Error saving Security Log settings - $0"), errMsg.desc)
);
- this.setState({
- loading: false
- });
});
}
- refreshConfig(refesh) {
- this.setState({
- loading: true,
- loaded: false,
- });
+ refreshConfig(loading) {
+ if (!loading) {
+ this.setState({
+ loading: true,
+ loaded: false,
+ });
+ }
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
--
2.48.1

View File

@ -0,0 +1,138 @@
From b35e7c454e1be8055d828c8bc89123d8e2a1e9d0 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Wed, 19 Feb 2025 12:18:59 +0000
Subject: [PATCH] Issue 6623 - UI - Generic updates (#6624)
Bug description:
Missing dot in warning alert when changing suffix backend state.
No success alert upon deleting local password policy.
Tool tips for part of Syntax Settings for local password policies do not show.
Fixes: https://github.com/389ds/389-ds-base/issues/6623
Reviewed by: @mreynolds389 (Thank you)
---
.../389-console/src/lib/database/localPwp.jsx | 29 +++++++++++--------
.../389-console/src/lib/database/suffix.jsx | 4 +--
2 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/src/cockpit/389-console/src/lib/database/localPwp.jsx b/src/cockpit/389-console/src/lib/database/localPwp.jsx
index f71477900..ff114bb01 100644
--- a/src/cockpit/389-console/src/lib/database/localPwp.jsx
+++ b/src/cockpit/389-console/src/lib/database/localPwp.jsx
@@ -542,10 +542,10 @@ class CreatePolicy extends React.Component {
isDisabled={!this.props.passwordchecksyntax}
/>
</GridItem>
- <GridItem className="ds-label" offset={5} span={3} title={_("Reject passwords with fewer than this many alpha characters (passwordMinAlphas).")}>
+ <GridItem className="ds-label" offset={5} span={3}>
{_("Minimum Alpha's")}
</GridItem>
- <GridItem span={1}>
+ <GridItem span={1} title={_("Reject passwords with fewer than this many alpha characters (passwordMinAlphas).")}>
<TextInput
type="number"
id="create_passwordminalphas"
@@ -574,10 +574,10 @@ class CreatePolicy extends React.Component {
isDisabled={!this.props.passwordchecksyntax}
/>
</GridItem>
- <GridItem className="ds-label" offset={5} span={3} title={_("Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).")}>
+ <GridItem className="ds-label" offset={5} span={3}>
{_("Minimum Special")}
</GridItem>
- <GridItem span={1}>
+ <GridItem span={1} title={_("Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).")}>
<TextInput
type="number"
id="create_passwordminspecials"
@@ -606,10 +606,10 @@ class CreatePolicy extends React.Component {
isDisabled={!this.props.passwordchecksyntax}
/>
</GridItem>
- <GridItem className="ds-label" offset={5} span={3} title={_("Reject passwords with fewer than this many lowercase characters (passwordMinLowers).")}>
+ <GridItem className="ds-label" offset={5} span={3}>
{_("Minimum Lowercase")}
</GridItem>
- <GridItem span={1}>
+ <GridItem span={1} title={_("Reject passwords with fewer than this many lowercase characters (passwordMinLowers).")}>
<TextInput
type="number"
id="create_passwordminlowers"
@@ -638,10 +638,10 @@ class CreatePolicy extends React.Component {
isDisabled={!this.props.passwordchecksyntax}
/>
</GridItem>
- <GridItem className="ds-label" offset={5} span={3} title={_("The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).")}>
+ <GridItem className="ds-label" offset={5} span={3}>
{_("Minimum Categories")}
</GridItem>
- <GridItem span={1}>
+ <GridItem span={1} title={_("The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).")}>
<TextInput
type="number"
id="create_passwordmincategories"
@@ -670,10 +670,10 @@ class CreatePolicy extends React.Component {
isDisabled={!this.props.passwordchecksyntax}
/>
</GridItem>
- <GridItem className="ds-label" offset={5} span={3} title={_("The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).")}>
+ <GridItem className="ds-label" offset={5} span={3}>
{_("Max Repeated Chars")}
</GridItem>
- <GridItem span={1}>
+ <GridItem span={1} title={_("The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).")}>
<TextInput
type="number"
id="create_passwordmaxrepeats"
@@ -702,10 +702,10 @@ class CreatePolicy extends React.Component {
isDisabled={!this.props.passwordchecksyntax}
/>
</GridItem>
- <GridItem className="ds-label" offset={5} span={3} title={_("The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).")}>
+ <GridItem className="ds-label" offset={5} span={3}>
{_("Max Sequence Sets")}
</GridItem>
- <GridItem span={1}>
+ <GridItem span={1} title={_("The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).")}>
<TextInput
type="number"
id="create_passwordmaxseqsets"
@@ -1855,7 +1855,12 @@ export class LocalPwPolicy extends React.Component {
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
this.handleLoadPolicies();
+ this.props.addNotification(
+ "success",
+ "Successfully deleted password policy"
+ )
})
+
.fail(err => {
const errMsg = JSON.parse(err);
this.handleLoadPolicies();
diff --git a/src/cockpit/389-console/src/lib/database/suffix.jsx b/src/cockpit/389-console/src/lib/database/suffix.jsx
index c6f19e640..bd59653dd 100644
--- a/src/cockpit/389-console/src/lib/database/suffix.jsx
+++ b/src/cockpit/389-console/src/lib/database/suffix.jsx
@@ -812,7 +812,7 @@ export class Suffix extends React.Component {
savingConfig: true
});
log_cmd("saveSuffixConfig", "Save suffix config", cmd);
- const msg = "Successfully updated suffix configuration";
+ const msg = "Successfully updated suffix configuration.";
cockpit
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
@@ -821,7 +821,7 @@ export class Suffix extends React.Component {
if (requireRestart) {
this.props.addNotification(
"warning",
- msg + _("You must restart the Directory Server for these changes to take effect.")
+ msg + _(" You must restart the Directory Server for these changes to take effect.")
);
}
this.setState({
--
2.48.1

View File

@ -1,856 +0,0 @@
From ec7eb7e785076d19c0567f3af96837e8b94d3cb3 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Tue, 7 Jan 2025 11:40:16 +0100
Subject: [PATCH] Issue 6372 - Deadlock while doing online backup (#6475)
* Issue 6372 - Deadlock while doing online backup
Sometime server hangs during online backup because of a deadlock due to lock order inversion between the dse backup_lock mutex and the dse rwlock.
Solution:
Add functions to manage the lock/unlock to ensure consistency.
Ensure that threads always tries to lock dse_backup_lock mutex before the dse write lock
Code cleanup:
Move the backup_lock into the dse struct
Avoid the obsolete warning during tests (I think that we will have to do a second cleanup phase later to see if we could not replace self.conn.add_s by self.create .
Issue: #6372
Reviewed by: @mreynolds389 (Thanks!)
(cherry picked from commit 7e98ab34305c081859ca0ee5a30888991898a452)
---
.../tests/suites/backups/backup_test.py | 249 +++++++++++++++++-
ldap/servers/slapd/dse.c | 170 +++++++-----
ldap/servers/slapd/libglobs.c | 3 -
ldap/servers/slapd/main.c | 1 -
ldap/servers/slapd/slapi-private.h | 2 -
src/lib389/lib389/tasks.py | 38 +--
6 files changed, 357 insertions(+), 106 deletions(-)
diff --git a/dirsrvtests/tests/suites/backups/backup_test.py b/dirsrvtests/tests/suites/backups/backup_test.py
index 7d60cf44c..1246469a3 100644
--- a/dirsrvtests/tests/suites/backups/backup_test.py
+++ b/dirsrvtests/tests/suites/backups/backup_test.py
@@ -7,21 +7,30 @@
# --- END COPYRIGHT BLOCK ---
#
import logging
-import pytest
import os
import shutil
+import time
+import glob
+import subprocess
+import pytest
+import ldap
from datetime import datetime
-from lib389._constants import DEFAULT_SUFFIX, INSTALL_LATEST_CONFIG
+from lib389._constants import DN_DM, PASSWORD, DEFAULT_SUFFIX, INSTALL_LATEST_CONFIG
from lib389.properties import BACKEND_SAMPLE_ENTRIES, TASK_WAIT
-from lib389.topologies import topology_st as topo, topology_m2 as topo_m2
-from lib389.backend import Backend
+from lib389.topologies import topology_st as topo, topology_m2 as topo_m2, set_timeout
+from lib389.backend import Backends, Backend
+from lib389.dbgen import dbgen_users
from lib389.tasks import BackupTask, RestoreTask
from lib389.config import BDB_LDBMConfig
+from lib389.idm.nscontainer import nsContainers
from lib389 import DSEldif
from lib389.utils import ds_is_older, get_default_db_lib
from lib389.replica import ReplicationManager
+from threading import Thread, Event
import tempfile
+
+
pytestmark = pytest.mark.tier1
DEBUGGING = os.getenv("DEBUGGING", default=False)
@@ -31,6 +40,86 @@ else:
logging.getLogger(__name__).setLevel(logging.INFO)
log = logging.getLogger(__name__)
+# test_online_backup_and_dse_write may hang if 6372 is not fixed
+# so lets use a shorter timeout
+set_timeout(30*60)
+
+event = Event()
+
+BESTRUCT = [
+ { "bename" : "be1", "suffix": "dc=be1", "nbusers": 1000 },
+ { "bename" : "be2", "suffix": "dc=be2", "nbusers": 1000 },
+ { "bename" : "be3", "suffix": "dc=be3", "nbusers": 1000 },
+]
+
+class DseConfigContextManager:
+ """Change a config parameter is dse.ldif and restore it afterwards."""
+
+ def __init__(self, inst, dn, attr, value):
+ self.inst = inst
+ self.dn = dn
+ self.attr = attr
+ self.value = value
+ self.oldvalue = None
+ self.dseldif = DSEldif(inst)
+
+ def __enter__(self):
+ self.inst.stop()
+ self.oldvalue = self.dseldif.get(self.dn, self.attr, single=True)
+ self.dseldif.replace(self.dn, self.attr, self.value)
+ log.info(f"Switching {self.dn}:{self.attr} to {self.value}")
+ self.inst.start()
+
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ self.inst.stop()
+ log.info(f"Switching {self.dn}:{self.attr} to {self.oldvalue}")
+ self.dseldif.replace(self.dn, self.attr, self.oldvalue)
+ self.inst.start()
+
+
+@pytest.fixture(scope="function")
+def mytopo(topo, request):
+ bes = []
+
+ def fin():
+ for be in bes:
+ be.delete()
+ for bak_dir in glob.glob(f'{inst.ds_paths.backup_dir}/*'):
+ shutil.rmtree(bak_dir)
+
+ if not DEBUGGING:
+ request.addfinalizer(fin)
+
+ inst = topo.standalone
+
+ ldif_files = {}
+ for d in BESTRUCT:
+ bename = d['bename']
+ suffix = d['suffix']
+ nbusers = d['nbusers']
+ log.info(f'Adding suffix: {suffix} and backend: {bename}...')
+ backends = Backends(inst)
+ try:
+ be = backends.create(properties={'nsslapd-suffix': suffix, 'name': bename})
+ # Insert at list head so that children backends get deleted before parent one.
+ bes.insert(0, be)
+ except ldap.UNWILLING_TO_PERFORM as e:
+ if str(e) == "Mapping tree for this suffix exists!":
+ pass
+ else:
+ raise e
+
+ ldif_dir = inst.get_ldif_dir()
+ ldif_files[bename] = os.path.join(ldif_dir, f'default_{bename}.ldif')
+ dbgen_users(inst, nbusers, ldif_files[bename], suffix)
+ inst.stop()
+ for d in BESTRUCT:
+ bename = d['bename']
+ inst.ldif2db(bename, None, None, None, ldif_files[bename])
+ inst.start()
+ return topo
+
+
def test_missing_backend(topo):
"""Test that an error is returned when a restore is performed for a
backend that is no longer present.
@@ -80,8 +169,6 @@ def test_missing_backend(topo):
assert restore_task.get_exit_code() != 0
-@pytest.mark.bz1851967
-@pytest.mark.ds4112
@pytest.mark.skipif(ds_is_older('1.4.1'), reason="Not implemented")
@pytest.mark.skipif(get_default_db_lib() == "mdb", reason="Not supported over mdb")
def test_db_home_dir_online_backup(topo):
@@ -99,14 +186,16 @@ def test_db_home_dir_online_backup(topo):
2. Failure
3. Success
"""
- bdb_ldbmconfig = BDB_LDBMConfig(topo.standalone)
- dseldif = DSEldif(topo.standalone)
- topo.standalone.stop()
- with tempfile.TemporaryDirectory() as backup_dir:
- dseldif.replace(bdb_ldbmconfig.dn, 'nsslapd-db-home-directory', f'{backup_dir}')
- topo.standalone.start()
- topo.standalone.tasks.db2bak(backup_dir=f'{backup_dir}', args={TASK_WAIT: True})
- assert topo.standalone.ds_error_log.match(f".*Failed renaming {backup_dir}.bak back to {backup_dir}")
+ inst = topo.standalone
+ dn = BDB_LDBMConfig(inst).dn
+ attr = 'nsslapd-db-home-directory'
+ with tempfile.TemporaryDirectory() as dbhome_dir:
+ with DseConfigContextManager(inst, dn, attr, dbhome_dir):
+ backup_dir = str(dbhome_dir)
+ inst.tasks.db2bak(backup_dir=backup_dir, args={TASK_WAIT: True})
+ assert inst.ds_error_log.match(f".*Failed renaming {backup_dir}.bak back to {backup_dir}")
+
+
def test_replication(topo_m2):
"""Test that if the dbhome directory is set causing an online backup to fail,
@@ -168,6 +257,138 @@ def test_replication(topo_m2):
repl.wait_for_replication(S1, S2)
+def test_after_db_log_rotation(topo):
+ """Test that off line backup restore works as expected.
+
+ :id: 8a091d92-a1cf-11ef-823a-482ae39447e5
+ :setup: One standalone instance
+ :steps:
+ 1. Stop instance
+ 2. Perform off line backup on instance
+ 3. Start instance
+ 4. Perform modify operation until db log file rotates
+ 5. Stop instance
+ 6. Restore instance from backup
+ 7. Start instance
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. Success
+ 7. Success
+ """
+ inst = topo.standalone
+ with tempfile.TemporaryDirectory(dir=inst.ds_paths.backup_dir) as backup_dir:
+ # repl.wait_for_replication perform some changes and wait until they get replicated
+ inst.stop()
+ assert inst.db2bak(backup_dir)
+ inst.start()
+ cmd = [ 'ldclt', '-h', 'localhost', '-b', DEFAULT_SUFFIX,
+ '-p', str(inst.port), '-t', '60', '-N', '2',
+ '-D', DN_DM, '-w', PASSWORD, '-f', "ou=People",
+ '-e', 'attreplace=description:XXXXXX' ]
+ log.info(f'Running {cmd}')
+ # Perform modify operations until log file rolls
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
+ log.info(f'STDOUT: {result.stdout}')
+ log.info(f'STDERR: {result.stderr}')
+ if get_default_db_lib() == 'bdb':
+ while os.path.isfile(f'{inst.ds_paths.db_dir}/log.0000000001'):
+ subprocess.run(cmd, capture_output=True, text=True, check=True)
+ inst.stop()
+ assert inst.bak2db(backup_dir)
+ inst.start()
+
+
+def test_backup_task_after_failure(mytopo):
+ """Test that new backup task is successful after a failure.
+ backend that is no longer present.
+
+ :id: a6c24898-2cd9-11ef-8c09-482ae39447e5
+ :setup: Standalone Instance with multiple backends
+ :steps:
+ 1. Cleanup
+ 2. Perform a back up
+ 3. Rename the backup directory while waiting for backup completion.
+ 4. Check that backup failed.
+ 5. Perform a back up
+ 6. Check that backup succeed.
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Backup should fail
+ 5. Success
+ 6. Backup should succeed
+ """
+
+ inst = mytopo.standalone
+ tasks = inst.tasks
+ archive_dir1 = f'{inst.ds_paths.backup_dir}/bak1'
+ archive_dir1b = f'{inst.ds_paths.backup_dir}/bak1b'
+ archive_dir2 = f'{inst.ds_paths.backup_dir}/bak2'
+
+ # Sometime the backup complete too fast, so lets retry if first
+ # backup is successful
+ for retry in range(50):
+ # Step 1. Perform cleanup
+ for dir in glob.glob(f'{inst.ds_paths.backup_dir}/*'):
+ shutil.rmtree(dir)
+ # Step 2. Perform a backup
+ tasks.db2bak(backup_dir=archive_dir1)
+ # Step 3. Wait until task is completed, trying to rename backup directory
+ done,exitCode,warningCode = (False, None, None)
+ while not done:
+ if os.path.isdir(archive_dir1):
+ os.rename(archive_dir1, archive_dir1b)
+ done,exitCode,warningCode = tasks.checkTask(tasks.entry)
+ time.sleep(0.01)
+ if exitCode != 0:
+ break
+ # Step 4. Check that backup failed.
+ # If next assert fails too often, that means that the backup is too fast
+ # A fix would would probably be to add more backends within mytopo
+ assert exitCode != 0, "Backup did not fail as expected."
+ # Step 5. Perform a seconf backup after backup failure
+ exitCode = tasks.db2bak(backup_dir=archive_dir2, args={TASK_WAIT: True})
+ # Step 6. Check it is successful
+ assert exitCode == 0, "Backup failed. Issue #6229 may not be fixed."
+
+
+def load_dse(inst):
+ conts = nsContainers(inst, 'cn=config')
+ while not event.is_set():
+ cont = conts.create(properties={'cn': 'test_online_backup_and_dse_write'})
+ cont.delete()
+
+
+def test_online_backup_and_dse_write(topo):
+ """Test online backup while attempting to add/delete entries in dse.ldif.
+
+ :id: 4a1edd2c-be15-11ef-8bc8-482ae39447e5
+ :setup: One standalone instance
+ :steps:
+ 1. Start a thread that loops adding then removing in the dse.ldif
+ 2. Perform 10 online backups
+ 3. Stop the thread
+ :expectedresults:
+ 1. Success
+ 2. Success (or timeout if issue #6372 is not fixed.)
+ 3. Success
+ """
+ inst = topo.standalone
+ t = Thread(target=load_dse, args=[inst])
+ t.start()
+ for x in range(10):
+ with tempfile.TemporaryDirectory() as backup_dir:
+ assert inst.tasks.db2bak(backup_dir=f'{backup_dir}', args={TASK_WAIT: True}) == 0
+ event.set()
+ t.join()
+ event.clear()
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
index 2edc3f1f6..e3157c1ce 100644
--- a/ldap/servers/slapd/dse.c
+++ b/ldap/servers/slapd/dse.c
@@ -41,6 +41,7 @@
#include <pwd.h>
/* Needed to access read_config_dse */
#include "proto-slap.h"
+#include <stdbool.h>
#include <unistd.h> /* provides fsync/close */
@@ -73,9 +74,6 @@
#define DSE_USE_LOCK 1
#define DSE_NO_LOCK 0
-/* Global lock used during backups */
-static PRLock *backup_lock = NULL;
-
struct dse_callback
{
int operation;
@@ -104,6 +102,8 @@ struct dse
/* initialize the dse */
int dse_is_updateable; /* if non-zero, this DSE can be written to */
int dse_readonly_error_reported; /* used to ensure that read-only errors are logged only once */
+ pthread_mutex_t dse_backup_lock; /* used to block write when online backup is in progress */
+ bool dse_backup_in_progress; /* tell that online backup is in progress (protected by dse_rwlock) */
};
struct dse_node
@@ -155,6 +155,91 @@ static int dse_write_entry(caddr_t data, caddr_t arg);
static int ldif_record_end(char *p);
static int dse_call_callback(struct dse *pdse, Slapi_PBlock *pb, int operation, int flags, Slapi_Entry *entryBefore, Slapi_Entry *entryAfter, int *returncode, char *returntext);
+/* Lock the dse in read mode */
+INLINE_DIRECTIVE static void
+dse_lock_read(struct dse *pdse, int use_lock)
+{
+ if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) {
+ slapi_rwlock_rdlock(pdse->dse_rwlock);
+ }
+}
+
+/* Lock the dse in write mode and wait until the */
+INLINE_DIRECTIVE static void
+dse_lock_write(struct dse *pdse, int use_lock)
+{
+ if (use_lock != DSE_USE_LOCK || !pdse->dse_rwlock) {
+ return;
+ }
+ slapi_rwlock_wrlock(pdse->dse_rwlock);
+ while (pdse->dse_backup_in_progress) {
+ slapi_rwlock_unlock(pdse->dse_rwlock);
+ /* Wait util dse_backup_lock is unlocked */
+ pthread_mutex_lock(&pdse->dse_backup_lock);
+ pthread_mutex_unlock(&pdse->dse_backup_lock);
+ slapi_rwlock_wrlock(pdse->dse_rwlock);
+ }
+}
+
+/* release the dse lock */
+INLINE_DIRECTIVE static void
+dse_lock_unlock(struct dse *pdse, int use_lock)
+{
+ if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) {
+ slapi_rwlock_unlock(pdse->dse_rwlock);
+ }
+}
+
+/* Call cb(pdse) */
+INLINE_DIRECTIVE static void
+dse_call_cb(void (*cb)(struct dse*))
+{
+ Slapi_Backend *be = slapi_be_select_by_instance_name("DSE");
+ if (be) {
+ struct dse *pdse = NULL;
+ slapi_be_Rlock(be);
+ pdse = be->be_database->plg_private;
+ if (pdse) {
+ cb(pdse);
+ }
+ slapi_be_Unlock(be);
+ }
+}
+
+/* Helper for dse_backup_lock() */
+static void
+dse_backup_lock_cb(struct dse *pdse)
+{
+ pthread_mutex_lock(&pdse->dse_backup_lock);
+ slapi_rwlock_wrlock(pdse->dse_rwlock);
+ pdse->dse_backup_in_progress = true;
+ slapi_rwlock_unlock(pdse->dse_rwlock);
+}
+
+/* Helper for dse_backup_unlock() */
+static void
+dse_backup_unlock_cb(struct dse *pdse)
+{
+ slapi_rwlock_wrlock(pdse->dse_rwlock);
+ pdse->dse_backup_in_progress = false;
+ slapi_rwlock_unlock(pdse->dse_rwlock);
+ pthread_mutex_unlock(&pdse->dse_backup_lock);
+}
+
+/* Tells that a backup thread is starting */
+void
+dse_backup_lock()
+{
+ dse_call_cb(dse_backup_lock_cb);
+}
+
+/* Tells that a backup thread is ending */
+void
+dse_backup_unlock()
+{
+ dse_call_cb(dse_backup_unlock_cb);
+}
+
/*
* Map a DN onto a dse_node.
* Returns NULL if not found.
@@ -192,18 +277,12 @@ dse_get_entry_copy(struct dse *pdse, const Slapi_DN *dn, int use_lock)
Slapi_Entry *e = NULL;
struct dse_node *n;
- if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) {
- slapi_rwlock_rdlock(pdse->dse_rwlock);
- }
-
+ dse_lock_read(pdse, use_lock);
n = dse_find_node(pdse, dn);
if (n != NULL) {
e = slapi_entry_dup(n->entry);
}
-
- if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) {
- slapi_rwlock_unlock(pdse->dse_rwlock);
- }
+ dse_lock_unlock(pdse, use_lock);
return e;
}
@@ -393,6 +472,7 @@ dse_new(char *filename, char *tmpfilename, char *backfilename, char *startokfile
pdse->dse_callback = NULL;
pdse->dse_is_updateable = dse_permission_to_write(pdse,
SLAPI_LOG_TRACE);
+ pthread_mutex_init(&pdse->dse_backup_lock, NULL);
}
slapi_ch_free((void **)&realconfigdir);
}
@@ -429,8 +509,7 @@ dse_destroy(struct dse *pdse)
if (NULL == pdse) {
return 0; /* no one checks this return value */
}
- if (pdse->dse_rwlock)
- slapi_rwlock_wrlock(pdse->dse_rwlock);
+ dse_lock_write(pdse, DSE_USE_LOCK);
slapi_ch_free((void **)&(pdse->dse_filename));
slapi_ch_free((void **)&(pdse->dse_tmpfile));
slapi_ch_free((void **)&(pdse->dse_fileback));
@@ -439,8 +518,8 @@ dse_destroy(struct dse *pdse)
dse_callback_deletelist(&pdse->dse_callback);
charray_free(pdse->dse_filelist);
nentries = avl_free(pdse->dse_tree, dse_internal_delete_entry);
+ dse_lock_unlock(pdse, DSE_USE_LOCK);
if (pdse->dse_rwlock) {
- slapi_rwlock_unlock(pdse->dse_rwlock);
slapi_destroy_rwlock(pdse->dse_rwlock);
}
slapi_ch_free((void **)&pdse);
@@ -928,9 +1007,7 @@ dse_check_for_readonly_error(Slapi_PBlock *pb, struct dse *pdse)
{
int rc = 0; /* default: no error */
- if (pdse->dse_rwlock)
- slapi_rwlock_rdlock(pdse->dse_rwlock);
-
+ dse_lock_read(pdse, DSE_USE_LOCK);
if (!pdse->dse_is_updateable) {
if (!pdse->dse_readonly_error_reported) {
if (NULL != pdse->dse_filename) {
@@ -944,9 +1021,7 @@ dse_check_for_readonly_error(Slapi_PBlock *pb, struct dse *pdse)
}
rc = 1; /* return an error to the client */
}
-
- if (pdse->dse_rwlock)
- slapi_rwlock_unlock(pdse->dse_rwlock);
+ dse_lock_unlock(pdse, DSE_USE_LOCK);
if (rc != 0) {
slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL,
@@ -973,8 +1048,6 @@ dse_write_file_nolock(struct dse *pdse)
fpw.fpw_rc = 0;
fpw.fpw_prfd = NULL;
- dse_backup_lock();
-
if (NULL != pdse->dse_filename) {
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();
@@ -1107,8 +1180,7 @@ dse_add_entry_pb(struct dse *pdse, Slapi_Entry *e, Slapi_PBlock *pb)
slapi_pblock_get(pb, SLAPI_DSE_MERGE_WHEN_ADDING, &merge);
/* keep write lock during both tree update and file write operations */
- if (pdse->dse_rwlock)
- slapi_rwlock_wrlock(pdse->dse_rwlock);
+ dse_lock_write(pdse, DSE_USE_LOCK);
if (merge) {
rc = avl_insert(&(pdse->dse_tree), n, entry_dn_cmp, dupentry_merge);
} else {
@@ -1131,8 +1203,7 @@ dse_add_entry_pb(struct dse *pdse, Slapi_Entry *e, Slapi_PBlock *pb)
} else { /* duplicate entry ignored */
dse_node_delete(&n); /* This also deletes the contained entry */
}
- if (pdse->dse_rwlock)
- slapi_rwlock_unlock(pdse->dse_rwlock);
+ dse_lock_unlock(pdse, DSE_USE_LOCK);
if (rc == -1) {
/* duplicate entry ignored */
@@ -1299,8 +1370,7 @@ dse_replace_entry(struct dse *pdse, Slapi_Entry *e, int write_file, int use_lock
int rc = -1;
if (NULL != e) {
struct dse_node *n = dse_node_new(e);
- if (use_lock && pdse->dse_rwlock)
- slapi_rwlock_wrlock(pdse->dse_rwlock);
+ dse_lock_write(pdse, use_lock);
rc = avl_insert(&(pdse->dse_tree), n, entry_dn_cmp, dupentry_replace);
if (write_file)
dse_write_file_nolock(pdse);
@@ -1310,8 +1380,7 @@ dse_replace_entry(struct dse *pdse, Slapi_Entry *e, int write_file, int use_lock
dse_node_delete(&n);
rc = 0; /* for return to caller */
}
- if (use_lock && pdse->dse_rwlock)
- slapi_rwlock_unlock(pdse->dse_rwlock);
+ dse_lock_unlock(pdse, use_lock);
}
return rc;
}
@@ -1398,8 +1467,7 @@ dse_delete_entry(struct dse *pdse, Slapi_PBlock *pb, const Slapi_Entry *e)
slapi_pblock_get(pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &dont_write_file);
/* keep write lock for both tree deleting and file writing */
- if (pdse->dse_rwlock)
- slapi_rwlock_wrlock(pdse->dse_rwlock);
+ dse_lock_write(pdse, DSE_USE_LOCK);
if ((deleted_node = (struct dse_node *)avl_delete(&pdse->dse_tree, n, entry_dn_cmp))) {
dse_node_delete(&deleted_node);
}
@@ -1411,8 +1479,7 @@ dse_delete_entry(struct dse *pdse, Slapi_PBlock *pb, const Slapi_Entry *e)
SLAPI_OPERATION_DELETE);
dse_write_file_nolock(pdse);
}
- if (pdse->dse_rwlock)
- slapi_rwlock_unlock(pdse->dse_rwlock);
+ dse_lock_unlock(pdse, DSE_USE_LOCK);
return 1;
}
@@ -1574,11 +1641,9 @@ do_dse_search(struct dse *pdse, Slapi_PBlock *pb, int scope, const Slapi_DN *bas
* entries that change, we skip looking through the DSE entries.
*/
if (pb_op == NULL || !operation_is_flag_set(pb_op, OP_FLAG_PS_CHANGESONLY)) {
- if (pdse->dse_rwlock)
- slapi_rwlock_rdlock(pdse->dse_rwlock);
+ dse_lock_read(pdse, DSE_USE_LOCK);
dse_apply_nolock(pdse, dse_search_filter_entry, (caddr_t)&stuff);
- if (pdse->dse_rwlock)
- slapi_rwlock_unlock(pdse->dse_rwlock);
+ dse_lock_unlock(pdse, DSE_USE_LOCK);
}
if (stuff.ss) /* something was found which matched our criteria */
@@ -2925,32 +2990,3 @@ dse_next_search_entry(Slapi_PBlock *pb)
return 0;
}
-/* When a backup is occurring we can not allow the writing the dse.ldif file */
-void
-dse_init_backup_lock()
-{
- backup_lock = PR_NewLock();
-}
-
-void
-dse_destroy_backup_lock()
-{
- PR_DestroyLock(backup_lock);
- backup_lock = NULL;
-}
-
-void
-dse_backup_lock()
-{
- if (backup_lock) {
- PR_Lock(backup_lock);
- }
-}
-
-void
-dse_backup_unlock()
-{
- if (backup_lock) {
- PR_Unlock(backup_lock);
- }
-}
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index caf6eaa6a..f8f6096c7 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -2020,9 +2020,6 @@ FrontendConfig_init(void)
/* Done, unlock! */
CFG_UNLOCK_WRITE(cfg);
- /* init the dse file backup lock */
- dse_init_backup_lock();
-
init_config_get_and_set();
}
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
index ee32c1a6c..5c841c528 100644
--- a/ldap/servers/slapd/main.c
+++ b/ldap/servers/slapd/main.c
@@ -1157,7 +1157,6 @@ cleanup:
SSL_ClearSessionCache();
ndn_cache_destroy();
NSS_Shutdown();
- dse_destroy_backup_lock();
/*
* Server has stopped, lets force everything to disk: logs
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
index ee7659ac0..1ceb36beb 100644
--- a/ldap/servers/slapd/slapi-private.h
+++ b/ldap/servers/slapd/slapi-private.h
@@ -1411,8 +1411,6 @@ void modify_update_last_modified_attr(Slapi_PBlock *pb, Slapi_Mods *smods);
void add_internal_modifiersname(Slapi_PBlock *pb, Slapi_Entry *e);
/* dse.c */
-void dse_init_backup_lock(void);
-void dse_destroy_backup_lock(void);
void dse_backup_lock(void);
void dse_backup_unlock(void);
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
index c1a2e7aaa..6bf302862 100644
--- a/src/lib389/lib389/tasks.py
+++ b/src/lib389/lib389/tasks.py
@@ -525,7 +525,7 @@ class Tasks(object):
entry.setValues('nsIncludeSuffix', suffix)
# start the task and possibly wait for task completion
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
exitCode = 0
warningCode = 0
@@ -598,7 +598,7 @@ class Tasks(object):
entry.setValues('nsExportReplica', 'true')
# start the task and possibly wait for task completion
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
exitCode = 0
warningCode = 0
if args and args.get(TASK_WAIT, False):
@@ -649,7 +649,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add the backup task (%s)", dn)
return -1
@@ -706,7 +706,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add the backup task (%s)", dn)
return -1
@@ -834,7 +834,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add the index task for %s", attrname)
return -1
@@ -914,7 +914,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add the memberOf fixup task")
return -1
@@ -975,7 +975,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add the fixup tombstone task")
return -1
@@ -1031,7 +1031,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Automember Rebuild Membership task")
return -1
@@ -1087,7 +1087,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Automember Export Updates task")
return -1
@@ -1138,7 +1138,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Automember Map Updates task")
return -1
@@ -1183,7 +1183,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Fixup Linked Attributes task")
return -1
@@ -1227,7 +1227,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Schema Reload task")
return -1
@@ -1272,7 +1272,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add fixupWinsyncMembers 'memberuid task'")
return -1
@@ -1319,7 +1319,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Syntax Validate task")
return -1
@@ -1370,7 +1370,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add USN tombstone cleanup task")
return -1
@@ -1421,7 +1421,7 @@ class Tasks(object):
entry.setValues('logchanges', logchanges)
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Sysconfig Reload task")
return -1
@@ -1473,7 +1473,7 @@ class Tasks(object):
entry.setValues('replica-force-cleaning', 'yes')
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add cleanAllRUV task")
return (dn, -1)
@@ -1528,7 +1528,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add Abort cleanAllRUV task")
return (dn, -1)
@@ -1582,7 +1582,7 @@ class Tasks(object):
# start the task and possibly wait for task completion
try:
- self.conn.add_s(entry)
+ self.conn.add_s(entry, escapehatch='i am sure')
except ldap.ALREADY_EXISTS:
self.log.error("Fail to add upgradedb task")
return -1
--
2.48.1

View File

@ -0,0 +1,443 @@
From 1d3443dbc15f254622157563af359cb8fbc51855 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 26 Mar 2025 16:33:22 -0400
Subject: [PATCH] Issue 6695 - UI - fix more minor issues
Description:
fix the following items:
MemberOf page - subtree scopes - allows you to create blank values, and does not validate suffixes
Local password policy - copy/paste error added a duplicate label for a checkbox (send expiring warning)
Create instance modal - did not validate database name
Update connection monitor chart to include detailed information for connection status (established, close wait, and time wait)
relates: https://github.com/389ds/389-ds-base/issues/6695
Reviewed by: spichugi(Thanks!)
---
src/cockpit/389-console/src/dsModals.jsx | 22 ++--
.../389-console/src/lib/database/localPwp.jsx | 3 -
.../src/lib/monitor/serverMonitor.jsx | 114 +++++++++++++++---
.../389-console/src/lib/plugins/memberOf.jsx | 18 +--
src/lib389/lib389/monitor.py | 12 ++
5 files changed, 128 insertions(+), 41 deletions(-)
diff --git a/src/cockpit/389-console/src/dsModals.jsx b/src/cockpit/389-console/src/dsModals.jsx
index e3ee18fc0..bf19ce144 100644
--- a/src/cockpit/389-console/src/dsModals.jsx
+++ b/src/cockpit/389-console/src/dsModals.jsx
@@ -4,7 +4,13 @@ import PropTypes from "prop-types";
import { DoubleConfirmModal } from "./lib/notifications.jsx";
import { BackupTable } from "./lib/database/databaseTables.jsx";
import { BackupModal } from "./lib/database/backups.jsx";
-import { log_cmd, bad_file_name, valid_dn, callCmdStreamPassword } from "./lib/tools.jsx";
+import {
+ log_cmd,
+ bad_file_name,
+ valid_dn,
+ valid_db_name,
+ callCmdStreamPassword
+} from "./lib/tools.jsx";
import {
Button,
Checkbox,
@@ -103,10 +109,6 @@ export class CreateInstanceModal extends React.Component {
'createDM'
];
- const optionalAttrs = [
- 'createDBName'
- ];
-
// Handle server ID
if (this.state.createServerId !== "") {
if (this.state.createServerId.length > 80) {
@@ -142,11 +144,9 @@ export class CreateInstanceModal extends React.Component {
}
if (this.state.createDBCheckbox) {
- for (const attr of optionalAttrs) {
- if (this.state[attr] === "") {
- all_good = false;
- errObj[attr] = true;
- }
+ if (!valid_db_name(this.state.createDBName)) {
+ all_good = false;
+ errObj["createDBName"] = true;
}
if (!valid_dn(this.state.createDBSuffix)) {
all_good = false;
@@ -636,7 +636,7 @@ export class CreateInstanceModal extends React.Component {
<FormHelperText >
<HelperText>
<HelperTextItem variant="error">
- {_("Name is required")}
+ {createDBName === "" ? _("Name is required") : "Invalid database name"}
</HelperTextItem>
</HelperText>
</FormHelperText>
diff --git a/src/cockpit/389-console/src/lib/database/localPwp.jsx b/src/cockpit/389-console/src/lib/database/localPwp.jsx
index ff114bb01..8586ba932 100644
--- a/src/cockpit/389-console/src/lib/database/localPwp.jsx
+++ b/src/cockpit/389-console/src/lib/database/localPwp.jsx
@@ -398,9 +398,6 @@ class CreatePolicy extends React.Component {
</GridItem>
</Grid>
<Grid className="ds-margin-top" title={_("Always return a password expiring control when requested (passwordSendExpiringTime).")}>
- <GridItem className="ds-label" span={4}>
- {_("Send Password Expiring Warning")}
- </GridItem>
<GridItem span={4}>
<Checkbox
id="create_passwordsendexpiringtime"
diff --git a/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
index 513e1d792..a736ff532 100644
--- a/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
@@ -37,6 +37,7 @@ import {
import { SyncAltIcon } from "@patternfly/react-icons";
const _ = cockpit.gettext;
+const refresh_interval = 10000; // 10 seconds
export class ServerMonitor extends React.Component {
constructor (props) {
@@ -47,12 +48,20 @@ export class ServerMonitor extends React.Component {
const initResChart = [];
const initSwapChart = [];
const initConnChart = [];
- for (let idx = 1; idx <= 6; idx++) {
- initCPUChart.push({ name: 'CPU', x: "0:00:00", y: 0 });
- initResChart.push({ name: 'Resident', x: "0:00:00", y: 0 });
- initVirtChart.push({ name: 'Virtual', x: "0:00:00", y: 0 });
- initSwapChart.push({ name: 'Swap', x: "0:00:00", y: 0 });
- initConnChart.push({ name: 'Connection', x: "0:00:00", y: 0 });
+ const initConnEstablishedChart = [];
+ const initConnTimeWaitChart = [];
+ const initConnCloseWaitChart = [];
+ for (let idx = 0; idx <= 5; idx++) {
+ const value = refresh_interval / 1000;
+ const x_value = "0:00:" + (idx === 0 ? "00" : value * idx).toString();
+ initCPUChart.push({ name: 'CPU', x: x_value, y: 0 });
+ initResChart.push({ name: 'Resident', x: x_value, y: 0 });
+ initVirtChart.push({ name: 'Virtual', x: x_value, y: 0 });
+ initSwapChart.push({ name: 'Swap', x: x_value, y: 0 });
+ initConnChart.push({ name: 'Connections', x: x_value, y: 0 });
+ initConnTimeWaitChart.push({ name: 'Connections time wait', x: x_value, y: 0 });
+ initConnCloseWaitChart.push({ name: 'Connections close wait', x: x_value, y: 0 });
+ initConnEstablishedChart.push({ name: 'Connections established', x: x_value, y: 0 });
}
this.state = {
@@ -71,11 +80,17 @@ export class ServerMonitor extends React.Component {
initResChart,
initSwapChart,
initConnChart,
+ initConnEstablishedChart,
+ initConnTimeWaitChart,
+ initConnCloseWaitChart,
cpuChart: [...initCPUChart],
memVirtChart: [...initVirtChart],
memResChart: [...initResChart],
swapChart: [...initSwapChart],
connChart: [...initConnChart],
+ connEstablishedChart: [...initConnEstablishedChart],
+ connTimeWaitChart: [...initConnTimeWaitChart],
+ connCloseWaitChart: [...initConnCloseWaitChart],
};
this.handleNavSelect = (event, tabIndex) => {
@@ -110,6 +125,9 @@ export class ServerMonitor extends React.Component {
memResChart: [...this.state.initResChart],
swapChart: [...this.state.initSwapChart],
connChart: [...this.state.initConnChart],
+ connCloseWaitChart: [...this.state.initConnCloseWaitChart],
+ connTimeWaitChart: [...this.state.initConnTimeWaitChart],
+ connEstablishedChart: [...this.state.initConnEstablishedChart],
});
}
@@ -123,6 +141,9 @@ export class ServerMonitor extends React.Component {
let res_mem = 0;
let swap_mem = 0;
let current_conns = 0;
+ let conn_established = 0;
+ let conn_close_wait = 0;
+ let conn_time_wait = 0;
let total_threads = 0;
let conn_highmark = this.state.conn_highmark;
let cpu_tick_values = this.state.cpu_tick_values;
@@ -147,6 +168,9 @@ export class ServerMonitor extends React.Component {
res_mem = attrs['rss'][0];
swap_mem = attrs['swap'][0];
current_conns = attrs['connection_count'][0];
+ conn_established = attrs['connection_established_count'][0];
+ conn_close_wait = attrs['connection_close_wait_count'][0];
+ conn_time_wait = attrs['connection_time_wait_count'][0];
total_threads = attrs['total_threads'][0];
mem_total = attrs['total_mem'][0];
@@ -196,6 +220,18 @@ export class ServerMonitor extends React.Component {
connChart.shift();
connChart.push({ name: _("Connections"), x: interval, y: parseInt(current_conns) });
+ const connEstablishedChart = this.state.connEstablishedChart;
+ connEstablishedChart.shift();
+ connEstablishedChart.push({ name: _("Connections established"), x: interval, y: parseInt(conn_established) });
+
+ const connTimeWaitChart = this.state.connTimeWaitChart;
+ connTimeWaitChart.shift();
+ connTimeWaitChart.push({ name: _("Connections time wait"), x: interval, y: parseInt(conn_time_wait) });
+
+ const connCloseWaitChart = this.state.connCloseWaitChart;
+ connCloseWaitChart.shift();
+ connCloseWaitChart.push({ name: _("Connections close wait"), x: interval, y: parseInt(conn_close_wait) });
+
this.setState({
cpu_tick_values,
conn_tick_values,
@@ -204,8 +240,14 @@ export class ServerMonitor extends React.Component {
memResChart,
swapChart,
connChart,
+ connTimeWaitChart,
+ connCloseWaitChart,
+ connEstablishedChart,
conn_highmark,
current_conns,
+ conn_close_wait,
+ conn_time_wait,
+ conn_established,
mem_virt_size: virt_mem,
mem_res_size: res_mem,
mem_swap_size: swap_mem,
@@ -224,7 +266,7 @@ export class ServerMonitor extends React.Component {
startRefresh() {
this.setState({
- chart_refresh: setInterval(this.refreshCharts, 10000),
+ chart_refresh: setInterval(this.refreshCharts, refresh_interval),
});
}
@@ -236,8 +278,14 @@ export class ServerMonitor extends React.Component {
const {
cpu,
connChart,
+ connTimeWaitChart,
+ connCloseWaitChart,
+ connEstablishedChart,
cpuChart,
current_conns,
+ conn_established,
+ conn_close_wait,
+ conn_time_wait,
memResChart,
memVirtChart,
swapChart,
@@ -312,15 +360,33 @@ export class ServerMonitor extends React.Component {
<Card className="ds-margin-top-lg">
<CardBody>
<Grid>
- <GridItem span="4" className="ds-center" title={_("Established client connections to the server")}>
- <TextContent>
- <Text className="ds-margin-top-xlg" component={TextVariants.h3}>
- {_("Connections")}
+ <GridItem span="4" title={_("Established client connections to the server")}>
+ <div className="ds-center" >
+ <TextContent>
+ <Text component={TextVariants.h2}>
+ {_("Connections")}
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text component={TextVariants.h6}>
+ <b>{numToCommas(current_conns)}</b>
+ </Text>
+ </TextContent>
+ <Divider className="ds-margin-top ds-margin-bottom"/>
+ </div>
+ <TextContent className="ds-margin-top-lg" title="Connections that are in an ESTABLISHED state">
+ <Text component={TextVariants.p}>
+ Established: &nbsp;&nbsp;<b>{numToCommas(conn_established)}</b>
</Text>
</TextContent>
- <TextContent>
- <Text component={TextVariants.h6}>
- <b>{numToCommas(current_conns)}</b>
+ <TextContent className="ds-margin-top-lg" title="Connections that are in a CLOSE_WAIT state">
+ <Text component={TextVariants.p}>
+ Close wait: &nbsp;&nbsp;<b>{numToCommas(conn_close_wait)}</b>
+ </Text>
+ </TextContent>
+ <TextContent className="ds-margin-top-lg" title="Connections that are in a TIME_WAIT state">
+ <Text component={TextVariants.p}>
+ Time wait: &nbsp;&nbsp;<b>{numToCommas(conn_time_wait)}</b>
</Text>
</TextContent>
</GridItem>
@@ -329,13 +395,13 @@ export class ServerMonitor extends React.Component {
ariaDesc="connection stats"
ariaTitle={_("Live Connection Statistics")}
containerComponent={<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}`} constrainToVisibleArea />}
- height={200}
+ height={220}
minDomain={{ y: 0 }}
padding={{
bottom: 30,
- left: 55,
+ left: 60,
top: 10,
- right: 25,
+ right: 30,
}}
>
<ChartAxis />
@@ -344,6 +410,18 @@ export class ServerMonitor extends React.Component {
<ChartArea
data={connChart}
/>
+ <ChartArea
+ data={connEstablishedChart}
+ interpolation="monotoneX"
+ />
+ <ChartArea
+ data={connTimeWaitChart}
+ interpolation="monotoneX"
+ />
+ <ChartArea
+ data={connCloseWaitChart}
+ interpolation="monotoneX"
+ />
</ChartGroup>
</Chart>
</GridItem>
@@ -372,7 +450,7 @@ export class ServerMonitor extends React.Component {
ariaDesc="cpu"
ariaTitle={_("Server CPU Usage")}
containerComponent={<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}%`} constrainToVisibleArea />}
- height={200}
+ height={220}
minDomain={{ y: 0 }}
padding={{
bottom: 30,
diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
index 7c1b02297..704d6d0b1 100644
--- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
@@ -225,7 +225,7 @@ class MemberOf extends React.Component {
// Handle scope subtree
this.handleSubtreeScopeSelect = (event, selection) => {
- if (selection === "") {
+ if (!selection.trim() || !valid_dn(selection)) {
this.setState({isSubtreeScopeOpen: false});
return;
}
@@ -257,7 +257,7 @@ class MemberOf extends React.Component {
}, () => { this.validateConfig() });
};
this.handleSubtreeScopeCreateOption = newValue => {
- if (newValue && !this.state.memberOfEntryScopeOptions.includes(newValue)) {
+ if (newValue.trim() && valid_dn(newValue) && !this.state.memberOfEntryScopeOptions.includes(newValue)) {
this.setState({
memberOfEntryScopeOptions: [...this.state.memberOfEntryScopeOptions, newValue],
isSubtreeScopeOpen: false
@@ -267,7 +267,7 @@ class MemberOf extends React.Component {
// Handle Exclude Scope subtree
this.handleExcludeScopeSelect = (event, selection) => {
- if (selection === "") {
+ if (!selection.trim() || !valid_dn(selection)) {
this.setState({isExcludeScopeOpen: false});
return;
}
@@ -299,7 +299,7 @@ class MemberOf extends React.Component {
}, () => { this.validateConfig() });
};
this.handleExcludeCreateOption = newValue => {
- if (newValue && !this.state.memberOfEntryScopeOptions.includes(newValue)) {
+ if (newValue.trim() && valid_dn(newValue) && !this.state.memberOfEntryScopeOptions.includes(newValue)) {
this.setState({
memberOfEntryScopeExcludeOptions: [...this.state.memberOfEntryScopeExcludeOptions, newValue],
isExcludeScopeOpen: false
@@ -310,7 +310,7 @@ class MemberOf extends React.Component {
// Modal scope and exclude Scope
// Handle scope subtree
this.handleConfigScopeSelect = (event, selection) => {
- if (selection === "") {
+ if (selection.trim() === "" || !valid_dn(selection)) {
this.setState({isConfigSubtreeScopeOpen: false});
return;
}
@@ -342,7 +342,7 @@ class MemberOf extends React.Component {
}, () => { this.validateModal() });
};
this.handleConfigCreateOption = newValue => {
- if (newValue && !this.state.configEntryScopeOptions.includes(newValue)) {
+ if (newValue.trim() && valid_dn(newValue) && !this.state.configEntryScopeOptions.includes(newValue)) {
this.setState({
configEntryScopeOptions: [...this.state.configEntryScopeOptions, newValue],
isConfigSubtreeScopeOpen: false
@@ -352,7 +352,7 @@ class MemberOf extends React.Component {
// Handle Exclude Scope subtree
this.handleConfigExcludeScopeSelect = (event, selection) => {
- if (selection === "") {
+ if (selection.trim() === "" || !valid_dn(selection)) {
this.setState({isConfigExcludeScopeOpen: false});
return;
}
@@ -384,7 +384,7 @@ class MemberOf extends React.Component {
}, () => { this.validateModal() });
};
this.handleConfigExcludeCreateOption = newValue => {
- if (newValue && !this.state.configEntryScopeExcludeOptions.includes(newValue)) {
+ if (newValue.trim() && valid_dn(newValue) && !this.state.configEntryScopeExcludeOptions.includes(newValue)) {
this.setState({
configEntryScopeExcludeOptions: [...this.state.configEntryScopeExcludeOptions, newValue],
isConfigExcludeScopeOpen: false
@@ -1563,7 +1563,7 @@ class MemberOf extends React.Component {
))}
</Select>
<FormHelperText >
- {_("A subtree is required, and values must be valid DN's")}
+ {"values must be valid DN's"}
</FormHelperText>
</GridItem>
<GridItem className="ds-left-margin" span={3}>
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
index 196577ed5..ec82b0346 100644
--- a/src/lib389/lib389/monitor.py
+++ b/src/lib389/lib389/monitor.py
@@ -119,14 +119,26 @@ class Monitor(DSLdapObject):
sslport = str(self._instance.sslport)
conn_count = 0
+ conn_established_count = 0
+ conn_close_wait_count = 0
+ conn_time_wait_count = 0
conns = psutil.net_connections()
for conn in conns:
if len(conn[4]) > 0:
conn_port = str(conn[4][1])
if conn_port in (port, sslport):
+ if conn[5] == 'TIME_WAIT':
+ conn_time_wait_count += 1
+ if conn[5] == 'CLOSE_WAIT':
+ conn_close_wait_count += 1
+ if conn[5] == 'ESTABLISHED':
+ conn_established_count += 1
conn_count += 1
stats['connection_count'] = [str(conn_count)]
+ stats['connection_established_count'] = [str(conn_established_count)]
+ stats['connection_close_wait_count'] = [str(conn_close_wait_count)]
+ stats['connection_time_wait_count'] = [str(conn_time_wait_count)]
return stats
--
2.48.1

View File

@ -1,245 +0,0 @@
From dab3b79e8de4ff8825c9f73685bf59be7bc19f97 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 | 59 ++++++++++++++++++-------------
ldap/servers/slapd/opshared.c | 32 ++++++++---------
ldap/servers/slapd/pagedresults.c | 9 +++++
ldap/servers/slapd/slap.h | 2 +-
4 files changed, 61 insertions(+), 41 deletions(-)
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
index 56e20bd13..1a20796b9 100644
--- a/ldap/servers/slapd/daemon.c
+++ b/ldap/servers/slapd/daemon.c
@@ -1546,7 +1546,29 @@ setup_pr_read_pds(Connection_Table *ct, int listnum)
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) {
@@ -1554,35 +1576,24 @@ setup_pr_read_pds(Connection_Table *ct, int listnum)
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[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++;
- }
+ 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++;
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
index 540597f45..7dc2d5983 100644
--- a/ldap/servers/slapd/opshared.c
+++ b/ldap/servers/slapd/opshared.c
@@ -251,7 +251,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;
@@ -852,7 +852,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
/* may be the object exist somewhere else
* wait the end of the loop to send back this error
*/
- flag_no_such_object = 1;
+ flag_no_such_object = true;
} else {
/* err something other than LDAP_NO_SUCH_OBJECT, so the backend will
* have sent the result -
@@ -862,35 +862,35 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
/* 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 9959c927e..642aefb3d 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;
}
@@ -936,7 +943,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 15b69e69e..ca2f1f12c 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -77,7 +77,7 @@ static char ptokPBE[34] = "Internal (Software) Token ";
#include <sys/statvfs.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.1

View File

@ -0,0 +1,116 @@
From 5770bb74af3f51d8d35734a6de8d442948a78701 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 28 Mar 2025 10:01:22 -0400
Subject: [PATCH] Issue 6704 - UI - Add error log buffering config
Description:
Add error log buffering setting to the UI
Relates: https://github.com/389ds/389-ds-base/issues/6704
Reviewed by: jchapman (Thanks!)
---
.../389-console/src/lib/server/errorLog.jsx | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/cockpit/389-console/src/lib/server/errorLog.jsx b/src/cockpit/389-console/src/lib/server/errorLog.jsx
index 0ad36e594..14e6aa40a 100644
--- a/src/cockpit/389-console/src/lib/server/errorLog.jsx
+++ b/src/cockpit/389-console/src/lib/server/errorLog.jsx
@@ -38,6 +38,7 @@ const settings_attrs = [
'nsslapd-errorlog',
'nsslapd-errorlog-level',
'nsslapd-errorlog-logging-enabled',
+ 'nsslapd-errorlog-logbuffering',
];
const _ = cockpit.gettext;
@@ -363,6 +364,7 @@ export class ServerErrorLog extends React.Component {
const attrs = config.attrs;
let enabled = false;
let compressed = false;
+ let buffering = false;
const level_val = parseInt(attrs['nsslapd-errorlog-level'][0]);
const rows = [...this.state.rows];
@@ -372,6 +374,9 @@ export class ServerErrorLog extends React.Component {
if (attrs['nsslapd-errorlog-compress'][0] === "on") {
compressed = true;
}
+ if (attrs['nsslapd-errorlog-logbuffering'][0] === "on") {
+ buffering = true;
+ }
for (const row in rows) {
if (rows[row].level & level_val) {
@@ -403,6 +408,7 @@ export class ServerErrorLog extends React.Component {
'nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
'nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
'nsslapd-errorlog-compress': compressed,
+ 'nsslapd-errorlog-logbuffering': buffering,
rows,
// Record original values
_rows: JSON.parse(JSON.stringify(rows)),
@@ -421,6 +427,7 @@ export class ServerErrorLog extends React.Component {
'_nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
'_nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
'_nsslapd-errorlog-compress': compressed,
+ '_nsslapd-errorlog-logbuffering': buffering,
})
);
})
@@ -441,6 +448,7 @@ export class ServerErrorLog extends React.Component {
const attrs = this.state.attrs;
let enabled = false;
let compressed = false;
+ let buffering = false;
const level_val = parseInt(attrs['nsslapd-errorlog-level'][0]);
const rows = [...this.state.rows];
@@ -454,6 +462,9 @@ export class ServerErrorLog extends React.Component {
if (attrs['nsslapd-errorlog-compress'][0] === "on") {
compressed = true;
}
+ if (attrs['nsslapd-errorlog-logbuffering'][0] === "on") {
+ buffering = true;
+ }
for (const row in rows) {
if (rows[row].level & level_val) {
rows[row].selected = true;
@@ -483,6 +494,7 @@ export class ServerErrorLog extends React.Component {
'nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
'nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
'nsslapd-errorlog-compress': compressed,
+ 'nsslapd-errorlog-logbuffering': buffering,
rows,
// Record original values
_rows: JSON.parse(JSON.stringify(rows)),
@@ -501,6 +513,7 @@ export class ServerErrorLog extends React.Component {
'_nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],
'_nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],
'_nsslapd-errorlog-compress': compressed,
+ '_nsslapd-errorlog-logbuffering': buffering,
}, this.props.enableTree);
}
@@ -592,6 +605,16 @@ export class ServerErrorLog extends React.Component {
/>
</FormGroup>
</Form>
+ <Checkbox
+ className="ds-left-margin-md ds-margin-top-lg"
+ id="nsslapd-errorlog-logbuffering"
+ isChecked={this.state['nsslapd-errorlog-logbuffering']}
+ onChange={(e, checked) => {
+ this.handleChange(e, "settings");
+ }}
+ title={"This applies to the error log. Enable error log buffering when using verbose logging levels, otherwise verbose logging levels will impact server performance (nsslapd-errorlog-logbuffering)."}
+ label={_("Error Log Buffering Enabled")}
+ />
<ExpandableSection
className="ds-left-margin-md ds-margin-top-lg ds-font-size-md"
--
2.48.1

View File

@ -1,157 +0,0 @@
From 294b9a665b7b77e2c6332618870d336e71f356b3 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 | 10 ++---
src/lib389/lib389/lint.py | 15 ++++++++
src/lib389/lib389/plugins.py | 37 ++++++++++++++++++-
3 files changed, 56 insertions(+), 6 deletions(-)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_config_test.py b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
index c462805e1..c7570cbf2 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_config_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
@@ -210,6 +210,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)
@@ -231,7 +232,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
@@ -246,8 +247,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
@@ -257,6 +258,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,8 +281,6 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
standalone.restart()
-@pytest.mark.ds50873
-@pytest.mark.bz1685160
@pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented")
def test_healthcheck_virtual_attr_incorrectly_indexed(topology_st):
"""Check if HealthCheck returns DSVIRTLE0001 code
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
index 9baa710de..bc21d2355 100644
--- a/src/lib389/lib389/lint.py
+++ b/src/lib389/lib389/lint.py
@@ -292,6 +292,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 2f1969f03..75c16a7c8 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

View File

@ -0,0 +1,474 @@
From d72ead0f12ae36fabc83022415c48b2f0f79b5c9 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Sun, 30 Mar 2025 15:49:45 -0400
Subject: [PATCH] Issue 6700 - CLI/UI - include superior objectclasses' allowed
and requires attrs
Description:
When you get/list an objectclass it only lists its level of allowed and
required objectclasses, but it should also include all its superior
objectclasses' allowed and required attributes.
Added an option to the CLI to also include all the parent/superior
required and allowed attributes
Relates: https://github.com/389ds/389-ds-base/issues/6700
Reviewed by: spichugi & tbordaz(Thanks!)
---
.../suites/clu/dsconf_schema_superior_test.py | 122 ++++++++++++++++++
.../tests/suites/schema/schema_test.py | 9 +-
.../src/lib/ldap_editor/lib/utils.jsx | 3 +-
src/cockpit/389-console/src/schema.jsx | 6 +-
src/lib389/lib389/cli_conf/schema.py | 12 +-
src/lib389/lib389/schema.py | 105 +++++++++++----
6 files changed, 219 insertions(+), 38 deletions(-)
create mode 100644 dirsrvtests/tests/suites/clu/dsconf_schema_superior_test.py
diff --git a/dirsrvtests/tests/suites/clu/dsconf_schema_superior_test.py b/dirsrvtests/tests/suites/clu/dsconf_schema_superior_test.py
new file mode 100644
index 000000000..185e16af2
--- /dev/null
+++ b/dirsrvtests/tests/suites/clu/dsconf_schema_superior_test.py
@@ -0,0 +1,122 @@
+# --- 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 json
+import os
+import subprocess
+import pytest
+from lib389.topologies import topology_st as topo
+
+log = logging.getLogger(__name__)
+
+
+def execute_dsconf_command(dsconf_cmd, subcommands):
+ """Execute dsconf command and return output and return code"""
+
+ cmdline = dsconf_cmd + subcommands
+ proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
+ out, _ = proc.communicate()
+ return out.decode('utf-8'), proc.returncode
+
+
+def get_dsconf_base_cmd(topo):
+ """Return base dsconf command list"""
+ return ['/usr/sbin/dsconf', topo.standalone.serverid,
+ '-j', 'schema']
+
+
+def test_schema_oc_superior(topo):
+ """Specify a test case purpose or name here
+
+ :id: d12aab4a-1436-43eb-802a-0661281a13d0
+ :setup: Standalone Instance
+ :steps:
+ 1. List all the schema
+ 2. List all the schema and include superior OC's attrs
+ 3. Get objectclass list
+ 4. Get objectclass list and include superior OC's attrs
+ 5. Get objectclass
+ 6. Get objectclass and include superior OC's attrs
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ """
+
+ dsconf_cmd = get_dsconf_base_cmd(topo)
+
+ # Test default schema list
+ output, rc = execute_dsconf_command(dsconf_cmd, ['list'])
+ assert rc == 0
+ json_result = json.loads(output)
+ for schema_item in json_result:
+ if 'name' in schema_item and schema_item['name'] == 'inetOrgPerson':
+ assert len(schema_item['must']) == 0
+ break
+
+ # Test including the OC superior attributes
+ output, rc = execute_dsconf_command(dsconf_cmd, ['list',
+ '--include-oc-sup'])
+ assert rc == 0
+ json_result = json.loads(output)
+ for schema_item in json_result:
+ if 'name' in schema_item and schema_item['name'] == 'inetOrgPerson':
+ assert len(schema_item['must']) > 0 and \
+ 'cn' in schema_item['must'] and 'sn' in schema_item['must']
+ break
+
+ # Test default objectclass list
+ output, rc = execute_dsconf_command(dsconf_cmd, ['objectclasses', 'list'])
+ assert rc == 0
+ json_result = json.loads(output)
+ for schema_item in json_result:
+ if 'name' in schema_item and schema_item['name'] == 'inetOrgPerson':
+ assert len(schema_item['must']) == 0
+ break
+
+ # Test objectclass list and inslude superior attributes
+ output, rc = execute_dsconf_command(dsconf_cmd, ['objectclasses', 'list',
+ '--include-sup'])
+ assert rc == 0
+ json_result = json.loads(output)
+ for schema_item in json_result:
+ if 'name' in schema_item and schema_item['name'] == 'inetOrgPerson':
+ assert len(schema_item['must']) > 0 and \
+ 'cn' in schema_item['must'] and 'sn' in schema_item['must']
+ break
+
+ # Test default objectclass query
+ output, rc = execute_dsconf_command(dsconf_cmd, ['objectclasses', 'query',
+ 'inetOrgPerson'])
+ assert rc == 0
+ result = json.loads(output)
+ schema_item = result['oc']
+ assert 'names' in schema_item
+ assert schema_item['names'][0] == 'inetOrgPerson'
+ assert len(schema_item['must']) == 0
+
+ # Test objectclass query and include superior attributes
+ output, rc = execute_dsconf_command(dsconf_cmd, ['objectclasses', 'query',
+ 'inetOrgPerson',
+ '--include-sup'])
+ assert rc == 0
+ result = json.loads(output)
+ schema_item = result['oc']
+ assert 'names' in schema_item
+ assert schema_item['names'][0] == 'inetOrgPerson'
+ assert len(schema_item['must']) > 0 and 'cn' in schema_item['must'] \
+ and 'sn' in schema_item['must']
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", CURRENT_FILE])
diff --git a/dirsrvtests/tests/suites/schema/schema_test.py b/dirsrvtests/tests/suites/schema/schema_test.py
index afc9cc678..8ca15af70 100644
--- a/dirsrvtests/tests/suites/schema/schema_test.py
+++ b/dirsrvtests/tests/suites/schema/schema_test.py
@@ -232,7 +232,7 @@ def test_gecos_mixed_definition_topo(topo_m2, request):
repl = ReplicationManager(DEFAULT_SUFFIX)
m1 = topo_m2.ms["supplier1"]
m2 = topo_m2.ms["supplier2"]
-
+
# create a test user
testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX)
@@ -343,7 +343,7 @@ def test_gecos_directoryString_wins_M1(topo_m2, request):
repl = ReplicationManager(DEFAULT_SUFFIX)
m1 = topo_m2.ms["supplier1"]
m2 = topo_m2.ms["supplier2"]
-
+
# create a test user
testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX)
@@ -471,7 +471,7 @@ def test_gecos_directoryString_wins_M2(topo_m2, request):
repl = ReplicationManager(DEFAULT_SUFFIX)
m1 = topo_m2.ms["supplier1"]
m2 = topo_m2.ms["supplier2"]
-
+
# create a test user
testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX)
@@ -623,11 +623,10 @@ def test_definition_with_sharp(topology_st, request):
# start the instances
inst.start()
- i# Check that server is really running.
+ # Check that server is really running.
assert inst.status()
-
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx b/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx
index fc9c898fa..cd94063ec 100644
--- a/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx
+++ b/src/cockpit/389-console/src/lib/ldap_editor/lib/utils.jsx
@@ -873,7 +873,8 @@ export function getAllObjectClasses (serverId, allOcCallback) {
'ldapi://%2fvar%2frun%2fslapd-' + serverId + '.socket',
'schema',
'objectclasses',
- 'list'
+ 'list',
+ '--include-sup'
];
const result = [];
log_cmd("getAllObjectClasses", "", cmd);
diff --git a/src/cockpit/389-console/src/schema.jsx b/src/cockpit/389-console/src/schema.jsx
index e39f9fef2..19854e785 100644
--- a/src/cockpit/389-console/src/schema.jsx
+++ b/src/cockpit/389-console/src/schema.jsx
@@ -440,7 +440,8 @@ export class Schema extends React.Component {
"-j",
"ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
"schema",
- "list"
+ "list",
+ "--include-oc-sup"
];
log_cmd("loadSchemaData", "Get schema objects in one batch", cmd);
cockpit
@@ -568,7 +569,8 @@ export class Schema extends React.Component {
"schema",
"objectclasses",
"query",
- name
+ name,
+ "--include-sup"
];
log_cmd("openObjectclassModal", "Fetch ObjectClass data from schema", cmd);
diff --git a/src/lib389/lib389/cli_conf/schema.py b/src/lib389/lib389/cli_conf/schema.py
index 7a06c91bc..7782aa5e5 100644
--- a/src/lib389/lib389/cli_conf/schema.py
+++ b/src/lib389/lib389/cli_conf/schema.py
@@ -31,7 +31,8 @@ def list_all(inst, basedn, log, args):
if args is not None and args.json:
json = True
- objectclass_elems = schema.get_objectclasses(json=json)
+ objectclass_elems = schema.get_objectclasses(include_sup=args.include_oc_sup,
+ json=json)
attributetype_elems = schema.get_attributetypes(json=json)
matchingrule_elems = schema.get_matchingrules(json=json)
@@ -67,7 +68,7 @@ def list_objectclasses(inst, basedn, log, args):
log = log.getChild('list_objectclasses')
schema = Schema(inst)
if args is not None and args.json:
- print(dump_json(schema.get_objectclasses(json=True), indent=4))
+ print(dump_json(schema.get_objectclasses(include_sup=args.include_sup, json=True), indent=4))
else:
for oc in schema.get_objectclasses():
print(oc)
@@ -108,7 +109,7 @@ def query_objectclass(inst, basedn, log, args):
schema = Schema(inst)
# Need the query type
oc = _get_arg(args.name, msg="Enter objectclass to query")
- result = schema.query_objectclass(oc, json=args.json)
+ result = schema.query_objectclass(oc, include_sup=args.include_sup, json=args.json)
if args.json:
print(dump_json(result, indent=4))
else:
@@ -339,6 +340,9 @@ def create_parser(subparsers):
schema_subcommands = schema_parser.add_subparsers(help='schema')
schema_list_parser = schema_subcommands.add_parser('list', help='List all schema objects on this system', formatter_class=CustomHelpFormatter)
schema_list_parser.set_defaults(func=list_all)
+ schema_list_parser.add_argument('--include-oc-sup', action='store_true',
+ default=False,
+ help="Include the superior objectclasses' \"may\" and \"must\" attributes")
attributetypes_parser = schema_subcommands.add_parser('attributetypes', help='Work with attribute types on this system', formatter_class=CustomHelpFormatter)
attributetypes_subcommands = attributetypes_parser.add_subparsers(help='schema')
@@ -365,9 +369,11 @@ def create_parser(subparsers):
objectclasses_subcommands = objectclasses_parser.add_subparsers(help='schema')
oc_list_parser = objectclasses_subcommands.add_parser('list', help='List available objectClasses on this system', formatter_class=CustomHelpFormatter)
oc_list_parser.set_defaults(func=list_objectclasses)
+ oc_list_parser.add_argument('--include-sup', action='store_true', default=False, help="Include the superior objectclasses' \"may\" and \"must\" attributes")
oc_query_parser = objectclasses_subcommands.add_parser('query', help='Query an objectClass', formatter_class=CustomHelpFormatter)
oc_query_parser.set_defaults(func=query_objectclass)
oc_query_parser.add_argument('name', nargs='?', help='ObjectClass to query')
+ oc_query_parser.add_argument('--include-sup', action='store_true', default=False, help="Include the superior objectclasses' \"may\" and \"must\" attributes")
oc_add_parser = objectclasses_subcommands.add_parser('add', help='Add an objectClass to this system', formatter_class=CustomHelpFormatter)
oc_add_parser.set_defaults(func=add_objectclass)
_add_parser_args(oc_add_parser, 'objectclasses')
diff --git a/src/lib389/lib389/schema.py b/src/lib389/lib389/schema.py
index a47e13db8..2e8aa3ed8 100755
--- a/src/lib389/lib389/schema.py
+++ b/src/lib389/lib389/schema.py
@@ -116,15 +116,66 @@ class Schema(DSLdapObject):
result = ATTR_SYNTAXES
return result
- def _get_schema_objects(self, object_model, json=False):
- """Get all the schema objects for a specific model: Attribute, Objectclass,
- or Matchingreule.
+ def gather_oc_sup_attrs(self, oc, sup_oc, ocs, processed_ocs=None):
+ """
+ Recursively build up all the objectclass superiors' may/must
+ attributes
+
+ @param oc - original objectclass we are building up
+ @param sup_oc - superior objectclass that we are gathering must/may
+ attributes from, and for following its superior
+ objectclass
+ @param ocs - all objectclasses
+ @param processed_ocs - list of all the superior objectclasees we have
+ already processed. Used for checking if we
+ somehow get into an infinite loop
+ """
+ if processed_ocs is None:
+ # First pass, init our values
+ sup_oc = oc
+ processed_ocs = [sup_oc['names'][0]]
+ elif sup_oc['names'][0] in processed_ocs:
+ # We're looping, need to abort. This should never happen because
+ # of how the schema is structured, but perhaps a bug was
+ # introduced in the server schema handling?
+ return
+
+ # update processed list to prevent loops
+ processed_ocs.append(sup_oc['names'][0])
+
+ for soc in sup_oc['sup']:
+ if soc.lower() == "top":
+ continue
+ # Get sup_oc
+ for obj in ocs:
+ oc_dict = vars(ObjectClass(obj))
+ name = oc_dict['names'][0]
+ if name.lower() == soc.lower():
+ # Found the superior, get it's attributes
+ for attr in oc_dict['may']:
+ if attr not in oc['may']:
+ oc['may'] = oc['may'] + (attr,)
+ for attr in oc_dict['must']:
+ if attr not in oc['must']:
+ oc['must'] = oc['must'] + (attr,)
+
+ # Sort the tuples
+ oc['may'] = tuple(sorted(oc['may']))
+ oc['must'] = tuple(sorted(oc['must']))
+
+ # Now recurse and check this objectclass
+ self.gather_oc_sup_attrs(oc, oc_dict, ocs, processed_ocs)
+
+ def _get_schema_objects(self, object_model, include_sup=False, json=False):
+ """Get all the schema objects for a specific model:
+
+ Attribute, ObjectClass, or MatchingRule.
"""
attr_name = self._get_attr_name_by_model(object_model)
results = self.get_attr_vals_utf8(attr_name)
+ object_insts = []
if json:
- object_insts = []
for obj in results:
obj_i = vars(object_model(obj))
if len(obj_i["names"]) == 1:
@@ -136,20 +187,9 @@ class Schema(DSLdapObject):
else:
obj_i['name'] = ""
- # Temporary workaround for X-ORIGIN in ObjectClass objects.
- # It should be removed after https://github.com/python-ldap/python-ldap/pull/247 is merged
- if " X-ORIGIN " in obj and obj_i['names'] == vars(object_model(obj))['names']:
- remainder = obj.split(" X-ORIGIN ")[1]
- if remainder[:1] == "(":
- # Have multiple values
- end = remainder.rfind(')')
- vals = remainder[1:end]
- vals = re.findall(X_ORIGIN_REGEX, vals)
- # For now use the first value, but this should be a set (another bug in python-ldap)
- obj_i['x_origin'] = vals[0]
- else:
- # Single X-ORIGIN value
- obj_i['x_origin'] = obj.split(" X-ORIGIN ")[1].split("'")[1]
+ if object_model is ObjectClass and include_sup:
+ self.gather_oc_sup_attrs(obj_i, None, results)
+
object_insts.append(obj_i)
object_insts = sorted(object_insts, key=itemgetter('name'))
@@ -161,11 +201,20 @@ class Schema(DSLdapObject):
return {'type': 'list', 'items': object_insts}
else:
- object_insts = [object_model(obj_i) for obj_i in results]
+ for obj_i in results:
+ obj_i = object_model(obj_i)
+ if object_model is ObjectClass and include_sup:
+ obj_ii = vars(obj_i)
+ self.gather_oc_sup_attrs(obj_ii, None, results)
+ obj_i.may = obj_ii['may']
+ obj_i.must = obj_ii['must']
+ object_insts.append(obj_i)
return sorted(object_insts, key=lambda x: x.names, reverse=False)
- def _get_schema_object(self, name, object_model, json=False):
- objects = self._get_schema_objects(object_model, json=json)
+ def _get_schema_object(self, name, object_model, include_sup=False, json=False):
+ objects = self._get_schema_objects(object_model,
+ include_sup=include_sup,
+ json=json)
if json:
schema_object = [obj_i for obj_i in objects["items"] if name.lower() in
list(map(str.lower, obj_i["names"]))]
@@ -227,7 +276,6 @@ class Schema(DSLdapObject):
def _remove_schema_object(self, name, object_model):
attr_name = self._get_attr_name_by_model(object_model)
schema_object = self._get_schema_object(name, object_model)
-
return self.remove(attr_name, str(schema_object))
def _edit_schema_object(self, name, parameters, object_model):
@@ -371,7 +419,6 @@ class Schema(DSLdapObject):
:param name: the name of the objectClass you want to remove.
:type name: str
"""
-
return self._remove_schema_object(name, ObjectClass)
def edit_attributetype(self, name, parameters):
@@ -396,7 +443,7 @@ class Schema(DSLdapObject):
return self._edit_schema_object(name, parameters, ObjectClass)
- def get_objectclasses(self, json=False):
+ def get_objectclasses(self, include_sup=False, json=False):
"""Returns a list of ldap.schema.models.ObjectClass objects for all
objectClasses supported by this instance.
@@ -404,7 +451,8 @@ class Schema(DSLdapObject):
:type json: bool
"""
- return self._get_schema_objects(ObjectClass, json=json)
+ return self._get_schema_objects(ObjectClass, include_sup=include_sup,
+ json=json)
def get_attributetypes(self, json=False):
"""Returns a list of ldap.schema.models.AttributeType objects for all
@@ -447,7 +495,8 @@ class Schema(DSLdapObject):
else:
return matching_rule
- def query_objectclass(self, objectclassname, json=False):
+ def query_objectclass(self, objectclassname, include_sup=False,
+ json=False):
"""Returns a single ObjectClass instance that matches objectclassname.
Returns None if the objectClass doesn't exist.
@@ -462,7 +511,9 @@ class Schema(DSLdapObject):
<ldap.schema.models.ObjectClass instance>
"""
- objectclass = self._get_schema_object(objectclassname, ObjectClass, json=json)
+ objectclass = self._get_schema_object(objectclassname, ObjectClass,
+ include_sup=include_sup,
+ json=json)
if json:
result = {'type': 'schema', 'oc': objectclass}
--
2.48.1

View File

@ -0,0 +1,701 @@
From 626fa9af91b28a39d662678c94c5475a46a2280b Mon Sep 17 00:00:00 2001
From: Mike Weinberg <5876158+mikeweinberg@users.noreply.github.com>
Date: Tue, 24 Dec 2024 21:41:59 -0500
Subject: [PATCH] Issue 6464 - UI - Fixed spelling in cockpit messages
Description:
I used typos-cli to assist in correcting more spelling errors in cockpit 389-console.
I focused only on messages displayed to users in the cockpit project.
I also attempted to fix the Japanese translation file so it wouldn't break.
Relates: https://github.com/389ds/389-ds-base/issues/6464
Reviewed by: @droideck
---
src/cockpit/389-console/po/ja.po | 52 +++++++++----------
src/cockpit/389-console/src/database.jsx | 2 +-
src/cockpit/389-console/src/dsModals.jsx | 4 +-
.../src/lib/database/databaseConfig.jsx | 2 +-
.../src/lib/database/databaseModal.jsx | 2 +-
.../src/lib/database/globalPwp.jsx | 2 +-
.../389-console/src/lib/database/indexes.jsx | 4 +-
.../389-console/src/lib/database/localPwp.jsx | 4 +-
.../ldap_editor/wizards/operations/aciNew.jsx | 2 +-
.../wizards/operations/addCosDefinition.jsx | 4 +-
.../src/lib/monitor/monitorModals.jsx | 2 +-
.../src/lib/monitor/replMonitor.jsx | 2 +-
.../389-console/src/lib/plugins/memberOf.jsx | 2 +-
.../src/lib/plugins/pamPassThru.jsx | 10 ++--
.../lib/plugins/passthroughAuthentication.jsx | 6 +--
.../389-console/src/lib/plugins/usn.jsx | 2 +-
.../src/lib/replication/replConfig.jsx | 2 +-
.../src/lib/replication/replModals.jsx | 4 +-
.../src/lib/replication/replTasks.jsx | 6 +--
.../lib/security/certificateManagement.jsx | 8 +--
.../src/lib/security/securityModals.jsx | 4 +-
src/cockpit/389-console/src/schema.jsx | 6 +--
22 files changed, 66 insertions(+), 66 deletions(-)
diff --git a/src/cockpit/389-console/po/ja.po b/src/cockpit/389-console/po/ja.po
index 444857f55..b4085518a 100644
--- a/src/cockpit/389-console/po/ja.po
+++ b/src/cockpit/389-console/po/ja.po
@@ -285,7 +285,7 @@ msgstr "接続名"
#: src/lib/monitor/monitorModals.jsx:634
msgid ""
-"Bind password for the specified instance. You can also speciy a password "
+"Bind password for the specified instance. You can also specify a password "
"file but the filename needs to be inside of brackets [/PATH/FILE]"
msgstr ""
"指定されたインスタンスのバインドパスワードを入力してください。または、パス"
@@ -1813,9 +1813,9 @@ msgstr "レプリカDSRC情報を読み込んでいます ..."
#: src/lib/monitor/replMonitor.jsx:1501
msgid ""
-"Only one monitor configuraton can be saved in the server's '~/.dsrc' file. "
+"Only one monitor configuration can be saved in the server's '~/.dsrc' file. "
"There is already an existing monitor configuration, and if you proceed it "
-"will be completely overwritten with the new configuraton."
+"will be completely overwritten with the new configuration."
msgstr ""
"サーバの '~/.dsrc' ファイルに保存できる監視設定は1つだけです。すでに既存"
"の監視設定がありますが、このまま進むと新しい設定で完全に上書きされます。"
@@ -3361,7 +3361,7 @@ msgid "Suffix is required."
msgstr "サフィックスが必要です。"
#: src/lib/plugins/usn.jsx:221
-msgid "Cleanup USN Tombstones task was successfull"
+msgid "Cleanup USN Tombstones task was successful"
msgstr "USN Tombstonesのクリーンアップタスクが正常に終了しました"
#: src/lib/plugins/usn.jsx:232
@@ -3584,7 +3584,7 @@ msgid "Fixup DN is required."
msgstr "修正DNは必須です。"
#: src/lib/plugins/memberOf.jsx:624
-msgid "Fixup task for $0 was successfull"
+msgid "Fixup task for $0 was successful"
msgstr "$0 の修正タスクが成功しました"
#: src/lib/plugins/memberOf.jsx:780
@@ -4320,7 +4320,7 @@ msgid "Error during the pamConfig entry $0 operation - $1"
msgstr "pamConfigエントリの$0に失敗しました - $1"
#: src/lib/plugins/pamPassThru.jsx:651
-msgid "$0 PAM Passthough Auth Config Entry"
+msgid "$0 PAM Passthrough Auth Config Entry"
msgstr "PAMパススルー認証の設定を$0"
#: src/lib/plugins/pamPassThru.jsx:702
@@ -4708,7 +4708,7 @@ msgstr "サフィックスの作成"
#: src/lib/database/databaseModal.jsx:286
msgid ""
"Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP "
-"Distiguished Name (DN)"
+"Distinguished Name (DN)"
msgstr ""
"データベースのサフィックスは 'dc=example,dc=com' のようなものです。サフィック"
"スは有効なLDAPの識別名(DN)でなければなりません。"
@@ -5466,7 +5466,7 @@ msgstr "パスワードの有効期限を設定する"
#: src/lib/database/localPwp.jsx:349 src/lib/database/localPwp.jsx:2732
#: src/lib/database/globalPwp.jsx:1176
msgid ""
-"The maxiumum age of a password in seconds before it expires (passwordMaxAge)."
+"The maximum age of a password in seconds before it expires (passwordMaxAge)."
msgstr "パスワードの有効期限の最大値を秒単位で指定します。(passwordMaxAge)"
#: src/lib/database/localPwp.jsx:353 src/lib/database/localPwp.jsx:2734
@@ -6821,7 +6821,7 @@ msgid "Index Types"
msgstr "インデックスタイプ"
#: src/lib/database/indexes.jsx:875 src/lib/database/indexes.jsx:1015
-msgid "Equailty Indexing"
+msgid "Equality Indexing"
msgstr "等価性インデックス化"
#: src/lib/database/indexes.jsx:887 src/lib/database/indexes.jsx:1025
@@ -7689,7 +7689,7 @@ msgstr "バインドDNグループ"
#: src/lib/replication/replConfig.jsx:630
msgid ""
-"The interval to check for any changes in the group memebrship specified in "
+"The interval to check for any changes in the group membership specified in "
"the Bind DN Group and automatically rebuilds the list for the replication "
"managers accordingly. (nsds5replicabinddngroupcheckinterval)."
msgstr ""
@@ -7853,7 +7853,7 @@ msgid "Local RUV"
msgstr "ローカルRUV"
#: src/lib/replication/replTasks.jsx:365
-msgid "RRefresh the RUV for this suffixs"
+msgid "Refresh the RUV for this suffix"
msgstr "このサフィックスのRUVを更新します"
#: src/lib/replication/replTasks.jsx:375 src/lib/replication/replTables.jsx:290
@@ -7861,7 +7861,7 @@ msgid "Remote RUV's"
msgstr "リモートRUV"
#: src/lib/replication/replTasks.jsx:379
-msgid "Refresh the remote RUVs for this suffixs"
+msgid "Refresh the remote RUVs for this suffix"
msgstr "このサフィックスのリモートRUVを更新します"
#: src/lib/replication/replTasks.jsx:394
@@ -8330,7 +8330,7 @@ msgstr "スケジュール"
#: src/lib/replication/replModals.jsx:1402
msgid ""
"By default replication updates are sent to the replica as soon as possible, "
-"but if there is a need for replication updates to only be sent on certains "
+"but if there is a need for replication updates to only be sent on certain "
"days and within certain windows of time then you can setup a custom "
"replication schedule."
msgstr ""
@@ -9823,7 +9823,7 @@ msgid "User DN Aliases (userdn)"
msgstr "ユーザDNエイリアス"
#: src/lib/ldap_editor/wizards/operations/aciNew.jsx:1470
-msgid "Special bind rules for user DN catagories"
+msgid "Special bind rules for user DN categories"
msgstr "ユーザDNカテゴリに特別なバインドルールを設定"
#: src/lib/ldap_editor/wizards/operations/aciNew.jsx:1473
@@ -10114,15 +10114,15 @@ msgid "Choose The New CoS Template Parent DN"
msgstr "新しいCoSテンプレートの親DNを選択してください"
#: src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx:992
-msgid "Leaving CoS Definiton Creation Wizard"
+msgid "Leaving CoS Definition Creation Wizard"
msgstr "CoS定義作成ウィザードを終了します"
#: src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx:1004
msgid ""
-"You are about to leave CoS Definiton creation wizard. After you click "
+"You are about to leave CoS Definition creation wizard. After you click "
"'Confirm', you'll appear in CoS Template creation wizard and you won't able "
"to return from there until the process is finished. Then you'll be able to "
-"use the created entry in the CoS definiton creation. It'll be preselected "
+"use the created entry in the CoS definition creation. It'll be preselected "
"for you automatically."
msgstr ""
"CoS定義作成ウィザードを終了し、CoSテンプレート作成ウィザードに移動します。移"
@@ -11644,11 +11644,11 @@ msgid "Upload PEM File"
msgstr "PEMファイルをアップロード"
#: src/lib/security/securityModals.jsx:290
-msgid "Choose a cerificate from the server's certificate directory"
+msgid "Choose a certificate from the server's certificate directory"
msgstr "サーバの証明書ディレクトリから証明書を選択してください"
#: src/lib/security/securityModals.jsx:294
-msgid "Choose Cerificate From Server"
+msgid "Choose Certificate From Server"
msgstr "サーバから証明書を選択"
#: src/lib/security/securityModals.jsx:313
@@ -12004,7 +12004,7 @@ msgid "Loading Certificates ..."
msgstr "証明書を読み込んでいます..."
#: src/lib/security/certificateManagement.jsx:1245
-msgid "Trusted Certificate Authorites"
+msgid "Trusted Certificate Authorities"
msgstr "信頼された認証局"
#: src/lib/security/certificateManagement.jsx:1261
@@ -12075,7 +12075,7 @@ msgstr "警告 - CA証明書のプロパティを変更しています"
#: src/lib/security/certificateManagement.jsx:1466
msgid ""
-"Removing the 'C' or 'T' flags from the SSL trust catagory could break all "
+"Removing the 'C' or 'T' flags from the SSL trust category could break all "
"TLS connectivity to and from the server, are you sure you want to proceed?"
msgstr ""
"SSL信頼カテゴリから 'C' または 'T' フラグを削除すると、サーバとのすべてのTLS"
@@ -14119,7 +14119,7 @@ msgstr "サフィックスDN"
#: src/database.jsx:1431
msgid ""
"Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP "
-"Distiguished Name (DN)."
+"Distinguished Name (DN)."
msgstr ""
"'dc=example,dc=com'のようなデータベースサフィックスを設定します。サフィックス"
"には有効なLDAP識別名(DN)を設定してください。"
@@ -14406,7 +14406,7 @@ msgstr "データベースを作成する"
#: src/dsModals.jsx:559
msgid ""
"Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP "
-"Distiguished Name (DN)"
+"Distinguished Name (DN)"
msgstr ""
"'dc=example,dc=com'のようなデータベースサフィックスを設定します。サフィックス"
"には有効なLDAP識別名(DN)を設定してください。"
@@ -14807,7 +14807,7 @@ msgid "ObjectClass Name is required."
msgstr "オブジェクトクラス名は必須です。"
#: src/schema.jsx:764
-msgid "ObjectClass $0 - $1 operation was successfull"
+msgid "ObjectClass $0 - $1 operation was successful"
msgstr "オブジェクトクラス $0 の $1 を行いました"
#: src/schema.jsx:779
@@ -14823,7 +14823,7 @@ msgid "Error during Attribute removal operation - $0"
msgstr "属性の削除に失敗しました - $0"
#: src/schema.jsx:1081
-msgid "Attribute $0 - add operation was successfull"
+msgid "Attribute $0 - add operation was successful"
msgstr "属性 $0 を追加しました"
#: src/schema.jsx:1096
@@ -14831,7 +14831,7 @@ msgid "Error during the Attribute add operation - $0"
msgstr "属性の追加に失敗しました - $0"
#: src/schema.jsx:1192
-msgid "Attribute $0 - replace operation was successfull"
+msgid "Attribute $0 - replace operation was successful"
msgstr "属性 $0 を置き換えました"
#: src/schema.jsx:1207
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
index 509f7105c..c0c4be414 100644
--- a/src/cockpit/389-console/src/database.jsx
+++ b/src/cockpit/389-console/src/database.jsx
@@ -1487,7 +1487,7 @@ class CreateSuffixModal extends React.Component {
<FormGroup
label={_("Suffix DN")}
fieldId="createSuffix"
- title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN).")}
+ title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distinguished Name (DN).")}
>
<TextInput
isRequired
diff --git a/src/cockpit/389-console/src/dsModals.jsx b/src/cockpit/389-console/src/dsModals.jsx
index bf19ce144..367cf4759 100644
--- a/src/cockpit/389-console/src/dsModals.jsx
+++ b/src/cockpit/389-console/src/dsModals.jsx
@@ -324,7 +324,7 @@ export class CreateInstanceModal extends React.Component {
})
.done(() => {
// Success!!! Now set Root DN pw, and cleanup everything up...
- log_cmd("handleCreateInstance", "Instance creation compelete, remove INF file...", rm_cmd);
+ log_cmd("handleCreateInstance", "Instance creation complete, remove INF file...", rm_cmd);
cockpit.spawn(rm_cmd, { superuser: true });
const dm_pw_cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + newServerId + '.socket',
@@ -585,7 +585,7 @@ export class CreateInstanceModal extends React.Component {
/>
</Grid>
<div className={createDBCheckbox ? "" : "ds-hidden"}>
- <Grid title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)")}>
+ <Grid title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distinguished Name (DN)")}>
<GridItem className="ds-label" offset={1} span={3}>
{_("Database Suffix")}
</GridItem>
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
index 52a2cf2df..4c7fce706 100644
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
@@ -1692,7 +1692,7 @@ export class GlobalDatabaseConfigMDB extends React.Component {
</GridItem>
</Grid>
<Grid
- title={_("The maximun number of read transactions that can be opened simultaneously. A value of 0 means this value is computed by the server (nsslapd-mdb-max-readers).")}
+ title={_("The maximum number of read transactions that can be opened simultaneously. A value of 0 means this value is computed by the server (nsslapd-mdb-max-readers).")}
className="ds-margin-top-xlg"
>
<GridItem className="ds-label" span={4}>
diff --git a/src/cockpit/389-console/src/lib/database/databaseModal.jsx b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
index 8d668494e..2edf42b4b 100644
--- a/src/cockpit/389-console/src/lib/database/databaseModal.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
@@ -284,7 +284,7 @@ class CreateSubSuffixModal extends React.Component {
]}
>
<Form isHorizontal autoComplete="off">
- <Grid className="ds-margin-top" title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)")}>
+ <Grid className="ds-margin-top" title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distinguished Name (DN)")}>
<GridItem className="ds-label" span={3}>
{_("Sub-Suffix DN")}
</GridItem>
diff --git a/src/cockpit/389-console/src/lib/database/globalPwp.jsx b/src/cockpit/389-console/src/lib/database/globalPwp.jsx
index 67a0358ad..17a34bbf4 100644
--- a/src/cockpit/389-console/src/lib/database/globalPwp.jsx
+++ b/src/cockpit/389-console/src/lib/database/globalPwp.jsx
@@ -1334,7 +1334,7 @@ export class GlobalPwPolicy extends React.Component {
if (this.state.passwordexp) {
pwExpirationRows = (
<div className="ds-margin-left">
- <Grid className="ds-margin-top" title={_("The maxiumum age of a password in seconds before it expires (passwordMaxAge).")}>
+ <Grid className="ds-margin-top" title={_("The maximum age of a password in seconds before it expires (passwordMaxAge).")}>
<GridItem className="ds-label" span={5}>
{_("Password Expiration Time")}
</GridItem>
diff --git a/src/cockpit/389-console/src/lib/database/indexes.jsx b/src/cockpit/389-console/src/lib/database/indexes.jsx
index 9af43722d..0d3492c22 100644
--- a/src/cockpit/389-console/src/lib/database/indexes.jsx
+++ b/src/cockpit/389-console/src/lib/database/indexes.jsx
@@ -872,7 +872,7 @@ class AddIndexModal extends React.Component {
onChange={(e, checked) => {
handleChange(e);
}}
- label={_("Equailty Indexing")}
+ label={_("Equality Indexing")}
/>
</GridItem>
</Grid>
@@ -1013,7 +1013,7 @@ class EditIndexModal extends React.Component {
onChange={(e, checked) => {
handleChange(e);
}}
- label={_("Equailty Indexing")}
+ label={_("Equality Indexing")}
/>
</div>
);
diff --git a/src/cockpit/389-console/src/lib/database/localPwp.jsx b/src/cockpit/389-console/src/lib/database/localPwp.jsx
index 8586ba932..cb84be906 100644
--- a/src/cockpit/389-console/src/lib/database/localPwp.jsx
+++ b/src/cockpit/389-console/src/lib/database/localPwp.jsx
@@ -344,7 +344,7 @@ class CreatePolicy extends React.Component {
</Grid>
<div className="ds-left-indent">
<Grid
- title={_("The maxiumum age of a password in seconds before it expires (passwordMaxAge).")}
+ title={_("The maximum age of a password in seconds before it expires (passwordMaxAge).")}
className="ds-margin-top"
>
<GridItem className="ds-label" span={4}>
@@ -2743,7 +2743,7 @@ export class LocalPwPolicy extends React.Component {
if (this.state.passwordexp) {
pwExpirationRows = (
<div className="ds-margin-left">
- <Grid className="ds-margin-top" title={_("The maxiumum age of a password in seconds before it expires (passwordMaxAge).")}>
+ <Grid className="ds-margin-top" title={_("The maximum age of a password in seconds before it expires (passwordMaxAge).")}>
<GridItem className="ds-label" span={5}>
{_("Password Expiration Time")}
</GridItem>
diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx
index 1ea66e809..eda2fe73e 100644
--- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx
+++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx
@@ -1498,7 +1498,7 @@ class AddNewAci extends React.Component {
<FormSelectOption key="roledn" label={_("Role DN (roledn)")} value="roledn" title={_("Bind rules for Roles")} />
</>}
{!this.state.haveUserRules && !this.state.haveUserAttrRules &&
- <FormSelectOption key="special" label={_("User DN Aliases (userdn)")} value="User DN Aliases" title={_("Special bind rules for user DN catagories")} />}
+ <FormSelectOption key="special" label={_("User DN Aliases (userdn)")} value="User DN Aliases" title={_("Special bind rules for user DN categories")} />}
{!this.state.haveUserRules && !this.state.haveUserAttrRules &&
<FormSelectOption key="userattr" label={_("User Attribute (userattr)")} value="userattr" title={_("Bind rule to specify which attribute must match between the entry used to bind to the directory and the targeted entry")} />}
<FormSelectOption key="authmethod" label={_("Authentication Method (authmethod)")} value="authmethod" title={_("Specify the authentication methods to restrict")} />
diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx
index 961f5030e..a08a4a468 100644
--- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx
+++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx
@@ -999,7 +999,7 @@ class AddCosDefinition extends React.Component {
</Modal>
<Modal
variant={ModalVariant.small}
- title={_("Leaving CoS Definiton Creation Wizard")}
+ title={_("Leaving CoS Definition Creation Wizard")}
isOpen={this.state.isConfirmModalOpen}
onClose={this.handleConfirmModalToggle}
actions={[
@@ -1011,7 +1011,7 @@ class AddCosDefinition extends React.Component {
</Button>
]}
>
- {_("You are about to leave CoS Definiton creation wizard. After you click 'Confirm', you'll appear in CoS Template creation wizard and you won't able to return from there until the process is finished. Then you'll be able to use the created entry in the CoS definiton creation. It'll be preselected for you automatically.")}
+ {_("You are about to leave CoS Definition creation wizard. After you click 'Confirm', you'll appear in CoS Template creation wizard and you won't able to return from there until the process is finished. Then you'll be able to use the created entry in the CoS definition creation. It'll be preselected for you automatically.")}
</Modal>
<Modal
variant={ModalVariant.medium}
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index 0e32a2193..7a65a9872 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -630,7 +630,7 @@ class ReportConnectionModal extends React.Component {
/>
</GridItem>
</Grid>
- <Grid title={_("Bind password for the specified instance. You can also speciy a password file but the filename needs to be inside of brackets [/PATH/FILE]")}>
+ <Grid title={_("Bind password for the specified instance. You can also specify a password file but the filename needs to be inside of brackets [/PATH/FILE]")}>
<GridItem className="ds-label" span={3}>
{_("Password")}
</GridItem>
diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
index c6d338b61..8fe4fe1d8 100644
--- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
@@ -1503,7 +1503,7 @@ export class ReplMonitor extends React.Component {
}
let overwriteWarning = (
- _("Only one monitor configuraton can be saved in the server's '~/.dsrc' file. There is already an existing monitor configuration, and if you proceed it will be completely overwritten with the new configuraton."));
+ _("Only one monitor configuration can be saved in the server's '~/.dsrc' file. There is already an existing monitor configuration, and if you proceed it will be completely overwritten with the new configuration."));
if (this.state.credRows.length === 0 && this.state.aliasRows.length === 0) {
overwriteWarning = (
_("This will save the current credentials and aliases to the server's '~/.dsrc' file so it can be reused in the future."));
diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
index 704d6d0b1..00a334621 100644
--- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
@@ -648,7 +648,7 @@ class MemberOf extends React.Component {
.done(content => {
this.props.addNotification(
"success",
- cockpit.format(_("Fixup task for $0 was successfull"), this.state.fixupDN)
+ cockpit.format(_("Fixup task for $0 was successful"), this.state.fixupDN)
);
this.props.toggleLoadingHandler();
this.setState({
diff --git a/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx b/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx
index 9908694a9..288b4b533 100644
--- a/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx
@@ -285,7 +285,7 @@ class PAMPassthroughAuthentication extends React.Component {
"list",
];
this.props.toggleLoadingHandler();
- log_cmd("loadPAMConfigs", "Get PAM Passthough Authentication Plugin Configs", cmd);
+ log_cmd("loadPAMConfigs", "Get PAM Passthrough Authentication Plugin Configs", cmd);
cockpit
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
@@ -348,7 +348,7 @@ class PAMPassthroughAuthentication extends React.Component {
this.props.toggleLoadingHandler();
log_cmd(
"openModal",
- "Fetch the PAM Passthough Authentication Plugin pamConfig entry",
+ "Fetch the PAM Passthrough Authentication Plugin pamConfig entry",
cmd
);
cockpit
@@ -485,7 +485,7 @@ class PAMPassthroughAuthentication extends React.Component {
this.props.toggleLoadingHandler();
log_cmd(
"deletePAMConfig",
- "Delete the PAM Passthough Authentication Plugin pamConfig entry",
+ "Delete the PAM Passthrough Authentication Plugin pamConfig entry",
cmd
);
cockpit
@@ -595,7 +595,7 @@ class PAMPassthroughAuthentication extends React.Component {
});
log_cmd(
"pamPassthroughAuthOperation",
- `Do the ${action} operation on the PAM Passthough Authentication Plugin`,
+ `Do the ${action} operation on the PAM Passthrough Authentication Plugin`,
cmd
);
cockpit
@@ -649,7 +649,7 @@ class PAMPassthroughAuthentication extends React.Component {
extraPrimaryProps.spinnerAriaValueText = _("Saving");
}
- const title = cockpit.format(_("$0 PAM Passthough Auth Config Entry"), (newPAMConfigEntry ? _("Add") : _("Edit")));
+ const title = cockpit.format(_("$0 PAM Passthrough Auth Config Entry"), (newPAMConfigEntry ? _("Add") : _("Edit")));
return (
<div className={savingPAM ? "ds-disabled" : ""}>
diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
index 530039c1e..28e8c9ad0 100644
--- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
@@ -210,7 +210,7 @@ class PassthroughAuthentication extends React.Component {
"list"
];
this.props.toggleLoadingHandler();
- log_cmd("loadURLs", "Get Passthough Authentication Plugin Configs", cmd);
+ log_cmd("loadURLs", "Get Passthrough Authentication Plugin Configs", cmd);
cockpit
.spawn(cmd, { superuser: true, err: "message" })
.done(content => {
@@ -299,7 +299,7 @@ class PassthroughAuthentication extends React.Component {
modalSpinning: true
});
- log_cmd("deleteURL", "Delete the Passthough Authentication Plugin URL entry", cmd);
+ log_cmd("deleteURL", "Delete the Passthrough Authentication Plugin URL entry", cmd);
cockpit
.spawn(cmd, {
superuser: true,
@@ -367,7 +367,7 @@ class PassthroughAuthentication extends React.Component {
});
log_cmd(
"PassthroughAuthOperation",
- `Do the ${action} operation on the Passthough Authentication Plugin`,
+ `Do the ${action} operation on the Passthrough Authentication Plugin`,
cmd
);
cockpit
diff --git a/src/cockpit/389-console/src/lib/plugins/usn.jsx b/src/cockpit/389-console/src/lib/plugins/usn.jsx
index b21bf126c..a7ade0675 100644
--- a/src/cockpit/389-console/src/lib/plugins/usn.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/usn.jsx
@@ -218,7 +218,7 @@ class USNPlugin extends React.Component {
.done(content => {
this.props.addNotification(
"success",
- _("Cleanup USN Tombstones task was successfull")
+ _("Cleanup USN Tombstones task was successful")
);
this.props.toggleLoadingHandler();
this.setState({
diff --git a/src/cockpit/389-console/src/lib/replication/replConfig.jsx b/src/cockpit/389-console/src/lib/replication/replConfig.jsx
index cc96cf530..902be1485 100644
--- a/src/cockpit/389-console/src/lib/replication/replConfig.jsx
+++ b/src/cockpit/389-console/src/lib/replication/replConfig.jsx
@@ -660,7 +660,7 @@ export class ReplConfig extends React.Component {
</GridItem>
</Grid>
<Grid
- title={_("The interval to check for any changes in the group memebrship specified in the Bind DN Group and automatically rebuilds the list for the replication managers accordingly. (nsds5replicabinddngroupcheckinterval).")}
+ title={_("The interval to check for any changes in the group membership specified in the Bind DN Group and automatically rebuilds the list for the replication managers accordingly. (nsds5replicabinddngroupcheckinterval).")}
className="ds-margin-top"
>
<GridItem className="ds-label" span={3}>
diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx b/src/cockpit/389-console/src/lib/replication/replModals.jsx
index f6c623041..41ce1fbfb 100644
--- a/src/cockpit/389-console/src/lib/replication/replModals.jsx
+++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx
@@ -661,7 +661,7 @@ export class WinsyncAgmtModal extends React.Component {
<GridItem span={12}>
<TextContent>
<Text component={TextVariants.h5}>
- {_("By default replication updates are sent to the replica as soon as possible, but if there is a need for replication updates to only be sent on certains days and within certain windows of time then you can setup a custom replication schedule.")}
+ {_("By default replication updates are sent to the replica as soon as possible, but if there is a need for replication updates to only be sent on certain days and within certain windows of time then you can setup a custom replication schedule.")}
</Text>
</TextContent>
</GridItem>
@@ -1406,7 +1406,7 @@ export class ReplAgmtModal extends React.Component {
<GridItem span={12}>
<TextContent>
<Text component={TextVariants.h5}>
- {_("By default replication updates are sent to the replica as soon as possible, but if there is a need for replication updates to only be sent on certains days and within certain windows of time then you can setup a custom replication schedule.")}
+ {_("By default replication updates are sent to the replica as soon as possible, but if there is a need for replication updates to only be sent on certain days and within certain windows of time then you can setup a custom replication schedule.")}
</Text>
</TextContent>
</GridItem>
diff --git a/src/cockpit/389-console/src/lib/replication/replTasks.jsx b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
index 99646ae3b..f0c902198 100644
--- a/src/cockpit/389-console/src/lib/replication/replTasks.jsx
+++ b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
@@ -359,7 +359,7 @@ export class ReplRUV extends React.Component {
{_("Local RUV")}
<Button
variant="plain"
- aria-label={_("Refresh the RUV for this suffixs")}
+ aria-label={_("Refresh the RUV for this suffix")}
onClick={() => {
this.props.reload(this.props.suffix);
}}
@@ -374,7 +374,7 @@ export class ReplRUV extends React.Component {
{_("Remote RUV's")}
<Button
variant="plain"
- aria-label={_("Refresh the remote RUVs for this suffixs")}
+ aria-label={_("Refresh the remote RUVs for this suffix")}
onClick={() => {
this.props.reload(this.props.suffix);
}}
@@ -456,7 +456,7 @@ export class ReplRUV extends React.Component {
checked={this.state.modalChecked}
mTitle={_("Initialize Replication Changelog From LDIF")}
mMsg={_("Are you sure you want to attempt to initialize the changelog from LDIF? This will reject all operations during during the initialization.")}
- mSpinningMsg={_("Initialzing Replication Change Log ...")}
+ mSpinningMsg={_("Initializing Replication Change Log ...")}
mBtnName={_("Import Changelog LDIF")}
/>
<ExportCLModal
diff --git a/src/cockpit/389-console/src/lib/security/certificateManagement.jsx b/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
index edcfb301f..a24120225 100644
--- a/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
+++ b/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
@@ -523,7 +523,7 @@ export class CertificateManagement extends React.Component {
});
this.props.addNotification(
"error",
- cockpit.format(_("Faield to create temporary certificate file: $0"), err)
+ cockpit.format(_("Failed to create temporary certificate file: $0"), err)
);
});
} else {
@@ -1242,7 +1242,7 @@ export class CertificateManagement extends React.Component {
} else {
certificatePage = (
<Tabs isBox isSecondary className="ds-margin-top-xlg ds-left-indent" activeKey={this.state.activeTabKey} onSelect={this.handleNavSelect}>
- <Tab eventKey={0} title={<TabTitleText>{_("Trusted Certificate Authorites")} <font size="2">({this.state.CACerts.length})</font></TabTitleText>}>
+ <Tab eventKey={0} title={<TabTitleText>{_("Trusted Certificate Authorities")} <font size="2">({this.state.CACerts.length})</font></TabTitleText>}>
<div className="ds-margin-top-lg ds-left-indent">
<CertTable
certs={this.state.CACerts}
@@ -1297,7 +1297,7 @@ export class CertificateManagement extends React.Component {
this.showAddCSRModal();
}}
>
- {_("Create Certificate Sigining Request")}
+ {_("Create Certificate Signing Request")}
</Button>
</div>
</Tab>
@@ -1464,7 +1464,7 @@ export class CertificateManagement extends React.Component {
item={this.state.certName}
checked={this.state.modalChecked}
mTitle={_("Warning - Altering CA Certificate Properties")}
- mMsg={_("Removing the 'C' or 'T' flags from the SSL trust catagory could break all TLS connectivity to and from the server, are you sure you want to proceed?")}
+ mMsg={_("Removing the 'C' or 'T' flags from the SSL trust category could break all TLS connectivity to and from the server, are you sure you want to proceed?")}
mSpinningMsg={_("Editing CA Certificate ...")}
mBtnName={_("Change Trust Flags")}
/>
diff --git a/src/cockpit/389-console/src/lib/security/securityModals.jsx b/src/cockpit/389-console/src/lib/security/securityModals.jsx
index fcb9184c6..9f17e05c2 100644
--- a/src/cockpit/389-console/src/lib/security/securityModals.jsx
+++ b/src/cockpit/389-console/src/lib/security/securityModals.jsx
@@ -288,11 +288,11 @@ export class SecurityAddCertModal extends React.Component {
browseButtonText={_("Upload PEM File")}
/>
</div>
- <div title={_("Choose a cerificate from the server's certificate directory")}>
+ <div title={_("Choose a certificate from the server's certificate directory")}>
<Radio
id="certRadioSelect"
className="ds-margin-top-lg"
- label={_("Choose Cerificate From Server")}
+ label={_("Choose Certificate From Server")}
name="certChoice"
isChecked={certRadioSelect}
onChange={handleRadioChange}
diff --git a/src/cockpit/389-console/src/schema.jsx b/src/cockpit/389-console/src/schema.jsx
index 19854e785..8b7a6cda9 100644
--- a/src/cockpit/389-console/src/schema.jsx
+++ b/src/cockpit/389-console/src/schema.jsx
@@ -766,7 +766,7 @@ export class Schema extends React.Component {
console.info("cmdOperationObjectclass", "Result", content);
this.props.addNotification(
"success",
- cockpit.format(_("ObjectClass $0 - $1 operation was successfull"), ocName, action)
+ cockpit.format(_("ObjectClass $0 - $1 operation was successful"), ocName, action)
);
this.loadSchemaData();
this.closeObjectclassModal();
@@ -1083,7 +1083,7 @@ export class Schema extends React.Component {
console.info("cmdOperationAttribute", "Result", content);
this.props.addNotification(
"success",
- cockpit.format(_("Attribute $0 - add operation was successfull"), atName)
+ cockpit.format(_("Attribute $0 - add operation was successful"), atName)
);
this.loadSchemaData();
this.closeAttributeModal();
@@ -1194,7 +1194,7 @@ export class Schema extends React.Component {
console.info("cmdOperationAttribute", "Result", content);
this.props.addNotification(
"success",
- cockpit.format(_("Attribute $0 - replace operation was successfull"), atName)
+ cockpit.format(_("Attribute $0 - replace operation was successful"), atName)
);
this.loadSchemaData();
this.closeAttributeModal();
--
2.48.1

View File

@ -1,791 +0,0 @@
From 09daeb90f13bd76004e377e27295606b0bc5f579 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)
---
.../tests/suites/indexes/regression_test.py | 29 +++
.../tests/suites/vlv/regression_test.py | 184 +++++++++++++++++-
ldap/servers/slapd/back-ldbm/cleanup.c | 8 +
.../back-ldbm/db-mdb/mdb_import_threads.c | 34 +++-
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 1 +
.../slapd/back-ldbm/db-mdb/mdb_layer.h | 1 +
ldap/servers/slapd/back-ldbm/db-mdb/mdb_txn.c | 12 +-
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 | 33 ++--
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 | 13 +-
src/lib389/lib389/backend.py | 10 +
17 files changed, 335 insertions(+), 60 deletions(-)
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
index b077b529a..08d78a899 100644
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
@@ -547,6 +547,35 @@ def test_task_and_be(topo, add_backend_and_ldif_50K_users):
assert user.get_attr_val_utf8_l('description') == descval
+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 873eada20..aca638f28 100644
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
@@ -138,7 +138,7 @@ def add_users(inst, users_num, suffix=DEFAULT_SUFFIX):
def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
- scope=ldap.SCOPE_SUBTREE, prefix="vlv"):
+ scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
vlv_searches = VLVSearch(inst)
vlv_search_properties = {
"objectclass": ["top", "vlvSearch"],
@@ -156,7 +156,7 @@ def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
vlv_index_properties = {
"objectclass": ["top", "vlvIndex"],
"cn": f"{prefix}Idx",
- "vlvsort": "cn",
+ "vlvsort": vlvsort,
}
vlv_index.create(
basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
@@ -266,6 +266,40 @@ def vlv_setup_with_two_backend(topology_st, request):
return topology_st
+@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.fixture
def freeipa(topology_st):
# generate a standalone instance with same vlv config than freeipa
@@ -939,6 +973,152 @@ def test_vlv_by_keyword(freeipa):
assert f'cn={idx},ou=certificateRepository,ou=ca,o=ipaca' in dns
+def get_timestamp_attr():
+ current_datetime = datetime.now()
+ return f'tsattr-{current_datetime.timestamp()}'.replace(".", "-")
+
+
+def perform_vlv_search(conn, alog, basedn, vlvcrit=True, ssscrit=True, scope=ldap.SCOPE_SUBTREE, sss='cn', filter='(uid=*)'):
+ timestamp = get_timestamp_attr()
+ vlv_control = VLVRequestControl(criticality=vlvcrit,
+ before_count=1,
+ after_count=1,
+ offset=4,
+ content_count=0,
+ greater_than_or_equal=None,
+ context_id=None)
+
+ sss_control = SSSRequestControl(criticality=ssscrit, ordering_rules=[sss])
+ log.info(f'perform_vlv_search: basedn={basedn} ={vlvcrit} ssscrit={ssscrit} sss={sss} filter={filter} timestamp={timestamp}')
+
+ with suppress(ldap.LDAPError):
+ result = conn.search_ext_s(
+ base=basedn,
+ scope=scope,
+ filterstr=filter,
+ attrlist=[timestamp],
+ serverctrls=[vlv_control, sss_control]
+ )
+ line = alog.match(f'.*SRCH.*{timestamp}.*')
+ log.info(f'perform_vlv_search: line={line}')
+ match = re.match(r'.*conn=(\d+) op=(\d+).*', line[0])
+ conn,op = match.group(1,2)
+ log.info(f'perform_vlv_search: conn={conn} op={op} ')
+ lines = ''.join(alog.match(f'.*conn={conn} op={op} .*', after_pattern=f'.*{timestamp}.*'))
+ log.info(f'perform_vlv_search: lines={lines}')
+ return lines
+
+
+def test_vlv_logs(vlv_setup_nested_backends):
+ """Test than VLV abd SORT lines are properply written
+
+ :id: a1d9ad9e-7cbb-11ef-82e3-083a88554478
+ :setup: Standalone instance with two backens and one level scoped vlv
+ :steps:
+ 1. Check that standard search returns 16 valid certificate
+ 2. Check that vlv search returns 16 valid certificate
+
+ :expectedresults:
+ 1. Should Success.
+ 2. Should Success.
+ """
+ inst = vlv_setup_nested_backends.standalone
+ beh = vlv_setup_nested_backends.beh
+ tasks = Tasks(inst)
+ conn = open_new_ldapi_conn(inst.serverid)
+ conn_demo = open_new_ldapi_conn(inst.serverid)
+ alog = DirsrvAccessLog(inst)
+ dn1 = beh.data['be1']['dn']
+ dn2 = beh.data['be2']['dn']
+ suffix1 = beh.data['be1']['suffix']
+ suffix2 = beh.data['be2']['suffix']
+ conn_demo.bind_s(dn2, DEMO_PW)
+
+ VLV_FEATURE_DN = 'oid=2.16.840.1.113730.3.4.9,cn=features,cn=config'
+ VLV_DEFAULT_ACI = b'(targetattr != "aci")(version 3.0; acl "VLV Request Control"; ' + \
+ b'allow( read , search, compare, proxy ) userdn = "ldap:///all";)'
+ VLV_DENY_ACI = b'(targetattr != "aci")(version 3.0; acl "VLV Request Control"; ' + \
+ f'deny( read , search, compare, proxy ) userdn = "ldap:///{dn2}";)'.encode('utf8')
+
+ # Set VLV feature ACI
+ mod = (ldap.MOD_REPLACE, 'aci', [ VLV_DEFAULT_ACI, VLV_DENY_ACI ])
+ conn.modify_s(VLV_FEATURE_DN, [mod,])
+
+ # Invalid ACL
+ res = perform_vlv_search(conn_demo, alog, suffix2)
+ assert 'SORT' in res
+ assert 'VLV' in res
+ assert 'err=50 ' in res
+
+ # Sucessful VLV
+ res = perform_vlv_search(conn, alog, suffix2)
+ assert 'SORT' in res
+ assert 'VLV' in res
+ assert 'err=0 ' in res
+
+ # Multiple backends SSS and VLV are critical
+ res = perform_vlv_search(conn, alog, suffix1)
+ assert 'SORT' in res
+ assert 'VLV' in res
+ assert 'err=76 ' in res
+
+ # Multiple backends SSS is critical VLV is not critical
+ res = perform_vlv_search(conn, alog, suffix1, vlvcrit=False)
+ assert 'SORT' in res
+ assert 'VLV' in res
+ assert 'err=12 ' in res
+
+ # Multiple backends SSS and VLV are not critical
+ res = perform_vlv_search(conn, alog, suffix1, vlvcrit=False, ssscrit=False)
+ assert 'SORT' in res
+ assert 'VLV' in res
+ assert 'err=0 ' in res
+
+
+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/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
index 716e3c0f3..1463069ad 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
@@ -23,6 +23,7 @@
*/
#include <stddef.h>
+#include <stdbool.h>
#include <assert.h>
#include "mdb_import.h"
#include "../vlv_srch.h"
@@ -152,6 +153,9 @@ static void dbmdb_import_writeq_push(ImportCtx_t *ctx, WriterQueueData_t *wqd);
static int have_workers_finished(ImportJob *job);
struct backentry *dbmdb_import_prepare_worker_entry(WorkerQueueData_t *wqelmnt);
+/* Mutex needed for extended matching rules */
+static pthread_mutex_t extended_mr_mutex = PTHREAD_MUTEX_INITIALIZER;
+
/***************************************************************************/
/**************************** utility functions ****************************/
/***************************************************************************/
@@ -3192,6 +3196,23 @@ is_reindexed_attr(const char *attrname, const ImportCtx_t *ctx, char **list)
return (list && attr_in_list(attrname, list));
}
+/*
+ * Determine if vlv require extended matching rule evaluation
+ */
+static bool
+vlv_has_emr(struct vlvIndex *p)
+{
+ if (p->vlv_sortkey != NULL) {
+ /* Foreach sorted attribute... */
+ for (int sortattr = 0; p->vlv_sortkey[sortattr] != NULL; sortattr++) {
+ if (p->vlv_sortkey[sortattr]->sk_matchruleoid != NULL) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
static void
process_vlv_index(backentry *ep, ImportWorkerInfo *info)
{
@@ -3214,7 +3235,18 @@ process_vlv_index(backentry *ep, ImportWorkerInfo *info)
slapi_pblock_set(pb, SLAPI_BACKEND, be);
if (vlv_index && vlv_index->vlv_attrinfo &&
is_reindexed_attr(vlv_index->vlv_attrinfo->ai_type , ctx, ctx->indexVlvs)) {
- ret = vlv_update_index(vlv_index, (dbi_txn_t*)&txn, inst->inst_li, pb, NULL, ep);
+ if (vlv_has_emr(vlv_index)) {
+ /*
+ * Serialize if there is an extended matching rule
+ * Because matchrule_values_to_keys is not thread safe when indexing
+ * because new mr_indexer are created) but that need to be double checked)
+ */
+ pthread_mutex_lock(&extended_mr_mutex);
+ ret = vlv_update_index(vlv_index, (dbi_txn_t*)&txn, inst->inst_li, pb, NULL, ep);
+ pthread_mutex_unlock(&extended_mr_mutex);
+ } else {
+ ret = vlv_update_index(vlv_index, (dbi_txn_t*)&txn, inst->inst_li, pb, NULL, ep);
+ }
}
if (0 != ret) {
/* Something went wrong, eg disk filled up */
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 35f8173a7..40cc29c89 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
@@ -346,6 +346,7 @@ dbmdb_close(struct ldbminfo *li, int dbmode)
}
return_value |= dbmdb_post_close(li, dbmode);
+ shutdown_mdbtxn();
return return_value;
}
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
index 96e02bf74..fe230d60e 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
@@ -506,6 +506,7 @@ void dbmdb_free_stats(dbmdb_stats_t **stats);
int dbmdb_reset_vlv_file(backend *be, const char *filename);
/* mdb_txn.c */
+void shutdown_mdbtxn(void);
int dbmdb_start_txn(const char *funcname, dbi_txn_t *parent_txn, int flags, dbi_txn_t **txn);
int dbmdb_end_txn(const char *funcname, int rc, dbi_txn_t **txn);
void init_mdbtxn(dbmdb_ctx_t *ctx);
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_txn.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_txn.c
index 21c53dd8c..74088db89 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_txn.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_txn.c
@@ -41,8 +41,10 @@ cleanup_mdbtxn_stack(void *arg)
dbmdb_txn_t *txn2;
*anchor = NULL;
+ if (anchor == (dbmdb_txn_t **) PR_GetThreadPrivate(thread_private_mdb_txn_stack)) {
+ PR_SetThreadPrivate(thread_private_mdb_txn_stack, NULL);
+ }
slapi_ch_free((void**)&anchor);
- PR_SetThreadPrivate(thread_private_mdb_txn_stack, NULL);
while (txn) {
txn2 = txn->parent;
TXN_ABORT(TXN(txn));
@@ -68,6 +70,14 @@ static dbmdb_txn_t **get_mdbtxnanchor(void)
return anchor;
}
+void shutdown_mdbtxn(void)
+{
+ dbmdb_txn_t **anchor = (dbmdb_txn_t **) PR_GetThreadPrivate(thread_private_mdb_txn_stack);
+ if (anchor) {
+ PR_SetThreadPrivate(thread_private_mdb_txn_stack, NULL);
+ }
+}
+
static void push_mdbtxn(dbmdb_txn_t *txn)
{
dbmdb_txn_t **anchor = get_mdbtxnanchor();
diff --git a/ldap/servers/slapd/back-ldbm/dblayer.c b/ldap/servers/slapd/back-ldbm/dblayer.c
index 30cd0c76a..46421ef3b 100644
--- a/ldap/servers/slapd/back-ldbm/dblayer.c
+++ b/ldap/servers/slapd/back-ldbm/dblayer.c
@@ -454,8 +454,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 dbi_env_t.
@@ -627,6 +631,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);
@@ -1374,6 +1381,17 @@ 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);
+}
+
const char *
dblayer_get_db_suffix(Slapi_Backend *be)
{
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attr.c b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
index 07f3058a3..30bfd1349 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 a1e57c172..5c26d44a2 100644
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
@@ -88,6 +88,7 @@ int dblayer_erase_index_file(backend *be, struct attrinfo *a, PRBool use_lock, i
int dblayer_get_id2entry(backend *be, dbi_db_t **ppDB);
int dblayer_get_changelog(backend *be, dbi_db_t ** ppDB, int create);
int dblayer_release_id2entry(backend *be, dbi_db_t *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);
@@ -541,7 +542,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 65af5dcf9..f6a77b39a 100644
--- a/ldap/servers/slapd/back-ldbm/sort.c
+++ b/ldap/servers/slapd/back-ldbm/sort.c
@@ -528,30 +528,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;
+ Slapi_Value **va_a = valueset_get_valuearray(&attr_a->a_present_values);
+ Slapi_Value **va_b = valueset_get_valuearray(&attr_b->a_present_values);
- 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);
+ 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 ((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;
+ 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) {
@@ -574,9 +562,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 2a7747dfa..4e40d1a41 100644
--- a/ldap/servers/slapd/back-ldbm/vlv.c
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
@@ -705,7 +705,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)
@@ -759,10 +759,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);
}
}
@@ -882,6 +880,13 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
}
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 {
@@ -1073,11 +1078,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);
}
@@ -1726,11 +1731,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 6b55fa7fd..5978af48f 100644
--- a/ldap/servers/slapd/back-ldbm/vlv_srch.c
+++ b/ldap/servers/slapd/back-ldbm/vlv_srch.c
@@ -190,6 +190,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));
@@ -204,7 +207,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 aeba95dc7..b262820c5 100644
--- a/ldap/servers/slapd/plugin_mr.c
+++ b/ldap/servers/slapd/plugin_mr.c
@@ -392,29 +392,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 */
- /* coverity[var_deref_model] */
- 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 0ed00a4a7..1319fa0cd 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -1105,6 +1105,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

View File

@ -0,0 +1,38 @@
From 6e4c0d5e3fef215a6ae52662222551c3e8e4f78a Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Fri, 4 Apr 2025 09:09:34 +0000
Subject: [PATCH] Issue 6481 - When ports that are in use are used to update a
DS instance the error message is not helpful (#6723)
Bug description: The initial fix for this issue used sudo lsof to determineif
a server port was in use. lsof is not installed by default and using sudo can
ask for a password.
Fix description: Use ss -ntplu (which is installed by default) to detect if a
port is taken.
Fixes: https://github.com/389ds/389-ds-base/issues/6481
Fix Author: Viktor Ashirov <vashirov@redhat.com>
Reviewed by: @progier389 (Thank you)
---
src/cockpit/389-console/src/lib/tools.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx
index ba43bdd6c..384ed7862 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -221,7 +221,7 @@ export function is_port_in_use(port) {
return;
}
- let cmd = ['bash', '-c', `sudo lsof -i :${port} || echo "free"`];
+ let cmd = ['bash', '-c', `ss -ntplu | grep -w :${port} || echo "free"`];
log_cmd("is_port_in_use", cmd);
cockpit
--
2.48.1

View File

@ -1,52 +0,0 @@
From 6286aee26537f4e1e7dd42af538c75ccd7cf9af8 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 8b5938350..fcc6ebfb4 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_str = 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

View File

@ -1,219 +0,0 @@
From 1b48c4174857db5c7c1dc1aa6ad3d0a5155c72c5 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 21 Nov 2024 16:56:45 -0500
Subject: [PATCH] Issue 6427 - fix various memory leaks
Description:
This was all on main branch
- Display attributes were leaked on searches
- Work thread indexes were leaked at shutdown
- Accept thread was leaked at shutdown
- When we reused a search pblock, we did not free the "operation" and "search
attributes" before overwriting them with freshly allocated structures.
Fixes: https://github.com/389ds/389-ds-base/issues/6427
Reviewed by: progier (Thanks!)
---
ldap/servers/slapd/connection.c | 30 ++++++++++++++-----------
ldap/servers/slapd/daemon.c | 7 ++++--
ldap/servers/slapd/plugin_internal_op.c | 18 +++++++++++++++
ldap/servers/slapd/proto-slap.h | 1 +
ldap/servers/slapd/result.c | 5 ++---
5 files changed, 43 insertions(+), 18 deletions(-)
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
index 07d629475..bb4fcd77f 100644
--- a/ldap/servers/slapd/connection.c
+++ b/ldap/servers/slapd/connection.c
@@ -40,20 +40,22 @@ static void log_ber_too_big_error(const Connection *conn,
static PRStack *op_stack; /* stack of Slapi_Operation * objects so we don't have to malloc/free every time */
static PRInt32 op_stack_size; /* size of op_stack */
-
struct Slapi_op_stack
{
PRStackElem stackelem; /* must be first in struct for PRStack to work */
Slapi_Operation *op;
};
-static void add_work_q(work_q_item *, struct Slapi_op_stack *);
-static work_q_item *get_work_q(struct Slapi_op_stack **);
+/* worker threads */
+static int32_t max_threads = 0;
+static int32_t *threads_indexes = NULL;
/*
* We maintain a global work queue of items that have not yet
* been handed off to an operation thread.
*/
+static void add_work_q(work_q_item *, struct Slapi_op_stack *);
+static work_q_item *get_work_q(struct Slapi_op_stack **);
struct Slapi_work_q
{
PRStackElem stackelem; /* must be first in struct for PRStack to work */
@@ -430,9 +432,7 @@ void
init_op_threads()
{
pthread_condattr_t condAttr;
- int32_t max_threads = config_get_threadnumber();
int32_t rc;
- int32_t *threads_indexes;
/* Initialize the locks and cv */
if ((rc = pthread_mutex_init(&work_q_lock, NULL)) != 0) {
@@ -463,13 +463,13 @@ init_op_threads()
op_stack = PR_CreateStack("connection_operation");
alloc_per_thread_snmp_vars(max_threads);
init_thread_private_snmp_vars();
-
+ max_threads = config_get_threadnumber();
threads_indexes = (int32_t *) slapi_ch_calloc(max_threads, sizeof(int32_t));
for (size_t i = 0; i < max_threads; i++) {
threads_indexes[i] = i + 1; /* idx 0 is reserved for global snmp_vars */
}
-
+
/* start the operation threads */
for (size_t i = 0; i < max_threads; i++) {
PR_SetConcurrency(4);
@@ -486,10 +486,14 @@ init_op_threads()
g_incr_active_threadcnt();
}
}
- /* Here we should free thread_indexes, but because of the dynamic of the new
- * threads (connection_threadmain) we are not sure when it can be freed.
- * Let's accept that unique initialization leak (typically 32 to 64 bytes)
- */
+ /* We will free threads_indexes at the very end of slapd_daemon() */
+}
+
+/* Called at shutdown to silence ASAN and friends */
+void
+free_worker_thread_indexes()
+{
+ slapi_ch_free((void **)&threads_indexes);
}
static void
@@ -1227,7 +1231,7 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
/* Process HAProxy header */
if (conn->c_haproxyheader_read == 0) {
conn->c_haproxyheader_read = 1;
- /*
+ /*
* We only check for HAProxy header if nsslapd-haproxy-trusted-ip is configured.
* If it is we proceed with the connection only if it's comming from trusted
* proxy server with correct and complete header.
@@ -1253,7 +1257,7 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
/* Now, reset RC and set it to 0 only if a match is found */
haproxy_rc = -1;
- /*
+ /*
* 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,
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
index 1a20796b9..c09e54c7f 100644
--- a/ldap/servers/slapd/daemon.c
+++ b/ldap/servers/slapd/daemon.c
@@ -1323,8 +1323,11 @@ slapd_daemon(daemon_ports_t *ports)
slapi_log_err(SLAPI_LOG_ERR, "slapd_daemon", "Failed to remove pid file %s\n", get_pid_file());
}
}
-}
+ /* final cleanup for ASAN and other analyzers */
+ PR_JoinThread(accept_thread_p);
+ free_worker_thread_indexes();
+}
void
ct_thread_cleanup(void)
@@ -1654,7 +1657,7 @@ handle_pr_read_ready(Connection_Table *ct, int list_num, PRIntn num_poll __attri
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/plugin_internal_op.c b/ldap/servers/slapd/plugin_internal_op.c
index 0d553d1ba..0164a70be 100644
--- a/ldap/servers/slapd/plugin_internal_op.c
+++ b/ldap/servers/slapd/plugin_internal_op.c
@@ -249,6 +249,12 @@ slapi_search_internal_set_pb(Slapi_PBlock *pb, const char *base, int scope, cons
return;
}
+ /* Free op just in case this pb is being reused */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ operation_free(&op, NULL);
+ slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &tmp_attrs);
+ slapi_ch_array_free(tmp_attrs);
+
op = internal_operation_new(SLAPI_OPERATION_SEARCH, operation_flags);
slapi_pblock_set(pb, SLAPI_OPERATION, op);
slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET_DN, (void *)base);
@@ -276,6 +282,12 @@ slapi_search_internal_set_pb_ext(Slapi_PBlock *pb, Slapi_DN *sdn, int scope, con
return;
}
+ /* Free op/attrs just in case this pb is being reused */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ operation_free(&op, NULL);
+ slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &tmp_attrs);
+ slapi_ch_array_free(tmp_attrs);
+
op = internal_operation_new(SLAPI_OPERATION_SEARCH, operation_flags);
slapi_pblock_set(pb, SLAPI_OPERATION, op);
slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET_DN,
@@ -305,6 +317,12 @@ slapi_seq_internal_set_pb(Slapi_PBlock *pb, char *base, int type, char *attrname
return;
}
+ /* Free op/attrs just in case this pb is being reused */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ operation_free(&op, NULL);
+ slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &tmp_attrs);
+ slapi_ch_array_free(tmp_attrs);
+
op = internal_operation_new(SLAPI_OPERATION_SEARCH, operation_flags);
slapi_pblock_set(pb, SLAPI_OPERATION, op);
slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET_DN, (void *)base);
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 214f2e609..2031cfb7d 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -1543,6 +1543,7 @@ int connection_is_free(Connection *conn, int user_lock);
int connection_is_active_nolock(Connection *conn);
ber_slen_t openldap_read_function(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len);
int32_t connection_has_psearch(Connection *c);
+void free_worker_thread_indexes(void);
/*
* saslbind.c
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
index 97af5a2b8..9772e8213 100644
--- a/ldap/servers/slapd/result.c
+++ b/ldap/servers/slapd/result.c
@@ -1392,9 +1392,8 @@ exit:
if (NULL != typelist) {
slapi_vattr_attrs_free(&typelist, typelist_flags);
}
- if (NULL != default_attrs) {
- slapi_ch_free((void **)&default_attrs);
- }
+ slapi_ch_array_free(default_attrs);
+
return rc;
}
--
2.48.1

View File

@ -1,4 +1,4 @@
From ea415990f55326ecc7595fbbcf3a63c43c1cb8fd Mon Sep 17 00:00:00 2001
From 3ea3e8697de1602c2ecaa58b4cbad69fe775af35 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Wed, 19 Feb 2025 18:56:34 -0800
Subject: [PATCH] Issue 6553 - Update concread to 0.5.4 and refactor statistics
@ -14,9 +14,10 @@ Fixes: https://github.com/389ds/389-ds-base/issues/6553
Reviewed by: @firstyear
---
ldap/servers/slapd/dn.c | 4 +-
src/Cargo.lock | 457 ++++++++++++++++++-------------------
src/librslapd/Cargo.toml | 3 +-
src/librslapd/src/cache.rs | 331 +++++++++++++++++++++++---
4 files changed, 533 insertions(+), 276 deletions(-)
src/librslapd/src/cache.rs | 331 ++++++++++++++++++++++++---
4 files changed, 526 insertions(+), 269 deletions(-)
diff --git a/ldap/servers/slapd/dn.c b/ldap/servers/slapd/dn.c
index 093019e28..5fbe67d07 100644

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
From 91f5045aa6b0221d1aea82b282cf1d8849dae6f8 Mon Sep 17 00:00:00 2001
From e5be53625de462b41f55231aa5ff8e1d24f934c7 Mon Sep 17 00:00:00 2001
From: Pierre Rogier <progier@redhat.com>
Date: Thu, 27 Feb 2025 16:36:48 +0100
Subject: [PATCH] Security fix for CVE-2025-2487
@ -144,10 +144,10 @@ index 7bb56ef2c..907b4367a 100644
struct backentry *entry = NULL;
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
index ed0c126f8..8f7122d16 100644
index b7453697f..dec3a0c6d 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
@@ -433,6 +433,8 @@ ldbm_back_add(Slapi_PBlock *pb)
@@ -435,6 +435,8 @@ ldbm_back_add(Slapi_PBlock *pb)
slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_back_add",
"find_entry2modify_only returned NULL parententry pdn: %s, uniqueid: %s\n",
slapi_sdn_get_dn(&parentsdn), addr.uniqueid ? addr.uniqueid : "none");
@ -157,7 +157,7 @@ index ed0c126f8..8f7122d16 100644
modify_init(&parent_modify_c, parententry);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
index 1b5c1fecd..350e40354 100644
index 29df2ce75..24c62a952 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
@@ -177,6 +177,12 @@ modify_update_all(backend *be, Slapi_PBlock *pb, modify_context *mc, back_txn *t
@ -174,10 +174,10 @@ index 1b5c1fecd..350e40354 100644
* Update the ID to Entry index.
* Note that id2entry_add replaces the entry, so the Entry ID stays the same.
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 659b7a33a..a373ad227 100644
index 76b8d49d7..e3b7e5783 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -495,8 +495,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
@@ -497,8 +497,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
slapi_pblock_get(pb, SLAPI_TARGET_ADDRESS, &old_addr);
e = find_entry2modify(pb, be, old_addr, &txn, &result_sent);
if (e == NULL) {
@ -188,7 +188,7 @@ index 659b7a33a..a373ad227 100644
}
if (slapi_entry_flag_is_set(e->ep_entry, SLAPI_ENTRY_FLAG_TOMBSTONE) &&
!is_resurect_operation) {
@@ -528,6 +528,11 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
@@ -530,6 +530,11 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
oldparent_addr.uniqueid = NULL;
}
parententry = find_entry2modify_only(pb, be, &oldparent_addr, &txn, &result_sent);
@ -200,7 +200,7 @@ index 659b7a33a..a373ad227 100644
modify_init(&parent_modify_context, parententry);
/* Fetch and lock the new parent of the entry that is moving */
@@ -538,6 +543,10 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
@@ -540,6 +545,10 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
}
newparententry = find_entry2modify_only(pb, be, newsuperior_addr, &txn, &result_sent);
slapi_ch_free_string(&newsuperior_addr->uniqueid);

View File

@ -1,37 +0,0 @@
From 984c653218cf386e449dc4db89b085a49b535f8d Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 18 Dec 2024 16:55:06 -0500
Subject: [PATCH] Issue 6442 - Fix latest covscan memory leaks (part 2)
Description:
Missed part of the fix becuase of a rebase issue
Fixes: https://github.com/389ds/389-ds-base/issues/6442
Reviewed by: progier & spichugi(Thanks!!)
---
ldap/servers/slapd/plugin_internal_op.c | 7 -------
1 file changed, 7 deletions(-)
diff --git a/ldap/servers/slapd/plugin_internal_op.c b/ldap/servers/slapd/plugin_internal_op.c
index 4f87ad3cf..96e705471 100644
--- a/ldap/servers/slapd/plugin_internal_op.c
+++ b/ldap/servers/slapd/plugin_internal_op.c
@@ -249,13 +249,6 @@ slapi_search_internal_set_pb(Slapi_PBlock *pb, const char *base, int scope, cons
return;
}
- slapi_pblock_get(pb, SLAPI_OPERATION, &op);
- operation_free(&op, NULL);
- slapi_pblock_set(pb, SLAPI_OPERATION, NULL);
- slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &tmp_attrs);
- slapi_ch_array_free(tmp_attrs);
- slapi_pblock_set(pb, SLAPI_SEARCH_ATTRS, NULL);
-
op = internal_operation_new(SLAPI_OPERATION_SEARCH, operation_flags);
slapi_pblock_set(pb, SLAPI_OPERATION, op);
slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET_DN, (void *)base);
--
2.48.1

View File

@ -1,410 +0,0 @@
From 2ee7095bc757d10e1a1056ac5957444a8bce8623 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 !!)
---
.../suites/replication/single_master_test.py | 128 ++++++++++++++++
ldap/servers/plugins/replication/repl5.h | 4 +
ldap/servers/plugins/replication/repl5_agmt.c | 138 +++++++++++++++++-
.../plugins/replication/repl5_agmtlist.c | 1 +
.../plugins/replication/repl_cleanallruv.c | 1 +
.../plugins/replication/repl_globals.c | 3 +
6 files changed, 272 insertions(+), 3 deletions(-)
diff --git a/dirsrvtests/tests/suites/replication/single_master_test.py b/dirsrvtests/tests/suites/replication/single_master_test.py
index 448cf305a..56e3e49d3 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
@@ -156,6 +157,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 2ba2cfaa7..6e5552f54 100644
--- a/ldap/servers/plugins/replication/repl5.h
+++ b/ldap/servers/plugins/replication/repl5.h
@@ -169,6 +169,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;
@@ -435,6 +438,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 fd5f23a77..6ffb074d4 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"
#include "../../slapd/back-ldbm/dbimpl.h" /* for dblayer_is_lmdb */
#define DEFAULT_TIMEOUT 120 /* (seconds) default outbound LDAP connection */
@@ -532,9 +533,32 @@ 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';
+ 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;
@@ -2527,6 +2551,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)
{
@@ -3146,6 +3277,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 0ebbc376a..9109f92a3 100644
--- a/ldap/servers/plugins/replication/repl5_agmtlist.c
+++ b/ldap/servers/plugins/replication/repl5_agmtlist.c
@@ -786,6 +786,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/repl_cleanallruv.c b/ldap/servers/plugins/replication/repl_cleanallruv.c
index 4052d98fd..d002f823d 100644
--- a/ldap/servers/plugins/replication/repl_cleanallruv.c
+++ b/ldap/servers/plugins/replication/repl_cleanallruv.c
@@ -2441,6 +2441,7 @@ clean_agmts(cleanruv_data *data)
"Cleaning agmt (%s) ...", agmt_get_long_name(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 24204a639..03547d9a7 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.48.1

View File

@ -119,9 +119,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.19"
version = "1.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
dependencies = [
"jobserver",
"libc",
@ -161,7 +161,8 @@ dependencies = [
[[package]]
name = "concread"
version = "0.5.5"
source = "git+https://github.com/389ds/concread?branch=unstable_name_collisions#caae357e1b24dc65fac7320760d4833debf9bc33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdefc169c45893a578093c2f90733e3c56b60e67b0a8670a16ade3437b2fe392"
dependencies = [
"ahash",
"arc-swap",
@ -391,9 +392,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "log"

View File

@ -1,27 +0,0 @@
diff --git a/.cargo/config.in b/.cargo/config.in
index d7d8ff4d4..d61993c54 100644
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -2,5 +2,10 @@
registry = "https://github.com/rust-lang/crates.io-index"
@rust_vendor_sources@
+[source."git+https://github.com/389ds/concread?branch=unstable_name_collisions"]
+git = "https://github.com/389ds/concread"
+branch = "unstable_name_collisions"
+replace-with = "vendored-sources"
+
[source.vendored-sources]
directory = "./vendor"
diff --git a/src/Cargo.toml b/src/Cargo.toml
index 95c1ae3f5..4daf9cf7b 100644
--- a/src/Cargo.toml
+++ b/src/Cargo.toml
@@ -15,4 +15,6 @@ members = [
panic = "abort"
lto = true
+[patch.crates-io]
+concread = { git = "https://github.com/389ds/concread", branch = "unstable_name_collisions" }

View File

@ -46,8 +46,8 @@ ExcludeArch: i686
Summary: 389 Directory Server (base)
Name: 389-ds-base
Version: 2.5.2
Release: 9%{?dist}
Version: 2.6.1
Release: 8%{?dist}
License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (0BSD OR Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR BSD-2-Clause OR MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT OR Zlib) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND MIT AND MPL-2.0 AND Zlib
URL: https://www.port389.org
Conflicts: selinux-policy-base < 3.9.8
@ -70,7 +70,7 @@ Provides: bundled(crate(base64)) = 0.13.1
Provides: bundled(crate(bitflags)) = 2.9.0
Provides: bundled(crate(byteorder)) = 1.5.0
Provides: bundled(crate(cbindgen)) = 0.26.0
Provides: bundled(crate(cc)) = 1.2.19
Provides: bundled(crate(cc)) = 1.2.18
Provides: bundled(crate(cfg-if)) = 1.0.0
Provides: bundled(crate(clap)) = 3.2.25
Provides: bundled(crate(clap_lex)) = 0.2.4
@ -94,7 +94,7 @@ Provides: bundled(crate(indexmap)) = 1.9.3
Provides: bundled(crate(itoa)) = 1.0.15
Provides: bundled(crate(jobserver)) = 0.1.33
Provides: bundled(crate(libc)) = 0.2.171
Provides: bundled(crate(linux-raw-sys)) = 0.9.4
Provides: bundled(crate(linux-raw-sys)) = 0.9.3
Provides: bundled(crate(log)) = 0.4.27
Provides: bundled(crate(lru)) = 0.13.0
Provides: bundled(crate(memchr)) = 2.7.4
@ -223,6 +223,7 @@ BuildRequires: python%{python3_pkgversion}-argparse-manpage
BuildRequires: python%{python3_pkgversion}-libselinux
BuildRequires: python%{python3_pkgversion}-policycoreutils
BuildRequires: python%{python3_pkgversion}-cryptography
BuildRequires: python%{python3_pkgversion}-psutil
# For cockpit
%if %{use_cockpit}
@ -285,41 +286,37 @@ Source2: %{name}-devel.README
Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2
%endif
Source4: 389-ds-base.sysusers
# Vendored cargo crates update
Source5: vendor-%{version}-1.tar.gz
Source6: Cargo-%{version}-1.lock
Patch: 0001-Issue-6312-In-branch-2.5-healthcheck-report-an-inval.patch
Patch: 0002-Issue-6316-lmdb-reindex-is-broken-if-index-type-is-s.patch
Patch: 0003-Issue-6192-Test-failure-test_match_large_valueset.patch
Patch: 0004-Issue-6307-Wrong-set-of-entries-returned-for-some-se.patch
Patch: 0005-Issue-6381-CleanAllRUV-move-changelog-purging-to-the.patch
Patch: 0006-Issue-6390-Adjust-cleanAllRUV-max-per-txn-and-interv.patch
Patch: 0007-Issue-6284-BUG-freelist-ordering-causes-high-wtime-6.patch
Patch: 0008-Issue-6296-basic_test.py-test_conn_limits-fails-in-m.patch
Patch: 0009-Issue-5798-Fix-dsconf-config-multi-valued-attr-opera.patch
Patch: 0010-Issue-6417-If-an-entry-RDN-is-identical-to-the-suffi.patch
Patch: 0011-Issue-6417-2nd-If-an-entry-RDN-is-identical-to-the-s.patch
Patch: 0012-Issue-6417-3rd-If-an-entry-RDN-is-identical-to-the-s.patch
Patch: 0013-Issue-6432-Crash-during-bind-when-acct-policy-plugin.patch
Patch: 0014-Issue-6386-backup-restore-broken-after-db-log-rotati.patch
Patch: 0015-Issue-6446-on-replica-consumer-account-policy-plugin.patch
Patch: 0016-Issue-6446-Fix-test_acct_policy_consumer-test-to-wai.patch
Patch: 0017-Issue-6554-During-import-of-entries-without-nsUnique.patch
Patch: 0018-Issue-6561-TLS-1.2-stickiness-in-FIPS-mode.patch
Patch: 0019-Issue-6229-After-an-initial-failure-subsequent-onlin.patch
Patch: 0020-Issue-6372-Deadlock-while-doing-online-backup-6475.patch
Patch: 0021-Issue-6509-Race-condition-with-Paged-Result-searches.patch
Patch: 0022-Issue-6436-MOD-on-a-large-group-slow-if-substring-in.patch
Patch: 0023-Issue-6494-Various-errors-when-using-extended-matchi.patch
Patch: 0024-Issue-6485-Fix-double-free-in-USN-cleanup-task.patch
Patch: 0025-Issue-6427-fix-various-memory-leaks.patch
Patch: 0026-Issue-6442-Fix-latest-covscan-memory-leaks.patch
Patch: 0027-Issue-6442-Fix-latest-covscan-memory-leaks-part-2.patch
Patch: 0028-Issue-6553-Update-concread-to-0.5.4-and-refactor-sta.patch
Patch: 0029-Security-fix-for-CVE-2025-2487.patch
Patch: 0030-Issue-6470-Some-replication-status-data-are-reset-up.patch
Patch: cargo.patch
Patch: 0001-Issue-6468-Fix-building-for-older-versions-of-Python.patch
Patch: 0002-Issue-6489-After-log-rotation-refresh-the-FD-pointer.patch
Patch: 0003-Issue-6374-nsslapd-mdb-max-dbs-autotuning-doesn-t-wo.patch
Patch: 0004-Issue-6090-Fix-dbscan-options-and-man-pages-6315.patch
Patch: 0005-Issue-6566-RI-plugin-failure-to-handle-a-modrdn-for-.patch
Patch: 0006-Issue-6258-Mitigate-race-condition-in-paged_results_.patch
Patch: 0007-Issue-6229-After-an-initial-failure-subsequent-onlin.patch
Patch: 0008-Issue-6554-During-import-of-entries-without-nsUnique.patch
Patch: 0009-Issue-6561-TLS-1.2-stickiness-in-FIPS-mode.patch
Patch: 0010-Issue-6090-dbscan-use-bdb-by-default.patch
Patch: 0011-Issue-6375-UI-Update-cockpit.js-code-to-the-latest-v.patch
Patch: 0012-Bump-esbuild-from-0.24.0-to-0.25.0-in-src-cockpit-38.patch
Patch: 0013-Issue-6625-UI-fix-various-issues-with-LDAP-browser-e.patch
Patch: 0014-Issue-6625-UI-fix-next-round-of-bugs.patch
Patch: 0015-Issue-6625-UI-various-fixes-part-3.patch
Patch: 0016-Issue-6429-UI-clicking-on-a-database-suffix-under-th.patch
Patch: 0017-Issue-6656-UI-Enhance-Monitor-Log-Viewer-with-Patter.patch
Patch: 0018-Issue-6665-UI-Need-to-refresh-log-settings-after-sav.patch
Patch: 0019-Issue-6623-UI-Generic-updates-6624.patch
Patch: 0020-Issue-6695-UI-fix-more-minor-issues.patch
Patch: 0021-Issue-6704-UI-Add-error-log-buffering-config.patch
Patch: 0022-Issue-6700-CLI-UI-include-superior-objectclasses-all.patch
Patch: 0023-Issue-6464-UI-Fixed-spelling-in-cockpit-messages.patch
Patch: 0024-Issue-6481-When-ports-that-are-in-use-are-used-to-up.patch
Patch: 0025-Issue-6553-Update-concread-to-0.5.4-and-refactor-sta.patch
Patch: 0026-Security-fix-for-CVE-2025-2487.patch
%description
389 Directory Server is an LDAPv3 compliant server. The base package includes
@ -765,42 +762,75 @@ exit 0
%endif
%changelog
* Mon Apr 14 2025 Viktor Ashirov <vashirov@redhat.com> - 2.5.2-9
- Resolves: RHEL-83874 - CVE-2025-2487 389-ds-base: null pointer dereference leads to denial of service [rhel-9.5.z]
- Resolves: RHEL-80712 - Increased memory consumption caused by NDN cache [rhel-9.5.z]
- Resolves: RHEL-87194 - Some replication status data are reset upon a restart. [rhel-9.5.z]
* Wed Apr 09 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-8
- Resolves: RHEL-83876 - CVE-2025-2487 389-ds-base: null pointer dereference leads to denial of service [rhel-9.6]
* Wed Mar 05 2025 Viktor Ashirov <vashirov@redhat.com> - 2.5.2-8
- Fix changelog
* Wed Apr 09 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-7
- Bump version to 2.6.1-7
* Tue Mar 04 2025 Viktor Ashirov <vashirov@redhat.com> - 2.5.2-6
- Resolves: RHEL-69825 - "Duplicated DN detected" errors when creating indexes or importing entries. [rhel-9.5.z]
- Resolves: RHEL-70126 - Crash in attrlist_find() when the Account Policy plugin is enabled. [rhel-9.5.z]
- Resolves: RHEL-74152 - backup/restore broken [rhel-9.5.z]
- Resolves: RHEL-74157 - If an entry RDN is identical to the suffix, then Entryrdn gets broken during a reindex [rhel-9.5.z]
- Resolves: RHEL-74167 - On replica consumer, account policy plugin fails to manage the last login history [rhel-9.5.z]
- Resolves: RHEL-78343 - During import of entries without nsUniqueId, a supplier generates duplicate nsUniqueId (LMDB only) [rhel-9.5.z]
- Resolves: RHEL-79497 - Failed to set sslversionmax to TLS1.3 in FIPS mode with dsconf $INSTANCE security set --tls-protocol-max TLS1.3 [rhel-9.5.z]
- Resolves: RHEL-81102 - After an initial failure, subsequent online backups will not work. [rhel-9.5.z]
- Resolves: RHEL-81107 - Online backup hangs sporadically. [rhel-9.5.z]
- Resolves: RHEL-81113 - IPA LDAP error code T3 when no exceeded time limit from a paged search result [rhel-9.5.z]
- Resolves: RHEL-81139 - Healthcheck tool should warn admin about creating a substring index on membership attribute [rhel-9.5.z]
- Resolves: RHEL-81146 - 389DirectoryServer Process Stops When Setting up Sorted VLV Index [rhel-9.5.z]
- Resolves: RHEL-81157 - AddressSanitizer: double-free [rhel-9.5.z]
- Resolves: RHEL-81171 - leaked_storage: Variable "childelems" going out of scope leaks the storage it points to. [rhel-9.5.z]
* Wed Apr 09 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-6
- Resolves: RHEL-86065 - Backport lib389 fixes required for WebUI [rhel-9.6.z]
- Resolves: RHEL-80713 - Increased memory consumption caused by NDN cache [rhel-9.6.z]
* Fri Jan 24 2025 Viktor Ashirov <vashirov@redhat.com> - 2.5.2-5
- Resolves: RHEL-74350 - Some nsslapd-haproxy-trusted-ip values are discarded upon a restart. [rhel-9.5.z]
* Fri Mar 14 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-5
- Resolves: RHEL-82271 - ipa-restore is failing with "Failed to start Directory Service"
* Mon Dec 09 2024 Viktor Ashirov <vashirov@redhat.com> - 2.5.2-4
- Resolves: RHEL-70257 - Freelist ordering causes high wtime [rhel-9.5.z]
* Wed Feb 19 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-4
- Resolves: RHEL-78722 - Failed to set sslversionmax to TLS1.3 in FIPS mode with dsconf $INSTANCE security set --tls-protocol-max TLS1.3
* Thu Dec 05 2024 James Chapman <jachapma@redhat.com> - 2.5.2-3
- Bump version to 2.5.2-3
- Resolves: RHEL-65775 - Wrong set of entries returned for some search filters [rhel-9.5.z]
- Resolves: RHEL-66138 - deadlock during cleanAllRuv [rhel-9.5.z]
- Resolves: RHEL-67163 - cleanallruv consums CPU and is slow [rhel-9.5.z]
- Resolves: RHEL-70257 - Freelist ordering causes high wtime [rhel-9.5.z]
* Wed Feb 12 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-3
- Resolves: RHEL-18333 Can't rename users member of automember rule
- Resolves: RHEL-61341 After an initial failure, subsequent online backups will not work.
- Resolves: RHEL-63887 nsslapd-mdb-max-dbs autotuning doesn't work properly
- Resolves: RHEL-63891 dbscan crashes when showing statistics for MDB
- Resolves: RHEL-63998 dsconf should check for number of available named databases
- Resolves: RHEL-78344 During import of entries without nsUniqueId, a supplier generates duplicate nsUniqueId (LMDB only) [rhel-9]
* Sat Feb 01 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-2
- Resolves: RHEL-76748: ns-slapd crashes with data directory 2 days old
* Tue Jan 28 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-1
- Update to 2.6.1
- Resolves: RHEL-5151 - [RFE] defer memberof nested updates
- Resolves: RHEL-54148 - leaked_storage: Variable "childelems" going out of scope leaks the storage it points to.
- Resolves: RHEL-60135 - deadlock during cleanAllRuv
- Resolves: RHEL-61341 - After an initial failure, subsequent online backups will not work.
- Resolves: RHEL-61349 - Remove deprecated setting for HR time stamps in logs
- Resolves: RHEL-62875 - Passwords are not being updated to use the configured storage scheme ( nsslapd-enable-upgrade-hash is enabled ).
- Resolves: RHEL-64438 - VLV errors with RSNv3 and pruning enabled [rhel-9]
- Resolves: RHEL-64854 - cleanallruv consums CPU and is slow
- Resolves: RHEL-65506 - AddressSanitizer: double-free
- Resolves: RHEL-65512 - AddressSanitizer: heap-use-after-free in import_abort_all
- Resolves: RHEL-65561 - LeakSanitizer: detected memory leaks in dbmdb_public_db_op
- Resolves: RHEL-65662 - Replication issue between masters using cert based authentication
- Resolves: RHEL-65664 - LDAP unprotected search query during certificate based authentication
- Resolves: RHEL-65665 - Ambiguous warning about SELinux in dscreate for non-root user
- Resolves: RHEL-65741 - LeakSanitizer: memory leak in ldbm_entryrdn.c
- Resolves: RHEL-65776 - Wrong set of entries returned for some search filters [rhel-9]
- Resolves: RHEL-67004 - "dsconf config replace" should handle multivalued attributes.
- Resolves: RHEL-67005 - Online backup hangs sporadically.
- Resolves: RHEL-67008 - Some replication status data are reset upon a restart.
- Resolves: RHEL-67020 - 389DirectoryServer Process Stops When Setting up Sorted VLV Index
- Resolves: RHEL-67024 - Some nsslapd-haproxy-trusted-ip values are discarded upon a restart.
- Resolves: RHEL-69806 - ipahealthcheck.ds.replication displays WARNING '1 conflict entries found under the replication suffix'
- Resolves: RHEL-69826 - "Duplicated DN detected" errors when creating indexes or importing entries. [rhel-9]
- Resolves: RHEL-70127 - Crash in attrlist_find() when the Account Policy plugin is enabled. [rhel-9]
- Resolves: RHEL-70252 - Freelist ordering causes high wtime
- Resolves: RHEL-71218 - Sub suffix causes "id2entry - Could not open id2entry err 0" error when the Directory Server starts [rhel-9]
- Resolves: RHEL-74153 - backup/restore broken [rhel-9]
- Resolves: RHEL-74158 - If an entry RDN is identical to the suffix, then Entryrdn gets broken during a reindex [rhel-9]
- Resolves: RHEL-74163 - Crash during bind when acct policy plugin does not have "alwaysrecordlogin" set [rhel-9]
- Resolves: RHEL-74168 - On replica consumer, account policy plugin fails to manage the last login history [rhel-9]
- Resolves: RHEL-74174 - Replication broken after backup restore with freeipa configuration [rhel-9]
- Resolves: RHEL-74353 - nsslapd-haproxy-trusted-ip is not in schema [rhel-9]
- Resolves: RHEL-76019 - IPA LDAP error code T3 when no exceeded time limit from a paged search result [rhel-9]
* Mon Dec 16 2024 Viktor Ashirov <vashirov@redhat.com> - 2.6.0-2
- Fix License tag
* Mon Dec 16 2024 Viktor Ashirov <vashirov@redhat.com> - 2.6.0-1
- Update to 2.6.0
- Resolves: RHEL-67195 - Rebase 389-ds-base to 2.6.0
* Mon Sep 16 2024 Viktor Ashirov <vashirov@redhat.com> - 2.5.2-2
- Bump version to 2.5.2-2