Import from AlmaLinux stable repository
This commit is contained in:
parent
f8c6c89bf0
commit
9cc5c67ef7
@ -1,3 +1,3 @@
|
||||
25969f6e65d79aa29671eff7185e4307ff3c08a0 SOURCES/389-ds-base-2.6.1.tar.bz2
|
||||
e9ce5b0affef3f7a319958610c5382152f1b559f SOURCES/389-ds-base-2.7.0.tar.bz2
|
||||
1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
2a7e210570b6f8a8a962efe173d2ae31441da7fb SOURCES/vendor-2.6.1-3.tar.gz
|
||||
b183c1ebee9c1d81d4b394df6de6521a8b333cbc SOURCES/vendor-2.7.0-1.tar.gz
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
SOURCES/389-ds-base-2.6.1.tar.bz2
|
||||
SOURCES/389-ds-base-2.7.0.tar.bz2
|
||||
SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
SOURCES/vendor-2.6.1-3.tar.gz
|
||||
SOURCES/vendor-2.7.0-1.tar.gz
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From e822bcb828c02c8f3ba3c0f0ab2e281a7ef23e32 Mon Sep 17 00:00:00 2001
|
||||
From 5903fac2334f984d18aea663735fb260d6b100ed Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 22 Oct 2024 17:26:46 +0200
|
||||
Subject: [PATCH] Issue 6377 - syntax error in setup.py (#6378)
|
||||
@ -1,60 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,146 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 70faa600c976e4f2d5486a7d680aae6edf7f20b4 Mon Sep 17 00:00:00 2001
|
||||
From a91c2641646824e44ef3b31a7eea238e3f55e5c3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 1 Jul 2025 12:44:04 +0200
|
||||
Subject: [PATCH] Issue 6838 - lib389/replica.py is using nonexistent
|
||||
@ -20,7 +20,7 @@ Reviewed by: @mreynolds389 (Thanks!)
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 5183c41e9..5a33140be 100644
|
||||
index 8791f7f4c..78d6eb4eb 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -917,7 +917,7 @@ class RUV(object):
|
||||
@ -1,311 +0,0 @@
|
||||
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
|
||||
|
||||
@ -0,0 +1,351 @@
|
||||
From 4eef34cec551582d1de23266bc6cde84a7e38b5d Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 24 Mar 2025 10:43:21 +0100
|
||||
Subject: [PATCH] Issue 6680 - instance read-only mode is broken (#6681)
|
||||
|
||||
Read only mode is broken because some plugins fails to starts as they are not able to create/updates some entries in the dse backend.
|
||||
Solution is to allow interrnal operations to write in dse.backend but not modify the dse.ldif (except for the special case when trying to modify nsslapd-readonly flags (to be allowed to set/unset the readonly mode)
|
||||
|
||||
Issue: #6680
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (thanks!)
|
||||
---
|
||||
.../tests/suites/config/regression_test.py | 60 ++++++++++
|
||||
ldap/servers/slapd/dse.c | 110 +++++++++++++++++-
|
||||
ldap/servers/slapd/mapping_tree.c | 90 ++++++++++++--
|
||||
3 files changed, 247 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/regression_test.py b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
index 8dbba8cd2..6e313ac8a 100644
|
||||
--- a/dirsrvtests/tests/suites/config/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
@@ -28,6 +28,8 @@ CUSTOM_MEM = '9100100100'
|
||||
IDLETIMEOUT = 5
|
||||
DN_TEST_USER = f'uid={TEST_USER_PROPERTIES["uid"]},ou=People,{DEFAULT_SUFFIX}'
|
||||
|
||||
+RO_ATTR = 'nsslapd-readonly'
|
||||
+
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def idletimeout_topo(topo, request):
|
||||
@@ -190,3 +192,61 @@ def test_idletimeout(idletimeout_topo, dn, expected_result):
|
||||
except ldap.SERVER_DOWN:
|
||||
result = True
|
||||
assert expected_result == result
|
||||
+
|
||||
+
|
||||
+def test_instance_readonly_mode(topo):
|
||||
+ """Check that readonly mode is supported
|
||||
+
|
||||
+ :id: 34d2e28e-04d7-11f0-b0cf-482ae39447e5
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Set readonly mode
|
||||
+ 2. Stop the instance
|
||||
+ 3. Get dse.ldif modification time
|
||||
+ 4. Start the instance
|
||||
+ 5. Get dse.ldif modification time
|
||||
+ 6. Check that modification time has not changed
|
||||
+ 7. Check that readonly mode is set
|
||||
+ 8. Try to modify another config attribute
|
||||
+ 9. Unset readonly mode
|
||||
+ 10. Restart the instance
|
||||
+ 11. Check that modification time has not changed
|
||||
+ 12. Check that modification time has changed
|
||||
+ 13. Check that readonly mode is unset
|
||||
+ 14. Try to modify another config attribute
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Should get ldap.UNWILLING_TO_PERFORM exception
|
||||
+ 9. Success
|
||||
+ 10. Success
|
||||
+ 11. Success
|
||||
+ 12. Success
|
||||
+ 13. Success
|
||||
+ 14. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ dse_path = f'{topo.standalone.get_config_dir()}/dse.ldif'
|
||||
+ inst.config.replace(RO_ATTR, 'on')
|
||||
+ inst.stop()
|
||||
+ dse_mtime = os.stat(dse_path).st_mtime
|
||||
+ inst.start()
|
||||
+ new_dse_mtime = os.stat(dse_path).st_mtime
|
||||
+ assert dse_mtime == new_dse_mtime
|
||||
+ assert inst.config.get_attr_val_utf8(RO_ATTR) == "on"
|
||||
+ attr = 'nsslapd-errorlog-maxlogsize'
|
||||
+ val = inst.config.get_attr_val_utf8(attr)
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ inst.config.replace(attr, val)
|
||||
+ inst.config.replace(RO_ATTR, 'off')
|
||||
+ inst.restart()
|
||||
+ new_dse_mtime = os.stat(dse_path).st_mtime
|
||||
+ assert dse_mtime != new_dse_mtime
|
||||
+ assert inst.config.get_attr_val_utf8(RO_ATTR) == "off"
|
||||
+ inst.config.replace(attr, val)
|
||||
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
|
||||
index e3157c1ce..0f266f0d7 100644
|
||||
--- a/ldap/servers/slapd/dse.c
|
||||
+++ b/ldap/servers/slapd/dse.c
|
||||
@@ -1031,6 +1031,114 @@ dse_check_for_readonly_error(Slapi_PBlock *pb, struct dse *pdse)
|
||||
return rc; /* no error */
|
||||
}
|
||||
|
||||
+/* Trivial wrapper around slapi_re_comp to handle errors */
|
||||
+static Slapi_Regex *
|
||||
+recomp(const char *regexp)
|
||||
+{
|
||||
+ char *error = "";
|
||||
+ Slapi_Regex *re = slapi_re_comp(regexp, &error);
|
||||
+ if (re == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "is_readonly_set_in_dse",
|
||||
+ "Failed to compile '%s' regular expression. Error is %s\n",
|
||||
+ regexp, error);
|
||||
+ }
|
||||
+ slapi_ch_free_string(&error);
|
||||
+ return re;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check if "nsslapd-readonly: on" is in cn-config in dse.ldif file
|
||||
+ * ( If the flag is set in memory but on in the file, the file should
|
||||
+ * be written (to let dsconf able to modify the nsslapd-readonly flag)
|
||||
+ */
|
||||
+static bool
|
||||
+is_readonly_set_in_dse(const char *dsename)
|
||||
+{
|
||||
+ Slapi_Regex *re_config = recomp("^dn:\\s+cn=config\\s*$");
|
||||
+ Slapi_Regex *re_isro = recomp("^" CONFIG_READONLY_ATTRIBUTE ":\\s+on\\s*$");
|
||||
+ Slapi_Regex *re_eoe = recomp("^$");
|
||||
+ bool isconfigentry = false;
|
||||
+ bool isro = false;
|
||||
+ FILE *fdse = NULL;
|
||||
+ char line[128];
|
||||
+ char *error = NULL;
|
||||
+ const char *regexp = "";
|
||||
+
|
||||
+ if (!dsename) {
|
||||
+ goto done;
|
||||
+ }
|
||||
+ if (re_config == NULL || re_isro == NULL || re_eoe == NULL) {
|
||||
+ goto done;
|
||||
+ }
|
||||
+ fdse = fopen(dsename, "r");
|
||||
+ if (fdse == NULL) {
|
||||
+ /* No dse file, we need to write it */
|
||||
+ goto done;
|
||||
+ }
|
||||
+ while (fgets(line, (sizeof line), fdse)) {
|
||||
+ /* Convert the read line to lowercase */
|
||||
+ for (char *pt=line; *pt; pt++) {
|
||||
+ if (isalpha(*pt)) {
|
||||
+ *pt = tolower(*pt);
|
||||
+ }
|
||||
+ }
|
||||
+ if (slapi_re_exec_nt(re_config, line)) {
|
||||
+ isconfigentry = true;
|
||||
+ }
|
||||
+ if (slapi_re_exec_nt(re_eoe, line)) {
|
||||
+ if (isconfigentry) {
|
||||
+ /* End of config entry ==> readonly flag is not set */
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ if (isconfigentry && slapi_re_exec_nt(re_isro, line)) {
|
||||
+ /* Found readonly flag */
|
||||
+ isro = true;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+done:
|
||||
+ if (fdse) {
|
||||
+ (void) fclose(fdse);
|
||||
+ }
|
||||
+ slapi_re_free(re_config);
|
||||
+ slapi_re_free(re_isro);
|
||||
+ slapi_re_free(re_eoe);
|
||||
+ return isro;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check if dse.ldif can be written
|
||||
+ * Beware that even in read-only mode dse.ldif file
|
||||
+ * should still be written to change the nsslapd-readonly value
|
||||
+ */
|
||||
+static bool
|
||||
+check_if_readonly(struct dse *pdse)
|
||||
+{
|
||||
+ static bool ro = false;
|
||||
+
|
||||
+ if (pdse->dse_filename == NULL) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (!slapi_config_get_readonly()) {
|
||||
+ ro = false;
|
||||
+ return ro;
|
||||
+ }
|
||||
+ if (ro) {
|
||||
+ /* read-only mode and dse is up to date ==> Do not modify it. */
|
||||
+ return ro;
|
||||
+ }
|
||||
+ /* First attempt to write the dse.ldif since readonly mode is enabled.
|
||||
+ * Lets check if "nsslapd-readonly: on" is in cn=config entry
|
||||
+ * and allow to write the dse.ldif if it is the case
|
||||
+ */
|
||||
+ if (is_readonly_set_in_dse(pdse->dse_filename)) {
|
||||
+ /* read-only mode and dse is up to date ==> Do not modify it. */
|
||||
+ ro = true;
|
||||
+ }
|
||||
+ /* Read only mode but nsslapd-readonly value is not up to date. */
|
||||
+ return ro;
|
||||
+}
|
||||
|
||||
/*
|
||||
* Write the AVL tree of entries back to the LDIF file.
|
||||
@@ -1041,7 +1149,7 @@ dse_write_file_nolock(struct dse *pdse)
|
||||
FPWrapper fpw;
|
||||
int rc = 0;
|
||||
|
||||
- if (dont_ever_write_dse_files) {
|
||||
+ if (dont_ever_write_dse_files || check_if_readonly(pdse)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/mapping_tree.c b/ldap/servers/slapd/mapping_tree.c
|
||||
index dd7b1af37..e51b3b948 100644
|
||||
--- a/ldap/servers/slapd/mapping_tree.c
|
||||
+++ b/ldap/servers/slapd/mapping_tree.c
|
||||
@@ -2058,6 +2058,82 @@ slapi_dn_write_needs_referral(Slapi_DN *target_sdn, Slapi_Entry **referral)
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
+
|
||||
+/*
|
||||
+ * This function dermines if an operation should be rejected
|
||||
+ * when readonly mode is enabled.
|
||||
+ * All operations are rejected except:
|
||||
+ * - if they target a private backend that is not the DSE backend
|
||||
+ * - if they are read operations (SEARCH, COMPARE, BIND, UNBIND)
|
||||
+ * - if they are tombstone fixup operation (i.e: tombstone purging)
|
||||
+ * - if they are internal operation that targets the DSE backend.
|
||||
+ * (change will then be done in memory but not written in dse.ldif)
|
||||
+ * - single modify modify operation on cn=config changing nsslapd-readonly
|
||||
+ * (to allow "dsconf instance config replace nsslapd-readonly=xxx",
|
||||
+ change will then be done both in memory and in dse.ldif)
|
||||
+ */
|
||||
+static bool
|
||||
+is_rejected_op(Slapi_Operation *op, Slapi_Backend *be)
|
||||
+{
|
||||
+ const char *betype = slapi_be_gettype(be);
|
||||
+ unsigned long be_op_type = operation_get_type(op);
|
||||
+ int isdse = (betype && strcmp(betype, "DSE") == 0);
|
||||
+
|
||||
+ /* Private backend operations are not rejected */
|
||||
+
|
||||
+ /* Read operations are not rejected */
|
||||
+ if ((be_op_type == SLAPI_OPERATION_SEARCH) ||
|
||||
+ (be_op_type == SLAPI_OPERATION_COMPARE) ||
|
||||
+ (be_op_type == SLAPI_OPERATION_BIND) ||
|
||||
+ (be_op_type == SLAPI_OPERATION_UNBIND)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ /* Tombstone fixup are not rejected. */
|
||||
+ if (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_FIXUP)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (!isdse) {
|
||||
+ /* write operation on readonly backends are rejected */
|
||||
+ if (be->be_readonly) {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ /* private backends (DSE excepted) are not backed on files
|
||||
+ * so write operations are accepted.
|
||||
+ * but other operations (not on DSE) are rejected.
|
||||
+ */
|
||||
+ if (slapi_be_private(be)) {
|
||||
+ return false;
|
||||
+ } else {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Allowed operations in dse backend are:
|
||||
+ * - the internal operations and
|
||||
+ * - modify of nsslapd-readonly flag in cn=config
|
||||
+ */
|
||||
+
|
||||
+ if (operation_is_flag_set(op, OP_FLAG_INTERNAL)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (be_op_type == SLAPI_OPERATION_MODIFY) {
|
||||
+ Slapi_DN *sdn = operation_get_target_spec(op);
|
||||
+ Slapi_DN config = {0};
|
||||
+ LDAPMod **mods = op->o_params.p.p_modify.modify_mods;
|
||||
+ slapi_sdn_init_ndn_byref(&config, SLAPD_CONFIG_DN);
|
||||
+ if (mods && mods[0] && !mods[1] &&
|
||||
+ slapi_sdn_compare(sdn, &config) == 0 &&
|
||||
+ strcasecmp(mods[0]->mod_type, CONFIG_READONLY_ATTRIBUTE) == 0) {
|
||||
+ /* Single modifier impacting nsslapd-readonly */
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Description:
|
||||
* The reason we have a mapping tree. This function selects a backend or
|
||||
@@ -2095,7 +2171,6 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re
|
||||
int ret;
|
||||
int scope = LDAP_SCOPE_BASE;
|
||||
int op_type;
|
||||
- int fixup = 0;
|
||||
|
||||
if (slapi_atomic_load_32(&mapping_tree_freed, __ATOMIC_RELAXED)) {
|
||||
/* shutdown detected */
|
||||
@@ -2112,7 +2187,6 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re
|
||||
|
||||
/* Get the target for this op */
|
||||
target_sdn = operation_get_target_spec(op);
|
||||
- fixup = operation_is_flag_set(op, OP_FLAG_TOMBSTONE_FIXUP);
|
||||
|
||||
PR_ASSERT(mapping_tree_inited == 1);
|
||||
|
||||
@@ -2161,22 +2235,14 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re
|
||||
* or if the whole server is readonly AND backend is public (!private)
|
||||
*/
|
||||
if ((ret == LDAP_SUCCESS) && *be && !be_isdeleted(*be) &&
|
||||
- (((*be)->be_readonly && !fixup) ||
|
||||
- ((slapi_config_get_readonly() && !fixup) &&
|
||||
- !slapi_be_private(*be)))) {
|
||||
- unsigned long be_op_type = operation_get_type(op);
|
||||
-
|
||||
- if ((be_op_type != SLAPI_OPERATION_SEARCH) &&
|
||||
- (be_op_type != SLAPI_OPERATION_COMPARE) &&
|
||||
- (be_op_type != SLAPI_OPERATION_BIND) &&
|
||||
- (be_op_type != SLAPI_OPERATION_UNBIND)) {
|
||||
+ ((*be)->be_readonly || slapi_config_get_readonly()) &&
|
||||
+ is_rejected_op(op, *be)) {
|
||||
if (errorbuf) {
|
||||
PL_strncpyz(errorbuf, slapi_config_get_readonly() ? "Server is read-only" : "database is read-only", ebuflen);
|
||||
}
|
||||
ret = LDAP_UNWILLING_TO_PERFORM;
|
||||
slapi_be_Unlock(*be);
|
||||
*be = NULL;
|
||||
- }
|
||||
}
|
||||
|
||||
return ret;
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,894 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 4d3c4331208173c4efd30b40e7b79f26907850a1 Mon Sep 17 00:00:00 2001
|
||||
From 5613937623f0037a54490b22c60f7eb1aa52cf4e Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 25 Jun 2025 14:11:05 +0000
|
||||
Subject: [PATCH] =?UTF-8?q?Issue=206825=20-=20RootDN=20Access=20Control=20?=
|
||||
@ -63,7 +63,7 @@ index 65486fff8..1456f5ebe 100644
|
||||
if args.allow_host is not None:
|
||||
for hostname in args.allow_host:
|
||||
diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
|
||||
index c6f60f4a2..73b2b141e 100644
|
||||
index afc282e94..3937fc1a8 100644
|
||||
--- a/src/lib389/lib389/utils.py
|
||||
+++ b/src/lib389/lib389/utils.py
|
||||
@@ -31,6 +31,7 @@ import logging
|
||||
@ -0,0 +1,50 @@
|
||||
From b8cac173ca2549d2142332107e06fcb4bd34bd65 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 8 Mar 2024 16:15:52 +0000
|
||||
Subject: [PATCH] Issue 6119 - Synchronise accept_thread with slapd_daemon
|
||||
(#6120)
|
||||
|
||||
Bug Description: A corner cases exists, where the slapd_daemon has
|
||||
begun its shutdown process but the accept_thread is still running
|
||||
and capable of handling new connections. When this scenario occurs,
|
||||
the connection subsystem has been partially deallocated and is in
|
||||
an unstable state. A segfault is generated when attempting to get a
|
||||
new connection from the connection table.
|
||||
|
||||
Fix Description: The connection table is only deallocated when the
|
||||
number of active threads is 0. Modify the accept_thread to adjust the
|
||||
the active thread count during creation/destruction, meaning the connection
|
||||
table can only be freed when the accept_thread has completed
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6119
|
||||
|
||||
Reviewed by: @tbordaz, @Firstyear , @mreynolds389 (Thank you)
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 5d01a2526..a43fc9285 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -868,6 +868,8 @@ accept_thread(void *vports)
|
||||
slapi_ch_free((void **)&listener_idxs);
|
||||
slapd_sockets_ports_free(ports);
|
||||
slapi_ch_free((void **)&fds);
|
||||
+ g_decr_active_threadcnt();
|
||||
+ slapi_log_err(SLAPI_LOG_INFO, "slapd_daemon", "slapd shutting down - accept_thread\n");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1158,6 +1160,8 @@ slapd_daemon(daemon_ports_t *ports)
|
||||
slapi_log_err(SLAPI_LOG_EMERG, "slapd_daemon", "Unable to fd accept thread - Shutting Down (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)\n",
|
||||
errorCode, slapd_pr_strerror(errorCode));
|
||||
g_set_shutdown(SLAPI_SHUTDOWN_EXIT);
|
||||
+ } else{
|
||||
+ g_incr_active_threadcnt();
|
||||
}
|
||||
|
||||
#ifdef WITH_SYSTEMD
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
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
|
||||
|
||||
127
SOURCES/0006-Issue-6782-Improve-paged-result-locking.patch
Normal file
127
SOURCES/0006-Issue-6782-Improve-paged-result-locking.patch
Normal file
@ -0,0 +1,127 @@
|
||||
From 7943443bb92fca6676922349fb12503a527cb6b1 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 15 May 2025 10:35:27 -0400
|
||||
Subject: [PATCH] Issue 6782 - Improve paged result locking
|
||||
|
||||
Description:
|
||||
|
||||
When cleaning a slot, instead of mem setting everything to Zero and restoring
|
||||
the mutex, manually reset all the values leaving the mutex pointer
|
||||
intact.
|
||||
|
||||
There is also a deadlock possibility when checking for abandoned PR search
|
||||
in opshared.c, and we were checking a flag value outside of the per_conn
|
||||
lock.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6782
|
||||
|
||||
Reviewed by: progier & spichugi(Thanks!!)
|
||||
---
|
||||
ldap/servers/slapd/opshared.c | 10 +++++++++-
|
||||
ldap/servers/slapd/pagedresults.c | 27 +++++++++++++++++----------
|
||||
2 files changed, 26 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
|
||||
index 7dc2d5983..14a7dcdfb 100644
|
||||
--- a/ldap/servers/slapd/opshared.c
|
||||
+++ b/ldap/servers/slapd/opshared.c
|
||||
@@ -592,6 +592,14 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
int32_t tlimit;
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_TIMELIMIT, &tlimit);
|
||||
pagedresults_set_timelimit(pb_conn, operation, (time_t)tlimit, pr_idx);
|
||||
+ /* When using this mutex in conjunction with the main paged
|
||||
+ * result lock, you must do so in this order:
|
||||
+ *
|
||||
+ * --> pagedresults_lock()
|
||||
+ * --> pagedresults_mutex
|
||||
+ * <-- pagedresults_mutex
|
||||
+ * <-- pagedresults_unlock()
|
||||
+ */
|
||||
pagedresults_mutex = pageresult_lock_get_addr(pb_conn);
|
||||
}
|
||||
|
||||
@@ -717,11 +725,11 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
pr_search_result = pagedresults_get_search_result(pb_conn, operation, 1 /*locked*/, pr_idx);
|
||||
if (pr_search_result) {
|
||||
if (pagedresults_is_abandoned_or_notavailable(pb_conn, 1 /*locked*/, pr_idx)) {
|
||||
+ pthread_mutex_unlock(pagedresults_mutex);
|
||||
pagedresults_unlock(pb_conn, pr_idx);
|
||||
/* Previous operation was abandoned and the simplepaged object is not in use. */
|
||||
send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
|
||||
rc = LDAP_SUCCESS;
|
||||
- pthread_mutex_unlock(pagedresults_mutex);
|
||||
goto free_and_return;
|
||||
} else {
|
||||
slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, pr_search_result);
|
||||
diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
|
||||
index 642aefb3d..c3f3aae01 100644
|
||||
--- a/ldap/servers/slapd/pagedresults.c
|
||||
+++ b/ldap/servers/slapd/pagedresults.c
|
||||
@@ -48,7 +48,6 @@ pageresult_lock_get_addr(Connection *conn)
|
||||
static void
|
||||
_pr_cleanup_one_slot(PagedResults *prp)
|
||||
{
|
||||
- PRLock *prmutex = NULL;
|
||||
if (!prp) {
|
||||
return;
|
||||
}
|
||||
@@ -56,13 +55,17 @@ _pr_cleanup_one_slot(PagedResults *prp)
|
||||
/* sr is left; release it. */
|
||||
prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
|
||||
}
|
||||
- /* clean up the slot */
|
||||
- if (prp->pr_mutex) {
|
||||
- /* pr_mutex is reused; back it up and reset it. */
|
||||
- prmutex = prp->pr_mutex;
|
||||
- }
|
||||
- memset(prp, '\0', sizeof(PagedResults));
|
||||
- prp->pr_mutex = prmutex;
|
||||
+
|
||||
+ /* clean up the slot except the mutex */
|
||||
+ prp->pr_current_be = NULL;
|
||||
+ prp->pr_search_result_set = NULL;
|
||||
+ prp->pr_search_result_count = 0;
|
||||
+ prp->pr_search_result_set_size_estimate = 0;
|
||||
+ prp->pr_sort_result_code = 0;
|
||||
+ prp->pr_timelimit_hr.tv_sec = 0;
|
||||
+ prp->pr_timelimit_hr.tv_nsec = 0;
|
||||
+ prp->pr_flags = 0;
|
||||
+ prp->pr_msgid = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1007,7 +1010,8 @@ op_set_pagedresults(Operation *op)
|
||||
|
||||
/*
|
||||
* pagedresults_lock/unlock -- introduced to protect search results for the
|
||||
- * asynchronous searches.
|
||||
+ * asynchronous searches. Do not call these functions while the PR conn lock
|
||||
+ * is held (e.g. pageresult_lock_get_addr(conn))
|
||||
*/
|
||||
void
|
||||
pagedresults_lock(Connection *conn, int index)
|
||||
@@ -1045,6 +1049,8 @@ int
|
||||
pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index)
|
||||
{
|
||||
PagedResults *prp;
|
||||
+ int32_t result;
|
||||
+
|
||||
if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
|
||||
return 1; /* not abandoned, but do not want to proceed paged results op. */
|
||||
}
|
||||
@@ -1052,10 +1058,11 @@ pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int inde
|
||||
pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
}
|
||||
prp = conn->c_pagedresults.prl_list + index;
|
||||
+ result = prp->pr_flags & CONN_FLAG_PAGEDRESULTS_ABANDONED;
|
||||
if (!locked) {
|
||||
pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
}
|
||||
- return prp->pr_flags & CONN_FLAG_PAGEDRESULTS_ABANDONED;
|
||||
+ return result;
|
||||
}
|
||||
|
||||
int
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,566 +0,0 @@
|
||||
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
|
||||
backups fail (#6230)
|
||||
|
||||
* Issue 6229 - After an initial failure, subsequent online backups will not work
|
||||
|
||||
Several issues related to backup task error handling:
|
||||
Backends stay busy after the failure
|
||||
Exit code is 0 in some cases
|
||||
Crash if failing to open the backup directory
|
||||
And a more general one:
|
||||
lib389 Task DN collision
|
||||
|
||||
Solutions:
|
||||
Always reset the busy flags that have been set
|
||||
Ensure that 0 is not returned in error case
|
||||
Avoid closing NULL directory descriptor
|
||||
Use a timestamp having milliseconds precision to create the task DN
|
||||
|
||||
Issue: #6229
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
|
||||
(cherry picked from commit 04a0b6ac776a1d588ec2e10ff651e5015078ad21)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/archive.c | 45 +++++-----
|
||||
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 3 +
|
||||
src/lib389/lib389/__init__.py | 10 +--
|
||||
src/lib389/lib389/tasks.py | 82 +++++++++----------
|
||||
4 files changed, 70 insertions(+), 70 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/archive.c b/ldap/servers/slapd/back-ldbm/archive.c
|
||||
index 0460a42f6..6658cc80a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/archive.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/archive.c
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "back-ldbm.h"
|
||||
#include "dblayer.h"
|
||||
|
||||
+#define NO_OBJECT ((Object*)-1)
|
||||
+
|
||||
int
|
||||
ldbm_temporary_close_all_instances(Slapi_PBlock *pb)
|
||||
{
|
||||
@@ -270,6 +272,7 @@ ldbm_back_ldbm2archive(Slapi_PBlock *pb)
|
||||
int run_from_cmdline = 0;
|
||||
Slapi_Task *task;
|
||||
struct stat sbuf;
|
||||
+ Object *last_busy_inst_obj = NO_OBJECT;
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &li);
|
||||
slapi_pblock_get(pb, SLAPI_SEQ_VAL, &rawdirectory);
|
||||
@@ -380,13 +383,12 @@ ldbm_back_ldbm2archive(Slapi_PBlock *pb)
|
||||
|
||||
/* to avoid conflict w/ import, do this check for commandline, as well */
|
||||
{
|
||||
- Object *inst_obj, *inst_obj2;
|
||||
ldbm_instance *inst = NULL;
|
||||
|
||||
/* server is up -- mark all backends busy */
|
||||
- for (inst_obj = objset_first_obj(li->li_instance_set); inst_obj;
|
||||
- inst_obj = objset_next_obj(li->li_instance_set, inst_obj)) {
|
||||
- inst = (ldbm_instance *)object_get_data(inst_obj);
|
||||
+ for (last_busy_inst_obj = objset_first_obj(li->li_instance_set); last_busy_inst_obj;
|
||||
+ last_busy_inst_obj = objset_next_obj(li->li_instance_set, last_busy_inst_obj)) {
|
||||
+ inst = (ldbm_instance *)object_get_data(last_busy_inst_obj);
|
||||
|
||||
/* check if an import/restore is already ongoing... */
|
||||
if (instance_set_busy(inst) != 0 || dblayer_in_import(inst) != 0) {
|
||||
@@ -400,20 +402,6 @@ ldbm_back_ldbm2archive(Slapi_PBlock *pb)
|
||||
"another task and cannot be disturbed.",
|
||||
inst->inst_name);
|
||||
}
|
||||
-
|
||||
- /* painfully, we have to clear the BUSY flags on the
|
||||
- * backends we'd already marked...
|
||||
- */
|
||||
- for (inst_obj2 = objset_first_obj(li->li_instance_set);
|
||||
- inst_obj2 && (inst_obj2 != inst_obj);
|
||||
- inst_obj2 = objset_next_obj(li->li_instance_set,
|
||||
- inst_obj2)) {
|
||||
- inst = (ldbm_instance *)object_get_data(inst_obj2);
|
||||
- instance_set_not_busy(inst);
|
||||
- }
|
||||
- if (inst_obj2 && inst_obj2 != inst_obj)
|
||||
- object_release(inst_obj2);
|
||||
- object_release(inst_obj);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
@@ -427,18 +415,26 @@ ldbm_back_ldbm2archive(Slapi_PBlock *pb)
|
||||
goto err;
|
||||
}
|
||||
|
||||
- if (!run_from_cmdline) {
|
||||
+err:
|
||||
+ /* Clear all BUSY flags that have been previously set */
|
||||
+ if (last_busy_inst_obj != NO_OBJECT) {
|
||||
ldbm_instance *inst;
|
||||
Object *inst_obj;
|
||||
|
||||
- /* none of these backends are busy anymore */
|
||||
- for (inst_obj = objset_first_obj(li->li_instance_set); inst_obj;
|
||||
+ for (inst_obj = objset_first_obj(li->li_instance_set);
|
||||
+ inst_obj && (inst_obj != last_busy_inst_obj);
|
||||
inst_obj = objset_next_obj(li->li_instance_set, inst_obj)) {
|
||||
inst = (ldbm_instance *)object_get_data(inst_obj);
|
||||
instance_set_not_busy(inst);
|
||||
}
|
||||
+ if (last_busy_inst_obj != NULL) {
|
||||
+ /* release last seen object for aborted objset_next_obj iterations */
|
||||
+ if (inst_obj != NULL) {
|
||||
+ object_release(inst_obj);
|
||||
+ }
|
||||
+ object_release(last_busy_inst_obj);
|
||||
+ }
|
||||
}
|
||||
-err:
|
||||
if (return_value) {
|
||||
if (dir_bak) {
|
||||
slapi_log_err(SLAPI_LOG_ERR,
|
||||
@@ -727,7 +723,10 @@ ldbm_archive_config(char *bakdir, Slapi_Task *task)
|
||||
}
|
||||
|
||||
error:
|
||||
- PR_CloseDir(dirhandle);
|
||||
+ if (NULL != dirhandle) {
|
||||
+ PR_CloseDir(dirhandle);
|
||||
+ dirhandle = NULL;
|
||||
+ }
|
||||
dse_backup_unlock();
|
||||
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 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
|
||||
@@ -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");
|
||||
+ if (0 == return_value) {
|
||||
+ return_value = -1;
|
||||
+ }
|
||||
}
|
||||
|
||||
goto bail;
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index 368741a66..cb372c138 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -69,7 +69,7 @@ from lib389.utils import (
|
||||
get_user_is_root)
|
||||
from lib389.paths import Paths
|
||||
from lib389.nss_ssl import NssSsl
|
||||
-from lib389.tasks import BackupTask, RestoreTask
|
||||
+from lib389.tasks import BackupTask, RestoreTask, Task
|
||||
from lib389.dseldif import DSEldif
|
||||
|
||||
# mixin
|
||||
@@ -1424,7 +1424,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
name, self.ds_paths.prefix)
|
||||
|
||||
# create the archive
|
||||
- name = "backup_%s_%s.tar.gz" % (self.serverid, time.strftime("%m%d%Y_%H%M%S"))
|
||||
+ name = "backup_%s_%s.tar.gz" % (self.serverid, Task.get_timestamp())
|
||||
backup_file = os.path.join(backup_dir, name)
|
||||
tar = tarfile.open(backup_file, "w:gz")
|
||||
tar.extraction_filter = (lambda member, path: member)
|
||||
@@ -2810,7 +2810,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
else:
|
||||
# No output file specified. Use the default ldif location/name
|
||||
cmd.append('-a')
|
||||
- tnow = datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||
+ tnow = Task.get_timestamp()
|
||||
if bename:
|
||||
ldifname = os.path.join(self.ds_paths.ldif_dir, "%s-%s-%s.ldif" % (self.serverid, bename, tnow))
|
||||
else:
|
||||
@@ -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
|
||||
- tnow = datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||
+ tnow = Task.get_timestamp()
|
||||
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
|
||||
@@ -3506,7 +3506,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
|
||||
if archive is None:
|
||||
# Use the instance name and date/time as the default backup name
|
||||
- tnow = datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||
+ tnow = Task.get_timestamp()
|
||||
if self.serverid is not None:
|
||||
backup_dir_name = "%s-%s" % (self.serverid, tnow)
|
||||
else:
|
||||
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
|
||||
index 6c2adb5b2..6bf302862 100644
|
||||
--- a/src/lib389/lib389/tasks.py
|
||||
+++ b/src/lib389/lib389/tasks.py
|
||||
@@ -118,7 +118,7 @@ class Task(DSLdapObject):
|
||||
return super(Task, self).create(rdn, properties, basedn)
|
||||
|
||||
@staticmethod
|
||||
- def _get_task_date():
|
||||
+ def get_timestamp():
|
||||
"""Return a timestamp to use in naming new task entries."""
|
||||
|
||||
return datetime.now().isoformat()
|
||||
@@ -132,7 +132,7 @@ class AutomemberRebuildMembershipTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'automember_rebuild_' + Task._get_task_date()
|
||||
+ self.cn = 'automember_rebuild_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + "," + DN_AUTOMEMBER_REBUILD_TASK
|
||||
|
||||
super(AutomemberRebuildMembershipTask, self).__init__(instance, dn)
|
||||
@@ -147,7 +147,7 @@ class AutomemberAbortRebuildTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'automember_abort_' + Task._get_task_date()
|
||||
+ self.cn = 'automember_abort_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + "," + DN_AUTOMEMBER_ABORT_REBUILD_TASK
|
||||
|
||||
super(AutomemberAbortRebuildTask, self).__init__(instance, dn)
|
||||
@@ -161,7 +161,7 @@ class FixupLinkedAttributesTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'fixup_linked_attrs_' + Task._get_task_date()
|
||||
+ self.cn = 'fixup_linked_attrs_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + "," + DN_FIXUP_LINKED_ATTIBUTES
|
||||
|
||||
super(FixupLinkedAttributesTask, self).__init__(instance, dn)
|
||||
@@ -175,7 +175,7 @@ class MemberUidFixupTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'memberUid_fixup_' + Task._get_task_date()
|
||||
+ self.cn = 'memberUid_fixup_' + Task.get_timestamp()
|
||||
dn = f"cn={self.cn},cn=memberuid task,cn=tasks,cn=config"
|
||||
|
||||
super(MemberUidFixupTask, self).__init__(instance, dn)
|
||||
@@ -190,7 +190,7 @@ class MemberOfFixupTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'memberOf_fixup_' + Task._get_task_date()
|
||||
+ self.cn = 'memberOf_fixup_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + "," + DN_MBO_TASK
|
||||
|
||||
super(MemberOfFixupTask, self).__init__(instance, dn)
|
||||
@@ -205,7 +205,7 @@ class USNTombstoneCleanupTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'usn_cleanup_' + Task._get_task_date()
|
||||
+ self.cn = 'usn_cleanup_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=USN tombstone cleanup task," + DN_TASKS
|
||||
|
||||
super(USNTombstoneCleanupTask, self).__init__(instance, dn)
|
||||
@@ -225,7 +225,7 @@ class csngenTestTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'csngenTest_' + Task._get_task_date()
|
||||
+ self.cn = 'csngenTest_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=csngen_test," + DN_TASKS
|
||||
super(csngenTestTask, self).__init__(instance, dn)
|
||||
|
||||
@@ -238,7 +238,7 @@ class EntryUUIDFixupTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'entryuuid_fixup_' + Task._get_task_date()
|
||||
+ self.cn = 'entryuuid_fixup_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + "," + DN_EUUID_TASK
|
||||
super(EntryUUIDFixupTask, self).__init__(instance, dn)
|
||||
self._must_attributes.extend(['basedn'])
|
||||
@@ -252,7 +252,7 @@ class DBCompactTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'compact_db_' + Task._get_task_date()
|
||||
+ self.cn = 'compact_db_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + "," + DN_COMPACTDB_TASK
|
||||
super(DBCompactTask, self).__init__(instance, dn)
|
||||
|
||||
@@ -265,7 +265,7 @@ class SchemaReloadTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'schema_reload_' + Task._get_task_date()
|
||||
+ self.cn = 'schema_reload_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=schema reload task," + DN_TASKS
|
||||
super(SchemaReloadTask, self).__init__(instance, dn)
|
||||
|
||||
@@ -278,7 +278,7 @@ class SyntaxValidateTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'syntax_validate_' + Task._get_task_date()
|
||||
+ self.cn = 'syntax_validate_' + Task.get_timestamp()
|
||||
dn = f"cn={self.cn},cn=syntax validate,cn=tasks,cn=config"
|
||||
|
||||
super(SyntaxValidateTask, self).__init__(instance, dn)
|
||||
@@ -295,7 +295,7 @@ class AbortCleanAllRUVTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'abortcleanallruv_' + Task._get_task_date()
|
||||
+ self.cn = 'abortcleanallruv_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=abort cleanallruv," + DN_TASKS
|
||||
|
||||
super(AbortCleanAllRUVTask, self).__init__(instance, dn)
|
||||
@@ -312,7 +312,7 @@ class CleanAllRUVTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'cleanallruv_' + Task._get_task_date()
|
||||
+ self.cn = 'cleanallruv_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=cleanallruv," + DN_TASKS
|
||||
self._properties = None
|
||||
|
||||
@@ -359,7 +359,7 @@ class ImportTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'import_' + Task._get_task_date()
|
||||
+ self.cn = 'import_' + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (self.cn, DN_IMPORT_TASK)
|
||||
self._properties = None
|
||||
|
||||
@@ -388,7 +388,7 @@ class ExportTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'export_' + Task._get_task_date()
|
||||
+ self.cn = 'export_' + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (self.cn, DN_EXPORT_TASK)
|
||||
self._properties = None
|
||||
|
||||
@@ -411,7 +411,7 @@ class BackupTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'backup_' + Task._get_task_date()
|
||||
+ self.cn = 'backup_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=backup," + DN_TASKS
|
||||
self._properties = None
|
||||
|
||||
@@ -426,7 +426,7 @@ class RestoreTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'restore_' + Task._get_task_date()
|
||||
+ self.cn = 'restore_' + Task.get_timestamp()
|
||||
dn = "cn=" + self.cn + ",cn=restore," + DN_TASKS
|
||||
self._properties = None
|
||||
|
||||
@@ -513,7 +513,7 @@ class Tasks(object):
|
||||
raise ValueError("Import file (%s) does not exist" % input_file)
|
||||
|
||||
# Prepare the task entry
|
||||
- cn = "import_" + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = "import_" + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (cn, DN_IMPORT_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -581,7 +581,7 @@ class Tasks(object):
|
||||
raise ValueError("output_file is mandatory")
|
||||
|
||||
# Prepare the task entry
|
||||
- cn = "export_" + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = "export_" + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (cn, DN_EXPORT_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.update({
|
||||
@@ -637,7 +637,7 @@ class Tasks(object):
|
||||
raise ValueError("You must specify a backup directory.")
|
||||
|
||||
# build the task entry
|
||||
- cn = "backup_" + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = "backup_" + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (cn, DN_BACKUP_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.update({
|
||||
@@ -694,7 +694,7 @@ class Tasks(object):
|
||||
raise ValueError("Backup file (%s) does not exist" % backup_dir)
|
||||
|
||||
# build the task entry
|
||||
- cn = "restore_" + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = "restore_" + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (cn, DN_RESTORE_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.update({
|
||||
@@ -789,7 +789,7 @@ class Tasks(object):
|
||||
attrs.append(attr)
|
||||
else:
|
||||
attrs.append(attrname)
|
||||
- cn = "index_vlv_%s" % (time.strftime("%m%d%Y_%H%M%S", time.localtime()))
|
||||
+ cn = "index_vlv_%s" % (Task.get_timestamp())
|
||||
dn = "cn=%s,%s" % (cn, DN_INDEX_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.update({
|
||||
@@ -803,7 +803,7 @@ class Tasks(object):
|
||||
#
|
||||
# Reindex all attributes - gather them first...
|
||||
#
|
||||
- cn = "index_all_%s" % (time.strftime("%m%d%Y_%H%M%S", time.localtime()))
|
||||
+ cn = "index_all_%s" % (Task.get_timestamp())
|
||||
dn = ('cn=%s,cn=ldbm database,cn=plugins,cn=config' % backend)
|
||||
try:
|
||||
indexes = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, '(objectclass=nsIndex)')
|
||||
@@ -815,7 +815,7 @@ class Tasks(object):
|
||||
#
|
||||
# Reindex specific attributes
|
||||
#
|
||||
- cn = "index_attrs_%s" % (time.strftime("%m%d%Y_%H%M%S", time.localtime()))
|
||||
+ cn = "index_attrs_%s" % (Task.get_timestamp())
|
||||
if isinstance(attrname, (tuple, list)):
|
||||
# Need to guarantee this is a list (and not a tuple)
|
||||
for attr in attrname:
|
||||
@@ -903,8 +903,7 @@ class Tasks(object):
|
||||
|
||||
suffix = ents[0].getValue(attr)
|
||||
|
||||
- cn = "fixupmemberof_" + time.strftime("%m%d%Y_%H%M%S",
|
||||
- time.localtime())
|
||||
+ cn = "fixupmemberof_" + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (cn, DN_MBO_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -965,8 +964,7 @@ class Tasks(object):
|
||||
if len(ents) != 1:
|
||||
raise ValueError("invalid backend name: %s" % bename)
|
||||
|
||||
- cn = "fixupTombstone_" + time.strftime("%m%d%Y_%H%M%S",
|
||||
- time.localtime())
|
||||
+ cn = "fixupTombstone_" + Task.get_timestamp()
|
||||
dn = "cn=%s,%s" % (cn, DN_TOMB_FIXUP_TASK)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1019,7 +1017,7 @@ class Tasks(object):
|
||||
@return exit code
|
||||
'''
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=automember rebuild membership,cn=tasks,cn=config' % cn)
|
||||
|
||||
entry = Entry(dn)
|
||||
@@ -1077,7 +1075,7 @@ class Tasks(object):
|
||||
if not ldif_out:
|
||||
raise ValueError("Missing ldif_out")
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=automember export updates,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1129,7 +1127,7 @@ class Tasks(object):
|
||||
if not ldif_out or not ldif_in:
|
||||
raise ValueError("Missing ldif_out and/or ldif_in")
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=automember map updates,cn=tasks,cn=config' % cn)
|
||||
|
||||
entry = Entry(dn)
|
||||
@@ -1175,7 +1173,7 @@ class Tasks(object):
|
||||
@return exit code
|
||||
'''
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=fixup linked attributes,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1219,7 +1217,7 @@ class Tasks(object):
|
||||
@return exit code
|
||||
'''
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=schema reload task,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1264,7 +1262,7 @@ class Tasks(object):
|
||||
@return exit code
|
||||
'''
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=memberuid task,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1311,7 +1309,7 @@ class Tasks(object):
|
||||
@return exit code
|
||||
'''
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=syntax validate,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1358,7 +1356,7 @@ class Tasks(object):
|
||||
@return exit code
|
||||
'''
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=USN tombstone cleanup task,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1413,7 +1411,7 @@ class Tasks(object):
|
||||
if not configfile:
|
||||
raise ValueError("Missing required paramter: configfile")
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=sysconfig reload,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1464,7 +1462,7 @@ class Tasks(object):
|
||||
if not suffix:
|
||||
raise ValueError("Missing required paramter: suffix")
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=cleanallruv,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1516,7 +1514,7 @@ class Tasks(object):
|
||||
if not suffix:
|
||||
raise ValueError("Missing required paramter: suffix")
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=abort cleanallruv,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1571,7 +1569,7 @@ class Tasks(object):
|
||||
if not nsArchiveDir:
|
||||
raise ValueError("Missing required paramter: nsArchiveDir")
|
||||
|
||||
- cn = 'task-' + time.strftime("%m%d%Y_%H%M%S", time.localtime())
|
||||
+ cn = 'task-' + Task.get_timestamp()
|
||||
dn = ('cn=%s,cn=upgradedb,cn=tasks,cn=config' % cn)
|
||||
entry = Entry(dn)
|
||||
entry.setValues('objectclass', 'top', 'extensibleObject')
|
||||
@@ -1616,6 +1614,6 @@ class LDAPIMappingReloadTask(Task):
|
||||
"""
|
||||
|
||||
def __init__(self, instance, dn=None):
|
||||
- self.cn = 'reload-' + Task._get_task_date()
|
||||
+ self.cn = 'reload-' + Task.get_timestamp()
|
||||
dn = f'cn={self.cn},cn=reload ldapi mappings,cn=tasks,cn=config'
|
||||
super(LDAPIMappingReloadTask, self).__init__(instance, dn)
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 61daedf85c534308ec5cc806ddd3d78c7df74b1f Mon Sep 17 00:00:00 2001
|
||||
From b6729a99f3a3d4c6ebe82d4bb60ea2a6f8727782 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 27 Jun 2025 18:43:39 -0700
|
||||
Subject: [PATCH] Issue 6822 - Backend creation cleanup and Database UI tab
|
||||
@ -14,16 +14,16 @@ Fixes: https://github.com/389ds/389-ds-base/issues/6822
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
src/cockpit/389-console/src/database.jsx | 119 ++++++++------
|
||||
src/cockpit/389-console/src/monitor.jsx | 165 +++++++++++---------
|
||||
src/cockpit/389-console/src/monitor.jsx | 172 +++++++++++---------
|
||||
src/cockpit/389-console/src/replication.jsx | 55 ++++---
|
||||
src/lib389/lib389/backend.py | 18 ++-
|
||||
4 files changed, 210 insertions(+), 147 deletions(-)
|
||||
src/lib389/lib389/backend.py | 18 +-
|
||||
4 files changed, 210 insertions(+), 154 deletions(-)
|
||||
|
||||
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
|
||||
index a2e6004a2..86b642b92 100644
|
||||
index c0c4be414..276125dfc 100644
|
||||
--- a/src/cockpit/389-console/src/database.jsx
|
||||
+++ b/src/cockpit/389-console/src/database.jsx
|
||||
@@ -480,6 +480,59 @@ export class Database extends React.Component {
|
||||
@@ -478,6 +478,59 @@ export class Database extends React.Component {
|
||||
}
|
||||
|
||||
loadSuffixTree(fullReset) {
|
||||
@ -83,7 +83,7 @@ index a2e6004a2..86b642b92 100644
|
||||
const cmd = [
|
||||
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
|
||||
"backend", "get-tree",
|
||||
@@ -493,58 +546,20 @@ export class Database extends React.Component {
|
||||
@@ -491,58 +544,20 @@ export class Database extends React.Component {
|
||||
suffixData = JSON.parse(content);
|
||||
this.processTree(suffixData);
|
||||
}
|
||||
@ -157,10 +157,10 @@ index a2e6004a2..86b642b92 100644
|
||||
if (fullReset) {
|
||||
current_node = DB_CONFIG;
|
||||
diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx
|
||||
index 7e0e0c5d4..c89be9051 100644
|
||||
index ad48d1f87..91a8e3e37 100644
|
||||
--- a/src/cockpit/389-console/src/monitor.jsx
|
||||
+++ b/src/cockpit/389-console/src/monitor.jsx
|
||||
@@ -199,6 +199,84 @@ export class Monitor extends React.Component {
|
||||
@@ -200,6 +200,84 @@ export class Monitor extends React.Component {
|
||||
}
|
||||
|
||||
loadSuffixTree(fullReset) {
|
||||
@ -245,7 +245,7 @@ index 7e0e0c5d4..c89be9051 100644
|
||||
const cmd = [
|
||||
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
|
||||
"backend", "get-tree",
|
||||
@@ -209,76 +287,7 @@ export class Monitor extends React.Component {
|
||||
@@ -210,83 +288,7 @@ export class Monitor extends React.Component {
|
||||
.done(content => {
|
||||
const treeData = JSON.parse(content);
|
||||
this.processTree(treeData);
|
||||
@ -270,6 +270,13 @@ index 7e0e0c5d4..c89be9051 100644
|
||||
- item: "sync-report",
|
||||
- type: "repl-mon",
|
||||
- },
|
||||
- {
|
||||
- name: _("Log Analysis"),
|
||||
- icon: <MonitoringIcon />,
|
||||
- id: "log-analysis",
|
||||
- item: "log-analysis",
|
||||
- type: "repl-mon",
|
||||
- }
|
||||
- ],
|
||||
- },
|
||||
- {
|
||||
@ -323,7 +330,7 @@ index 7e0e0c5d4..c89be9051 100644
|
||||
let current_node = this.state.node_name;
|
||||
let type = this.state.node_type;
|
||||
if (fullReset) {
|
||||
@@ -288,6 +297,22 @@ export class Monitor extends React.Component {
|
||||
@@ -296,6 +298,22 @@ export class Monitor extends React.Component {
|
||||
basicData[2].children = treeData; // database node
|
||||
this.processReplSuffixes(basicData[1].children);
|
||||
|
||||
@ -1,165 +0,0 @@
|
||||
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
|
||||
supplier generates duplicate nsUniqueId (LMDB only) (#6582)
|
||||
|
||||
Bug description:
|
||||
During an import the entry is prepared (schema, operational
|
||||
attributes, password encryption,...) before starting the
|
||||
update of the database and indexes.
|
||||
A step of the preparation is to assign a value to 'nsuniqueid'
|
||||
operational attribute. 'nsuniqueid' must be unique.
|
||||
In LMDB the preparation is done by multiple threads (workers).
|
||||
In such case the 'nsuniqueid' are generated in parallel and
|
||||
as it is time based several values can be duplicated.
|
||||
|
||||
Fix description:
|
||||
To prevent that the routine dbmdb_import_generate_uniqueid
|
||||
should make sure to synchronize the workers.
|
||||
|
||||
fixes: #6554
|
||||
|
||||
Reviewed by: Pierre Rogier
|
||||
---
|
||||
.../tests/suites/import/import_test.py | 79 ++++++++++++++++++-
|
||||
.../back-ldbm/db-mdb/mdb_import_threads.c | 11 +++
|
||||
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 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
|
||||
import pytest
|
||||
import time
|
||||
import glob
|
||||
+import re
|
||||
import logging
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from lib389.topologies import topology_st as topo
|
||||
-from lib389._constants import DEFAULT_SUFFIX, TaskWarning
|
||||
+from lib389.topologies import topology_m2 as topo_m2
|
||||
+from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX, TaskWarning
|
||||
from lib389.dbgen import dbgen_users
|
||||
from lib389.tasks import ImportTask
|
||||
from lib389.index import Indexes
|
||||
@@ -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):
|
||||
+ """Test that after an offline import all
|
||||
+ nsuniqueid are different
|
||||
+
|
||||
+ :id: a2541677-a288-4633-bacf-4050cc56016d
|
||||
+ :setup: MMR with 2 suppliers
|
||||
+ :steps:
|
||||
+ 1. stop the instance to do offline operations
|
||||
+ 2. Generate a 5K users LDIF file
|
||||
+ 3. Check that no uniqueid are present in the generated file
|
||||
+ 4. import the generated LDIF
|
||||
+ 5. export the database
|
||||
+ 6. Check that that exported LDIF contains more than 5K nsuniqueid
|
||||
+ 7. Check that there is no duplicate nsuniqued in exported LDIF
|
||||
+ :expectedresults:
|
||||
+ 1. Should succeeds
|
||||
+ 2. Should succeeds
|
||||
+ 3. Should succeeds
|
||||
+ 4. Should succeeds
|
||||
+ 5. Should succeeds
|
||||
+ 6. Should succeeds
|
||||
+ 7. Should succeeds
|
||||
+ """
|
||||
+ m1 = topo_m2.ms["supplier1"]
|
||||
+
|
||||
+ # Stop the instance
|
||||
+ m1.stop()
|
||||
+
|
||||
+ # Generate a test ldif (5k entries)
|
||||
+ log.info("Generating LDIF...")
|
||||
+ ldif_dir = m1.get_ldif_dir()
|
||||
+ import_ldif = ldif_dir + '/5k_users_import.ldif'
|
||||
+ dbgen_users(m1, 5000, import_ldif, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # Check that the generated LDIF does not contain nsuniqueid
|
||||
+ all_nsuniqueid = []
|
||||
+ with open(import_ldif, 'r') as file:
|
||||
+ for line in file:
|
||||
+ if line.lower().startswith("nsuniqueid: "):
|
||||
+ all_nsuniqueid.append(line.split(': ')[1])
|
||||
+ log.info("import file contains " + str(len(all_nsuniqueid)) + " nsuniqueid")
|
||||
+ assert len(all_nsuniqueid) == 0
|
||||
+
|
||||
+ # Import the "nsuniquied free" LDIF file
|
||||
+ if not m1.ldif2db('userRoot', None, None, None, import_ldif):
|
||||
+ assert False
|
||||
+
|
||||
+ # Export the DB that now should contain nsuniqueid
|
||||
+ export_ldif = ldif_dir + '/5k_user_export.ldif'
|
||||
+ log.info("export to file " + export_ldif)
|
||||
+ m1.db2ldif(bename=DEFAULT_BENAME, suffixes=[DEFAULT_SUFFIX],
|
||||
+ excludeSuffixes=None, repl_data=False,
|
||||
+ outputfile=export_ldif, encrypt=False)
|
||||
+
|
||||
+ # Check that the export LDIF contain nsuniqueid
|
||||
+ all_nsuniqueid = []
|
||||
+ with open(export_ldif, 'r') as file:
|
||||
+ for line in file:
|
||||
+ if line.lower().startswith("nsuniqueid: "):
|
||||
+ all_nsuniqueid.append(line.split(': ')[1])
|
||||
+ log.info("export file " + export_ldif + " contains " + str(len(all_nsuniqueid)) + " nsuniqueid")
|
||||
+ assert len(all_nsuniqueid) >= 5000
|
||||
+
|
||||
+ # Check that the nsuniqueid are unique
|
||||
+ assert len(set(all_nsuniqueid)) == len(all_nsuniqueid)
|
||||
+
|
||||
+ def fin():
|
||||
+ if os.path.exists(import_ldif):
|
||||
+ os.remove(import_ldif)
|
||||
+ if os.path.exists(export_ldif):
|
||||
+ os.remove(export_ldif)
|
||||
+ m1.start
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# 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 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
|
||||
@@ -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;
|
||||
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
if (!uniqueid && (job->uuid_gen_type != SLAPI_UNIQUEID_GENERATE_NONE)) {
|
||||
char *newuniqueid;
|
||||
|
||||
+ /* With 'mdb' we have several workers generating nsuniqueid
|
||||
+ * we need to serialize them to prevent generating duplicate value
|
||||
+ * From performance pov it only impacts import
|
||||
+ * The default value is SLAPI_UNIQUEID_GENERATE_TIME_BASED so
|
||||
+ * the only syscall is clock_gettime and then string formating
|
||||
+ * that should limit contention
|
||||
+ */
|
||||
+ pthread_mutex_lock(&mutex);
|
||||
+
|
||||
/* generate id based on dn */
|
||||
if (job->uuid_gen_type == SLAPI_UNIQUEID_GENERATE_NAME_BASED) {
|
||||
char *dn = slapi_entry_get_dn(e);
|
||||
@@ -624,6 +634,7 @@ dbmdb_import_generate_uniqueid(ImportJob *job, Slapi_Entry *e)
|
||||
/* time based */
|
||||
rc = slapi_uniqueIDGenerateString(&newuniqueid);
|
||||
}
|
||||
+ pthread_mutex_unlock(&mutex);
|
||||
|
||||
if (rc == UID_SUCCESS) {
|
||||
slapi_entry_set_uniqueid(e, newuniqueid);
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 33f67a8ada0258c88a4e5c6c5ce7ba3aa9a048d8 Mon Sep 17 00:00:00 2001
|
||||
From 0a7fe7c6e18759459499f468443ded4313ebdeab Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Wed, 9 Jul 2025 12:08:09 +0300
|
||||
Subject: [PATCH] Issue 6857 - uiduniq: allow specifying match rules in the
|
||||
@ -1,38 +0,0 @@
|
||||
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
|
||||
|
||||
Description:
|
||||
TLS 1.3 works with NSS in FIPS mode for quite some time now,
|
||||
this restriction is no longer needed.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6561
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/ssl.c | 8 --------
|
||||
1 file changed, 8 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
|
||||
index 94259efe7..84a7fb004 100644
|
||||
--- a/ldap/servers/slapd/ssl.c
|
||||
+++ b/ldap/servers/slapd/ssl.c
|
||||
@@ -1929,14 +1929,6 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
|
||||
*/
|
||||
sslStatus = SSL_VersionRangeGet(pr_sock, &slapdNSSVersions);
|
||||
if (sslStatus == SECSuccess) {
|
||||
- if (slapdNSSVersions.max > LDAP_OPT_X_TLS_PROTOCOL_TLS1_2 && fipsMode) {
|
||||
- /*
|
||||
- * FIPS & NSS currently only support a max version of TLS1.2
|
||||
- * (although NSS advertises 1.3 as a max range in FIPS mode),
|
||||
- * hopefully this code block can be removed soon...
|
||||
- */
|
||||
- slapdNSSVersions.max = LDAP_OPT_X_TLS_PROTOCOL_TLS1_2;
|
||||
- }
|
||||
/* Reset request range */
|
||||
sslStatus = SSL_VersionRangeSet(pr_sock, &slapdNSSVersions);
|
||||
if (sslStatus == SECSuccess) {
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 1246860fa3ee52ba113759b524aa56c948161a12 Mon Sep 17 00:00:00 2001
|
||||
From b28b00ee5169cfb00414bc9bcca67f88432ad567 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Thu, 10 Jul 2025 11:53:12 -0700
|
||||
Subject: [PATCH] Issue 6756 - CLI, UI - Properly handle disabled NDN cache
|
||||
@ -135,7 +135,7 @@ index 4a82eb0ef..b04ee67c9 100644
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
|
||||
index c0c4be414..a2e6004a2 100644
|
||||
index 276125dfc..86b642b92 100644
|
||||
--- a/src/cockpit/389-console/src/database.jsx
|
||||
+++ b/src/cockpit/389-console/src/database.jsx
|
||||
@@ -198,7 +198,7 @@ export class Database extends React.Component {
|
||||
@ -1,44 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 63552e6f8fcd1da02338c229f665772507016c5e Mon Sep 17 00:00:00 2001
|
||||
From 5198da59d622dbc39afe2ece9c6f40f4fb249d52 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 9 Jul 2025 14:18:50 -0400
|
||||
Subject: [PATCH] Issue 6859 - str2filter is not fully applying matching rules
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
From c018a0ca3758ecd716c0188c1821e24168fb71b8 Mon Sep 17 00:00:00 2001
|
||||
From 406563c136d78235751e34a3c7e22ccaf114f754 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 15 Jul 2025 17:56:18 -0400
|
||||
Subject: [PATCH] Issue 6872 - compressed log rotation creates files with world
|
||||
@ -57,7 +57,7 @@ index e30874cc0..3a987d62c 100644
|
||||
|
||||
def update_and_sleep(inst, suffix, sleep=True):
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index c1260a203..b5443830d 100644
|
||||
index a018ca2d5..178d29b89 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -172,17 +172,28 @@ get_syslog_loglevel(int loglevel)
|
||||
@ -146,7 +146,7 @@ index c1260a203..b5443830d 100644
|
||||
"failed to compress rotated auditfail log (%s)\n",
|
||||
newfile);
|
||||
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
|
||||
index 401a3dce8..9dee642b9 100644
|
||||
index a8e6b1210..9ef4ee4bf 100644
|
||||
--- a/ldap/servers/slapd/schema.c
|
||||
+++ b/ldap/servers/slapd/schema.c
|
||||
@@ -903,7 +903,7 @@ oc_check_allowed_sv(Slapi_PBlock *pb, Slapi_Entry *e, const char *type, struct o
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,116 @@
|
||||
From 9b8b23f6d46f16fbc1784b26cfc04dd6b4fa94e1 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 18 Jul 2025 18:50:33 -0700
|
||||
Subject: [PATCH] Issue 6878 - Prevent repeated disconnect logs during shutdown
|
||||
(#6879)
|
||||
|
||||
Description: Avoid logging non-active initialized connections via CONN in disconnect_server_nomutex_ext by adding a check to skip invalid conn=0 with invalid sockets, preventing excessive repeated messages.
|
||||
|
||||
Update ds_logs_test.py by adding test_no_repeated_disconnect_messages to verify the fix.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6878
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 51 ++++++++++++++++++-
|
||||
ldap/servers/slapd/connection.c | 15 +++---
|
||||
2 files changed, 59 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 2c22347bb..b86c72687 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -24,7 +24,7 @@ from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, Aut
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
from lib389.idm.group import Groups
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
-from lib389._constants import DEFAULT_SUFFIX, LOG_ACCESS_LEVEL, PASSWORD
|
||||
+from lib389._constants import DEFAULT_SUFFIX, LOG_ACCESS_LEVEL, PASSWORD, ErrorLog
|
||||
from lib389.utils import ds_is_older, ds_is_newer
|
||||
from lib389.config import RSA
|
||||
from lib389.dseldif import DSEldif
|
||||
@@ -1435,6 +1435,55 @@ def test_errorlog_buffering(topology_st, request):
|
||||
assert inst.ds_error_log.match(".*slapd_daemon - slapd started.*")
|
||||
|
||||
|
||||
+def test_no_repeated_disconnect_messages(topology_st):
|
||||
+ """Test that there are no repeated "Not setting conn 0 to be disconnected: socket is invalid" messages on restart
|
||||
+
|
||||
+ :id: 72b5e1ce-2db8-458f-b2cd-0a0b6525f51f
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Set error log level to CONNECTION
|
||||
+ 2. Clear existing error logs
|
||||
+ 3. Restart the server with 30 second timeout
|
||||
+ 4. Check error log for repeated disconnect messages
|
||||
+ 5. Verify there are no more than 10 occurrences of the disconnect message
|
||||
+ :expectedresults:
|
||||
+ 1. Error log level should be set successfully
|
||||
+ 2. Error logs should be cleared
|
||||
+ 3. Server should restart successfully within 30 seconds
|
||||
+ 4. Error log should be accessible
|
||||
+ 5. There should be no more than 10 repeated disconnect messages
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ log.info('Set error log level to CONNECTION')
|
||||
+ inst.config.loglevel([ErrorLog.CONNECT])
|
||||
+ current_level = inst.config.get_attr_val_int('nsslapd-errorlog-level')
|
||||
+ log.info(f'Error log level set to: {current_level}')
|
||||
+
|
||||
+ log.info('Clear existing error logs')
|
||||
+ inst.deleteErrorLogs()
|
||||
+
|
||||
+ log.info('Restart the server with 30 second timeout')
|
||||
+ inst.restart(timeout=30)
|
||||
+
|
||||
+ log.info('Check error log for repeated disconnect messages')
|
||||
+ disconnect_message = "Not setting conn 0 to be disconnected: socket is invalid"
|
||||
+
|
||||
+ # Count occurrences of the disconnect message
|
||||
+ error_log_lines = inst.ds_error_log.readlines()
|
||||
+ disconnect_count = 0
|
||||
+
|
||||
+ for line in error_log_lines:
|
||||
+ if disconnect_message in line:
|
||||
+ disconnect_count += 1
|
||||
+
|
||||
+ log.info(f'Found {disconnect_count} occurrences of disconnect message')
|
||||
+
|
||||
+ log.info('Verify there are no more than 10 occurrences')
|
||||
+ assert disconnect_count <= 10, f"Found {disconnect_count} repeated disconnect messages, expected <= 10"
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
|
||||
index bb4fcd77f..2967de15b 100644
|
||||
--- a/ldap/servers/slapd/connection.c
|
||||
+++ b/ldap/servers/slapd/connection.c
|
||||
@@ -2465,12 +2465,15 @@ disconnect_server_nomutex_ext(Connection *conn, PRUint64 opconnid, int opid, PRE
|
||||
}
|
||||
|
||||
} else {
|
||||
- slapi_log_err(SLAPI_LOG_CONNS, "disconnect_server_nomutex_ext",
|
||||
- "Not setting conn %d to be disconnected: %s\n",
|
||||
- conn->c_sd,
|
||||
- (conn->c_sd == SLAPD_INVALID_SOCKET) ? "socket is invalid" :
|
||||
- ((conn->c_connid != opconnid) ? "conn id does not match op conn id" :
|
||||
- ((conn->c_flags & CONN_FLAG_CLOSING) ? "conn is closing" : "unknown")));
|
||||
+ /* We avoid logging an invalid conn=0 connection as it is not a real connection. */
|
||||
+ if (!(conn->c_sd == SLAPD_INVALID_SOCKET && conn->c_connid == 0)) {
|
||||
+ slapi_log_err(SLAPI_LOG_CONNS, "disconnect_server_nomutex_ext",
|
||||
+ "Not setting conn %d to be disconnected: %s\n",
|
||||
+ conn->c_sd,
|
||||
+ (conn->c_sd == SLAPD_INVALID_SOCKET) ? "socket is invalid" :
|
||||
+ ((conn->c_connid != opconnid) ? "conn id does not match op conn id" :
|
||||
+ ((conn->c_flags & CONN_FLAG_CLOSING) ? "conn is closing" : "unknown")));
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,67 @@
|
||||
From fef4875a9c3d67ef424a1fb1698ae011152735b1 Mon Sep 17 00:00:00 2001
|
||||
From: Anuar Beisembayev <111912342+abeisemb@users.noreply.github.com>
|
||||
Date: Wed, 23 Jul 2025 23:48:11 -0400
|
||||
Subject: [PATCH] Issue 6772 - dsconf - Replicas with the "consumer" role allow
|
||||
for viewing and modification of their changelog. (#6773)
|
||||
|
||||
dsconf currently allows users to set and retrieve changelogs in consumer replicas, which do not have officially supported changelogs. This can lead to undefined behavior and confusion.
|
||||
This commit prints a warning message if the user tries to interact with a changelog on a consumer replica.
|
||||
|
||||
Resolves: https://github.com/389ds/389-ds-base/issues/6772
|
||||
|
||||
Reviewed by: @droideck
|
||||
---
|
||||
src/lib389/lib389/cli_conf/replication.py | 23 +++++++++++++++++++++++
|
||||
1 file changed, 23 insertions(+)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
|
||||
index 6f77f34ca..a18bf83ca 100644
|
||||
--- a/src/lib389/lib389/cli_conf/replication.py
|
||||
+++ b/src/lib389/lib389/cli_conf/replication.py
|
||||
@@ -686,6 +686,9 @@ def set_per_backend_cl(inst, basedn, log, args):
|
||||
replace_list = []
|
||||
did_something = False
|
||||
|
||||
+ if (is_replica_role_consumer(inst, suffix)):
|
||||
+ log.info("Warning: Changelogs are not supported for consumer replicas. You may run into undefined behavior.")
|
||||
+
|
||||
if args.encrypt:
|
||||
cl.replace('nsslapd-encryptionalgorithm', 'AES')
|
||||
del args.encrypt
|
||||
@@ -715,6 +718,10 @@ def set_per_backend_cl(inst, basedn, log, args):
|
||||
# that means there is a changelog config entry per backend (aka suffix)
|
||||
def get_per_backend_cl(inst, basedn, log, args):
|
||||
suffix = args.suffix
|
||||
+
|
||||
+ if (is_replica_role_consumer(inst, suffix)):
|
||||
+ log.info("Warning: Changelogs are not supported for consumer replicas. You may run into undefined behavior.")
|
||||
+
|
||||
cl = Changelog(inst, suffix)
|
||||
if args and args.json:
|
||||
log.info(cl.get_all_attrs_json())
|
||||
@@ -822,6 +829,22 @@ def del_repl_manager(inst, basedn, log, args):
|
||||
|
||||
log.info("Successfully deleted replication manager: " + manager_dn)
|
||||
|
||||
+def is_replica_role_consumer(inst, suffix):
|
||||
+ """Helper function for get_per_backend_cl and set_per_backend_cl.
|
||||
+ Makes sure the instance in question is not a consumer, which is a role that
|
||||
+ does not support changelogs.
|
||||
+ """
|
||||
+ replicas = Replicas(inst)
|
||||
+ try:
|
||||
+ replica = replicas.get(suffix)
|
||||
+ role = replica.get_role()
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ raise ValueError(f"Backend \"{suffix}\" is not enabled for replication")
|
||||
+
|
||||
+ if role == ReplicaRole.CONSUMER:
|
||||
+ return True
|
||||
+ else:
|
||||
+ return False
|
||||
|
||||
#
|
||||
# Agreements
|
||||
--
|
||||
2.49.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
From cfacc0ecc9de1d772472c2dc4e05702c4ff170e8 Mon Sep 17 00:00:00 2001
|
||||
From 4cb50f83397e6a5e14a9b75ed15f24189ee2792b Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 21 Jul 2025 18:07:21 -0400
|
||||
Subject: [PATCH] Issue 6893 - Log user that is updated during password modify
|
||||
@ -22,10 +22,10 @@ Reviewed by: spichugi & tbordaz(Thanks!!)
|
||||
1 file changed, 28 insertions(+), 28 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/passwd_extop.c b/ldap/servers/slapd/passwd_extop.c
|
||||
index f758ac018..4d185f8dd 100644
|
||||
index 4bb60afd6..0296d64fb 100644
|
||||
--- a/ldap/servers/slapd/passwd_extop.c
|
||||
+++ b/ldap/servers/slapd/passwd_extop.c
|
||||
@@ -456,12 +456,13 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
@@ -465,12 +465,13 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
BerElement *response_ber = NULL;
|
||||
Slapi_Entry *targetEntry = NULL;
|
||||
Connection *conn = NULL;
|
||||
@ -40,7 +40,7 @@ index f758ac018..4d185f8dd 100644
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "passwd_modify_extop", "=>\n");
|
||||
|
||||
@@ -639,7 +640,7 @@ parse_req_done:
|
||||
@@ -647,7 +648,7 @@ parse_req_done:
|
||||
}
|
||||
dn = slapi_sdn_get_ndn(target_sdn);
|
||||
if (dn == NULL || *dn == '\0') {
|
||||
@ -49,7 +49,7 @@ index f758ac018..4d185f8dd 100644
|
||||
errMesg = "Invalid dn.";
|
||||
rc = LDAP_INVALID_DN_SYNTAX;
|
||||
goto free_and_return;
|
||||
@@ -716,14 +717,19 @@ parse_req_done:
|
||||
@@ -724,14 +725,19 @@ parse_req_done:
|
||||
ber_free(response_ber, 1);
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ index f758ac018..4d185f8dd 100644
|
||||
rc = LDAP_NO_SUCH_OBJECT;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -734,30 +740,18 @@ parse_req_done:
|
||||
@@ -742,30 +748,18 @@ parse_req_done:
|
||||
leak any useful information to the client such as current password
|
||||
wrong, etc.
|
||||
*/
|
||||
@ -110,7 +110,7 @@ index f758ac018..4d185f8dd 100644
|
||||
|
||||
/* Check if the pwpolicy control is present */
|
||||
slapi_pblock_get(pb, SLAPI_PWPOLICY, &need_pwpolicy_ctrl);
|
||||
@@ -789,10 +783,7 @@ parse_req_done:
|
||||
@@ -797,10 +791,7 @@ parse_req_done:
|
||||
/* Check if password policy allows users to change their passwords. We need to do
|
||||
* this here since the normal modify code doesn't perform this check for
|
||||
* internal operations. */
|
||||
@ -122,7 +122,7 @@ index f758ac018..4d185f8dd 100644
|
||||
if (NULL == bindSDN) {
|
||||
bindSDN = slapi_sdn_new_normdn_byref(bindDN);
|
||||
}
|
||||
@@ -840,6 +831,15 @@ free_and_return:
|
||||
@@ -848,6 +839,15 @@ free_and_return:
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop",
|
||||
"%s\n", errMesg ? errMesg : "success");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
From a1ed57bede43d6af8418dd029e640f3e839c9110 Mon Sep 17 00:00:00 2001
|
||||
From ffc3a81ed5852b7f1fbaed79b9b776af23d65b7c Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 23 Jul 2025 19:35:32 -0400
|
||||
Subject: [PATCH] Issue 6895 - Crash if repl keep alive entry can not be
|
||||
@ -0,0 +1,352 @@
|
||||
From 191634746fdcb7e26a154cd00a22324e02a10110 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 10:50:26 -0700
|
||||
Subject: [PATCH] Issue 6250 - Add test for entryUSN overflow on failed add
|
||||
operations (#6821)
|
||||
|
||||
Description: Add comprehensive test to reproduce the entryUSN
|
||||
overflow issue where failed attempts to add existing entries followed by
|
||||
modify operations cause entryUSN values to underflow/overflow instead of
|
||||
incrementing properly.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6250
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
.../suites/plugins/entryusn_overflow_test.py | 323 ++++++++++++++++++
|
||||
1 file changed, 323 insertions(+)
|
||||
create mode 100644 dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py
|
||||
new file mode 100644
|
||||
index 000000000..a23d734ca
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py
|
||||
@@ -0,0 +1,323 @@
|
||||
+# --- 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 ldap
|
||||
+import logging
|
||||
+import pytest
|
||||
+import time
|
||||
+import random
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.config import Config
|
||||
+from lib389.plugins import USNPlugin
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.rootdse import RootDSE
|
||||
+
|
||||
+pytestmark = pytest.mark.tier2
|
||||
+
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+# Test constants
|
||||
+DEMO_USER_BASE_DN = "uid=demo_user,ou=people," + DEFAULT_SUFFIX
|
||||
+TEST_USER_PREFIX = "Demo User"
|
||||
+MAX_USN_64BIT = 18446744073709551615 # 2^64 - 1
|
||||
+ITERATIONS = 10
|
||||
+ADD_EXISTING_ENTRY_MAX_ATTEMPTS = 5
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="module")
|
||||
+def setup_usn_test(topology_st, request):
|
||||
+ """Setup USN plugin and test data for entryUSN overflow testing"""
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ log.info("Enable the USN plugin...")
|
||||
+ plugin = USNPlugin(inst)
|
||||
+ plugin.enable()
|
||||
+ plugin.enable_global_mode()
|
||||
+
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Create initial test users
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ created_users = []
|
||||
+
|
||||
+ log.info("Creating initial test users...")
|
||||
+ for i in range(3):
|
||||
+ user_props = {
|
||||
+ 'uid': f'{TEST_USER_PREFIX}-{i}',
|
||||
+ 'cn': f'{TEST_USER_PREFIX}-{i}',
|
||||
+ 'sn': f'User{i}',
|
||||
+ 'uidNumber': str(1000 + i),
|
||||
+ 'gidNumber': str(1000 + i),
|
||||
+ 'homeDirectory': f'/home/{TEST_USER_PREFIX}-{i}',
|
||||
+ 'userPassword': 'password123'
|
||||
+ }
|
||||
+ try:
|
||||
+ user = users.create(properties=user_props)
|
||||
+ created_users.append(user)
|
||||
+ log.info(f"Created user: {user.dn}")
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"User {user_props['uid']} already exists, skipping creation")
|
||||
+ user = users.get(user_props['uid'])
|
||||
+ created_users.append(user)
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info("Cleaning up test users...")
|
||||
+ for user in created_users:
|
||||
+ try:
|
||||
+ user.delete()
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return created_users
|
||||
+
|
||||
+
|
||||
+def test_entryusn_overflow_on_add_existing_entries(topology_st, setup_usn_test):
|
||||
+ """Test that reproduces entryUSN overflow when adding existing entries
|
||||
+
|
||||
+ :id: a5a8c33d-82f3-4113-be2b-027de51791c8
|
||||
+ :setup: Standalone instance with USN plugin enabled and test users
|
||||
+ :steps:
|
||||
+ 1. Record initial entryUSN values for existing users
|
||||
+ 2. Attempt to add existing entries multiple times (should fail)
|
||||
+ 3. Perform modify operations on the entries
|
||||
+ 4. Check that entryUSN values increment correctly without overflow
|
||||
+ 5. Verify lastusn values are consistent
|
||||
+ :expectedresults:
|
||||
+ 1. Initial entryUSN values are recorded successfully
|
||||
+ 2. Add operations fail with ALREADY_EXISTS error
|
||||
+ 3. Modify operations succeed
|
||||
+ 4. EntryUSN values increment properly without underflow/overflow
|
||||
+ 5. LastUSN values are consistent and increasing
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ users = setup_usn_test
|
||||
+
|
||||
+ # Enable detailed logging for debugging
|
||||
+ config = Config(inst)
|
||||
+ config.replace('nsslapd-accesslog-level', '260') # Internal op logging
|
||||
+ config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ config.replace('nsslapd-plugin-logging', 'on')
|
||||
+
|
||||
+ root_dse = RootDSE(inst)
|
||||
+
|
||||
+ log.info("Starting entryUSN overflow reproduction test")
|
||||
+
|
||||
+ # Record initial state
|
||||
+ initial_usn_values = {}
|
||||
+ for user in users:
|
||||
+ initial_usn = user.get_attr_val_int('entryusn')
|
||||
+ initial_usn_values[user.dn] = initial_usn
|
||||
+ log.info(f"Initial entryUSN for {user.get_attr_val_utf8('cn')}: {initial_usn}")
|
||||
+
|
||||
+ initial_lastusn = root_dse.get_attr_val_int("lastusn")
|
||||
+ log.info(f"Initial lastUSN: {initial_lastusn}")
|
||||
+
|
||||
+ # Perform test iterations
|
||||
+ for iteration in range(1, ITERATIONS + 1):
|
||||
+ log.info(f"\n--- Iteration {iteration} ---")
|
||||
+
|
||||
+ # Step 1: Try to add existing entries multiple times
|
||||
+ selected_user = random.choice(users)
|
||||
+ cn_value = selected_user.get_attr_val_utf8('cn')
|
||||
+ attempts = random.randint(1, ADD_EXISTING_ENTRY_MAX_ATTEMPTS)
|
||||
+
|
||||
+ log.info(f"Attempting to add existing entry '{cn_value}' {attempts} times")
|
||||
+
|
||||
+ # Get user attributes for recreation attempt
|
||||
+ user_attrs = {
|
||||
+ 'uid': selected_user.get_attr_val_utf8('uid'),
|
||||
+ 'cn': selected_user.get_attr_val_utf8('cn'),
|
||||
+ 'sn': selected_user.get_attr_val_utf8('sn'),
|
||||
+ 'uidNumber': selected_user.get_attr_val_utf8('uidNumber'),
|
||||
+ 'gidNumber': selected_user.get_attr_val_utf8('gidNumber'),
|
||||
+ 'homeDirectory': selected_user.get_attr_val_utf8('homeDirectory'),
|
||||
+ 'userPassword': 'password123'
|
||||
+ }
|
||||
+
|
||||
+ users_collection = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # Try to add the existing user multiple times
|
||||
+ for attempt in range(attempts):
|
||||
+ try:
|
||||
+ users_collection.create(properties=user_attrs)
|
||||
+ log.error(f"ERROR: Add operation should have failed but succeeded on attempt {attempt + 1}")
|
||||
+ assert False, "Add operation should have failed with ALREADY_EXISTS"
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"Attempt {attempt + 1}: Got expected ALREADY_EXISTS error")
|
||||
+ except Exception as e:
|
||||
+ log.error(f"Unexpected error on attempt {attempt + 1}: {e}")
|
||||
+ raise
|
||||
+
|
||||
+ # Step 2: Perform modify operation
|
||||
+ target_user = random.choice(users)
|
||||
+ cn_value = target_user.get_attr_val_utf8('cn')
|
||||
+ old_usn = target_user.get_attr_val_int('entryusn')
|
||||
+
|
||||
+ # Modify the user entry
|
||||
+ new_description = f"Modified in iteration {iteration} - {time.time()}"
|
||||
+ target_user.replace('description', new_description)
|
||||
+
|
||||
+ # Get new USN value
|
||||
+ new_usn = target_user.get_attr_val_int('entryusn')
|
||||
+
|
||||
+ log.info(f"Modified entry '{cn_value}': old USN = {old_usn}, new USN = {new_usn}")
|
||||
+
|
||||
+ # Step 3: Validate USN values
|
||||
+ # Check for overflow/underflow conditions
|
||||
+ assert new_usn > 0, f"EntryUSN should be positive, got {new_usn}"
|
||||
+ assert new_usn < MAX_USN_64BIT, f"EntryUSN overflow detected: {new_usn} >= {MAX_USN_64BIT}"
|
||||
+
|
||||
+ # Check that USN didn't wrap around (underflow detection)
|
||||
+ usn_diff = new_usn - old_usn
|
||||
+ assert usn_diff < 1000, f"USN increment too large, possible overflow: {usn_diff}"
|
||||
+
|
||||
+ # Verify lastUSN is also reasonable
|
||||
+ current_lastusn = root_dse.get_attr_val_int("lastusn")
|
||||
+ assert current_lastusn >= new_usn, f"LastUSN ({current_lastusn}) should be >= entryUSN ({new_usn})"
|
||||
+ assert current_lastusn < MAX_USN_64BIT, f"LastUSN overflow detected: {current_lastusn}"
|
||||
+
|
||||
+ log.info(f"USN validation passed for iteration {iteration}")
|
||||
+
|
||||
+ # Add a new entry occasionally to increase USN diversity
|
||||
+ if iteration % 3 == 0:
|
||||
+ new_user_props = {
|
||||
+ 'uid': f'{TEST_USER_PREFIX}-new-{iteration}',
|
||||
+ 'cn': f'{TEST_USER_PREFIX}-new-{iteration}',
|
||||
+ 'sn': f'NewUser{iteration}',
|
||||
+ 'uidNumber': str(2000 + iteration),
|
||||
+ 'gidNumber': str(2000 + iteration),
|
||||
+ 'homeDirectory': f'/home/{TEST_USER_PREFIX}-new-{iteration}',
|
||||
+ 'userPassword': 'newpassword123'
|
||||
+ }
|
||||
+ try:
|
||||
+ new_user = users_collection.create(properties=new_user_props)
|
||||
+ new_user_usn = new_user.get_attr_val_int('entryusn')
|
||||
+ log.info(f"Created new entry '{new_user.get_attr_val_utf8('cn')}' with USN: {new_user_usn}")
|
||||
+ users.append(new_user) # Add to cleanup list
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to create new user in iteration {iteration}: {e}")
|
||||
+
|
||||
+ # Final validation: Check all USN values are reasonable
|
||||
+ log.info("\nFinal USN validation")
|
||||
+ final_lastusn = root_dse.get_attr_val_int("lastusn")
|
||||
+
|
||||
+ for user in users:
|
||||
+ try:
|
||||
+ final_usn = user.get_attr_val_int('entryusn')
|
||||
+ cn_value = user.get_attr_val_utf8('cn')
|
||||
+ log.info(f"Final entryUSN for '{cn_value}': {final_usn}")
|
||||
+
|
||||
+ # Ensure no overflow occurred
|
||||
+ assert final_usn > 0, f"Final entryUSN should be positive for {cn_value}: {final_usn}"
|
||||
+ assert final_usn < MAX_USN_64BIT, f"EntryUSN overflow for {cn_value}: {final_usn}"
|
||||
+
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ log.info(f"User {user.dn} was deleted during test")
|
||||
+
|
||||
+ log.info(f"Final lastUSN: {final_lastusn}")
|
||||
+ assert final_lastusn > initial_lastusn, "LastUSN should have increased during test"
|
||||
+ assert final_lastusn < MAX_USN_64BIT, f"LastUSN overflow detected: {final_lastusn}"
|
||||
+
|
||||
+ log.info("EntryUSN overflow test completed successfully")
|
||||
+
|
||||
+
|
||||
+def test_entryusn_consistency_after_failed_adds(topology_st, setup_usn_test):
|
||||
+ """Test that entryUSN remains consistent after failed add operations
|
||||
+
|
||||
+ :id: e380ccad-527b-427e-a331-df5c41badbed
|
||||
+ :setup: Standalone instance with USN plugin enabled and test users
|
||||
+ :steps:
|
||||
+ 1. Record entryUSN values before failed add attempts
|
||||
+ 2. Attempt to add existing entries (should fail)
|
||||
+ 3. Verify entryUSN values haven't changed due to failed operations
|
||||
+ 4. Perform successful modify operations
|
||||
+ 5. Verify entryUSN increments correctly
|
||||
+ :expectedresults:
|
||||
+ 1. Initial entryUSN values recorded
|
||||
+ 2. Add operations fail as expected
|
||||
+ 3. EntryUSN values unchanged after failed adds
|
||||
+ 4. Modify operations succeed
|
||||
+ 5. EntryUSN values increment correctly without overflow
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ users = setup_usn_test
|
||||
+
|
||||
+ log.info("Testing entryUSN consistency after failed adds")
|
||||
+
|
||||
+ # Record USN values before any operations
|
||||
+ pre_operation_usns = {}
|
||||
+ for user in users:
|
||||
+ usn = user.get_attr_val_int('entryusn')
|
||||
+ pre_operation_usns[user.dn] = usn
|
||||
+ log.info(f"Pre-operation entryUSN for {user.get_attr_val_utf8('cn')}: {usn}")
|
||||
+
|
||||
+ # Attempt to add existing entries - these should fail
|
||||
+ users_collection = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ for user in users:
|
||||
+ cn_value = user.get_attr_val_utf8('cn')
|
||||
+ log.info(f"Attempting to add existing user: {cn_value}")
|
||||
+
|
||||
+ user_attrs = {
|
||||
+ 'uid': user.get_attr_val_utf8('uid'),
|
||||
+ 'cn': cn_value,
|
||||
+ 'sn': user.get_attr_val_utf8('sn'),
|
||||
+ 'uidNumber': user.get_attr_val_utf8('uidNumber'),
|
||||
+ 'gidNumber': user.get_attr_val_utf8('gidNumber'),
|
||||
+ 'homeDirectory': user.get_attr_val_utf8('homeDirectory'),
|
||||
+ 'userPassword': 'password123'
|
||||
+ }
|
||||
+
|
||||
+ try:
|
||||
+ users_collection.create(properties=user_attrs)
|
||||
+ assert False, f"Add operation should have failed for existing user {cn_value}"
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"Got expected ALREADY_EXISTS for {cn_value}")
|
||||
+
|
||||
+ # Verify USN values haven't changed after failed adds
|
||||
+ log.info("Verifying entryUSN values after failed add operations...")
|
||||
+ for user in users:
|
||||
+ current_usn = user.get_attr_val_int('entryusn')
|
||||
+ expected_usn = pre_operation_usns[user.dn]
|
||||
+ cn_value = user.get_attr_val_utf8('cn')
|
||||
+
|
||||
+ assert current_usn == expected_usn, \
|
||||
+ f"EntryUSN changed after failed add for {cn_value}: was {expected_usn}, now {current_usn}"
|
||||
+ log.info(f"EntryUSN unchanged for {cn_value}: {current_usn}")
|
||||
+
|
||||
+ # Now perform successful modify operations
|
||||
+ log.info("Performing successful modify operations...")
|
||||
+ for i, user in enumerate(users):
|
||||
+ cn_value = user.get_attr_val_utf8('cn')
|
||||
+ old_usn = user.get_attr_val_int('entryusn')
|
||||
+
|
||||
+ # Modify the user
|
||||
+ user.replace('description', f'Consistency test modification {i + 1}')
|
||||
+
|
||||
+ new_usn = user.get_attr_val_int('entryusn')
|
||||
+ log.info(f"Modified {cn_value}: USN {old_usn} -> {new_usn}")
|
||||
+
|
||||
+ # Verify proper increment
|
||||
+ assert (new_usn - old_usn) == 1, f"EntryUSN should increment by 1 for {cn_value}: {old_usn} -> {new_usn}"
|
||||
+ assert new_usn < MAX_USN_64BIT, f"EntryUSN overflow for {cn_value}: {new_usn}"
|
||||
+
|
||||
+ log.info("EntryUSN consistency test completed successfully")
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
\ No newline at end of file
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,975 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 7534ba4c47683fcb758d527b0658ee86099f5a4f Mon Sep 17 00:00:00 2001
|
||||
From 37a56f75afac2805e1ba958eebd496e77b7079e7 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 15:35:50 -0700
|
||||
Subject: [PATCH] Issue 6594 - Add test for numSubordinates replication
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,255 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From d648439d1a103ef8bd9973cad06e5097fcf8da30 Mon Sep 17 00:00:00 2001
|
||||
From e05653cbff500c47b89e43e4a1c85b7cb30321ff Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 15:41:29 -0700
|
||||
Subject: [PATCH] Issue 6884 - Mask password hashes in audit logs (#6885)
|
||||
@ -531,7 +531,7 @@ index 000000000..3b6a54849
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
\ No newline at end of file
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 3a3f9b28d..747448998 100644
|
||||
index 3945b0533..3a34959f6 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -39,6 +39,89 @@ static void write_audit_file(Slapi_PBlock *pb, Slapi_Entry *entry, int logtype,
|
||||
@ -716,7 +716,7 @@ index 3a3f9b28d..747448998 100644
|
||||
tmpsave = tmp;
|
||||
while ((tmp = strchr(tmp, '\n')) != NULL) {
|
||||
tmp++;
|
||||
@@ -667,6 +780,10 @@ write_audit_file(
|
||||
@@ -662,6 +775,10 @@ write_audit_file(
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -727,7 +727,7 @@ index 3a3f9b28d..747448998 100644
|
||||
switch (operationtype) {
|
||||
case LDAP_MOD_ADD:
|
||||
addlenstr(l, "add: ");
|
||||
@@ -691,18 +808,27 @@ write_audit_file(
|
||||
@@ -686,18 +803,27 @@ write_audit_file(
|
||||
break;
|
||||
}
|
||||
if (operationtype != LDAP_MOD_IGNORE) {
|
||||
@ -767,7 +767,7 @@ index 3a3f9b28d..747448998 100644
|
||||
}
|
||||
}
|
||||
addlenstr(l, "-\n");
|
||||
@@ -713,7 +839,7 @@ write_audit_file(
|
||||
@@ -708,7 +834,7 @@ write_audit_file(
|
||||
e = change;
|
||||
addlenstr(l, attr_changetype);
|
||||
addlenstr(l, ": add\n");
|
||||
@ -1,138 +0,0 @@
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,63 @@
|
||||
From 574a5295e13cf01c34226d676104057468198616 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 4 Oct 2024 08:55:11 -0700
|
||||
Subject: [PATCH] Issue 6339 - Address Coverity scan issues in memberof and
|
||||
bdb_layer (#6353)
|
||||
|
||||
Description: Add null check for memberof attribute in memberof.c
|
||||
Fix memory leak by freeing 'cookie' in memberof.c
|
||||
Add null check for database environment in bdb_layer.c
|
||||
Fix race condition by adding mutex lock/unlock in bdb_layer.c
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6339
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c | 17 ++++++++++++++---
|
||||
1 file changed, 14 insertions(+), 3 deletions(-)
|
||||
|
||||
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 b04cd68e2..4f069197e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
|
||||
@@ -6987,6 +6987,7 @@ bdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_t
|
||||
bdb_config *conf = (bdb_config *)li->li_dblayer_config;
|
||||
bdb_db_env **ppEnv = (bdb_db_env**)&priv->dblayer_env;
|
||||
char dbhome[MAXPATHLEN];
|
||||
+ bdb_db_env *pEnv = NULL;
|
||||
DB_ENV *bdb_env = NULL;
|
||||
DB *bdb_db = NULL;
|
||||
struct stat st = {0};
|
||||
@@ -7036,7 +7037,13 @@ bdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_t
|
||||
conf->bdb_tx_max = 50;
|
||||
rc = bdb_start(li, DBLAYER_NORMAL_MODE);
|
||||
if (rc == 0) {
|
||||
- bdb_env = ((struct bdb_db_env*)(priv->dblayer_env))->bdb_DB_ENV;
|
||||
+ pEnv = (bdb_db_env *)priv->dblayer_env;
|
||||
+ if (pEnv == NULL) {
|
||||
+ fprintf(stderr, "bdb_public_private_open: dbenv is not available (0x%p) for database %s\n",
|
||||
+ (void *)pEnv, db_filename ? db_filename : "unknown");
|
||||
+ return EINVAL;
|
||||
+ }
|
||||
+ bdb_env = pEnv->bdb_DB_ENV;
|
||||
}
|
||||
} else {
|
||||
/* Setup minimal environment */
|
||||
@@ -7080,8 +7087,12 @@ bdb_public_private_close(struct ldbminfo *li, dbi_env_t **env, dbi_db_t **db)
|
||||
if (priv) {
|
||||
/* Detect if db is fully set up in read write mode */
|
||||
bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env;
|
||||
- if (pEnv && pEnv->bdb_thread_count>0) {
|
||||
- rw = 1;
|
||||
+ if (pEnv) {
|
||||
+ pthread_mutex_lock(&pEnv->bdb_thread_count_lock);
|
||||
+ if (pEnv->bdb_thread_count > 0) {
|
||||
+ rw = 1;
|
||||
+ }
|
||||
+ pthread_mutex_unlock(&pEnv->bdb_thread_count_lock);
|
||||
}
|
||||
}
|
||||
if (rw == 0) {
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,443 +0,0 @@
|
||||
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: <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: <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: <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
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
From 972ddeed2029975d5d89e165db1db554f2e8bc28 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 29 Jul 2025 08:00:00 +0200
|
||||
Subject: [PATCH] Issue 6468 - CLI - Fix default error log level
|
||||
|
||||
Description:
|
||||
Default error log level is 16384
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6468
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_conf/logging.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/logging.py b/src/lib389/lib389/cli_conf/logging.py
|
||||
index d1e32822c..c48c75faa 100644
|
||||
--- a/src/lib389/lib389/cli_conf/logging.py
|
||||
+++ b/src/lib389/lib389/cli_conf/logging.py
|
||||
@@ -44,7 +44,7 @@ ERROR_LEVELS = {
|
||||
+ "methods used for a SASL bind"
|
||||
},
|
||||
"default": {
|
||||
- "level": 6384,
|
||||
+ "level": 16384,
|
||||
"desc": "Default logging level"
|
||||
},
|
||||
"filter": {
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,474 +0,0 @@
|
||||
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
|
||||
|
||||
222
SOURCES/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch
Normal file
222
SOURCES/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch
Normal file
@ -0,0 +1,222 @@
|
||||
From f28deac93c552a9c4dc9dd9c18f449fcd5cc7731 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 1 Aug 2025 09:28:39 -0700
|
||||
Subject: [PATCH] Issues 6913, 6886, 6250 - Adjust xfail marks (#6914)
|
||||
|
||||
Description: Some of the ACI invalid syntax issues were fixed,
|
||||
so we need to remove xfail marks.
|
||||
Disk space issue should have a 'skipif' mark.
|
||||
Display all attrs (nsslapd-auditlog-display-attrs: *) fails because of a bug.
|
||||
EntryUSN inconsistency and overflow bugs were exposed with the tests.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6913
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6886
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6250
|
||||
|
||||
Reviewed by: @vashirov (Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/acl/syntax_test.py | 13 ++++++++--
|
||||
.../tests/suites/import/regression_test.py | 18 +++++++-------
|
||||
.../logging/audit_password_masking_test.py | 24 +++++++++----------
|
||||
.../suites/plugins/entryusn_overflow_test.py | 2 ++
|
||||
4 files changed, 34 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/acl/syntax_test.py b/dirsrvtests/tests/suites/acl/syntax_test.py
|
||||
index 4edc7fa4b..ed9919ba3 100644
|
||||
--- a/dirsrvtests/tests/suites/acl/syntax_test.py
|
||||
+++ b/dirsrvtests/tests/suites/acl/syntax_test.py
|
||||
@@ -190,10 +190,9 @@ FAILED = [('test_targattrfilters_18',
|
||||
f'(all)userdn="ldap:///anyone";)'), ]
|
||||
|
||||
|
||||
-@pytest.mark.xfail(reason='https://bugzilla.redhat.com/show_bug.cgi?id=1691473')
|
||||
@pytest.mark.parametrize("real_value", [a[1] for a in FAILED],
|
||||
ids=[a[0] for a in FAILED])
|
||||
-def test_aci_invalid_syntax_fail(topo, real_value):
|
||||
+def test_aci_invalid_syntax_fail(topo, real_value, request):
|
||||
"""Try to set wrong ACI syntax.
|
||||
|
||||
:id: 83c40784-fff5-49c8-9535-7064c9c19e7e
|
||||
@@ -206,6 +205,16 @@ def test_aci_invalid_syntax_fail(topo, real_value):
|
||||
1. It should pass
|
||||
2. It should not pass
|
||||
"""
|
||||
+ # Mark specific test cases as xfail
|
||||
+ xfail_cases = [
|
||||
+ 'test_targattrfilters_18',
|
||||
+ 'test_targattrfilters_20',
|
||||
+ 'test_bind_rule_set_with_more_than_three'
|
||||
+ ]
|
||||
+
|
||||
+ if request.node.callspec.id in xfail_cases:
|
||||
+ pytest.xfail("DS6913 - This test case is expected to fail")
|
||||
+
|
||||
domain = Domain(topo.standalone, DEFAULT_SUFFIX)
|
||||
with pytest.raises(ldap.INVALID_SYNTAX):
|
||||
domain.add("aci", real_value)
|
||||
diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py
|
||||
index 2f850a19a..18611de35 100644
|
||||
--- a/dirsrvtests/tests/suites/import/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/import/regression_test.py
|
||||
@@ -323,7 +323,7 @@ ou: myDups00001
|
||||
|
||||
@pytest.mark.bz1749595
|
||||
@pytest.mark.tier2
|
||||
-@pytest.mark.xfail(not _check_disk_space(), reason="not enough disk space for lmdb map")
|
||||
+@pytest.mark.skipif(not _check_disk_space(), reason="not enough disk space for lmdb map")
|
||||
@pytest.mark.xfail(ds_is_older("1.3.10.1"), reason="bz1749595 not fixed on versions older than 1.3.10.1")
|
||||
def test_large_ldif2db_ancestorid_index_creation(topo, _set_mdb_map_size):
|
||||
"""Import with ldif2db a large file - check that the ancestorid index creation phase has a correct performance
|
||||
@@ -399,39 +399,39 @@ def test_large_ldif2db_ancestorid_index_creation(topo, _set_mdb_map_size):
|
||||
log.info('Starting the server')
|
||||
topo.standalone.start()
|
||||
|
||||
- # With lmdb there is no more any special phase for ancestorid
|
||||
+ # With lmdb there is no more any special phase for ancestorid
|
||||
# because ancestorsid get updated on the fly while processing the
|
||||
# entryrdn (by up the parents chain to compute the parentid
|
||||
- #
|
||||
+ #
|
||||
# But there is still a numSubordinates generation phase
|
||||
if get_default_db_lib() == "mdb":
|
||||
log.info('parse the errors logs to check lines with "Generating numSubordinates complete." are present')
|
||||
end_numsubordinates = str(topo.standalone.ds_error_log.match(r'.*Generating numSubordinates complete.*'))[1:-1]
|
||||
assert len(end_numsubordinates) > 0
|
||||
-
|
||||
+
|
||||
else:
|
||||
log.info('parse the errors logs to check lines with "Starting sort of ancestorid" are present')
|
||||
start_sort_str = str(topo.standalone.ds_error_log.match(r'.*Starting sort of ancestorid non-leaf IDs*'))[1:-1]
|
||||
assert len(start_sort_str) > 0
|
||||
-
|
||||
+
|
||||
log.info('parse the errors logs to check lines with "Finished sort of ancestorid" are present')
|
||||
end_sort_str = str(topo.standalone.ds_error_log.match(r'.*Finished sort of ancestorid non-leaf IDs*'))[1:-1]
|
||||
assert len(end_sort_str) > 0
|
||||
-
|
||||
+
|
||||
log.info('parse the error logs for the line with "Gathering ancestorid non-leaf IDs"')
|
||||
start_ancestorid_indexing_op_str = str(topo.standalone.ds_error_log.match(r'.*Gathering ancestorid non-leaf IDs*'))[1:-1]
|
||||
assert len(start_ancestorid_indexing_op_str) > 0
|
||||
-
|
||||
+
|
||||
log.info('parse the error logs for the line with "Created ancestorid index"')
|
||||
end_ancestorid_indexing_op_str = str(topo.standalone.ds_error_log.match(r'.*Created ancestorid index*'))[1:-1]
|
||||
assert len(end_ancestorid_indexing_op_str) > 0
|
||||
-
|
||||
+
|
||||
log.info('get the ancestorid non-leaf IDs indexing start and end time from the collected strings')
|
||||
# Collected lines look like : '[15/May/2020:05:30:27.245967313 -0400] - INFO - bdb_get_nonleaf_ids - import userRoot: Gathering ancestorid non-leaf IDs...'
|
||||
# We are getting the sec.nanosec part of the date, '27.245967313' in the above example
|
||||
start_time = (start_ancestorid_indexing_op_str.split()[0]).split(':')[3]
|
||||
end_time = (end_ancestorid_indexing_op_str.split()[0]).split(':')[3]
|
||||
-
|
||||
+
|
||||
log.info('Calculate the elapsed time for the ancestorid non-leaf IDs index creation')
|
||||
etime = (Decimal(end_time) - Decimal(start_time))
|
||||
# The time for the ancestorid index creation should be less than 10s for an offline import of an ldif file with 100000 entries / 5 entries per node
|
||||
diff --git a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
index 3b6a54849..69a36cb5d 100644
|
||||
--- a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
+++ b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
@@ -117,10 +117,10 @@ def check_password_masked(inst, log_format, expected_password, actual_password):
|
||||
|
||||
@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
("default", None),
|
||||
- ("default", "*"),
|
||||
+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("default", "userPassword"),
|
||||
("json", None),
|
||||
- ("json", "*"),
|
||||
+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("json", "userPassword")
|
||||
])
|
||||
def test_password_masking_add_operation(topo, log_format, display_attrs):
|
||||
@@ -173,10 +173,10 @@ def test_password_masking_add_operation(topo, log_format, display_attrs):
|
||||
|
||||
@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
("default", None),
|
||||
- ("default", "*"),
|
||||
+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("default", "userPassword"),
|
||||
("json", None),
|
||||
- ("json", "*"),
|
||||
+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("json", "userPassword")
|
||||
])
|
||||
def test_password_masking_modify_operation(topo, log_format, display_attrs):
|
||||
@@ -242,10 +242,10 @@ def test_password_masking_modify_operation(topo, log_format, display_attrs):
|
||||
|
||||
@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
("default", None),
|
||||
- ("default", "*"),
|
||||
+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("default", "nsslapd-rootpw"),
|
||||
("json", None),
|
||||
- ("json", "*"),
|
||||
+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("json", "nsslapd-rootpw")
|
||||
])
|
||||
def test_password_masking_rootpw_modify_operation(topo, log_format, display_attrs):
|
||||
@@ -297,10 +297,10 @@ def test_password_masking_rootpw_modify_operation(topo, log_format, display_attr
|
||||
|
||||
@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
("default", None),
|
||||
- ("default", "*"),
|
||||
+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("default", "nsmultiplexorcredentials"),
|
||||
("json", None),
|
||||
- ("json", "*"),
|
||||
+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("json", "nsmultiplexorcredentials")
|
||||
])
|
||||
def test_password_masking_multiplexor_credentials(topo, log_format, display_attrs):
|
||||
@@ -368,10 +368,10 @@ def test_password_masking_multiplexor_credentials(topo, log_format, display_attr
|
||||
|
||||
@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
("default", None),
|
||||
- ("default", "*"),
|
||||
+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("default", "nsDS5ReplicaCredentials"),
|
||||
("json", None),
|
||||
- ("json", "*"),
|
||||
+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("json", "nsDS5ReplicaCredentials")
|
||||
])
|
||||
def test_password_masking_replica_credentials(topo, log_format, display_attrs):
|
||||
@@ -432,10 +432,10 @@ def test_password_masking_replica_credentials(topo, log_format, display_attrs):
|
||||
|
||||
@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
("default", None),
|
||||
- ("default", "*"),
|
||||
+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("default", "nsDS5ReplicaBootstrapCredentials"),
|
||||
("json", None),
|
||||
- ("json", "*"),
|
||||
+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")),
|
||||
("json", "nsDS5ReplicaBootstrapCredentials")
|
||||
])
|
||||
def test_password_masking_bootstrap_credentials(topo, log_format, display_attrs):
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py
|
||||
index a23d734ca..8c3a537ab 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py
|
||||
@@ -81,6 +81,7 @@ def setup_usn_test(topology_st, request):
|
||||
return created_users
|
||||
|
||||
|
||||
+@pytest.mark.xfail(reason="DS6250")
|
||||
def test_entryusn_overflow_on_add_existing_entries(topology_st, setup_usn_test):
|
||||
"""Test that reproduces entryUSN overflow when adding existing entries
|
||||
|
||||
@@ -232,6 +233,7 @@ def test_entryusn_overflow_on_add_existing_entries(topology_st, setup_usn_test):
|
||||
log.info("EntryUSN overflow test completed successfully")
|
||||
|
||||
|
||||
+@pytest.mark.xfail(reason="DS6250")
|
||||
def test_entryusn_consistency_after_failed_adds(topology_st, setup_usn_test):
|
||||
"""Test that entryUSN remains consistent after failed add operations
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
From 58a9e1083865e75bba3cf9867a3df109031d7810 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 13:18:26 +0200
|
||||
Subject: [PATCH] Issue 6181 - RFE - Allow system to manage uid/gid at startup
|
||||
|
||||
Description:
|
||||
Expand CapabilityBoundingSet to include CAP_FOWNER
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6181
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6906
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
wrappers/systemd.template.service.in | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/wrappers/systemd.template.service.in b/wrappers/systemd.template.service.in
|
||||
index fa05c9f60..6db1f6f8f 100644
|
||||
--- a/wrappers/systemd.template.service.in
|
||||
+++ b/wrappers/systemd.template.service.in
|
||||
@@ -25,7 +25,7 @@ MemoryAccounting=yes
|
||||
|
||||
# Allow non-root instances to bind to low ports.
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
-CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE CAP_CHOWN
|
||||
+CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER
|
||||
|
||||
PrivateTmp=on
|
||||
# https://en.opensuse.org/openSUSE:Security_Features#Systemd_hardening_effort
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,701 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 66a7526ec1c3cdbc37189b7a34cc5ddb3c9b88f7 Mon Sep 17 00:00:00 2001
|
||||
From e03af0aa7e041fc2ca20caf3bcb5810e968043dc Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 13 May 2025 13:53:05 +0200
|
||||
Subject: [PATCH] Issue 6778 - Memory leak in
|
||||
@ -28,7 +28,7 @@ index bbed11802..60d7182e2 100644
|
||||
if (roles_cache_is_role_entry(role_entry) == 0) {
|
||||
/* Bad type */
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
return SLAPI_ROLE_DEFINITION_ERROR;
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ index bbed11802..60d7182e2 100644
|
||||
} else {
|
||||
/* Bad type */
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
return SLAPI_ROLE_DEFINITION_ERROR;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ index bbed11802..60d7182e2 100644
|
||||
if (filter_attr_value == NULL) {
|
||||
/* Means probably no attribute or no value there */
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
return SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED;
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ index bbed11802..60d7182e2 100644
|
||||
ROLE_FILTER_ATTR_NAME, filter_attr_value,
|
||||
ROLE_FILTER_ATTR_NAME);
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
slapi_ch_free_string(&filter_attr_value);
|
||||
return SLAPI_ROLE_ERROR_FILTER_BAD;
|
||||
}
|
||||
@ -64,7 +64,7 @@ index bbed11802..60d7182e2 100644
|
||||
if (filter == NULL) {
|
||||
/* An error has occured */
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
slapi_ch_free_string(&filter_attr_value);
|
||||
return SLAPI_ROLE_ERROR_FILTER_BAD;
|
||||
}
|
||||
@ -73,7 +73,7 @@ index bbed11802..60d7182e2 100644
|
||||
filter_attr_value,
|
||||
ROLE_FILTER_ATTR_NAME);
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
+ slapi_filter_free(filter, 1);
|
||||
slapi_ch_free_string(&filter_attr_value);
|
||||
return SLAPI_ROLE_ERROR_FILTER_BAD;
|
||||
@ -83,7 +83,7 @@ index bbed11802..60d7182e2 100644
|
||||
*result = this_role;
|
||||
} else {
|
||||
- slapi_ch_free((void **)&this_role);
|
||||
+ roles_cache_role_object_free(this_role);
|
||||
+ roles_cache_role_object_free((caddr_t)this_role);
|
||||
}
|
||||
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
|
||||
@ -1,478 +0,0 @@
|
||||
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
|
||||
tracking (#6607)
|
||||
|
||||
Description: Implement new cache statistics tracking with atomic counters
|
||||
and dedicated stats structs.
|
||||
Update concread dependency to 0.5.4 for improved cache performance.
|
||||
Add tests for cache statistics functionality.
|
||||
|
||||
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, 526 insertions(+), 269 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/dn.c b/ldap/servers/slapd/dn.c
|
||||
index 093019e28..5fbe67d07 100644
|
||||
--- a/ldap/servers/slapd/dn.c
|
||||
+++ b/ldap/servers/slapd/dn.c
|
||||
@@ -58,7 +58,7 @@ struct ndn_cache {
|
||||
|
||||
/*
|
||||
* This means we need 1 MB minimum per thread
|
||||
- *
|
||||
+ *
|
||||
*/
|
||||
#define NDN_CACHE_MINIMUM_CAPACITY 1048576
|
||||
/*
|
||||
@@ -3008,7 +3008,7 @@ ndn_cache_get_stats(uint64_t *hits, uint64_t *tries, uint64_t *size, uint64_t *m
|
||||
uint64_t freq_evicts;
|
||||
uint64_t recent_evicts;
|
||||
uint64_t p_weight;
|
||||
- cache_char_stats(cache,
|
||||
+ cache_char_stats(cache,
|
||||
&reader_hits,
|
||||
&reader_includes,
|
||||
&write_hits,
|
||||
diff --git a/src/librslapd/Cargo.toml b/src/librslapd/Cargo.toml
|
||||
index 6d8d63de4..6d9b621fc 100644
|
||||
--- a/src/librslapd/Cargo.toml
|
||||
+++ b/src/librslapd/Cargo.toml
|
||||
@@ -16,8 +16,7 @@ crate-type = ["staticlib", "lib"]
|
||||
[dependencies]
|
||||
slapd = { path = "../slapd" }
|
||||
libc = "0.2"
|
||||
-concread = "^0.2.20"
|
||||
+concread = "0.5.4"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26"
|
||||
-
|
||||
diff --git a/src/librslapd/src/cache.rs b/src/librslapd/src/cache.rs
|
||||
index b025c830a..e3c692865 100644
|
||||
--- a/src/librslapd/src/cache.rs
|
||||
+++ b/src/librslapd/src/cache.rs
|
||||
@@ -1,38 +1,171 @@
|
||||
// This exposes C-FFI capable bindings for the concread concurrently readable cache.
|
||||
+use concread::arcache::stats::{ARCacheWriteStat, ReadCountStat};
|
||||
use concread::arcache::{ARCache, ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
|
||||
-use std::convert::TryInto;
|
||||
+use concread::cowcell::CowCell;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
+#[derive(Clone, Debug, Default)]
|
||||
+struct CacheStats {
|
||||
+ reader_hits: u64, // Hits from read transactions (main + local)
|
||||
+ reader_includes: u64, // Number of includes from read transactions
|
||||
+ write_hits: u64, // Hits from write transactions
|
||||
+ write_inc_or_mod: u64, // Number of includes/modifications from write transactions
|
||||
+ freq_evicts: u64, // Number of evictions from frequent set
|
||||
+ recent_evicts: u64, // Number of evictions from recent set
|
||||
+ p_weight: u64, // Current cache weight between recent and frequent.
|
||||
+ shared_max: u64, // Maximum number of items in the shared cache.
|
||||
+ freq: u64, // Number of items in the frequent set at this point in time.
|
||||
+ recent: u64, // Number of items in the recent set at this point in time.
|
||||
+ all_seen_keys: u64, // Number of total keys seen through the cache's lifetime.
|
||||
+}
|
||||
+
|
||||
+impl CacheStats {
|
||||
+ fn new() -> Self {
|
||||
+ CacheStats::default()
|
||||
+ }
|
||||
+
|
||||
+ fn update_from_read_stat(&mut self, stat: ReadCountStat) {
|
||||
+ self.reader_hits += stat.main_hit + stat.local_hit;
|
||||
+ self.reader_includes += stat.include + stat.local_include;
|
||||
+ }
|
||||
+
|
||||
+ fn update_from_write_stat(&mut self, stat: &FFIWriteStat) {
|
||||
+ self.write_hits += stat.read_hits;
|
||||
+ self.write_inc_or_mod += stat.includes + stat.modifications;
|
||||
+ self.freq_evicts += stat.freq_evictions;
|
||||
+ self.recent_evicts += stat.recent_evictions;
|
||||
+ self.p_weight = stat.p_weight;
|
||||
+ self.shared_max = stat.shared_max;
|
||||
+ self.freq = stat.freq;
|
||||
+ self.recent = stat.recent;
|
||||
+ self.all_seen_keys = stat.all_seen_keys;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[derive(Debug, Default)]
|
||||
+pub struct FFIWriteStat {
|
||||
+ pub read_ops: u64,
|
||||
+ pub read_hits: u64,
|
||||
+ pub p_weight: u64,
|
||||
+ pub shared_max: u64,
|
||||
+ pub freq: u64,
|
||||
+ pub recent: u64,
|
||||
+ pub all_seen_keys: u64,
|
||||
+ pub includes: u64,
|
||||
+ pub modifications: u64,
|
||||
+ pub freq_evictions: u64,
|
||||
+ pub recent_evictions: u64,
|
||||
+ pub ghost_freq_revives: u64,
|
||||
+ pub ghost_rec_revives: u64,
|
||||
+ pub haunted_includes: u64,
|
||||
+}
|
||||
+
|
||||
+impl<K> ARCacheWriteStat<K> for FFIWriteStat {
|
||||
+ fn cache_clear(&mut self) {
|
||||
+ self.read_ops = 0;
|
||||
+ self.read_hits = 0;
|
||||
+ }
|
||||
+
|
||||
+ fn cache_read(&mut self) {
|
||||
+ self.read_ops += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn cache_hit(&mut self) {
|
||||
+ self.read_hits += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn p_weight(&mut self, p: u64) {
|
||||
+ self.p_weight = p;
|
||||
+ }
|
||||
+
|
||||
+ fn shared_max(&mut self, i: u64) {
|
||||
+ self.shared_max = i;
|
||||
+ }
|
||||
+
|
||||
+ fn freq(&mut self, i: u64) {
|
||||
+ self.freq = i;
|
||||
+ }
|
||||
+
|
||||
+ fn recent(&mut self, i: u64) {
|
||||
+ self.recent = i;
|
||||
+ }
|
||||
+
|
||||
+ fn all_seen_keys(&mut self, i: u64) {
|
||||
+ self.all_seen_keys = i;
|
||||
+ }
|
||||
+
|
||||
+ fn include(&mut self, _k: &K) {
|
||||
+ self.includes += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn include_haunted(&mut self, _k: &K) {
|
||||
+ self.haunted_includes += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn modify(&mut self, _k: &K) {
|
||||
+ self.modifications += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn ghost_frequent_revive(&mut self, _k: &K) {
|
||||
+ self.ghost_freq_revives += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn ghost_recent_revive(&mut self, _k: &K) {
|
||||
+ self.ghost_rec_revives += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn evict_from_recent(&mut self, _k: &K) {
|
||||
+ self.recent_evictions += 1;
|
||||
+ }
|
||||
+
|
||||
+ fn evict_from_frequent(&mut self, _k: &K) {
|
||||
+ self.freq_evictions += 1;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
pub struct ARCacheChar {
|
||||
inner: ARCache<CString, CString>,
|
||||
+ stats: CowCell<CacheStats>,
|
||||
}
|
||||
|
||||
pub struct ARCacheCharRead<'a> {
|
||||
- inner: ARCacheReadTxn<'a, CString, CString>,
|
||||
+ inner: ARCacheReadTxn<'a, CString, CString, ReadCountStat>,
|
||||
+ cache: &'a ARCacheChar,
|
||||
}
|
||||
|
||||
pub struct ARCacheCharWrite<'a> {
|
||||
- inner: ARCacheWriteTxn<'a, CString, CString>,
|
||||
+ inner: ARCacheWriteTxn<'a, CString, CString, FFIWriteStat>,
|
||||
+ cache: &'a ARCacheChar,
|
||||
+}
|
||||
+
|
||||
+impl ARCacheChar {
|
||||
+ fn new(max: usize, read_max: usize) -> Option<Self> {
|
||||
+ ARCacheBuilder::new()
|
||||
+ .set_size(max, read_max)
|
||||
+ .set_reader_quiesce(false)
|
||||
+ .build()
|
||||
+ .map(|inner| Self {
|
||||
+ inner,
|
||||
+ stats: CowCell::new(CacheStats::new()),
|
||||
+ })
|
||||
+ }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cache_char_create(max: usize, read_max: usize) -> *mut ARCacheChar {
|
||||
- let inner = if let Some(cache) = ARCacheBuilder::new().set_size(max, read_max).build() {
|
||||
- cache
|
||||
+ if let Some(cache) = ARCacheChar::new(max, read_max) {
|
||||
+ Box::into_raw(Box::new(cache))
|
||||
} else {
|
||||
- return std::ptr::null_mut();
|
||||
- };
|
||||
- let cache: Box<ARCacheChar> = Box::new(ARCacheChar { inner });
|
||||
- Box::into_raw(cache)
|
||||
+ std::ptr::null_mut()
|
||||
+ }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cache_char_free(cache: *mut ARCacheChar) {
|
||||
- // Should we be responsible to drain and free everything?
|
||||
debug_assert!(!cache.is_null());
|
||||
unsafe {
|
||||
- let _drop = Box::from_raw(cache);
|
||||
+ drop(Box::from_raw(cache));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,22 +186,22 @@ pub extern "C" fn cache_char_stats(
|
||||
) {
|
||||
let cache_ref = unsafe {
|
||||
debug_assert!(!cache.is_null());
|
||||
- &(*cache) as &ARCacheChar
|
||||
+ &(*cache)
|
||||
};
|
||||
- let stats = cache_ref.inner.view_stats();
|
||||
- *reader_hits = stats.reader_hits.try_into().unwrap();
|
||||
- *reader_includes = stats.reader_includes.try_into().unwrap();
|
||||
- *write_hits = stats.write_hits.try_into().unwrap();
|
||||
- *write_inc_or_mod = (stats.write_includes + stats.write_modifies)
|
||||
- .try_into()
|
||||
- .unwrap();
|
||||
- *shared_max = stats.shared_max.try_into().unwrap();
|
||||
- *freq = stats.freq.try_into().unwrap();
|
||||
- *recent = stats.recent.try_into().unwrap();
|
||||
- *freq_evicts = stats.freq_evicts.try_into().unwrap();
|
||||
- *recent_evicts = stats.recent_evicts.try_into().unwrap();
|
||||
- *p_weight = stats.p_weight.try_into().unwrap();
|
||||
- *all_seen_keys = stats.all_seen_keys.try_into().unwrap();
|
||||
+
|
||||
+ // Get stats snapshot
|
||||
+ let stats_read = cache_ref.stats.read();
|
||||
+ *reader_hits = stats_read.reader_hits;
|
||||
+ *reader_includes = stats_read.reader_includes;
|
||||
+ *write_hits = stats_read.write_hits;
|
||||
+ *write_inc_or_mod = stats_read.write_inc_or_mod;
|
||||
+ *freq_evicts = stats_read.freq_evicts;
|
||||
+ *recent_evicts = stats_read.recent_evicts;
|
||||
+ *p_weight = stats_read.p_weight;
|
||||
+ *shared_max = stats_read.shared_max;
|
||||
+ *freq = stats_read.freq;
|
||||
+ *recent = stats_read.recent;
|
||||
+ *all_seen_keys = stats_read.all_seen_keys;
|
||||
}
|
||||
|
||||
// start read
|
||||
@@ -79,7 +212,8 @@ pub extern "C" fn cache_char_read_begin(cache: *mut ARCacheChar) -> *mut ARCache
|
||||
&(*cache) as &ARCacheChar
|
||||
};
|
||||
let read_txn = Box::new(ARCacheCharRead {
|
||||
- inner: cache_ref.inner.read(),
|
||||
+ inner: cache_ref.inner.read_stats(ReadCountStat::default()),
|
||||
+ cache: cache_ref,
|
||||
});
|
||||
Box::into_raw(read_txn)
|
||||
}
|
||||
@@ -87,8 +221,20 @@ pub extern "C" fn cache_char_read_begin(cache: *mut ARCacheChar) -> *mut ARCache
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cache_char_read_complete(read_txn: *mut ARCacheCharRead) {
|
||||
debug_assert!(!read_txn.is_null());
|
||||
+
|
||||
unsafe {
|
||||
- let _drop = Box::from_raw(read_txn);
|
||||
+ let read_txn_box = Box::from_raw(read_txn);
|
||||
+ let read_stats = read_txn_box.inner.finish();
|
||||
+ let write_stats = read_txn_box
|
||||
+ .cache
|
||||
+ .inner
|
||||
+ .try_quiesce_stats(FFIWriteStat::default());
|
||||
+
|
||||
+ // Update stats
|
||||
+ let mut stats_write = read_txn_box.cache.stats.write();
|
||||
+ stats_write.update_from_read_stat(read_stats);
|
||||
+ stats_write.update_from_write_stat(&write_stats);
|
||||
+ stats_write.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +287,8 @@ pub extern "C" fn cache_char_write_begin(
|
||||
&(*cache) as &ARCacheChar
|
||||
};
|
||||
let write_txn = Box::new(ARCacheCharWrite {
|
||||
- inner: cache_ref.inner.write(),
|
||||
+ inner: cache_ref.inner.write_stats(FFIWriteStat::default()),
|
||||
+ cache: cache_ref,
|
||||
});
|
||||
Box::into_raw(write_txn)
|
||||
}
|
||||
@@ -149,15 +296,21 @@ pub extern "C" fn cache_char_write_begin(
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cache_char_write_commit(write_txn: *mut ARCacheCharWrite) {
|
||||
debug_assert!(!write_txn.is_null());
|
||||
- let wr = unsafe { Box::from_raw(write_txn) };
|
||||
- (*wr).inner.commit();
|
||||
+ unsafe {
|
||||
+ let write_txn_box = Box::from_raw(write_txn);
|
||||
+ let current_stats = write_txn_box.inner.commit();
|
||||
+
|
||||
+ let mut stats_write = write_txn_box.cache.stats.write();
|
||||
+ stats_write.update_from_write_stat(¤t_stats);
|
||||
+ stats_write.commit();
|
||||
+ }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cache_char_write_rollback(write_txn: *mut ARCacheCharWrite) {
|
||||
debug_assert!(!write_txn.is_null());
|
||||
unsafe {
|
||||
- let _drop = Box::from_raw(write_txn);
|
||||
+ drop(Box::from_raw(write_txn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +335,7 @@ pub extern "C" fn cache_char_write_include(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
- use crate::cache::*;
|
||||
+ use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cache_basic() {
|
||||
@@ -199,4 +352,116 @@ mod tests {
|
||||
cache_char_read_complete(read_txn);
|
||||
cache_char_free(cache_ptr);
|
||||
}
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_cache_stats() {
|
||||
+ let cache = cache_char_create(100, 8);
|
||||
+
|
||||
+ // Variables to store stats
|
||||
+ let mut reader_hits = 0;
|
||||
+ let mut reader_includes = 0;
|
||||
+ let mut write_hits = 0;
|
||||
+ let mut write_inc_or_mod = 0;
|
||||
+ let mut shared_max = 0;
|
||||
+ let mut freq = 0;
|
||||
+ let mut recent = 0;
|
||||
+ let mut freq_evicts = 0;
|
||||
+ let mut recent_evicts = 0;
|
||||
+ let mut p_weight = 0;
|
||||
+ let mut all_seen_keys = 0;
|
||||
+
|
||||
+ // Do some operations
|
||||
+ let key = CString::new("stats_test").unwrap();
|
||||
+ let value = CString::new("value").unwrap();
|
||||
+
|
||||
+ let write_txn = cache_char_write_begin(cache);
|
||||
+ cache_char_write_include(write_txn, key.as_ptr(), value.as_ptr());
|
||||
+ cache_char_write_commit(write_txn);
|
||||
+
|
||||
+ let read_txn = cache_char_read_begin(cache);
|
||||
+ let _ = cache_char_read_get(read_txn, key.as_ptr());
|
||||
+ cache_char_read_complete(read_txn);
|
||||
+
|
||||
+ // Get stats
|
||||
+ cache_char_stats(
|
||||
+ cache,
|
||||
+ &mut reader_hits,
|
||||
+ &mut reader_includes,
|
||||
+ &mut write_hits,
|
||||
+ &mut write_inc_or_mod,
|
||||
+ &mut shared_max,
|
||||
+ &mut freq,
|
||||
+ &mut recent,
|
||||
+ &mut freq_evicts,
|
||||
+ &mut recent_evicts,
|
||||
+ &mut p_weight,
|
||||
+ &mut all_seen_keys,
|
||||
+ );
|
||||
+
|
||||
+ // Verify that stats were updated
|
||||
+ assert!(write_inc_or_mod > 0);
|
||||
+ assert!(all_seen_keys > 0);
|
||||
+
|
||||
+ cache_char_free(cache);
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_cache_read_write_operations() {
|
||||
+ let cache = cache_char_create(100, 8);
|
||||
+
|
||||
+ // Create test data
|
||||
+ let key = CString::new("test_key").unwrap();
|
||||
+ let value = CString::new("test_value").unwrap();
|
||||
+
|
||||
+ // Test write operation
|
||||
+ let write_txn = cache_char_write_begin(cache);
|
||||
+ cache_char_write_include(write_txn, key.as_ptr(), value.as_ptr());
|
||||
+ cache_char_write_commit(write_txn);
|
||||
+
|
||||
+ // Test read operation
|
||||
+ let read_txn = cache_char_read_begin(cache);
|
||||
+ let result = cache_char_read_get(read_txn, key.as_ptr());
|
||||
+ assert!(!result.is_null());
|
||||
+
|
||||
+ // Verify the value
|
||||
+ let retrieved_value = unsafe { CStr::from_ptr(result) };
|
||||
+ assert_eq!(retrieved_value.to_bytes(), value.as_bytes());
|
||||
+
|
||||
+ cache_char_read_complete(read_txn);
|
||||
+ cache_char_free(cache);
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_cache_miss() {
|
||||
+ let cache = cache_char_create(100, 8);
|
||||
+ let read_txn = cache_char_read_begin(cache);
|
||||
+
|
||||
+ let missing_key = CString::new("nonexistent").unwrap();
|
||||
+ let result = cache_char_read_get(read_txn, missing_key.as_ptr());
|
||||
+ assert!(result.is_null());
|
||||
+
|
||||
+ cache_char_read_complete(read_txn);
|
||||
+ cache_char_free(cache);
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn test_write_rollback() {
|
||||
+ let cache = cache_char_create(100, 8);
|
||||
+
|
||||
+ let key = CString::new("rollback_test").unwrap();
|
||||
+ let value = CString::new("value").unwrap();
|
||||
+
|
||||
+ // Start write transaction and rollback
|
||||
+ let write_txn = cache_char_write_begin(cache);
|
||||
+ cache_char_write_include(write_txn, key.as_ptr(), value.as_ptr());
|
||||
+ cache_char_write_rollback(write_txn);
|
||||
+
|
||||
+ // Verify key doesn't exist
|
||||
+ let read_txn = cache_char_read_begin(cache);
|
||||
+ let result = cache_char_read_get(read_txn, key.as_ptr());
|
||||
+ assert!(result.is_null());
|
||||
+
|
||||
+ cache_char_read_complete(read_txn);
|
||||
+ cache_char_free(cache);
|
||||
+ }
|
||||
}
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 3d869dc07596802387ef9a1599bd8d8853bb90e7 Mon Sep 17 00:00:00 2001
|
||||
From c8c9d8814bd328d9772b6a248aa142b72430cba1 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 16 Jul 2025 11:22:30 +0200
|
||||
Subject: [PATCH] Issue 6778 - Memory leak in
|
||||
@ -24,7 +24,7 @@ Reviewed by: @progier389 (Thanks!)
|
||||
2 files changed, 82 insertions(+), 92 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
index 32b7657c0..570fca2d5 100644
|
||||
index d92d6f0c3..ec208bae9 100644
|
||||
--- a/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
@@ -510,6 +510,76 @@ def test_vattr_on_managed_role(topo, request):
|
||||
@ -0,0 +1,65 @@
|
||||
From f83a1996e3438e471cec086d53fb94be0c8666aa Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 7 Jul 2025 23:11:17 +0200
|
||||
Subject: [PATCH] Issue 6850 - AddressSanitizer: memory leak in mdb_init
|
||||
|
||||
Bug Description:
|
||||
`dbmdb_componentid` can be allocated multiple times. To avoid a memory
|
||||
leak, allocate it only once, and free at the cleanup.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6850
|
||||
|
||||
Reviewed by: @mreynolds389, @tbordaz (Tnanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c | 4 +++-
|
||||
ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c | 5 +++++
|
||||
3 files changed, 9 insertions(+), 2 deletions(-)
|
||||
|
||||
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 1f7b71442..bebc83b76 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
|
||||
@@ -146,7 +146,9 @@ dbmdb_compute_limits(struct ldbminfo *li)
|
||||
int mdb_init(struct ldbminfo *li, config_info *config_array)
|
||||
{
|
||||
dbmdb_ctx_t *conf = (dbmdb_ctx_t *)slapi_ch_calloc(1, sizeof(dbmdb_ctx_t));
|
||||
- dbmdb_componentid = generate_componentid(NULL, "db-mdb");
|
||||
+ if (dbmdb_componentid == NULL) {
|
||||
+ dbmdb_componentid = generate_componentid(NULL, "db-mdb");
|
||||
+ }
|
||||
|
||||
li->li_dblayer_config = conf;
|
||||
strncpy(conf->home, li->li_directory, MAXPATHLEN-1);
|
||||
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 3ecc47170..c6e9f8b01 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <prclist.h>
|
||||
#include <glob.h>
|
||||
|
||||
-Slapi_ComponentId *dbmdb_componentid;
|
||||
+Slapi_ComponentId *dbmdb_componentid = NULL;
|
||||
|
||||
#define BULKOP_MAX_RECORDS 100 /* Max records handled by a single bulk operations */
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c
|
||||
index 2d07db9b5..ae10ac7cf 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c
|
||||
@@ -49,6 +49,11 @@ dbmdb_cleanup(struct ldbminfo *li)
|
||||
}
|
||||
slapi_ch_free((void **)&(li->li_dblayer_config));
|
||||
|
||||
+ if (dbmdb_componentid != NULL) {
|
||||
+ release_componentid(dbmdb_componentid);
|
||||
+ dbmdb_componentid = NULL;
|
||||
+ }
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,216 +0,0 @@
|
||||
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
|
||||
|
||||
Description:
|
||||
A denial of service vulnerability was found in the 389 Directory Server.
|
||||
The 389 Directory Server may crash (Null Pointer Exception) after some
|
||||
failed rename subtree operations (i.e. MODDN) issued by a user having enough
|
||||
privileges to do so.
|
||||
|
||||
References:
|
||||
- https://access.redhat.com/security/cve/CVE-2025-2487
|
||||
- https://bugzilla.redhat.com/show_bug.cgi?id=2353071
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/findentry.c | 36 +++++++++++++++++-----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_add.c | 2 ++
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modify.c | 6 ++++
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 13 ++++++--
|
||||
4 files changed, 48 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/findentry.c b/ldap/servers/slapd/back-ldbm/findentry.c
|
||||
index 7bb56ef2c..907b4367a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/findentry.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/findentry.c
|
||||
@@ -99,6 +99,7 @@ find_entry_internal_dn(
|
||||
int isroot = 0;
|
||||
int op_type;
|
||||
int reverted_entry = 0;
|
||||
+ int return_err = LDAP_SUCCESS;
|
||||
|
||||
/* get the managedsait ldap message control */
|
||||
slapi_pblock_get(pb, SLAPI_MANAGEDSAIT, &managedsait);
|
||||
@@ -121,6 +122,7 @@ find_entry_internal_dn(
|
||||
if (rc) { /* if check_entry_for_referral returns non-zero, result is sent. */
|
||||
*rc = FE_RC_SENT_RESULT;
|
||||
}
|
||||
+ slapi_set_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, NULL);
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +155,12 @@ find_entry_internal_dn(
|
||||
slapi_log_err(SLAPI_LOG_ERR, "find_entry_internal_dn", "Retry count exceeded (%s)\n", slapi_sdn_get_dn(sdn));
|
||||
}
|
||||
if (reverted_entry) {
|
||||
+ CACHE_RETURN(&inst->inst_cache, &e);
|
||||
+ slapi_set_ldap_result(pb, LDAP_BUSY, NULL, NULL, 0, NULL);
|
||||
slapi_send_ldap_result(pb, LDAP_BUSY, NULL, "target entry busy because of a canceled operation", 0, NULL);
|
||||
+ if (rc) {
|
||||
+ *rc = FE_RC_SENT_RESULT; /* Result is sent */
|
||||
+ }
|
||||
return (NULL);
|
||||
}
|
||||
/*
|
||||
@@ -179,6 +186,7 @@ find_entry_internal_dn(
|
||||
if (rc) { /* if check_entry_for_referral returns non-zero, result is sent. */
|
||||
*rc = FE_RC_SENT_RESULT;
|
||||
}
|
||||
+ slapi_set_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, NULL);
|
||||
return (NULL);
|
||||
}
|
||||
/* else fall through to no such object */
|
||||
@@ -189,7 +197,7 @@ find_entry_internal_dn(
|
||||
if (me && !isroot) {
|
||||
/* If not root, you may not want to reveal it. */
|
||||
int acl_type = -1;
|
||||
- int return_err = LDAP_NO_SUCH_OBJECT;
|
||||
+ return_err = LDAP_NO_SUCH_OBJECT;
|
||||
err = LDAP_SUCCESS;
|
||||
switch (op_type) {
|
||||
case SLAPI_OPERATION_ADD:
|
||||
@@ -230,18 +238,22 @@ find_entry_internal_dn(
|
||||
* do not return the "matched" DN.
|
||||
* Plus, the bind case returns LDAP_INAPPROPRIATE_AUTH.
|
||||
*/
|
||||
+ slapi_set_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
|
||||
slapi_send_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
|
||||
} else {
|
||||
+ slapi_set_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
|
||||
slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT,
|
||||
(char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
|
||||
}
|
||||
} else {
|
||||
+ slapi_set_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
|
||||
slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT,
|
||||
(char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
|
||||
}
|
||||
} else {
|
||||
- slapi_send_ldap_result(pb, (LDAP_INVALID_DN_SYNTAX == err) ? LDAP_INVALID_DN_SYNTAX : LDAP_OPERATIONS_ERROR,
|
||||
- (char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
|
||||
+ return_err = (LDAP_INVALID_DN_SYNTAX == err) ? LDAP_INVALID_DN_SYNTAX : LDAP_OPERATIONS_ERROR;
|
||||
+ slapi_set_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
|
||||
+ slapi_send_ldap_result(pb, return_err, (char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
|
||||
}
|
||||
if (rc) {
|
||||
*rc = FE_RC_SENT_RESULT;
|
||||
@@ -265,13 +277,15 @@ find_entry_internal_uniqueid(
|
||||
backend *be,
|
||||
const char *uniqueid,
|
||||
int lock,
|
||||
- back_txn *txn)
|
||||
+ back_txn *txn,
|
||||
+ int *rc)
|
||||
{
|
||||
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
struct backentry *e;
|
||||
int err;
|
||||
size_t tries = 0;
|
||||
int reverted_entry = 0;
|
||||
+ int return_err = 0;
|
||||
|
||||
while ((tries < LDBM_CACHE_RETRY_COUNT) &&
|
||||
(e = uniqueid2entry(be, uniqueid, txn, &err)) != NULL) {
|
||||
@@ -307,12 +321,20 @@ find_entry_internal_uniqueid(
|
||||
}
|
||||
|
||||
if (reverted_entry) {
|
||||
+ slapi_set_ldap_result(pb, LDAP_BUSY, NULL, NULL, 0, NULL);
|
||||
slapi_send_ldap_result(pb, LDAP_BUSY, NULL, "target entry busy because of a canceled operation", 0, NULL);
|
||||
+ if (rc) {
|
||||
+ *rc = FE_RC_SENT_RESULT; /* Result is sent */
|
||||
+ }
|
||||
return (NULL);
|
||||
} else {
|
||||
/* entry not found */
|
||||
- slapi_send_ldap_result(pb, (0 == err || DBI_RC_NOTFOUND == err) ? LDAP_NO_SUCH_OBJECT : LDAP_OPERATIONS_ERROR, NULL /* matched */, NULL,
|
||||
- 0, NULL);
|
||||
+ return_err = (0 == err || DBI_RC_NOTFOUND == err) ? LDAP_NO_SUCH_OBJECT : LDAP_OPERATIONS_ERROR;
|
||||
+ slapi_set_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
|
||||
+ slapi_send_ldap_result(pb, return_err, NULL /* matched */, NULL, 0, NULL);
|
||||
+ if (rc) {
|
||||
+ *rc = FE_RC_SENT_RESULT; /* Result is sent */
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"find_entry_internal_uniqueid", "<= not found; uniqueid = (%s)\n",
|
||||
@@ -334,7 +356,7 @@ find_entry_internal(
|
||||
if (addr->uniqueid != NULL) {
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "find_entry_internal", "=> (uniqueid=%s) lock %d\n",
|
||||
addr->uniqueid, lock);
|
||||
- return (find_entry_internal_uniqueid(pb, be, addr->uniqueid, lock, txn));
|
||||
+ return (find_entry_internal_uniqueid(pb, be, addr->uniqueid, lock, txn, rc));
|
||||
} else {
|
||||
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 b7453697f..dec3a0c6d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
@@ -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");
|
||||
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
|
||||
+ goto error_return;
|
||||
}
|
||||
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 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
|
||||
slapi_pblock_get(pb, SLAPI_OPERATION, &operation);
|
||||
is_ruv = operation_is_flag_set(operation, OP_FLAG_REPL_RUV);
|
||||
}
|
||||
+ if (NULL == mc->new_entry) {
|
||||
+ /* test entry to avoid crashing in id2entry_add_ext */
|
||||
+ slapi_log_err(SLAPI_LOG_BACKLDBM, "modify_update_all",
|
||||
+ "No entry in modify_context ==> operation is aborted.\n");
|
||||
+ return -1;
|
||||
+ }
|
||||
/*
|
||||
* 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 76b8d49d7..e3b7e5783 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
@@ -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) {
|
||||
- ldap_result_code = -1;
|
||||
- goto error_return; /* error result sent by find_entry2modify() */
|
||||
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
|
||||
+ goto error_return; /* error result set and sent by find_entry2modify() */
|
||||
}
|
||||
if (slapi_entry_flag_is_set(e->ep_entry, SLAPI_ENTRY_FLAG_TOMBSTONE) &&
|
||||
!is_resurect_operation) {
|
||||
@@ -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);
|
||||
+ if (parententry == NULL) {
|
||||
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
|
||||
+ goto error_return; /* error result set and sent by find_entry2modify() */
|
||||
+ }
|
||||
+
|
||||
modify_init(&parent_modify_context, parententry);
|
||||
|
||||
/* Fetch and lock the new parent of the entry that is moving */
|
||||
@@ -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);
|
||||
+ if (newparententry == NULL) {
|
||||
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
|
||||
+ goto error_return; /* error result set and sent by find_entry2modify() */
|
||||
+ }
|
||||
modify_init(&newparent_modify_context, newparententry);
|
||||
}
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,300 +0,0 @@
|
||||
From 34f64ff2fd974db954265403c3380951fd977a29 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 4 Apr 2025 14:44:11 +0200
|
||||
Subject: [PATCH] Issue 6715 - dsconf backend replication monitor fails if
|
||||
replica id starts with 0 (#6716)
|
||||
|
||||
* Issue 6715 - dsconf backend replication monitor fails if replica id starts with 0
|
||||
lib389 fails to retrioeve the csn if the replicaid is not exactly the normalized form of a number
|
||||
Typically because 010 does not match with the RUV value.
|
||||
Solution: Ensure that rid get normalized before comparing them or using them in a dict
|
||||
|
||||
Issue: #6715
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit d19a4f9955aa0cdf3daf1c8a5ac0f6d83bfd9a7b)
|
||||
---
|
||||
.../suites/replication/regression_m2_test.py | 129 +++++++++++++++++-
|
||||
src/lib389/lib389/__init__.py | 5 +-
|
||||
src/lib389/lib389/agreement.py | 7 +-
|
||||
src/lib389/lib389/replica.py | 33 ++++-
|
||||
4 files changed, 163 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index 605c34adf..2283b9041 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -12,6 +12,7 @@ import time
|
||||
import logging
|
||||
import ldif
|
||||
import ldap
|
||||
+import pprint
|
||||
import pytest
|
||||
import subprocess
|
||||
import time
|
||||
@@ -29,7 +30,10 @@ from lib389.idm.group import Groups, Group
|
||||
from lib389.idm.domain import Domain
|
||||
from lib389.idm.directorymanager import DirectoryManager
|
||||
from lib389.idm.services import ServiceAccounts, ServiceAccount
|
||||
-from lib389.replica import Replicas, ReplicationManager, ReplicaRole, BootstrapReplicationManager
|
||||
+from lib389.replica import (
|
||||
+ Replicas, ReplicationManager, ReplicationMonitor, ReplicaRole,
|
||||
+ BootstrapReplicationManager, NormalizedRidDict
|
||||
+)
|
||||
from lib389.agreement import Agreements
|
||||
from lib389 import pid_from_file
|
||||
from lib389.dseldif import *
|
||||
@@ -1148,6 +1152,129 @@ def test_bulk_import(preserve_topo_m2):
|
||||
assert len(users_s1) == len(users_s2)
|
||||
|
||||
|
||||
+def check_monitoring_status(inst):
|
||||
+ creds = { 'binddn': DN_DM, 'bindpw': PW_DM }
|
||||
+ repl_monitor = ReplicationMonitor(inst)
|
||||
+ report_dict = repl_monitor.generate_report(lambda h,p: creds, use_json=True)
|
||||
+ log.debug(f'(Monitoring status: {pprint.pformat(report_dict)}')
|
||||
+
|
||||
+ agmts_status = {}
|
||||
+ for inst_status in report_dict.values():
|
||||
+ for replica_status in inst_status:
|
||||
+ suffix = replica_status['replica_root']
|
||||
+ rid = replica_status['replica_id']
|
||||
+ for agmt_status in replica_status['agmts_status']:
|
||||
+ rag_status = agmt_status['replication-status'][0]
|
||||
+ if 'Unavailable' in rag_status:
|
||||
+ aname = agmt_status['agmt-name'][0]
|
||||
+ url = f'{agmt_status["replica"][0]}/{suffix}'
|
||||
+ assert False, f"'Unavailable' found in agreement {aname} of replica {url} : {rag_status}"
|
||||
+
|
||||
+ assert 'Unavailable' not in str(report_dict)
|
||||
+
|
||||
+
|
||||
+def reinit_replica(S1, S2):
|
||||
+ # Reinit replication
|
||||
+ agmt = Agreements(S1).list()[0]
|
||||
+ agmt.begin_reinit()
|
||||
+ (done, error) = agmt.wait_reinit()
|
||||
+ assert done is True
|
||||
+ assert error is False
|
||||
+
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+ repl.wait_for_replication(S1, S2)
|
||||
+ repl.wait_for_replication(S2, S1)
|
||||
+
|
||||
+
|
||||
+def test_rid_starting_with_0(topo_m2, request):
|
||||
+ """Check that replication monitoring works if replica
|
||||
+ id starts with 0
|
||||
+
|
||||
+ :id: ed0176e6-0bf7-11f0-9846-482ae39447e5
|
||||
+ :setup: 2 Supplier Instances
|
||||
+ :steps:
|
||||
+ 1. Initialize replication to ensure that init status is set
|
||||
+ 2. Check that monitoring status does not contains 'Unavailable'
|
||||
+ 3. Change replica ids to 001 and 002
|
||||
+ 4. Initialize replication to ensure that init status is set
|
||||
+ 5. Check that monitoring status does not contains 'Unavailable'
|
||||
+ 6. Restore the replica ids to 1 and 2
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ """
|
||||
+ S1 = topo_m2.ms["supplier1"]
|
||||
+ S2 = topo_m2.ms["supplier2"]
|
||||
+ replicas = [ Replicas(inst).get(DEFAULT_SUFFIX) for inst in topo_m2 ]
|
||||
+
|
||||
+ # Reinit replication (to ensure that init status is set)
|
||||
+ reinit_replica(S1, S2)
|
||||
+
|
||||
+ # Get replication monitoring results
|
||||
+ check_monitoring_status(S1)
|
||||
+
|
||||
+ # Change replica id
|
||||
+ for replica,rid in zip(replicas, ['010', '020']):
|
||||
+ replica.replace('nsDS5ReplicaId', rid)
|
||||
+
|
||||
+ # Restore replica id in finalizer
|
||||
+ def fin():
|
||||
+ for replica,rid in zip(replicas, ['1', '2']):
|
||||
+ replica.replace('nsDS5ReplicaId', rid)
|
||||
+ reinit_replica(S1, S2)
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ # Reinit replication
|
||||
+ reinit_replica(S1, S2)
|
||||
+
|
||||
+ # Get replication monitoring results
|
||||
+ check_monitoring_status(S1)
|
||||
+
|
||||
+
|
||||
+def test_normalized_rid_dict():
|
||||
+ """Check that lib389.replica NormalizedRidDict class behaves as expected
|
||||
+
|
||||
+ :id: 0f88a29c-0fcd-11f0-b5df-482ae39447e5
|
||||
+ :setup: None
|
||||
+ :steps:
|
||||
+ 1. Initialize a NormalizedRidDict
|
||||
+ 2. Check that normalization do something
|
||||
+ 3. Check that key stored in NormalizedRidDict are normalized
|
||||
+ 4. Check that normalized and non normalized keys have the same value
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ """
|
||||
+
|
||||
+ sd = { '1': 'v1', '020': 'v2' }
|
||||
+ nsd = { NormalizedRidDict.normalize_rid(key): val for key,val in sd.items() }
|
||||
+ nkeys = list(nsd.keys())
|
||||
+
|
||||
+ # Initialize a NormalizedRidDict
|
||||
+ nrd = NormalizedRidDict()
|
||||
+ for key,val in sd.items():
|
||||
+ nrd[key] = val
|
||||
+
|
||||
+ # Check that normalization do something
|
||||
+ assert nkeys != list(sd.keys())
|
||||
+
|
||||
+ # Check that key stored in NormalizedRidDict are normalized
|
||||
+ for key in nrd.keys():
|
||||
+ assert key in nkeys
|
||||
+
|
||||
+ # Check that normalized and non normalized keys have the same value
|
||||
+ for key,val in sd.items():
|
||||
+ nkey = NormalizedRidDict.normalize_rid(key)
|
||||
+ assert nrd[key] == val
|
||||
+ assert nrd[nkey] == val
|
||||
+
|
||||
+
|
||||
@pytest.mark.ds49915
|
||||
@pytest.mark.bz1626375
|
||||
def test_online_reinit_may_hang(topo_with_sigkill):
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index ac8f5f3f2..1ac9770b0 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -60,6 +60,7 @@ from lib389.utils import (
|
||||
normalizeDN,
|
||||
escapeDNValue,
|
||||
ensure_bytes,
|
||||
+ ensure_int,
|
||||
ensure_str,
|
||||
ensure_list_str,
|
||||
format_cmd_list,
|
||||
@@ -3401,7 +3402,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
# Error
|
||||
consumer.close()
|
||||
return None
|
||||
- rid = ensure_str(replica_entries[0].getValue(REPL_ID))
|
||||
+ rid = ensure_int(replica_entries[0].getValue(REPL_ID))
|
||||
except:
|
||||
# Error
|
||||
consumer.close()
|
||||
@@ -3418,7 +3419,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
return error_msg
|
||||
elements = ensure_list_str(entry[0].getValues('nsds50ruv'))
|
||||
for ruv in elements:
|
||||
- if ('replica %s ' % rid) in ruv:
|
||||
+ if ('replica %d ' % rid) in ruv:
|
||||
ruv_parts = ruv.split()
|
||||
if len(ruv_parts) == 5:
|
||||
return ruv_parts[4]
|
||||
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
|
||||
index 170ab9050..745dd0bd0 100644
|
||||
--- a/src/lib389/lib389/agreement.py
|
||||
+++ b/src/lib389/lib389/agreement.py
|
||||
@@ -161,7 +161,7 @@ class Agreement(DSLdapObject):
|
||||
from lib389.replica import Replicas
|
||||
replicas = Replicas(self._instance)
|
||||
replica = replicas.get(suffix)
|
||||
- rid = replica.get_attr_val_utf8(REPL_ID)
|
||||
+ rid = int(replica.get_attr_val_utf8(REPL_ID))
|
||||
|
||||
# Open a connection to the consumer
|
||||
consumer = DirSrv(verbose=self._instance.verbose)
|
||||
@@ -191,12 +191,15 @@ class Agreement(DSLdapObject):
|
||||
else:
|
||||
elements = ensure_list_str(entry[0].getValues('nsds50ruv'))
|
||||
for ruv in elements:
|
||||
- if ('replica %s ' % rid) in ruv:
|
||||
+ if ('replica %d ' % rid) in ruv:
|
||||
ruv_parts = ruv.split()
|
||||
if len(ruv_parts) == 5:
|
||||
result_msg = ruv_parts[4]
|
||||
break
|
||||
except ldap.INVALID_CREDENTIALS as e:
|
||||
+ self._log.debug('Failed to search for the suffix ' +
|
||||
+ '({}) consumer ({}:{}) failed, error: {}'.format(
|
||||
+ suffix, host, port, e))
|
||||
raise(e)
|
||||
except ldap.LDAPError as e:
|
||||
self._log.debug('Failed to search for the suffix ' +
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 07f75b878..0486c48f8 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -822,6 +822,26 @@ class ReplicaLegacy(object):
|
||||
raise ValueError('Failed to update replica: ' + str(e))
|
||||
|
||||
|
||||
+class NormalizedRidDict(dict):
|
||||
+ """A dict whose key is a Normalized Replica ID
|
||||
+ """
|
||||
+
|
||||
+ @staticmethod
|
||||
+ def normalize_rid(rid):
|
||||
+ return int(rid)
|
||||
+
|
||||
+ def __init__(self):
|
||||
+ super().__init__()
|
||||
+
|
||||
+ def __getitem__(self, key):
|
||||
+ nkey = NormalizedRidDict.normalize_rid(key)
|
||||
+ return super().__getitem__(nkey)
|
||||
+
|
||||
+ def __setitem__(self, key, value):
|
||||
+ nkey = NormalizedRidDict.normalize_rid(key)
|
||||
+ super().__setitem__(nkey, value)
|
||||
+
|
||||
+
|
||||
class RUV(object):
|
||||
"""Represents the server in memory RUV object. The RUV contains each
|
||||
update vector the server knows of, along with knowledge of CSN state of the
|
||||
@@ -839,11 +859,11 @@ class RUV(object):
|
||||
else:
|
||||
self._log = logging.getLogger(__name__)
|
||||
self._rids = []
|
||||
- self._rid_url = {}
|
||||
- self._rid_rawruv = {}
|
||||
- self._rid_csn = {}
|
||||
- self._rid_maxcsn = {}
|
||||
- self._rid_modts = {}
|
||||
+ self._rid_url = NormalizedRidDict()
|
||||
+ self._rid_rawruv = NormalizedRidDict()
|
||||
+ self._rid_csn = NormalizedRidDict()
|
||||
+ self._rid_maxcsn = NormalizedRidDict()
|
||||
+ self._rid_modts = NormalizedRidDict()
|
||||
self._data_generation = None
|
||||
self._data_generation_csn = None
|
||||
# Process the array of data
|
||||
@@ -935,9 +955,10 @@ class RUV(object):
|
||||
:returns: str
|
||||
"""
|
||||
self._log.debug("Allocated rids: %s" % self._rids)
|
||||
+ rids = [ int(rid) for rid in self._rids ]
|
||||
for i in range(1, 65534):
|
||||
self._log.debug("Testing ... %s" % i)
|
||||
- if str(i) not in self._rids:
|
||||
+ if i not in rids:
|
||||
return str(i)
|
||||
raise Exception("Unable to alloc rid!")
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From eb3c3e6bc8719685b0c4777fa7373a271183707e Mon Sep 17 00:00:00 2001
|
||||
From e98acc1bfe2194fcdd0e420777eb65a20d55a64b Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 7 Jul 2025 22:01:09 +0200
|
||||
Subject: [PATCH] Issue 6848 - AddressSanitizer: leak in do_search
|
||||
@ -1,47 +0,0 @@
|
||||
From 496b3328181a6340622534daf52267e15b4bd75c Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Sun, 6 Apr 2025 21:23:08 +0000
|
||||
Subject: [PATCH] Issue 6713 - ns-slapd crash during mdb offline import (#6714)
|
||||
|
||||
Bug description: A segmentation fault is triggered in
|
||||
dbmdb_import_prepare_worker_entry() during an mdb offline import.
|
||||
|
||||
The import producer thread parses, validates and writes ldif entries
|
||||
to the worker queue, while the import worker threads simultaneously read,
|
||||
format and index entries before adding them to the DB. A race condition
|
||||
occurs when a worker thread reads an entry before the producer has
|
||||
fully written it, leading to a segmentation fault.
|
||||
|
||||
Fix description: Ensure thread safe access by locking the worker queue
|
||||
before writing entries.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6713
|
||||
|
||||
Reviewed by: @progier389, @bordaz (Thank you)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
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 0f445bb56..39d2b06f7 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
|
||||
@@ -417,13 +417,14 @@ dbmdb_import_workerq_push(ImportQueue_t *q, WorkerQueueData_t *data)
|
||||
safe_cond_wait(&q->cv, &q->mutex);
|
||||
}
|
||||
}
|
||||
- pthread_mutex_unlock(&q->mutex);
|
||||
if (q->job->flags & FLAG_ABORT) {
|
||||
/* in this case worker thread does not free the data so we should do it */
|
||||
dbmdb_import_workerq_free_data(data);
|
||||
+ pthread_mutex_unlock(&q->mutex);
|
||||
return -1;
|
||||
}
|
||||
dbmdb_dup_worker_slot(q, data, slot);
|
||||
+ pthread_mutex_unlock(&q->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From d3ca0c87414866363c237f3f9875cdf8d3cf26fb Mon Sep 17 00:00:00 2001
|
||||
From 120bc2666b682a27ffd6ace5cc238b33fab32c21 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 11 Jul 2025 12:32:38 +0200
|
||||
Subject: [PATCH] Issue 6865 - AddressSanitizer: leak in
|
||||
@ -1,601 +0,0 @@
|
||||
From fbba9c3591e78421486ac29174489b5d8881f525 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 25 Mar 2025 09:20:50 +0100
|
||||
Subject: [PATCH] Issue 6571 - Nested group does not receive memberOf attribute
|
||||
(#6679)
|
||||
|
||||
Bug description:
|
||||
There is a risk to create a loop in group membership.
|
||||
For example G2 is member of G1 and G1 is member of G2.
|
||||
Memberof plugins iterates from a node to its ancestors
|
||||
to update the 'memberof' values of the node.
|
||||
The plugin uses a valueset ('already_seen_ndn_vals')
|
||||
to keep the track of the nodes it already visited.
|
||||
It uses this valueset to detect a possible loop and
|
||||
in that case it does not add the ancestor as the
|
||||
memberof value of the node.
|
||||
This is an error in case there are multiples paths
|
||||
up to an ancestor.
|
||||
|
||||
Fix description:
|
||||
The ancestor should be added to the node systematically,
|
||||
just in case the ancestor is in 'already_seen_ndn_vals'
|
||||
it skips the final recursion
|
||||
|
||||
fixes: #6571
|
||||
|
||||
Reviewed by: Pierre Rogier, Mark Reynolds (Thanks !!!)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 109 +++++++++
|
||||
.../tests/suites/plugins/memberof_test.py | 5 +
|
||||
.../slapi_memberof/basic_interface_test.py | 226 ++++++++++++------
|
||||
ldap/servers/plugins/memberof/memberof.c | 52 ++--
|
||||
4 files changed, 291 insertions(+), 101 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 1adaaf634..845de8ac5 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -467,6 +467,21 @@ def _find_memberof_ext(server, user_dn=None, group_dn=None, find_result=True):
|
||||
else:
|
||||
assert (not found)
|
||||
|
||||
+def _check_membership(server, entry, expected_members, expected_memberof):
|
||||
+ assert server
|
||||
+ assert entry
|
||||
+
|
||||
+ memberof = entry.get_attr_vals('memberof')
|
||||
+ member = entry.get_attr_vals('member')
|
||||
+ assert len(member) == len(expected_members)
|
||||
+ assert len(memberof) == len(expected_memberof)
|
||||
+ for e in expected_members:
|
||||
+ server.log.info("Checking %s has member %s" % (entry.dn, e.dn))
|
||||
+ assert e.dn.encode() in member
|
||||
+ for e in expected_memberof:
|
||||
+ server.log.info("Checking %s is member of %s" % (entry.dn, e.dn))
|
||||
+ assert e.dn.encode() in memberof
|
||||
+
|
||||
|
||||
@pytest.mark.ds49161
|
||||
def test_memberof_group(topology_st):
|
||||
@@ -535,6 +550,100 @@ def test_memberof_group(topology_st):
|
||||
_find_memberof_ext(inst, dn1, g2n, True)
|
||||
_find_memberof_ext(inst, dn2, g2n, True)
|
||||
|
||||
+def test_multipaths(topology_st, request):
|
||||
+ """Test memberof succeeds to update memberof when
|
||||
+ there are multiple paths from a leaf to an intermediate node
|
||||
+
|
||||
+ :id: 35aa704a-b895-4153-9dcb-1e8a13612ebf
|
||||
+
|
||||
+ :setup: Single instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Create a graph G1->U1, G2->G21->U1
|
||||
+ 2. Add G2 as member of G1: G1->U1, G1->G2->G21->U1
|
||||
+ 3. Check members and memberof in entries G1,G2,G21,User1
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Graph should be created
|
||||
+ 2. succeed
|
||||
+ 3. Membership is okay
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.enable()
|
||||
+ memberof.replace('memberOfEntryScope', SUFFIX)
|
||||
+ if (memberof.get_memberofdeferredupdate() and memberof.get_memberofdeferredupdate().lower() == "on"):
|
||||
+ delay = 3
|
||||
+ else:
|
||||
+ delay = 0
|
||||
+ inst.restart()
|
||||
+
|
||||
+ #
|
||||
+ # Create the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ---------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 ----> Grp21 ------/
|
||||
+ #
|
||||
+ users = UserAccounts(inst, SUFFIX, rdn=None)
|
||||
+ user1 = users.create(properties={'uid': "user1",
|
||||
+ 'cn': "user1",
|
||||
+ 'sn': 'SN',
|
||||
+ 'description': 'leaf',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/user1'
|
||||
+ })
|
||||
+ group = Groups(inst, SUFFIX, rdn=None)
|
||||
+ g1 = group.create(properties={'cn': 'group1',
|
||||
+ 'member': user1.dn,
|
||||
+ 'description': 'group1'})
|
||||
+ g21 = group.create(properties={'cn': 'group21',
|
||||
+ 'member': user1.dn,
|
||||
+ 'description': 'group21'})
|
||||
+ g2 = group.create(properties={'cn': 'group2',
|
||||
+ 'member': [g21.dn],
|
||||
+ 'description': 'group2'})
|
||||
+
|
||||
+ # Enable debug logs if necessary
|
||||
+ #inst.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #inst.config.set('nsslapd-accesslog-level','260')
|
||||
+ #inst.config.set('nsslapd-plugin-logging', 'on')
|
||||
+ #inst.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #inst.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # \ ^
|
||||
+ # \ /
|
||||
+ # --> Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.add_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[g2, user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g1])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ def fin():
|
||||
+ try:
|
||||
+ user1.delete()
|
||||
+ g1.delete()
|
||||
+ g2.delete()
|
||||
+ g21.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+ request.addfinalizer(fin)
|
||||
|
||||
def _config_memberof_entrycache_on_modrdn_failure(server):
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/memberof_test.py b/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
index 0ebdf7fb3..912dead39 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
@@ -2169,9 +2169,14 @@ def test_complex_group_scenario_6(topology_st):
|
||||
|
||||
# add Grp[1-4] (uniqueMember) to grp5
|
||||
# it creates a membership loop !!!
|
||||
+ topology_st.standalone.config.replace('nsslapd-errorlog-level', '65536')
|
||||
mods = [(ldap.MOD_ADD, 'uniqueMember', memofegrp020_5)]
|
||||
for grp in [memofegrp020_1, memofegrp020_2, memofegrp020_3, memofegrp020_4]:
|
||||
topology_st.standalone.modify_s(ensure_str(grp), mods)
|
||||
+ topology_st.standalone.config.replace('nsslapd-errorlog-level', '0')
|
||||
+
|
||||
+ results = topology_st.standalone.ds_error_log.match('.*detecting a loop in group.*')
|
||||
+ assert results
|
||||
|
||||
time.sleep(5)
|
||||
# assert user[1-4] are member of grp20_[1-4]
|
||||
diff --git a/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py b/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py
|
||||
index c5ecf5227..cc25f7e6c 100644
|
||||
--- a/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py
|
||||
+++ b/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py
|
||||
@@ -4220,18 +4220,18 @@ def test_slapi_memberof_reuse_only_1(topo, request, install_test_plugin):
|
||||
|
||||
def test_slapi_memberof_reuse_only_2(topo, request, install_test_plugin):
|
||||
"""
|
||||
- Test that management hierarchy (manager) is computed with slapi_memberof
|
||||
+ Test that membership is computed with slapi_memberof
|
||||
It requires slapi_memberof to ONLY reuse the computed values
|
||||
from memberof plugins. As memberof plugin is enabled, it returns
|
||||
memberof.
|
||||
with following parameters
|
||||
- member attribute: memberof
|
||||
- - membership attribute: 'manager'
|
||||
+ - membership attribute: 'member'
|
||||
- span over all backends: 'off'
|
||||
- skip nesting membership: 'off'
|
||||
- - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <--
|
||||
+ - computation mode: MEMBEROF_REUSE_ONLY <--
|
||||
- Scope: None
|
||||
- - ExcludeScope: ou=foo1,dc=example,dc=com <--
|
||||
+ - ExcludeScope: dc=example,dc=com <--
|
||||
- Maximum return entries: None
|
||||
|
||||
:id: fb4f8c86-aa39-4252-90e0-36cfd7b3dd80
|
||||
@@ -4274,59 +4274,141 @@ def test_slapi_memberof_reuse_only_2(topo, request, install_test_plugin):
|
||||
--- e_1_parent_1_1_3_0
|
||||
---- e_1_parent_1_1_1_3_0
|
||||
"""
|
||||
+ # Configure memberof plugin to add 'memberof' attribute
|
||||
+ # to the members ('member') of groups that are in the suffix
|
||||
memberof = MemberOfPlugin(topo.standalone)
|
||||
memberof.enable()
|
||||
memberof.replace('memberOfAttr', 'memberof')
|
||||
- memberof.replace('memberOfGroupAttr', 'manager')
|
||||
+ memberof.replace('memberOfGroupAttr', 'member')
|
||||
memberof.replace('memberOfAllBackends', 'off')
|
||||
memberof.replace('memberOfSkipNested', 'off')
|
||||
memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX)
|
||||
topo.standalone.restart()
|
||||
|
||||
- user = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
+ #topo.standalone.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #topo.standalone.config.set('nsslapd-accesslog-level','260')
|
||||
+ #topo.standalone.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #topo.standalone.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+ #topo.standalone.config.set('nsslapd-plugin-logging', 'on')
|
||||
|
||||
# First subtree
|
||||
- e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0")
|
||||
-
|
||||
- e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)])
|
||||
-
|
||||
- e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)])
|
||||
-
|
||||
- e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)])
|
||||
- e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)])
|
||||
- e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)])
|
||||
- e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)])
|
||||
- e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)])
|
||||
-
|
||||
- e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)])
|
||||
-
|
||||
- e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)])
|
||||
-
|
||||
- e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)])
|
||||
- e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)])
|
||||
- e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)])
|
||||
- e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)])
|
||||
- e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)])
|
||||
-
|
||||
- # 2nd subtree
|
||||
- e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0")
|
||||
-
|
||||
- e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)])
|
||||
- e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)])
|
||||
- e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)])
|
||||
- e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)])
|
||||
-
|
||||
- # third subtree
|
||||
- e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0")
|
||||
-
|
||||
- e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)])
|
||||
-
|
||||
- e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)])
|
||||
-
|
||||
- e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)])
|
||||
-
|
||||
- e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)])
|
||||
+ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0")
|
||||
+ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0")
|
||||
+ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0")
|
||||
+ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0")
|
||||
+ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0")
|
||||
+ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0")
|
||||
+ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0")
|
||||
+ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0")
|
||||
+ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0")
|
||||
+ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0")
|
||||
+ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0")
|
||||
+ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0")
|
||||
+ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0")
|
||||
+ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0")
|
||||
+ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0")
|
||||
+
|
||||
+ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0")
|
||||
+ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0")
|
||||
+ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0")
|
||||
+ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0")
|
||||
+ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0")
|
||||
+
|
||||
+ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0")
|
||||
+ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0")
|
||||
+ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0")
|
||||
+ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0")
|
||||
+ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0")
|
||||
+
|
||||
+ # e_1_parent_0
|
||||
+ # - e_1_parent_1_0
|
||||
+ # - e_2_parent_1_0
|
||||
+ members = [ensure_bytes(e_1_parent_1_0),
|
||||
+ ensure_bytes(e_2_parent_1_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_1_parent_0, mod)
|
||||
+
|
||||
+ # - e_1_parent_1_0
|
||||
+ # -- e_1_parent_1_1_0
|
||||
+ # -- e_2_parent_1_1_0
|
||||
+ members = [ensure_bytes(e_1_parent_1_1_0),
|
||||
+ ensure_bytes(e_2_parent_1_1_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_1_parent_1_0, mod)
|
||||
+
|
||||
+ # -- e_1_parent_1_1_0
|
||||
+ # --- e_1_parent_1_1_1_0
|
||||
+ # --- e_2_parent_1_1_1_0
|
||||
+ # --- e_3_parent_1_1_1_0
|
||||
+ # --- e_4_parent_1_1_1_0
|
||||
+ # --- e_5_parent_1_1_1_0
|
||||
+ members = [ensure_bytes(e_1_parent_1_1_1_0),
|
||||
+ ensure_bytes(e_2_parent_1_1_1_0),
|
||||
+ ensure_bytes(e_3_parent_1_1_1_0),
|
||||
+ ensure_bytes(e_4_parent_1_1_1_0),
|
||||
+ ensure_bytes(e_5_parent_1_1_1_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_1_parent_1_1_0, mod)
|
||||
+
|
||||
+ # - e_2_parent_1_0
|
||||
+ # -- e_1_parent_2_1_0
|
||||
+ # -- e_2_parent_2_1_0
|
||||
+ # -- e_3_parent_2_1_0
|
||||
+ # -- e_4_parent_2_1_0
|
||||
+ members = [ensure_bytes(e_1_parent_2_1_0),
|
||||
+ ensure_bytes(e_2_parent_2_1_0),
|
||||
+ ensure_bytes(e_3_parent_2_1_0),
|
||||
+ ensure_bytes(e_4_parent_2_1_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_2_parent_1_0, mod)
|
||||
+
|
||||
+ # -- e_2_parent_2_1_0
|
||||
+ # --- e_1_parent_2_2_1_0
|
||||
+ members = [ensure_bytes(e_1_parent_2_2_1_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_2_parent_2_1_0, mod)
|
||||
+
|
||||
+ # e_2_parent_0
|
||||
+ # - e_1_parent_2_0
|
||||
+ # - e_2_parent_2_0
|
||||
+ # - e_3_parent_2_0
|
||||
+ # - e_4_parent_2_0
|
||||
+ members = [ensure_bytes(e_1_parent_2_0),
|
||||
+ ensure_bytes(e_2_parent_2_0),
|
||||
+ ensure_bytes(e_3_parent_2_0),
|
||||
+ ensure_bytes(e_4_parent_2_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_2_parent_0, mod)
|
||||
+
|
||||
+ # e_3_parent_0
|
||||
+ # - e_1_parent_3_0
|
||||
+ members = [ensure_bytes(e_1_parent_3_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_3_parent_0, mod)
|
||||
+
|
||||
+ # - e_1_parent_3_0
|
||||
+ # -- e_1_parent_1_3_0
|
||||
+ members = [ensure_bytes(e_1_parent_1_3_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_1_parent_3_0, mod)
|
||||
+
|
||||
+ # -- e_1_parent_1_3_0
|
||||
+ # --- e_1_parent_1_1_3_0
|
||||
+ members = [ensure_bytes(e_1_parent_1_1_3_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_1_parent_1_3_0, mod)
|
||||
+
|
||||
+ # --- e_1_parent_1_1_3_0
|
||||
+ # ---- e_1_parent_1_1_1_3_0
|
||||
+ members = [ensure_bytes(e_1_parent_1_1_1_3_0)]
|
||||
+ mod = [(ldap.MOD_REPLACE, 'member', members)]
|
||||
+ topo.standalone.modify_s(e_1_parent_1_1_3_0, mod)
|
||||
|
||||
+ #
|
||||
+ # configure the test plugin to request 'memberof' with the
|
||||
+ # same scope and groupAttr ('member') so that we can
|
||||
+ # reuse the values computed by memberof plugin
|
||||
+ #
|
||||
dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config'
|
||||
topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(),
|
||||
'cn': 'test_slapi_memberof',
|
||||
@@ -4337,7 +4419,7 @@ def test_slapi_memberof_reuse_only_2(topo, request, install_test_plugin):
|
||||
'nsslapd-plugin-depends-on-type': 'database',
|
||||
'nsslapd-pluginId': 'test_slapi_memberof-plugin',
|
||||
'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com',
|
||||
- 'slapimemberOfGroupAttr': 'manager',
|
||||
+ 'slapimemberOfGroupAttr': 'member',
|
||||
'slapimemberOfAttr': 'memberof',
|
||||
'slapimemberOfFlag': 'MEMBEROF_REUSE_ONLY',
|
||||
'slapimemberOfAllBackends': 'off',
|
||||
@@ -4350,63 +4432,63 @@ def test_slapi_memberof_reuse_only_2(topo, request, install_test_plugin):
|
||||
topo.standalone.restart()
|
||||
|
||||
# Check the first subtree
|
||||
- expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager")
|
||||
+ expected = [EMPTY_RESULT]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="member")
|
||||
_check_res_vs_expected("first subtree", res, expected)
|
||||
|
||||
# Check the second subtree
|
||||
- expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager")
|
||||
+ expected = [EMPTY_RESULT]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="member")
|
||||
_check_res_vs_expected("second subtree", res, expected)
|
||||
|
||||
# Check the third subtree
|
||||
- expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager")
|
||||
+ expected = [EMPTY_RESULT]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="member")
|
||||
_check_res_vs_expected("third subtree", res, expected)
|
||||
|
||||
# check e_1_parent_1_0
|
||||
- expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager")
|
||||
- _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected)
|
||||
+ expected = [e_1_parent_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="member")
|
||||
+ _check_res_vs_expected("Groups which e_1_parent_1_0 is member of", res, expected)
|
||||
|
||||
# check e_1_parent_1_1_0
|
||||
- expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager")
|
||||
+ expected = [e_1_parent_0, e_1_parent_1_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected)
|
||||
|
||||
# check e_2_parent_1_1_0
|
||||
- expected = [EMPTY_RESULT]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager")
|
||||
+ expected = [e_1_parent_0, e_1_parent_1_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected)
|
||||
|
||||
# check e_2_parent_1_0
|
||||
- expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager")
|
||||
+ expected = [e_1_parent_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected)
|
||||
|
||||
# check e_2_parent_2_1_0
|
||||
- expected = [e_1_parent_2_2_1_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager")
|
||||
+ expected = [e_1_parent_0, e_2_parent_1_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected)
|
||||
|
||||
# Check e_1_parent_3_0
|
||||
- expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager")
|
||||
+ expected = [e_3_parent_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected)
|
||||
|
||||
# Check e_1_parent_1_3_0
|
||||
- expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager")
|
||||
+ expected = [e_3_parent_0, e_1_parent_3_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected)
|
||||
|
||||
# Check e_1_parent_1_1_3_0
|
||||
- expected = [e_1_parent_1_1_1_3_0]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager")
|
||||
+ expected = [e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected)
|
||||
|
||||
# Check e_1_parent_1_1_1_3_0
|
||||
- expected = [EMPTY_RESULT]
|
||||
- res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager")
|
||||
+ expected = [e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0]
|
||||
+ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="member")
|
||||
_check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected)
|
||||
|
||||
def fin():
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 0f7b3a41d..563af47f8 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1602,7 +1602,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
ht_grp = ancestors_cache_lookup(config, (const void *)ndn);
|
||||
if (ht_grp) {
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%x)\n", ndn, ht_grp);
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%lx)\n", ndn, (ulong) ht_grp);
|
||||
#endif
|
||||
add_ancestors_cbdata(ht_grp, callback_data);
|
||||
*cached = 1;
|
||||
@@ -1610,7 +1610,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
}
|
||||
}
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", ndn);
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", slapi_sdn_get_ndn(sdn));
|
||||
#endif
|
||||
|
||||
/* Escape the dn, and build the search filter. */
|
||||
@@ -3243,7 +3243,8 @@ cache_ancestors(MemberOfConfig *config, Slapi_Value **member_ndn_val, memberof_g
|
||||
return;
|
||||
}
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- if (double_check = ancestors_cache_lookup(config, (const void*) key)) {
|
||||
+ double_check = ancestors_cache_lookup(config, (const void*) key);
|
||||
+ if (double_check) {
|
||||
dump_cache_entry(double_check, "read back");
|
||||
}
|
||||
#endif
|
||||
@@ -3273,13 +3274,13 @@ merge_ancestors(Slapi_Value **member_ndn_val, memberof_get_groups_data *v1, memb
|
||||
sval_dn = slapi_value_new_string(slapi_value_get_string(sval));
|
||||
if (sval_dn) {
|
||||
/* Use the normalized dn from v1 to search it
|
||||
- * in v2
|
||||
- */
|
||||
+ * in v2
|
||||
+ */
|
||||
val_sdn = slapi_sdn_new_dn_byval(slapi_value_get_string(sval_dn));
|
||||
sval_ndn = slapi_value_new_string(slapi_sdn_get_ndn(val_sdn));
|
||||
if (!slapi_valueset_find(
|
||||
((memberof_get_groups_data *)v2)->config->group_slapiattrs[0], v2_group_norm_vals, sval_ndn)) {
|
||||
-/* This ancestor was not already present in v2 => Add it
|
||||
+ /* This ancestor was not already present in v2 => Add it
|
||||
* Using slapi_valueset_add_value it consumes val
|
||||
* so do not free sval
|
||||
*/
|
||||
@@ -3328,7 +3329,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn, memberof_get
|
||||
|
||||
merge_ancestors(&member_ndn_val, &member_data, data);
|
||||
if (!cached && member_data.use_cache)
|
||||
- cache_ancestors(config, &member_ndn_val, &member_data);
|
||||
+ cache_ancestors(config, &member_ndn_val, data);
|
||||
|
||||
slapi_value_free(&member_ndn_val);
|
||||
slapi_valueset_free(groupvals);
|
||||
@@ -3389,25 +3390,6 @@ memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
- /* Have we been here before? Note that we don't loop through all of the group_slapiattrs
|
||||
- * in config. We only need this attribute for it's syntax so the comparison can be
|
||||
- * performed. Since all of the grouping attributes are validated to use the Dinstinguished
|
||||
- * Name syntax, we can safely just use the first group_slapiattr. */
|
||||
- if (slapi_valueset_find(
|
||||
- ((memberof_get_groups_data *)callback_data)->config->group_slapiattrs[0], already_seen_ndn_vals, group_ndn_val)) {
|
||||
- /* we either hit a recursive grouping, or an entry is
|
||||
- * a member of a group through multiple paths. Either
|
||||
- * way, we can just skip processing this entry since we've
|
||||
- * already gone through this part of the grouping hierarchy. */
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_get_groups_callback - Possible group recursion"
|
||||
- " detected in %s\n",
|
||||
- group_ndn);
|
||||
- slapi_value_free(&group_ndn_val);
|
||||
- ((memberof_get_groups_data *)callback_data)->use_cache = PR_FALSE;
|
||||
- goto bail;
|
||||
- }
|
||||
-
|
||||
/* if the group does not belong to an excluded subtree, adds it to the valueset */
|
||||
if (memberof_entry_in_scope(config, group_sdn)) {
|
||||
/* Push group_dn_val into the valueset. This memory is now owned
|
||||
@@ -3417,9 +3399,21 @@ memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
group_dn_val = slapi_value_new_string(group_dn);
|
||||
slapi_valueset_add_value_ext(groupvals, group_dn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
|
||||
- /* push this ndn to detect group recursion */
|
||||
- already_seen_ndn_val = slapi_value_new_string(group_ndn);
|
||||
- slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
+ if (slapi_valueset_find(
|
||||
+ ((memberof_get_groups_data *)callback_data)->config->group_slapiattrs[0], already_seen_ndn_vals, group_ndn_val)) {
|
||||
+ /* The group group_ndn_val has already been processed
|
||||
+ * skip the final recursion to prevent infinite loop
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_get_groups_callback - detecting a loop in group %s (stop building memberof)\n",
|
||||
+ group_ndn);
|
||||
+ ((memberof_get_groups_data *)callback_data)->use_cache = PR_FALSE;
|
||||
+ goto bail;
|
||||
+ } else {
|
||||
+ /* keep this ndn to detect a possible group recursion */
|
||||
+ already_seen_ndn_val = slapi_value_new_string(group_ndn);
|
||||
+ slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
+ }
|
||||
}
|
||||
if (!config->skip_nested || config->fixup_task) {
|
||||
/* now recurse to find ancestors groups of e */
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From ad74bf7d98f5db62613a6c6a31a512d613334038 Mon Sep 17 00:00:00 2001
|
||||
From 5cc13c70dfe22d95686bec9214c53f1b4114cd90 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 1 Aug 2025 13:27:02 +0100
|
||||
Subject: [PATCH] Issue 6768 - ns-slapd crashes when a referral is added
|
||||
@ -78,10 +78,10 @@ index fca48db0f..1bb94b53a 100644
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
|
||||
index 7dc2d5983..9ab269c1f 100644
|
||||
index 14a7dcdfb..03ed60981 100644
|
||||
--- a/ldap/servers/slapd/opshared.c
|
||||
+++ b/ldap/servers/slapd/opshared.c
|
||||
@@ -871,7 +871,9 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
@@ -879,7 +879,9 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
/* Free the results if not "no_such_object" */
|
||||
void *sr = NULL;
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr);
|
||||
@ -1,39 +0,0 @@
|
||||
From 317478d619b8b38acfde3f2510045412781dd0fc Mon Sep 17 00:00:00 2001
|
||||
From: Navid Yaghoobi <n.yaghoobi.s@gmail.com>
|
||||
Date: Mon, 12 Aug 2024 20:35:45 +1000
|
||||
Subject: [PATCH] Issue 6288 - dsidm crash with account policy when
|
||||
alt-state-attr is disabled (#6292)
|
||||
|
||||
Bug Description:
|
||||
If disable alt-state-attr for account policy plugin by using value 1.1 then
|
||||
dsidm command will crash if state-attr doesn't exit at that time.
|
||||
|
||||
Fix Description:
|
||||
Check if alt-state-attri key exist under account data dict before getting its value.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6288
|
||||
|
||||
Author: Navid Yaghoobi
|
||||
|
||||
Reviewed by: @progier389
|
||||
---
|
||||
src/lib389/lib389/idm/account.py | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py
|
||||
index 4b823b662..d892c8d0d 100644
|
||||
--- a/src/lib389/lib389/idm/account.py
|
||||
+++ b/src/lib389/lib389/idm/account.py
|
||||
@@ -140,7 +140,8 @@ class Account(DSLdapObject):
|
||||
"nsAccountLock", state_attr])
|
||||
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, state_attr)
|
||||
- if not last_login_time:
|
||||
+ # if last_login_time not exist then check alt_state_attr only if its not disabled and exist
|
||||
+ if not last_login_time and alt_state_attr in account_data:
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, alt_state_attr)
|
||||
|
||||
create_time = self._dict_get_with_ignore_indexerror(account_data, "createTimestamp")
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 8359d0f426341f6a7f76f94c0fe4d9bd64043f68 Mon Sep 17 00:00:00 2001
|
||||
From def739668dd2728825f1108911abc065f981010c Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 19 Aug 2025 16:10:09 -0700
|
||||
Subject: [PATCH] Issue 6940 - dsconf monitor server fails with ldapi:// due to
|
||||
@ -34,7 +34,7 @@ Reviewed by: @vashirov, @mreynolds389 (Thanks!!)
|
||||
3 files changed, 124 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index b76806572..39a2852e5 100644
|
||||
index 65e70c1dd..e6f9273eb 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -17,7 +17,7 @@
|
||||
@ -1,454 +0,0 @@
|
||||
From d3a4cfa61620e89c78b5d96ae6d048d40d0e47fa Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 28 Mar 2025 11:28:54 -0700
|
||||
Subject: [PATCH] Issue 6686 - CLI - Re-enabling user accounts that reached
|
||||
inactivity limit fails with error (#6687)
|
||||
|
||||
Description: When attempting to unlock a user account that has been locked due
|
||||
to exceeding the Account Policy Plugin's inactivity limit, the dsidm account
|
||||
unlock command fails with a Python type error: "float() argument must be a
|
||||
string or a number, not 'NoneType'".
|
||||
|
||||
Enhance the unlock method to properly handle different account locking states,
|
||||
including inactivity limit exceeded states.
|
||||
Add test cases to verify account inactivity locking/unlocking functionality
|
||||
with CoS and role-based indirect locking.
|
||||
|
||||
Fix CoS template class to include the required 'ldapsubentry' objectClass.
|
||||
Improv error messages to provide better guidance on unlocking indirectly
|
||||
locked accounts.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6686
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
.../clu/dsidm_account_inactivity_test.py | 329 ++++++++++++++++++
|
||||
src/lib389/lib389/cli_idm/account.py | 25 +-
|
||||
src/lib389/lib389/idm/account.py | 27 +-
|
||||
3 files changed, 376 insertions(+), 5 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py b/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
new file mode 100644
|
||||
index 000000000..88a34abf6
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
@@ -0,0 +1,329 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import ldap
|
||||
+import time
|
||||
+import pytest
|
||||
+import logging
|
||||
+import os
|
||||
+from datetime import datetime, timedelta
|
||||
+
|
||||
+from lib389 import DEFAULT_SUFFIX, DN_PLUGIN, DN_CONFIG
|
||||
+from lib389.cli_idm.account import entry_status, unlock
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.cli_base import FakeArgs
|
||||
+from lib389.utils import ds_is_older
|
||||
+from lib389.plugins import AccountPolicyPlugin, AccountPolicyConfigs
|
||||
+from lib389.idm.role import FilteredRoles
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.cos import CosTemplate, CosPointerDefinition
|
||||
+from lib389.idm.domain import Domain
|
||||
+from . import check_value_in_log_and_reset
|
||||
+
|
||||
+pytestmark = pytest.mark.tier0
|
||||
+
|
||||
+logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+# Constants
|
||||
+PLUGIN_ACCT_POLICY = "Account Policy Plugin"
|
||||
+ACCP_DN = f"cn={PLUGIN_ACCT_POLICY},{DN_PLUGIN}"
|
||||
+ACCP_CONF = f"{DN_CONFIG},{ACCP_DN}"
|
||||
+POLICY_NAME = "Account Inactivity Policy"
|
||||
+POLICY_DN = f"cn={POLICY_NAME},{DEFAULT_SUFFIX}"
|
||||
+COS_TEMPLATE_NAME = "TemplateCoS"
|
||||
+COS_TEMPLATE_DN = f"cn={COS_TEMPLATE_NAME},{DEFAULT_SUFFIX}"
|
||||
+COS_DEFINITION_NAME = "DefinitionCoS"
|
||||
+COS_DEFINITION_DN = f"cn={COS_DEFINITION_NAME},{DEFAULT_SUFFIX}"
|
||||
+TEST_USER_NAME = "test_inactive_user"
|
||||
+TEST_USER_DN = f"uid={TEST_USER_NAME},{DEFAULT_SUFFIX}"
|
||||
+TEST_USER_PW = "password"
|
||||
+INACTIVITY_LIMIT = 30
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def account_policy_setup(topology_st, request):
|
||||
+ """Set up account policy plugin, configuration, and CoS objects"""
|
||||
+ log.info("Setting up Account Policy Plugin and CoS")
|
||||
+
|
||||
+ # Enable Account Policy Plugin
|
||||
+ plugin = AccountPolicyPlugin(topology_st.standalone)
|
||||
+ if not plugin.status():
|
||||
+ plugin.enable()
|
||||
+ plugin.set('nsslapd-pluginarg0', ACCP_CONF)
|
||||
+
|
||||
+ # Configure Account Policy
|
||||
+ accp_configs = AccountPolicyConfigs(topology_st.standalone)
|
||||
+ accp_config = accp_configs.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': 'config',
|
||||
+ 'alwaysrecordlogin': 'yes',
|
||||
+ 'stateattrname': 'lastLoginTime',
|
||||
+ 'altstateattrname': '1.1',
|
||||
+ 'specattrname': 'acctPolicySubentry',
|
||||
+ 'limitattrname': 'accountInactivityLimit'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Add ACI for anonymous access if it doesn't exist
|
||||
+ domain = Domain(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ anon_aci = '(targetattr="*")(version 3.0; acl "Anonymous read access"; allow (read,search,compare) userdn="ldap:///anyone";)'
|
||||
+ domain.ensure_present('aci', anon_aci)
|
||||
+
|
||||
+ # Restart the server to apply plugin configuration
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ # Create or update account policy entry
|
||||
+ accp_configs = AccountPolicyConfigs(topology_st.standalone, basedn=DEFAULT_SUFFIX)
|
||||
+ policy = accp_configs.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': POLICY_NAME,
|
||||
+ 'objectClass': ['top', 'ldapsubentry', 'extensibleObject', 'accountpolicy'],
|
||||
+ 'accountInactivityLimit': str(INACTIVITY_LIMIT)
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create or update CoS template entry
|
||||
+ cos_template = CosTemplate(topology_st.standalone, dn=COS_TEMPLATE_DN)
|
||||
+ cos_template.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': COS_TEMPLATE_NAME,
|
||||
+ 'objectClass': ['top', 'cosTemplate', 'extensibleObject'],
|
||||
+ 'acctPolicySubentry': policy.dn
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create or update CoS definition entry
|
||||
+ cos_def = CosPointerDefinition(topology_st.standalone, dn=COS_DEFINITION_DN)
|
||||
+ cos_def.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': COS_DEFINITION_NAME,
|
||||
+ 'objectClass': ['top', 'ldapsubentry', 'cosSuperDefinition', 'cosPointerDefinition'],
|
||||
+ 'cosTemplateDn': COS_TEMPLATE_DN,
|
||||
+ 'cosAttribute': 'acctPolicySubentry default operational-default'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Restart server to ensure CoS is applied
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Cleaning up Account Policy settings')
|
||||
+ try:
|
||||
+ # Delete CoS and policy entries
|
||||
+ if cos_def.exists():
|
||||
+ cos_def.delete()
|
||||
+ if cos_template.exists():
|
||||
+ cos_template.delete()
|
||||
+ if policy.exists():
|
||||
+ policy.delete()
|
||||
+
|
||||
+ # Disable the plugin
|
||||
+ if plugin.status():
|
||||
+ plugin.disable()
|
||||
+ topology_st.standalone.restart()
|
||||
+ except Exception as e:
|
||||
+ log.error(f'Failed to clean up: {e}')
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return topology_st.standalone
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def create_test_user(topology_st, account_policy_setup, request):
|
||||
+ """Create a test user for the inactivity test"""
|
||||
+ log.info('Creating test user')
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ user = users.ensure_state(
|
||||
+ properties={
|
||||
+ 'uid': TEST_USER_NAME,
|
||||
+ 'cn': TEST_USER_NAME,
|
||||
+ 'sn': TEST_USER_NAME,
|
||||
+ 'userPassword': TEST_USER_PW,
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': f'/home/{TEST_USER_NAME}'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting test user')
|
||||
+ if user.exists():
|
||||
+ user.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ return user
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older("1.4.2"), reason="Indirect account locking not implemented")
|
||||
+def test_dsidm_account_inactivity_lock_unlock(topology_st, create_test_user):
|
||||
+ """Test dsidm account unlock functionality with indirectly locked accounts
|
||||
+
|
||||
+ :id: d7b57083-6111-4dbf-af84-6fca7fc7fb31
|
||||
+ :setup: Standalone instance with Account Policy Plugin and CoS configured
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. Bind as the test user to set lastLoginTime
|
||||
+ 3. Check account status - should be active
|
||||
+ 4. Set user's lastLoginTime to a time in the past that exceeds inactivity limit
|
||||
+ 5. Check account status - should be locked due to inactivity
|
||||
+ 6. Attempt to bind as the user - should fail with constraint violation
|
||||
+ 7. Unlock the account using dsidm account unlock
|
||||
+ 8. Verify account status is active again
|
||||
+ 9. Verify the user can bind again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Account status shows as activated
|
||||
+ 4. Success
|
||||
+ 5. Account status shows as inactivity limit exceeded
|
||||
+ 6. Bind attempt fails with constraint violation
|
||||
+ 7. Account unlocked successfully
|
||||
+ 8. Account status shows as activated
|
||||
+ 9. User can bind successfully
|
||||
+ """
|
||||
+ standalone = topology_st.standalone
|
||||
+ user = create_test_user
|
||||
+
|
||||
+ # Set up FakeArgs for dsidm commands
|
||||
+ args = FakeArgs()
|
||||
+ args.dn = user.dn
|
||||
+ args.json = False
|
||||
+ args.details = False
|
||||
+
|
||||
+ # 1. Check initial account status - should be active
|
||||
+ log.info('Step 1: Checking initial account status')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 2. Bind as test user to set initial lastLoginTime
|
||||
+ log.info('Step 2: Binding as test user to set lastLoginTime')
|
||||
+ try:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ conn.unbind()
|
||||
+ log.info("Successfully bound as test user")
|
||||
+ except ldap.LDAPError as e:
|
||||
+ pytest.fail(f"Failed to bind as test user: {e}")
|
||||
+
|
||||
+ # 3. Set lastLoginTime to a time in the past that exceeds inactivity limit
|
||||
+ log.info('Step 3: Setting lastLoginTime to the past')
|
||||
+ past_time = datetime.utcnow() - timedelta(seconds=INACTIVITY_LIMIT * 2)
|
||||
+ past_time_str = past_time.strftime('%Y%m%d%H%M%SZ')
|
||||
+ user.replace('lastLoginTime', past_time_str)
|
||||
+
|
||||
+ # 4. Check account status - should now be locked due to inactivity
|
||||
+ log.info('Step 4: Checking account status after setting old lastLoginTime')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: inactivity limit exceeded')
|
||||
+
|
||||
+ # 5. Attempt to bind as the user - should fail
|
||||
+ log.info('Step 5: Attempting to bind as user (should fail)')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION) as excinfo:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ assert "Account inactivity limit exceeded" in str(excinfo.value)
|
||||
+
|
||||
+ # 6. Unlock the account using dsidm account unlock
|
||||
+ log.info('Step 6: Unlocking the account with dsidm')
|
||||
+ unlock(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st,
|
||||
+ check_value='now unlocked by resetting lastLoginTime')
|
||||
+
|
||||
+ # 7. Verify account status is active again
|
||||
+ log.info('Step 7: Checking account status after unlock')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 8. Verify the user can bind again
|
||||
+ log.info('Step 8: Verifying user can bind again')
|
||||
+ try:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ conn.unbind()
|
||||
+ log.info("Successfully bound as test user after unlock")
|
||||
+ except ldap.LDAPError as e:
|
||||
+ pytest.fail(f"Failed to bind as test user after unlock: {e}")
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older("1.4.2"), reason="Indirect account locking not implemented")
|
||||
+def test_dsidm_indirectly_locked_via_role(topology_st, create_test_user):
|
||||
+ """Test dsidm account unlock functionality with accounts indirectly locked via role
|
||||
+
|
||||
+ :id: 7bfe69bb-cf99-4214-a763-051ab2b9cf89
|
||||
+ :setup: Standalone instance with Role and user configured
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. Create a Filtered Role that includes the test user
|
||||
+ 3. Lock the role
|
||||
+ 4. Check account status - should be indirectly locked through the role
|
||||
+ 5. Attempt to unlock the account - should fail with appropriate message
|
||||
+ 6. Unlock the role
|
||||
+ 7. Verify account status is active again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Account status shows as indirectly locked
|
||||
+ 5. Unlock attempt fails with appropriate error message
|
||||
+ 6. Success
|
||||
+ 7. Account status shows as activated
|
||||
+ """
|
||||
+ standalone = topology_st.standalone
|
||||
+ user = create_test_user
|
||||
+
|
||||
+ # Use FilteredRoles and ensure_state for role creation
|
||||
+ log.info('Step 1: Creating Filtered Role')
|
||||
+ roles = FilteredRoles(standalone, DEFAULT_SUFFIX)
|
||||
+ role = roles.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': 'TestFilterRole',
|
||||
+ 'nsRoleFilter': f'(uid={TEST_USER_NAME})'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Set up FakeArgs for dsidm commands
|
||||
+ args = FakeArgs()
|
||||
+ args.dn = user.dn
|
||||
+ args.json = False
|
||||
+ args.details = False
|
||||
+
|
||||
+ # 2. Check account status before locking role
|
||||
+ log.info('Step 2: Checking account status before locking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 3. Lock the role
|
||||
+ log.info('Step 3: Locking the role')
|
||||
+ role.lock()
|
||||
+
|
||||
+ # 4. Check account status - should be indirectly locked
|
||||
+ log.info('Step 4: Checking account status after locking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: indirectly locked through a Role')
|
||||
+
|
||||
+ # 5. Attempt to unlock the account - should fail
|
||||
+ log.info('Step 5: Attempting to unlock indirectly locked account')
|
||||
+ unlock(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st,
|
||||
+ check_value='Account is locked through role')
|
||||
+
|
||||
+ # 6. Unlock the role
|
||||
+ log.info('Step 6: Unlocking the role')
|
||||
+ role.unlock()
|
||||
+
|
||||
+ # 7. Verify account status is active again
|
||||
+ log.info('Step 7: Checking account status after unlocking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
\ No newline at end of file
|
||||
diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py
|
||||
index 15f766588..a0dfd8f65 100644
|
||||
--- a/src/lib389/lib389/cli_idm/account.py
|
||||
+++ b/src/lib389/lib389/cli_idm/account.py
|
||||
@@ -176,8 +176,29 @@ def unlock(inst, basedn, log, args):
|
||||
dn = _get_dn_arg(args.dn, msg="Enter dn to unlock")
|
||||
accounts = Accounts(inst, basedn)
|
||||
acct = accounts.get(dn=dn)
|
||||
- acct.unlock()
|
||||
- log.info(f'Entry {dn} is unlocked')
|
||||
+
|
||||
+ try:
|
||||
+ # Get the account status before attempting to unlock
|
||||
+ status = acct.status()
|
||||
+ state = status["state"]
|
||||
+
|
||||
+ # Attempt to unlock the account
|
||||
+ acct.unlock()
|
||||
+
|
||||
+ # Success message
|
||||
+ log.info(f'Entry {dn} is unlocked')
|
||||
+ if state == AccountState.DIRECTLY_LOCKED:
|
||||
+ log.info(f'The entry was directly locked')
|
||||
+ elif state == AccountState.INACTIVITY_LIMIT_EXCEEDED:
|
||||
+ log.info(f'The entry was locked due to inactivity and is now unlocked by resetting lastLoginTime')
|
||||
+
|
||||
+ except ValueError as e:
|
||||
+ # Provide a more detailed error message based on failure reason
|
||||
+ if "through role" in str(e):
|
||||
+ log.error(f"Cannot unlock {dn}: {str(e)}")
|
||||
+ log.info("To unlock this account, you must modify the role that's locking it.")
|
||||
+ else:
|
||||
+ log.error(f"Failed to unlock {dn}: {str(e)}")
|
||||
|
||||
|
||||
def reset_password(inst, basedn, log, args):
|
||||
diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py
|
||||
index d892c8d0d..faf6f6f16 100644
|
||||
--- a/src/lib389/lib389/idm/account.py
|
||||
+++ b/src/lib389/lib389/idm/account.py
|
||||
@@ -140,7 +140,7 @@ class Account(DSLdapObject):
|
||||
"nsAccountLock", state_attr])
|
||||
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, state_attr)
|
||||
- # if last_login_time not exist then check alt_state_attr only if its not disabled and exist
|
||||
+ # if last_login_time not exist then check alt_state_attr only if its not disabled and exist
|
||||
if not last_login_time and alt_state_attr in account_data:
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, alt_state_attr)
|
||||
|
||||
@@ -204,12 +204,33 @@ class Account(DSLdapObject):
|
||||
self.replace('nsAccountLock', 'true')
|
||||
|
||||
def unlock(self):
|
||||
- """Unset nsAccountLock"""
|
||||
+ """Unset nsAccountLock if it's set and reset lastLoginTime if account is locked due to inactivity"""
|
||||
|
||||
current_status = self.status()
|
||||
+
|
||||
if current_status["state"] == AccountState.ACTIVATED:
|
||||
raise ValueError("Account is already active")
|
||||
- self.remove('nsAccountLock', None)
|
||||
+
|
||||
+ if current_status["state"] == AccountState.DIRECTLY_LOCKED:
|
||||
+ # Account is directly locked with nsAccountLock attribute
|
||||
+ self.remove('nsAccountLock', None)
|
||||
+ elif current_status["state"] == AccountState.INACTIVITY_LIMIT_EXCEEDED:
|
||||
+ # Account is locked due to inactivity - reset lastLoginTime to current time
|
||||
+ # The lastLoginTime attribute stores its value in GMT/UTC time (Zulu time zone)
|
||||
+ current_time = time.strftime('%Y%m%d%H%M%SZ', time.gmtime())
|
||||
+ self.replace('lastLoginTime', current_time)
|
||||
+ elif current_status["state"] == AccountState.INDIRECTLY_LOCKED:
|
||||
+ # Account is locked through a role
|
||||
+ role_dn = current_status.get("role_dn")
|
||||
+ if role_dn:
|
||||
+ raise ValueError(f"Account is locked through role {role_dn}. "
|
||||
+ f"Please modify the role to unlock this account.")
|
||||
+ else:
|
||||
+ raise ValueError("Account is locked through an unknown role. "
|
||||
+ "Please check the roles configuration to unlock this account.")
|
||||
+ else:
|
||||
+ # Should not happen, but just in case
|
||||
+ raise ValueError(f"Unknown lock state: {current_status['state'].value}")
|
||||
|
||||
# If the account can be bound to, this will attempt to do so. We don't check
|
||||
# for exceptions, just pass them back!
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 644a4ca532b5728fcdb020ae55049b99a8607002 Mon Sep 17 00:00:00 2001
|
||||
From a3bb421391a65699a91343b35e7c0cf7f07a9add Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Thu, 21 Aug 2025 17:30:00 +0200
|
||||
Subject: [PATCH] =?UTF-8?q?Issue=206919=20-=20numSubordinates/tombstoneNum?=
|
||||
@ -209,10 +209,10 @@ index 9ba10657d..2624b2144 100644
|
||||
\ No newline at end of file
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
|
||||
index e0e80ff63..1b6413164 100644
|
||||
index 5dbf38054..d88ce7d98 100644
|
||||
--- a/ldap/servers/plugins/replication/cl5_api.c
|
||||
+++ b/ldap/servers/plugins/replication/cl5_api.c
|
||||
@@ -3208,7 +3208,7 @@ _cl5EnumConsumerRUV(const ruv_enum_data *element, void *arg)
|
||||
@@ -3211,7 +3211,7 @@ _cl5EnumConsumerRUV(const ruv_enum_data *element, void *arg)
|
||||
RUV *ruv;
|
||||
CSN *csn = NULL;
|
||||
|
||||
@ -856,10 +856,10 @@ index 762267dd9..12dfb419b 100644
|
||||
pthread_mutex_init(&job->wire_lock, NULL);
|
||||
pthread_cond_init(&job->wire_cv, NULL);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
|
||||
index 6e18db992..92dcade8c 100644
|
||||
index 0be6cab49..f66640d2e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
|
||||
@@ -209,8 +209,6 @@ void bdb_restore_file_update(struct ldbminfo *li, const char *directory);
|
||||
@@ -210,8 +210,6 @@ void bdb_restore_file_update(struct ldbminfo *li, const char *directory);
|
||||
int bdb_import_file_init(ldbm_instance *inst);
|
||||
void bdb_import_file_update(ldbm_instance *inst);
|
||||
int bdb_import_file_check(ldbm_instance *inst);
|
||||
@ -868,8 +868,8 @@ index 6e18db992..92dcade8c 100644
|
||||
void bdb_import_configure_index_buffer_size(size_t size);
|
||||
size_t bdb_import_get_index_buffer_size(void);
|
||||
int bdb_ldbm_back_wire_import(Slapi_PBlock *pb);
|
||||
@@ -229,6 +227,7 @@ int bdb_public_in_import(ldbm_instance *inst);
|
||||
int bdb_dblayer_cursor_iterate(dbi_cursor_t *cursor,
|
||||
@@ -230,6 +228,7 @@ int bdb_public_in_import(ldbm_instance *inst);
|
||||
int bdb_dblayer_cursor_iterate(dbi_cursor_t *cursor,
|
||||
int (*action_cb)(dbi_val_t *key, dbi_val_t *data, void *ctx),
|
||||
const dbi_val_t *startingkey, void *ctx);
|
||||
+dbi_error_t bdb_map_error(const char *funcname, int err);
|
||||
@ -1251,10 +1251,10 @@ index 39d2b06f7..6978bf5e3 100644
|
||||
/* Upgrade DN format only */
|
||||
/* Set current parentid to e_aux_attrs to remove it from the index file. */
|
||||
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 05f1e348d..0315693e0 100644
|
||||
index 1b161a578..682fd70e2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
|
||||
@@ -1333,7 +1333,7 @@ int dbmdb_open_cursor(dbmdb_cursor_t *dbicur, dbmdb_ctx_t *ctx, dbmdb_dbi_t *dbi
|
||||
@@ -1336,7 +1336,7 @@ int dbmdb_open_cursor(dbmdb_cursor_t *dbicur, dbmdb_ctx_t *ctx, dbmdb_dbi_t *dbi
|
||||
dbicur->dbi = dbi;
|
||||
if (ctx->readonly)
|
||||
flags |= MDB_RDONLY;
|
||||
@ -1418,10 +1418,10 @@ index 403ce6ae8..8b0386489 100644
|
||||
|
||||
/*
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index 65e70c1dd..b76806572 100644
|
||||
index e6f9273eb..39a2852e5 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -1756,7 +1756,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
@@ -1835,7 +1835,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
one entry.
|
||||
@param - entry dn
|
||||
@param - search scope, in ldap.SCOPE_BASE (default),
|
||||
@ -1431,10 +1431,10 @@ index 65e70c1dd..b76806572 100644
|
||||
SimpleLDAPObject
|
||||
@param attrlist - list of attributes to retrieve. eg ['cn', 'uid']
|
||||
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
|
||||
index d378d53aa..c78d9d160 100644
|
||||
index 1f9f1556f..37277296d 100644
|
||||
--- a/src/lib389/lib389/_mapped_object.py
|
||||
+++ b/src/lib389/lib389/_mapped_object.py
|
||||
@@ -184,7 +184,7 @@ class DSLdapObject(DSLogging, DSLint):
|
||||
@@ -200,7 +200,7 @@ class DSLdapObject(DSLogging, DSLint):
|
||||
if scope == 'base':
|
||||
search_scope = ldap.SCOPE_BASE
|
||||
elif scope == 'one':
|
||||
@ -1,272 +0,0 @@
|
||||
From c347142eb9e91b2b162eefa1ef64d9f282276d60 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 31 Mar 2025 11:05:01 +0200
|
||||
Subject: [PATCH] Issue 6571 - (2nd) Nested group does not receive memberOf
|
||||
attribute (#6697)
|
||||
|
||||
Bug description:
|
||||
erroneous debug change made in previous fix
|
||||
where cache_ancestors is called with the wrong parameter
|
||||
|
||||
Fix description:
|
||||
Restore the orginal param 'member_data'
|
||||
Increase the set of tests around multipaths
|
||||
|
||||
fixes: #6571
|
||||
|
||||
review by: Simon Pichugin (Thanks !!)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 154 ++++++++++++++++++
|
||||
ldap/servers/plugins/memberof/memberof.c | 50 +++++-
|
||||
2 files changed, 203 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 845de8ac5..3ffb26b01 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -598,6 +598,8 @@ def test_multipaths(topology_st, request):
|
||||
'homeDirectory': '/home/user1'
|
||||
})
|
||||
group = Groups(inst, SUFFIX, rdn=None)
|
||||
+ g0 = group.create(properties={'cn': 'group0',
|
||||
+ 'description': 'group0'})
|
||||
g1 = group.create(properties={'cn': 'group1',
|
||||
'member': user1.dn,
|
||||
'description': 'group1'})
|
||||
@@ -635,6 +637,158 @@ def test_multipaths(topology_st, request):
|
||||
_check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
_check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
|
||||
+ #inst.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #inst.config.set('nsslapd-accesslog-level','260')
|
||||
+ #inst.config.set('nsslapd-plugin-logging', 'on')
|
||||
+ #inst.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #inst.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.remove_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # \__________ ^
|
||||
+ # | /
|
||||
+ # v /
|
||||
+ # Grp2 --> Grp21 ----
|
||||
+ #
|
||||
+ g1.add_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.remove_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g0.add_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ ^
|
||||
+ # / /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g0.add_member(g1.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1,g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ \_____________ ^
|
||||
+ # / | /
|
||||
+ # / V /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g1.add_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1, g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g1, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ \_____________ ^
|
||||
+ # / | /
|
||||
+ # / V /
|
||||
+ # Grp0 ---> Grp2 Grp21 ---
|
||||
+ #
|
||||
+ g2.remove_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1, g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g1, g0])
|
||||
+
|
||||
def fin():
|
||||
try:
|
||||
user1.delete()
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 563af47f8..35fd1a4a0 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -3268,6 +3268,35 @@ merge_ancestors(Slapi_Value **member_ndn_val, memberof_get_groups_data *v1, memb
|
||||
Slapi_ValueSet *v2_group_norm_vals = *((memberof_get_groups_data *)v2)->group_norm_vals;
|
||||
int merged_cnt = 0;
|
||||
|
||||
+#if MEMBEROF_CACHE_DEBUG
|
||||
+ {
|
||||
+ Slapi_Value *val = 0;
|
||||
+ int hint = 0;
|
||||
+ struct berval *bv;
|
||||
+ hint = slapi_valueset_first_value(v2_groupvals, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "merge_ancestors: V2 contains %s\n",
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(v2_groupvals, hint, &val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_first_value(v1_groupvals, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "merge_ancestors: add %s (from V1)\n",
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(v1_groupvals, hint, &val);
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
hint = slapi_valueset_first_value(v1_groupvals, &sval);
|
||||
while (sval) {
|
||||
if (memberof_compare(config, member_ndn_val, &sval)) {
|
||||
@@ -3329,7 +3358,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn, memberof_get
|
||||
|
||||
merge_ancestors(&member_ndn_val, &member_data, data);
|
||||
if (!cached && member_data.use_cache)
|
||||
- cache_ancestors(config, &member_ndn_val, data);
|
||||
+ cache_ancestors(config, &member_ndn_val, &member_data);
|
||||
|
||||
slapi_value_free(&member_ndn_val);
|
||||
slapi_valueset_free(groupvals);
|
||||
@@ -4295,6 +4324,25 @@ memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
|
||||
|
||||
/* get a list of all of the groups this user belongs to */
|
||||
groups = memberof_get_groups(config, sdn);
|
||||
+#if MEMBEROF_CACHE_DEBUG
|
||||
+ {
|
||||
+ Slapi_Value *val = 0;
|
||||
+ int hint = 0;
|
||||
+ struct berval *bv;
|
||||
+ hint = slapi_valueset_first_value(groups, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_fix_memberof_callback: %s belongs to %s\n",
|
||||
+ ndn,
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(groups, hint, &val);
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
|
||||
if (config->group_filter) {
|
||||
if (slapi_filter_test_simple(e, config->group_filter)) {
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From df115041317b2be45416e39724c9fb0ddf440525 Mon Sep 17 00:00:00 2001
|
||||
From dd40581c66c702a9a5d34ad1c498d8957be51f81 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 17:12:33 -0400
|
||||
Subject: [PATCH] Issue 6910 - Fix latest coverity issues
|
||||
@ -141,7 +141,7 @@ index 3775e52c9..82cb60c96 100644
|
||||
if (!add_oc || added_oc) {
|
||||
/*
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
index b74c24158..bd7d25140 100644
|
||||
index 964fcc2b8..e4da351d9 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof_config.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
@@ -1,5 +1,5 @@
|
||||
@ -334,7 +334,7 @@ index bb515a23f..44a624fde 100644
|
||||
}
|
||||
if (return_value == EBUSY) {
|
||||
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 7c3a7dcc9..dae6e1387 100644
|
||||
index 4f069197e..e21f418be 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
|
||||
@@ -1,5 +1,5 @@
|
||||
@ -380,7 +380,7 @@ index 7c3a7dcc9..dae6e1387 100644
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -6297,6 +6303,7 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info)
|
||||
@@ -6359,6 +6365,7 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info)
|
||||
db->close(db, 0);
|
||||
rc = bdb_db_remove_ex((bdb_db_env *)priv->dblayer_env, path, NULL, PR_TRUE);
|
||||
inst->inst_changelog = NULL;
|
||||
@ -431,7 +431,7 @@ index 75e791135..bacbc9371 100644
|
||||
}
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
|
||||
index e3157c1ce..9994c6911 100644
|
||||
index 0f266f0d7..a0db367b2 100644
|
||||
--- a/ldap/servers/slapd/dse.c
|
||||
+++ b/ldap/servers/slapd/dse.c
|
||||
@@ -1,6 +1,6 @@
|
||||
@ -486,7 +486,7 @@ index 178d29b89..58f9fb4d6 100644
|
||||
fclose(source);
|
||||
PR_Delete(log_name); /* remove the old uncompressed log */
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index b0ec1c02c..1ff376412 100644
|
||||
index 0e2abea18..b0066faf8 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -1,6 +1,6 @@
|
||||
@ -516,7 +516,7 @@ index b0ec1c02c..1ff376412 100644
|
||||
return LDAP_PARAM_ERROR;
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/passwd_extop.c b/ldap/servers/slapd/passwd_extop.c
|
||||
index 4d185f8dd..ef2cb69cf 100644
|
||||
index 0296d64fb..3ade0be7f 100644
|
||||
--- a/ldap/servers/slapd/passwd_extop.c
|
||||
+++ b/ldap/servers/slapd/passwd_extop.c
|
||||
@@ -1,5 +1,5 @@
|
||||
@ -1,192 +0,0 @@
|
||||
From 9538dc81784a0625e2a21a5e2e262c9b949a4770 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 28 Mar 2025 17:32:14 +0100
|
||||
Subject: [PATCH] Issue 6698 - NPE after configuring invalid filtered role
|
||||
(#6699)
|
||||
|
||||
Server crash when doing search after configuring filtered role with invalid filter.
|
||||
Reason: The part of the filter that should be overwritten are freed before knowing that the filter is invalid.
|
||||
Solution: Check first that the filter is valid before freeing the filtere bits
|
||||
|
||||
Issue: #6698
|
||||
|
||||
Reviewed by: @tbordaz , @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit 31e120d2349eda7a41380cf78fc04cf41e394359)
|
||||
---
|
||||
dirsrvtests/tests/suites/roles/basic_test.py | 80 ++++++++++++++++++--
|
||||
ldap/servers/slapd/filter.c | 17 ++++-
|
||||
2 files changed, 88 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
index e53f9fccb..32b7657c0 100644
|
||||
--- a/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
@@ -28,6 +28,7 @@ from lib389.dbgen import dbgen_users
|
||||
from lib389.tasks import ImportTask
|
||||
from lib389.utils import get_default_db_lib
|
||||
from lib389.rewriters import *
|
||||
+from lib389._mapped_object import DSLdapObject
|
||||
from lib389.backend import Backends
|
||||
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
@@ -433,7 +434,6 @@ def test_vattr_on_filtered_role_restart(topo, request):
|
||||
log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
|
||||
assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
|
||||
|
||||
-
|
||||
log.info("Check the virtual attribute definition is found (after a required delay)")
|
||||
topo.standalone.restart()
|
||||
time.sleep(5)
|
||||
@@ -552,7 +552,7 @@ def test_managed_and_filtered_role_rewrite(topo, request):
|
||||
indexes = backend.get_indexes()
|
||||
try:
|
||||
index = indexes.create(properties={
|
||||
- 'cn': attrname,
|
||||
+ 'cn': attrname,
|
||||
'nsSystemIndex': 'false',
|
||||
'nsIndexType': ['eq', 'pres']
|
||||
})
|
||||
@@ -604,7 +604,6 @@ def test_managed_and_filtered_role_rewrite(topo, request):
|
||||
dn = "uid=%s0000%d,%s" % (RDN, i, PARENT)
|
||||
topo.standalone.modify_s(dn, [(ldap.MOD_REPLACE, 'nsRoleDN', [role.dn.encode()])])
|
||||
|
||||
-
|
||||
# Now check that search is fast, evaluating only 4 entries
|
||||
search_start = time.time()
|
||||
entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
@@ -687,7 +686,7 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
indexes = backend.get_indexes()
|
||||
try:
|
||||
index = indexes.create(properties={
|
||||
- 'cn': attrname,
|
||||
+ 'cn': attrname,
|
||||
'nsSystemIndex': 'false',
|
||||
'nsIndexType': ['eq', 'pres']
|
||||
})
|
||||
@@ -741,7 +740,7 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
|
||||
# Enable plugin level to check message
|
||||
topo.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
||||
-
|
||||
+
|
||||
# Now check that search is fast, evaluating only 4 entries
|
||||
search_start = time.time()
|
||||
entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(|(nsrole=%s)(nsrole=cn=not_such_entry_role,%s))" % (role.dn, DEFAULT_SUFFIX))
|
||||
@@ -769,6 +768,77 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+
|
||||
+def test_rewriter_with_invalid_filter(topo, request):
|
||||
+ """Test that server does not crash when having
|
||||
+ invalid filter in filtered role
|
||||
+
|
||||
+ :id: 5013b0b2-0af6-11f0-8684-482ae39447e5
|
||||
+ :setup: standalone server
|
||||
+ :steps:
|
||||
+ 1. Setup filtered role with good filter
|
||||
+ 2. Setup nsrole rewriter
|
||||
+ 3. Restart the server
|
||||
+ 4. Search for entries
|
||||
+ 5. Setup filtered role with bad filter
|
||||
+ 6. Search for entries
|
||||
+ :expectedresults:
|
||||
+ 1. Operation should succeed
|
||||
+ 2. Operation should succeed
|
||||
+ 3. Operation should succeed
|
||||
+ 4. Operation should succeed
|
||||
+ 5. Operation should succeed
|
||||
+ 6. Operation should succeed
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ entries = []
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.start()
|
||||
+ for entry in entries:
|
||||
+ entry.delete()
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Setup filtered role
|
||||
+ roles = FilteredRoles(inst, f'ou=people,{DEFAULT_SUFFIX}')
|
||||
+ filter_ko = '(&((objectClass=top)(objectClass=nsPerson))'
|
||||
+ filter_ok = '(&(objectClass=top)(objectClass=nsPerson))'
|
||||
+ role_properties = {
|
||||
+ 'cn': 'TestFilteredRole',
|
||||
+ 'nsRoleFilter': filter_ok,
|
||||
+ 'description': 'Test good filter',
|
||||
+ }
|
||||
+ role = roles.create(properties=role_properties)
|
||||
+ entries.append(role)
|
||||
+
|
||||
+ # Setup nsrole rewriter
|
||||
+ rewriters = Rewriters(inst)
|
||||
+ rewriter_properties = {
|
||||
+ "cn": "nsrole",
|
||||
+ "nsslapd-libpath": 'libroles-plugin',
|
||||
+ "nsslapd-filterrewriter": 'role_nsRole_filter_rewriter',
|
||||
+ }
|
||||
+ rewriter = rewriters.ensure_state(properties=rewriter_properties)
|
||||
+ entries.append(rewriter)
|
||||
+
|
||||
+ # Restart thge instance
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Search for entries
|
||||
+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
+
|
||||
+ # Set bad filter
|
||||
+ role_properties = {
|
||||
+ 'cn': 'TestFilteredRole',
|
||||
+ 'nsRoleFilter': filter_ko,
|
||||
+ 'description': 'Test bad filter',
|
||||
+ }
|
||||
+ role.ensure_state(properties=role_properties)
|
||||
+
|
||||
+ # Search for entries
|
||||
+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s -v %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/slapd/filter.c b/ldap/servers/slapd/filter.c
|
||||
index 44b726a34..2c89ca421 100644
|
||||
--- a/ldap/servers/slapd/filter.c
|
||||
+++ b/ldap/servers/slapd/filter.c
|
||||
@@ -1033,9 +1033,11 @@ slapi_filter_get_subfilt(
|
||||
}
|
||||
|
||||
/*
|
||||
- * Before calling this function, you must free all the parts
|
||||
+ * The function does not know how to free all the parts
|
||||
* which will be overwritten (i.e. slapi_free_the_filter_bits),
|
||||
- * this function dosn't know how to do that
|
||||
+ * so the caller must take care of that.
|
||||
+ * But it must do so AFTER calling slapi_filter_replace_ex to
|
||||
+ * avoid getting invalid filter if slapi_filter_replace_ex fails.
|
||||
*/
|
||||
int
|
||||
slapi_filter_replace_ex(Slapi_Filter *f, char *s)
|
||||
@@ -1094,8 +1096,15 @@ slapi_filter_free_bits(Slapi_Filter *f)
|
||||
int
|
||||
slapi_filter_replace_strfilter(Slapi_Filter *f, char *strfilter)
|
||||
{
|
||||
- slapi_filter_free_bits(f);
|
||||
- return (slapi_filter_replace_ex(f, strfilter));
|
||||
+ /* slapi_filter_replace_ex may fail and we cannot
|
||||
+ * free filter bits before calling it.
|
||||
+ */
|
||||
+ Slapi_Filter save_f = *f;
|
||||
+ int ret = slapi_filter_replace_ex(f, strfilter);
|
||||
+ if (ret == 0) {
|
||||
+ slapi_filter_free_bits(&save_f);
|
||||
+ }
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
From b79da81cd24edd12af1da894d6dbd6f08995bc9d Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 11 Aug 2025 13:19:13 +0200
|
||||
Subject: [PATCH] Issue 6929 - Compilation failure with rust-1.89 on Fedora ELN
|
||||
|
||||
Bug Description:
|
||||
The `ValueArrayRefIter` struct has a lifetime parameter `'a`.
|
||||
But in the `iter` method the return type doesn't specify the lifetime parameter.
|
||||
|
||||
Fix Description:
|
||||
Make the lifetime explicit.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6929
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
---
|
||||
src/slapi_r_plugin/src/value.rs | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs
|
||||
index 2fd35c808..fec74ac25 100644
|
||||
--- a/src/slapi_r_plugin/src/value.rs
|
||||
+++ b/src/slapi_r_plugin/src/value.rs
|
||||
@@ -61,7 +61,7 @@ impl ValueArrayRef {
|
||||
ValueArrayRef { raw_slapi_val }
|
||||
}
|
||||
|
||||
- pub fn iter(&self) -> ValueArrayRefIter {
|
||||
+ pub fn iter(&self) -> ValueArrayRefIter<'_> {
|
||||
ValueArrayRefIter {
|
||||
idx: 0,
|
||||
va_ref: &self,
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,508 +0,0 @@
|
||||
From 9382142d8521d914043255bea21154f15fec96e9 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Wed, 19 Mar 2025 19:04:30 +0100
|
||||
Subject: [PATCH] Issue 6626 - Ignore replica busy condition in healthcheck
|
||||
(#6630)
|
||||
|
||||
Replica Busy condition is expected when there is more than 2 suppliers so healthcheck should not report any error for such condition.
|
||||
Fixed issue in CI tests:
|
||||
|
||||
test_healthcheck_replication_out_of_sync_not_broken was unstable and redundant with test_healthcheck_replica_busy so I moved it back in health_repl_test.py and rewrite it to test working replication whose replica are not in sync (healthcheck should not report anything)
|
||||
some tests (not always the same) were randomly failing with an empty log (which is unexpected because healthcheck output should never be empty.
|
||||
I suspect the log capture mechanism so health_repl_test.py now run dsctl instance healthcheck using subprocess module and capture the output. So far I have not changed the other files because I have not noticed any failure.
|
||||
Issue: #6626
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit bc22cfa6184f51b8492c692f1c95e721d538ab5e)
|
||||
(cherry picked from commit 0ab605e44bf04954b499aaa7e704fc1ea339618b)
|
||||
---
|
||||
.../suites/healthcheck/health_repl_test.py | 252 ++++++++++++++----
|
||||
.../suites/healthcheck/health_sync_test.py | 132 ---------
|
||||
src/lib389/lib389/replica.py | 3 +
|
||||
3 files changed, 198 insertions(+), 189 deletions(-)
|
||||
delete mode 100644 dirsrvtests/tests/suites/healthcheck/health_sync_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_repl_test.py b/dirsrvtests/tests/suites/healthcheck/health_repl_test.py
|
||||
index 8f9136229..0c1fa783a 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_repl_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_repl_test.py
|
||||
@@ -9,56 +9,131 @@
|
||||
|
||||
import pytest
|
||||
import os
|
||||
-from contextlib import suppress
|
||||
+import random
|
||||
+import re
|
||||
+import string
|
||||
+import subprocess
|
||||
+import threading
|
||||
+import time
|
||||
+from contextlib import suppress, AbstractContextManager
|
||||
from lib389.backend import Backend, Backends
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.replica import Changelog, ReplicationManager, Replicas
|
||||
from lib389.utils import *
|
||||
from lib389._constants import *
|
||||
from lib389.cli_base import FakeArgs
|
||||
-from lib389.topologies import topology_m2, topology_m3
|
||||
from lib389.cli_ctl.health import health_check_run
|
||||
+from lib389.topologies import topology_m2, topology_m3
|
||||
from lib389.paths import Paths
|
||||
|
||||
CMD_OUTPUT = 'No issues found.'
|
||||
JSON_OUTPUT = '[]'
|
||||
|
||||
+LOGIC_DICT = {
|
||||
+ False: ( "not ", "", lambda x: x ),
|
||||
+ True: ( "", "not ", lambda x: not x )
|
||||
+ }
|
||||
+
|
||||
ds_paths = Paths()
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
-def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searched_code2=None):
|
||||
- args = FakeArgs()
|
||||
- args.instance = instance.serverid
|
||||
- args.verbose = instance.verbose
|
||||
- args.list_errors = False
|
||||
- args.list_checks = False
|
||||
- args.check = ['replication', 'backends:userroot:cl_trimming']
|
||||
- args.dry_run = False
|
||||
-
|
||||
+class LoadInstance(AbstractContextManager):
|
||||
+ @staticmethod
|
||||
+ def create_test_user(inst):
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ uid = str(20000 + int(inst.serverid[8:]))
|
||||
+ properties = {
|
||||
+ 'uid': f'testuser_{inst.serverid}',
|
||||
+ 'cn' : f'testuser_{inst.serverid}',
|
||||
+ 'sn' : 'user_{inst.serverid}',
|
||||
+ 'uidNumber' : uid,
|
||||
+ 'gidNumber' : uid,
|
||||
+ 'homeDirectory' : f'/home/testuser_{inst.serverid}'
|
||||
+ }
|
||||
+ return users.ensure_state(properties=properties)
|
||||
+
|
||||
+ def __init__(self, inst):
|
||||
+ self.inst = inst
|
||||
+ self.stop = threading.Event()
|
||||
+ self.thread = threading.Thread(target=self.loader)
|
||||
+ self.user = LoadInstance.create_test_user(inst)
|
||||
+
|
||||
+ def loader(self):
|
||||
+ while not self.stop.is_set():
|
||||
+ value = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
|
||||
+ self.user.replace('description', value)
|
||||
+ #log.info(f'Modified {self.user.dn} description with {value} on {self.inst.serverid}')
|
||||
+ time.sleep(0.001)
|
||||
+
|
||||
+ def __exit__(self, *args):
|
||||
+ self.stop.set()
|
||||
+ self.thread.join()
|
||||
+ self.user.delete()
|
||||
+
|
||||
+ def __enter__(self):
|
||||
+ self.thread.start()
|
||||
+ return self
|
||||
+
|
||||
+
|
||||
+class BreakReplication(AbstractContextManager):
|
||||
+ def __init__(self, topo, instances):
|
||||
+ self.topo = topo
|
||||
+ self.replicas = [ Replicas(inst).list()[0] for inst in instances ]
|
||||
+ self.oldvals = []
|
||||
+
|
||||
+ def __exit__(self, *args):
|
||||
+ for replica,oldval in self.oldvals:
|
||||
+ replica.replace('nsds5ReplicaBindDNGroup', oldval)
|
||||
+
|
||||
+ def __enter__(self):
|
||||
+ # Ensure replication sessions are stopped to avoid race conditions
|
||||
+ self.topo.pause_all_replicas()
|
||||
+ for replica in self.replicas:
|
||||
+ oldval = replica.get_attr_val_utf8('nsds5ReplicaBindDNGroup')
|
||||
+ replica.replace('nsds5ReplicaBindDNGroup', 'cn=repl')
|
||||
+ self.oldvals.append((replica, oldval))
|
||||
+ self.topo.resume_all_replicas()
|
||||
+ return self
|
||||
+
|
||||
+
|
||||
+def assert_is_in_result(result, searched_code, isnot=False):
|
||||
+ # Assert if searched_code is not in logcap
|
||||
+ if searched_code is None:
|
||||
+ return
|
||||
+
|
||||
+ # Handle positive and negative tests:
|
||||
+ nomatch, match, f = LOGIC_DICT[bool(isnot)]
|
||||
+ try:
|
||||
+ assert f(re.search(re.escape(searched_code), result))
|
||||
+ log.info(f'Searched code {searched_code} is {match}in healthcheck output')
|
||||
+ except AssertionError as exc:
|
||||
+ log.error(f'{searched_code} is {nomatch}in healthcheck output: {result}')
|
||||
+ raise
|
||||
+
|
||||
+
|
||||
+def run_healthcheck_and_check_result(topology, instance, searched_code, json, searched_code2=None, isnot=False):
|
||||
+ cmd = [ 'dsctl', ]
|
||||
if json:
|
||||
- log.info('Use healthcheck with --json option')
|
||||
- args.json = json
|
||||
- health_check_run(instance, topology.logcap.log, args)
|
||||
- assert topology.logcap.contains(searched_code)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code)
|
||||
-
|
||||
- if searched_code2 is not None:
|
||||
- assert topology.logcap.contains(searched_code2)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code2)
|
||||
- else:
|
||||
- log.info('Use healthcheck without --json option')
|
||||
- args.json = json
|
||||
- health_check_run(instance, topology.logcap.log, args)
|
||||
- assert topology.logcap.contains(searched_code)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code)
|
||||
-
|
||||
- if searched_code2 is not None:
|
||||
- assert topology.logcap.contains(searched_code2)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code2)
|
||||
-
|
||||
- log.info('Clear the log')
|
||||
- topology.logcap.flush()
|
||||
+ cmd.append('--json')
|
||||
+ if searched_code == CMD_OUTPUT:
|
||||
+ searched_code = JSON_OUTPUT
|
||||
+ cmd.append(instance.serverid)
|
||||
+ cmd.extend(['healthcheck', '--check', 'replication' , 'backends:userroot:cl_trimming'])
|
||||
+
|
||||
+ result = subprocess.run(cmd, capture_output=True, universal_newlines=True)
|
||||
+ log.info(f'Running: {cmd}')
|
||||
+ log.info(f'Stdout: {result.stdout}')
|
||||
+ log.info(f'Stderr: {result.stdout}')
|
||||
+ log.info(f'Return code: {result.returncode}')
|
||||
+ stdout = result.stdout
|
||||
+
|
||||
+ # stdout should not be empty
|
||||
+ assert stdout is not None
|
||||
+ assert len(stdout) > 0
|
||||
+ assert_is_in_result(stdout, searched_code, isnot=isnot)
|
||||
+ assert_is_in_result(stdout, searched_code2, isnot=isnot)
|
||||
+
|
||||
|
||||
|
||||
def set_changelog_trimming(instance):
|
||||
@@ -114,15 +189,15 @@ def test_healthcheck_replication_replica_not_reachable(topology_m2):
|
||||
with suppress(Exception):
|
||||
repl.wait_for_replication(M1, M2, timeout=5)
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, RET_CODE, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, RET_CODE, json=True)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, RET_CODE, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, RET_CODE, json=True)
|
||||
|
||||
log.info('Set nsds5replicaport for the replication agreement to a reachable port')
|
||||
agmt_m1.replace('nsDS5ReplicaPort', '{}'.format(M2.port))
|
||||
repl.wait_for_replication(M1, M2)
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, CMD_OUTPUT, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, JSON_OUTPUT, json=True)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, CMD_OUTPUT, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, JSON_OUTPUT, json=True)
|
||||
|
||||
|
||||
@pytest.mark.ds50873
|
||||
@@ -164,13 +239,13 @@ def test_healthcheck_changelog_trimming_not_configured(topology_m2):
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, RET_CODE, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, RET_CODE, json=True)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, RET_CODE, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, RET_CODE, json=True)
|
||||
|
||||
set_changelog_trimming(M1)
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, CMD_OUTPUT, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, JSON_OUTPUT, json=True)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, CMD_OUTPUT, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, JSON_OUTPUT, json=True)
|
||||
|
||||
|
||||
@pytest.mark.ds50873
|
||||
@@ -214,8 +289,8 @@ def test_healthcheck_replication_presence_of_conflict_entries(topology_m2):
|
||||
|
||||
repl.test_replication_topology(topology_m2)
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, RET_CODE, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m2, M1, RET_CODE, json=True)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, RET_CODE, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m2, M1, RET_CODE, json=True)
|
||||
|
||||
|
||||
def test_healthcheck_non_replicated_suffixes(topology_m2):
|
||||
@@ -253,6 +328,44 @@ def test_healthcheck_non_replicated_suffixes(topology_m2):
|
||||
|
||||
@pytest.mark.ds50873
|
||||
@pytest.mark.bz1685160
|
||||
+def test_healthcheck_replica_busy(topology_m3):
|
||||
+ """Check that HealthCheck does not returns DSREPLLE0003 code when a replicva is busy
|
||||
+
|
||||
+ :id: b7c4a5aa-ef98-11ef-87f5-482ae39447e5
|
||||
+ :setup: 3 MMR topology
|
||||
+ :steps:
|
||||
+ 1. Create a 3 suppliers full-mesh topology
|
||||
+ 2. Generate constant modify load on S1 and S2
|
||||
+ 3. Wait a bit to ensure stable replication flow
|
||||
+ 4. Perform a modify on S3
|
||||
+ 5. Use HealthCheck on S3 without --json option
|
||||
+ 6. Use HealthCheck on S3 with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Healthcheck should not reports DSREPLLE0003 code and related details
|
||||
+ 6. Healthcheck should not reports DSREPLLE0003 code and related details
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = 'DSREPLLE0003'
|
||||
+ # Is DSREPLLE0003 ignored if replica is busy ?
|
||||
+ ignored = not ds_is_older("2.7")
|
||||
+
|
||||
+ S1 = topology_m3.ms['supplier1']
|
||||
+ S2 = topology_m3.ms['supplier2']
|
||||
+ S3 = topology_m3.ms['supplier3']
|
||||
+ with LoadInstance(S1), LoadInstance(S2):
|
||||
+ # Wait a bit to let replication starts
|
||||
+ time.sleep(10)
|
||||
+ # Create user on S3 then remove it:
|
||||
+ LoadInstance(S3).user.delete()
|
||||
+ # S3 agrements should now be in the replica busy state
|
||||
+ run_healthcheck_and_check_result(topology_m3, S3, RET_CODE, json=False, isnot=ignored)
|
||||
+ run_healthcheck_and_check_result(topology_m3, S3, RET_CODE, json=True, isnot=ignored)
|
||||
+
|
||||
+
|
||||
@pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented")
|
||||
def test_healthcheck_replication_out_of_sync_broken(topology_m3):
|
||||
"""Check if HealthCheck returns DSREPLLE0001 code
|
||||
@@ -273,25 +386,50 @@ def test_healthcheck_replication_out_of_sync_broken(topology_m3):
|
||||
|
||||
RET_CODE = 'DSREPLLE0001'
|
||||
|
||||
- M1 = topology_m3.ms['supplier1']
|
||||
- M2 = topology_m3.ms['supplier2']
|
||||
- M3 = topology_m3.ms['supplier3']
|
||||
+ S1 = topology_m3.ms['supplier1']
|
||||
+ S2 = topology_m3.ms['supplier2']
|
||||
+ S3 = topology_m3.ms['supplier3']
|
||||
|
||||
log.info('Break supplier2 and supplier3')
|
||||
- replicas = Replicas(M2)
|
||||
- replica = replicas.list()[0]
|
||||
- replica.replace('nsds5ReplicaBindDNGroup', 'cn=repl')
|
||||
+ with BreakReplication(topology_m3, (S2, S3)):
|
||||
+ time.sleep(1)
|
||||
+ log.info('Perform update on supplier1')
|
||||
+ test_users_m1 = UserAccounts(S1, DEFAULT_SUFFIX)
|
||||
+ test_users_m1.create_test_user(1005, 2000)
|
||||
|
||||
- replicas = Replicas(M3)
|
||||
- replica = replicas.list()[0]
|
||||
- replica.replace('nsds5ReplicaBindDNGroup', 'cn=repl')
|
||||
+ time.sleep(3)
|
||||
+ run_healthcheck_and_check_result(topology_m3, S1, RET_CODE, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m3, S1, RET_CODE, json=True)
|
||||
|
||||
- log.info('Perform update on supplier1')
|
||||
- test_users_m1 = UserAccounts(M1, DEFAULT_SUFFIX)
|
||||
- test_users_m1.create_test_user(1005, 2000)
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_m3, M1, RET_CODE, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m3, M1, RET_CODE, json=True)
|
||||
+def test_healthcheck_replication_out_of_sync_not_broken(topology_m3):
|
||||
+ """Check that HealthCheck returns no issues when replication is in progress
|
||||
+
|
||||
+ :id: 8305000d-ba4d-4c00-8331-be0e8bd92150
|
||||
+ :setup: 3 MMR topology
|
||||
+ :steps:
|
||||
+ 1. Create a 3 suppliers full-mesh topology, all replicas being synchronized
|
||||
+ 2. Generate constant load on two supplier
|
||||
+ 3. Use HealthCheck without --json option
|
||||
+ 4. Use HealthCheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Healthcheck reports no issue found
|
||||
+ 4. Healthcheck reports no issue found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = CMD_OUTPUT
|
||||
+
|
||||
+ S1 = topology_m3.ms['supplier1']
|
||||
+ S2 = topology_m3.ms['supplier2']
|
||||
+ S3 = topology_m3.ms['supplier3']
|
||||
+
|
||||
+ with LoadInstance(S1), LoadInstance(S2):
|
||||
+ # Wait a bit to let replication starts
|
||||
+ time.sleep(10)
|
||||
+ run_healthcheck_and_check_result(topology_m3, S1, RET_CODE, json=False)
|
||||
+ run_healthcheck_and_check_result(topology_m3, S1, RET_CODE, json=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_sync_test.py b/dirsrvtests/tests/suites/healthcheck/health_sync_test.py
|
||||
deleted file mode 100644
|
||||
index 6701f7f5f..000000000
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_sync_test.py
|
||||
+++ /dev/null
|
||||
@@ -1,132 +0,0 @@
|
||||
-# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2020 Red Hat, Inc.
|
||||
-# All rights reserved.
|
||||
-#
|
||||
-# License: GPL (version 3 or any later version).
|
||||
-# See LICENSE for details.
|
||||
-# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
-
|
||||
-import pytest
|
||||
-import os
|
||||
-import time
|
||||
-from datetime import *
|
||||
-from lib389.idm.user import UserAccounts
|
||||
-from lib389.utils import *
|
||||
-from lib389._constants import *
|
||||
-from lib389.cli_base import FakeArgs
|
||||
-from lib389.topologies import topology_m3
|
||||
-from lib389.cli_ctl.health import health_check_run
|
||||
-from lib389.paths import Paths
|
||||
-
|
||||
-ds_paths = Paths()
|
||||
-log = logging.getLogger(__name__)
|
||||
-
|
||||
-
|
||||
-def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searched_code2=None):
|
||||
- args = FakeArgs()
|
||||
- args.instance = instance.serverid
|
||||
- args.verbose = instance.verbose
|
||||
- args.list_errors = False
|
||||
- args.list_checks = False
|
||||
- args.check = ['replication']
|
||||
- args.dry_run = False
|
||||
-
|
||||
- if json:
|
||||
- log.info('Use healthcheck with --json option')
|
||||
- args.json = json
|
||||
- health_check_run(instance, topology.logcap.log, args)
|
||||
- assert topology.logcap.contains(searched_code)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code)
|
||||
-
|
||||
- if searched_code2 is not None:
|
||||
- assert topology.logcap.contains(searched_code2)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code2)
|
||||
- else:
|
||||
- log.info('Use healthcheck without --json option')
|
||||
- args.json = json
|
||||
- health_check_run(instance, topology.logcap.log, args)
|
||||
- assert topology.logcap.contains(searched_code)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code)
|
||||
-
|
||||
- if searched_code2 is not None:
|
||||
- assert topology.logcap.contains(searched_code2)
|
||||
- log.info('Healthcheck returned searched code: %s' % searched_code2)
|
||||
-
|
||||
- log.info('Clear the log')
|
||||
- topology.logcap.flush()
|
||||
-
|
||||
-
|
||||
-# This test is in separate file because it is timeout specific
|
||||
-@pytest.mark.ds50873
|
||||
-@pytest.mark.bz1685160
|
||||
-@pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented")
|
||||
-#unstable or unstatus tests, skipped for now
|
||||
-@pytest.mark.flaky(max_runs=2, min_passes=1)
|
||||
-def test_healthcheck_replication_out_of_sync_not_broken(topology_m3):
|
||||
- """Check if HealthCheck returns DSREPLLE0003 code
|
||||
-
|
||||
- :id: 8305000d-ba4d-4c00-8331-be0e8bd92150
|
||||
- :setup: 3 MMR topology
|
||||
- :steps:
|
||||
- 1. Create a 3 suppliers full-mesh topology, all replicas being synchronized
|
||||
- 2. Stop M1
|
||||
- 3. Perform an update on M2 and M3.
|
||||
- 4. Check M2 and M3 are synchronized.
|
||||
- 5. From M2, reinitialize the M3 agreement
|
||||
- 6. Stop M2 and M3
|
||||
- 7. Restart M1
|
||||
- 8. Start M3
|
||||
- 9. Use HealthCheck without --json option
|
||||
- 10. Use HealthCheck with --json option
|
||||
- :expectedresults:
|
||||
- 1. Success
|
||||
- 2. Success
|
||||
- 3. Success
|
||||
- 4. Success
|
||||
- 5. Success
|
||||
- 6. Success
|
||||
- 7. Success
|
||||
- 8. Success
|
||||
- 9. Healthcheck reports DSREPLLE0003 code and related details
|
||||
- 10. Healthcheck reports DSREPLLE0003 code and related details
|
||||
- """
|
||||
-
|
||||
- RET_CODE = 'DSREPLLE0003'
|
||||
-
|
||||
- M1 = topology_m3.ms['supplier1']
|
||||
- M2 = topology_m3.ms['supplier2']
|
||||
- M3 = topology_m3.ms['supplier3']
|
||||
-
|
||||
- log.info('Stop supplier1')
|
||||
- M1.stop()
|
||||
-
|
||||
- log.info('Perform update on supplier2 and supplier3')
|
||||
- test_users_m2 = UserAccounts(M2, DEFAULT_SUFFIX)
|
||||
- test_users_m3 = UserAccounts(M3, DEFAULT_SUFFIX)
|
||||
- test_users_m2.create_test_user(1000, 2000)
|
||||
- for user_num in range(1001, 3000):
|
||||
- test_users_m3.create_test_user(user_num, 2000)
|
||||
- time.sleep(2)
|
||||
-
|
||||
- log.info('Stop M2 and M3')
|
||||
- M2.stop()
|
||||
- M3.stop()
|
||||
-
|
||||
- log.info('Start M1 first, then M2, so that M2 acquires M1')
|
||||
- M1.start()
|
||||
- M2.start()
|
||||
- time.sleep(2)
|
||||
-
|
||||
- log.info('Start M3 which should not be able to acquire M1 since M2 is updating it')
|
||||
- M3.start()
|
||||
- time.sleep(2)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_m3, M3, RET_CODE, json=False)
|
||||
- run_healthcheck_and_flush_log(topology_m3, M3, RET_CODE, json=True)
|
||||
-
|
||||
-
|
||||
-if __name__ == '__main__':
|
||||
- # Run isolated
|
||||
- # -s for DEBUG mode
|
||||
- CURRENT_FILE = os.path.realpath(__file__)
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 0486c48f8..0dc3a0f54 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -1276,6 +1276,9 @@ class Replica(DSLdapObject):
|
||||
report['check'] = f'replication:agmts_status'
|
||||
yield report
|
||||
elif status['state'] == 'amber':
|
||||
+ if "can't acquire busy replica" in status['reason']:
|
||||
+ # Ignore replica busy condition
|
||||
+ continue
|
||||
# Warning
|
||||
report = copy.deepcopy(DSREPLLE0003)
|
||||
report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
From 581a9db903790ebfedabff5b8056ad3b2eb393bb Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 6 Mar 2025 16:49:53 -0500
|
||||
Subject: [PATCH] Issue 6655 - fix replication release replica decoding error
|
||||
|
||||
Description:
|
||||
|
||||
When a start replication session extended op is received acquire and
|
||||
release exclusive access before returning the result to the client.
|
||||
Otherwise there is a race condition where a "end" replication extended
|
||||
op can arrive before the replica is released and that leads to a
|
||||
decoding error on the other replica.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6655
|
||||
|
||||
Reviewed by: spichugi, tbordaz, and vashirov(Thanks!!!)
|
||||
---
|
||||
.../suites/replication/acceptance_test.py | 10 +++++++-
|
||||
ldap/servers/plugins/replication/repl_extop.c | 24 ++++++++++++-------
|
||||
2 files changed, 24 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index ee82bd0df..547aee995 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2017 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -453,6 +453,13 @@ def test_multi_subsuffix_replication(topo_m4):
|
||||
f"User {user_dn} on supplier {user_obj._instance.serverid} "
|
||||
f"still has 'Description {j}'"
|
||||
)
|
||||
+
|
||||
+ # Check there are no decoding errors
|
||||
+ assert not topo_m4.ms["supplier1"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier2"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier3"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier4"].ds_error_log.match('.*decoding failed.*')
|
||||
+
|
||||
finally:
|
||||
for suffix, test_users in test_users_by_suffix.items():
|
||||
for user in test_users:
|
||||
@@ -507,6 +514,7 @@ def test_new_suffix(topo_m4, new_suffix):
|
||||
repl.remove_supplier(m1)
|
||||
repl.remove_supplier(m2)
|
||||
|
||||
+
|
||||
def test_many_attrs(topo_m4, create_entry):
|
||||
"""Check a replication with many attributes (add and delete)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
|
||||
index f53b9811f..818d99431 100644
|
||||
--- a/ldap/servers/plugins/replication/repl_extop.c
|
||||
+++ b/ldap/servers/plugins/replication/repl_extop.c
|
||||
@@ -1124,6 +1124,12 @@ send_response:
|
||||
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID);
|
||||
}
|
||||
|
||||
+ /* connext (release our hold on it at least) */
|
||||
+ if (NULL != connext) {
|
||||
+ /* don't free it, just let go of it */
|
||||
+ consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
+ }
|
||||
+
|
||||
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval);
|
||||
slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name,
|
||||
"multisupplier_extop_StartNSDS50ReplicationRequest - "
|
||||
@@ -1241,12 +1247,6 @@ send_response:
|
||||
if (NULL != ruv_bervals) {
|
||||
ber_bvecfree(ruv_bervals);
|
||||
}
|
||||
- /* connext (our hold on it at least) */
|
||||
- if (NULL != connext) {
|
||||
- /* don't free it, just let go of it */
|
||||
- consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
- connext = NULL;
|
||||
- }
|
||||
|
||||
return return_value;
|
||||
}
|
||||
@@ -1381,6 +1381,13 @@ multisupplier_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
send_response:
|
||||
+ /* connext (release our hold on it at least) */
|
||||
+ if (NULL != connext) {
|
||||
+ /* don't free it, just let go of it */
|
||||
+ consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
+ connext = NULL;
|
||||
+ }
|
||||
+
|
||||
/* Send the response code */
|
||||
if ((resp_bere = der_alloc()) == NULL) {
|
||||
goto free_and_return;
|
||||
@@ -1411,11 +1418,10 @@ free_and_return:
|
||||
if (NULL != resp_bval) {
|
||||
ber_bvfree(resp_bval);
|
||||
}
|
||||
- /* connext (our hold on it at least) */
|
||||
+ /* connext (release our hold on it if not already released) */
|
||||
if (NULL != connext) {
|
||||
/* don't free it, just let go of it */
|
||||
consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
- connext = NULL;
|
||||
}
|
||||
|
||||
return return_value;
|
||||
@@ -1508,7 +1514,7 @@ multisupplier_extop_abort_cleanruv(Slapi_PBlock *pb)
|
||||
rid);
|
||||
}
|
||||
/*
|
||||
- * Get the replica
|
||||
+ * Get the replica
|
||||
*/
|
||||
if ((r = replica_get_replica_from_root(repl_root)) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "multisupplier_extop_abort_cleanruv - "
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
From 0239d76ed22737a005a782882c6eddc7c3758654 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 19 May 2025 17:01:16 -0400
|
||||
Subject: [PATCH] Issue 6787 - Improve error message when bulk import
|
||||
connection is closed
|
||||
|
||||
Description:
|
||||
|
||||
If an online replication initialization connection is closed a vague error
|
||||
message is reported when the init is aborted:
|
||||
|
||||
factory_destructor - ERROR bulk import abandoned
|
||||
|
||||
It should be clear that the import is being abandoned because the connection
|
||||
was closed and identify the conn id.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6787
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/import.c | 7 +++++--
|
||||
ldap/servers/slapd/back-ldbm/proto-back-ldbm.h | 2 +-
|
||||
2 files changed, 6 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/import.c b/ldap/servers/slapd/back-ldbm/import.c
|
||||
index 30ec462fa..5a03bb533 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/import.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/import.c
|
||||
@@ -189,9 +189,10 @@ factory_constructor(void *object __attribute__((unused)), void *parent __attribu
|
||||
}
|
||||
|
||||
void
|
||||
-factory_destructor(void *extension, void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+factory_destructor(void *extension, void *object, void *parent __attribute__((unused)))
|
||||
{
|
||||
ImportJob *job = (ImportJob *)extension;
|
||||
+ Connection *conn = (Connection *)object;
|
||||
PRThread *thread;
|
||||
|
||||
if (extension == NULL)
|
||||
@@ -203,7 +204,9 @@ factory_destructor(void *extension, void *object __attribute__((unused)), void *
|
||||
*/
|
||||
thread = job->main_thread;
|
||||
slapi_log_err(SLAPI_LOG_ERR, "factory_destructor",
|
||||
- "ERROR bulk import abandoned\n");
|
||||
+ "ERROR bulk import abandoned: conn=%ld was closed\n",
|
||||
+ conn->c_connid);
|
||||
+
|
||||
import_abort_all(job, 1);
|
||||
/* wait for bdb_import_main to finish... */
|
||||
PR_JoinThread(thread);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
index a36f84aba..9a7ffee37 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
@@ -606,7 +606,7 @@ int ldbm_ancestorid_move_subtree(
|
||||
int ldbm_back_wire_import(Slapi_PBlock *pb);
|
||||
void import_abort_all(struct _ImportJob *job, int wait_for_them);
|
||||
void *factory_constructor(void *object __attribute__((unused)), void *parent __attribute__((unused)));
|
||||
-void factory_destructor(void *extension, void *object __attribute__((unused)), void *parent __attribute__((unused)));
|
||||
+void factory_destructor(void *extension, void *object, void *parent __attribute__((unused)));
|
||||
uint64_t wait_for_ref_count(Slapi_Counter *inst_ref_count);
|
||||
|
||||
/*
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,383 +0,0 @@
|
||||
From 542ccbc1004c88f4d6992aa68b4a49535deea165 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Thu, 24 Apr 2025 20:16:38 +0000
|
||||
Subject: [PATCH] Issue 6614 - CLI - Error when trying to display global DB
|
||||
stats with LMDB (#6622)
|
||||
|
||||
Bug description:
|
||||
Displaying global monitor stats fails with key error. Caused by BDB
|
||||
backend keys being used when MDB is the configured DB implementation.
|
||||
|
||||
Fix description:
|
||||
Ensure backend and monitor keys match the configured DB implementation.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6614
|
||||
|
||||
Reviewed by: @droideck, @progier389 (Thank you)
|
||||
---
|
||||
.../tests/suites/monitor/monitor_test.py | 3 +-
|
||||
src/lib389/lib389/_constants.py | 12 ++
|
||||
src/lib389/lib389/cli_conf/monitor.py | 130 ++++++++++--------
|
||||
src/lib389/lib389/monitor.py | 110 +++++++--------
|
||||
4 files changed, 136 insertions(+), 119 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/monitor/monitor_test.py b/dirsrvtests/tests/suites/monitor/monitor_test.py
|
||||
index 0f9d551db..8d88ba553 100644
|
||||
--- a/dirsrvtests/tests/suites/monitor/monitor_test.py
|
||||
+++ b/dirsrvtests/tests/suites/monitor/monitor_test.py
|
||||
@@ -101,13 +101,12 @@ def test_monitor_ldbm(topo):
|
||||
|
||||
# Check that known attributes exist (only NDN cache stats)
|
||||
assert 'normalizeddncachehits' in monitor
|
||||
-
|
||||
# Check for library specific attributes
|
||||
if db_lib == 'bdb':
|
||||
assert 'dbcachehits' in monitor
|
||||
assert 'nsslapd-db-configured-locks' in monitor
|
||||
elif db_lib == 'mdb':
|
||||
- pass
|
||||
+ assert 'dbcachehits' not in monitor
|
||||
else:
|
||||
# Unknown - the server would probably fail to start but check it anyway
|
||||
log.fatal(f'Unknown backend library: {db_lib}')
|
||||
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
|
||||
index f640e1e13..3e726ed53 100644
|
||||
--- a/src/lib389/lib389/_constants.py
|
||||
+++ b/src/lib389/lib389/_constants.py
|
||||
@@ -375,3 +375,15 @@ CONTAINER_TLS_SERVER_KEY = '/data/tls/server.key'
|
||||
CONTAINER_TLS_SERVER_CERT = '/data/tls/server.crt'
|
||||
CONTAINER_TLS_SERVER_CADIR = '/data/tls/ca'
|
||||
CONTAINER_TLS_PWDFILE = '/data/config/pwdfile.txt'
|
||||
+
|
||||
+# Describe what kind of Berkeley Database library is available
|
||||
+BDB_IMPL_STATUS = Enum('BDB_IMPL_STATUS', [
|
||||
+ 'UNKNOWN', # Unable to discover
|
||||
+ 'STANDARD', # os libdb rpm is installed
|
||||
+ 'BUNDLED', # lib389 bundled rpm is installed
|
||||
+ 'READ_ONLY', # Read-only version is available
|
||||
+ 'NONE' ]) # bdb is not usasable
|
||||
+
|
||||
+# DB implementation
|
||||
+DB_IMPL_BDB = "bdb"
|
||||
+DB_IMPL_MDB = "mdb"
|
||||
diff --git a/src/lib389/lib389/cli_conf/monitor.py b/src/lib389/lib389/cli_conf/monitor.py
|
||||
index 1f55fd8f8..b01796549 100644
|
||||
--- a/src/lib389/lib389/cli_conf/monitor.py
|
||||
+++ b/src/lib389/lib389/cli_conf/monitor.py
|
||||
@@ -10,8 +10,8 @@
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
-from lib389.monitor import (Monitor, MonitorLDBM, MonitorSNMP,
|
||||
- MonitorDiskSpace)
|
||||
+from lib389._constants import (DB_IMPL_BDB, DB_IMPL_MDB)
|
||||
+from lib389.monitor import (Monitor, MonitorLDBM, MonitorSNMP, MonitorDiskSpace)
|
||||
from lib389.chaining import (ChainingLinks)
|
||||
from lib389.backend import Backends
|
||||
from lib389.utils import convert_bytes
|
||||
@@ -129,27 +129,30 @@ def db_monitor(inst, basedn, log, args):
|
||||
# Gather the global DB stats
|
||||
report_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
ldbm_mon = ldbm_monitor.get_status()
|
||||
- dbcachesize = int(ldbm_mon['nsslapd-db-cache-size-bytes'][0])
|
||||
- # Warning: there are two different page sizes associated with bdb:
|
||||
- # - nsslapd-db-mp-pagesize the db mempool (i.e the db cache) page size which is usually 4K
|
||||
- # - nsslapd-db-pagesize the db instances (i.e id2entry, indexes, changelog) page size which
|
||||
- # is usually 8K
|
||||
- # To compute the db cache statistics we must use the nsslapd-db-mp-pagesize
|
||||
- if 'nsslapd-db-mp-pagesize' in ldbm_mon:
|
||||
- pagesize = int(ldbm_mon['nsslapd-db-mp-pagesize'][0])
|
||||
- else:
|
||||
- # targeting a remote instance that does not have github issue 5550 fix.
|
||||
- # So lets use the usual default file system preferred block size
|
||||
- # db cache free statistics may be wrong but we gave no way to
|
||||
- # compute it rightly.
|
||||
- pagesize = 4096
|
||||
- dbhitratio = ldbm_mon['dbcachehitratio'][0]
|
||||
- dbcachepagein = ldbm_mon['dbcachepagein'][0]
|
||||
- dbcachepageout = ldbm_mon['dbcachepageout'][0]
|
||||
- dbroevict = ldbm_mon['nsslapd-db-page-ro-evict-rate'][0]
|
||||
- dbpages = int(ldbm_mon['nsslapd-db-pages-in-use'][0])
|
||||
- dbcachefree = max(int(dbcachesize - (pagesize * dbpages)), 0)
|
||||
- dbcachefreeratio = dbcachefree/dbcachesize
|
||||
+ if ldbm_monitor.inst_db_impl == DB_IMPL_BDB:
|
||||
+ dbcachesize = int(ldbm_mon['nsslapd-db-cache-size-bytes'][0])
|
||||
+ # Warning: there are two different page sizes associated with bdb:
|
||||
+ # - nsslapd-db-mp-pagesize the db mempool (i.e the db cache) page size which is usually 4K
|
||||
+ # - nsslapd-db-pagesize the db instances (i.e id2entry, indexes, changelog) page size which
|
||||
+ # is usually 8K
|
||||
+ # To compute the db cache statistics we must use the nsslapd-db-mp-pagesize
|
||||
+ if 'nsslapd-db-mp-pagesize' in ldbm_mon:
|
||||
+ pagesize = int(ldbm_mon['nsslapd-db-mp-pagesize'][0])
|
||||
+ else:
|
||||
+ # targeting a remote instance that does not have github issue 5550 fix.
|
||||
+ # So lets use the usual default file system preferred block size
|
||||
+ # db cache free statistics may be wrong but we gave no way to
|
||||
+ # compute it rightly.
|
||||
+ pagesize = 4096
|
||||
+
|
||||
+ dbhitratio = ldbm_mon['dbcachehitratio'][0]
|
||||
+ dbcachepagein = ldbm_mon['dbcachepagein'][0]
|
||||
+ dbcachepageout = ldbm_mon['dbcachepageout'][0]
|
||||
+ dbroevict = ldbm_mon['nsslapd-db-page-ro-evict-rate'][0]
|
||||
+ dbpages = int(ldbm_mon['nsslapd-db-pages-in-use'][0])
|
||||
+ dbcachefree = max(int(dbcachesize - (pagesize * dbpages)), 0)
|
||||
+ dbcachefreeratio = dbcachefree/dbcachesize
|
||||
+
|
||||
ndnratio = ldbm_mon['normalizeddncachehitratio'][0]
|
||||
ndncursize = int(ldbm_mon['currentnormalizeddncachesize'][0])
|
||||
ndnmaxsize = int(ldbm_mon['maxnormalizeddncachesize'][0])
|
||||
@@ -165,14 +168,6 @@ def db_monitor(inst, basedn, log, args):
|
||||
# Build global cache stats
|
||||
result = {
|
||||
'date': report_time,
|
||||
- 'dbcache': {
|
||||
- 'hit_ratio': dbhitratio,
|
||||
- 'free': convert_bytes(str(dbcachefree)),
|
||||
- 'free_percentage': "{:.1f}".format(dbcachefreeratio * 100),
|
||||
- 'roevicts': dbroevict,
|
||||
- 'pagein': dbcachepagein,
|
||||
- 'pageout': dbcachepageout
|
||||
- },
|
||||
'ndncache': {
|
||||
'hit_ratio': ndnratio,
|
||||
'free': convert_bytes(str(ndnfree)),
|
||||
@@ -183,6 +178,16 @@ def db_monitor(inst, basedn, log, args):
|
||||
'backends': {},
|
||||
}
|
||||
|
||||
+ if ldbm_monitor.inst_db_impl == DB_IMPL_BDB:
|
||||
+ result['dbcache'] = {
|
||||
+ 'hit_ratio': dbhitratio,
|
||||
+ 'free': convert_bytes(str(dbcachefree)),
|
||||
+ 'free_percentage': "{:.1f}".format(dbcachefreeratio * 100),
|
||||
+ 'roevicts': dbroevict,
|
||||
+ 'pagein': dbcachepagein,
|
||||
+ 'pageout': dbcachepageout
|
||||
+ }
|
||||
+
|
||||
# Build the backend results
|
||||
for be in backend_objs:
|
||||
be_name = be.rdn
|
||||
@@ -202,17 +207,18 @@ def db_monitor(inst, basedn, log, args):
|
||||
else:
|
||||
entsize = int(entcur / entcnt)
|
||||
|
||||
- # Process DN cache stats
|
||||
- dncur = int(all_attrs['currentdncachesize'][0])
|
||||
- dnmax = int(all_attrs['maxdncachesize'][0])
|
||||
- dncnt = int(all_attrs['currentdncachecount'][0])
|
||||
- dnratio = all_attrs['dncachehitratio'][0]
|
||||
- dnfree = dnmax - dncur
|
||||
- dnfreep = "{:.1f}".format(dnfree / dnmax * 100)
|
||||
- if dncnt == 0:
|
||||
- dnsize = 0
|
||||
- else:
|
||||
- dnsize = int(dncur / dncnt)
|
||||
+ if ldbm_monitor.inst_db_impl == DB_IMPL_BDB:
|
||||
+ # Process DN cache stats
|
||||
+ dncur = int(all_attrs['currentdncachesize'][0])
|
||||
+ dnmax = int(all_attrs['maxdncachesize'][0])
|
||||
+ dncnt = int(all_attrs['currentdncachecount'][0])
|
||||
+ dnratio = all_attrs['dncachehitratio'][0]
|
||||
+ dnfree = dnmax - dncur
|
||||
+ dnfreep = "{:.1f}".format(dnfree / dnmax * 100)
|
||||
+ if dncnt == 0:
|
||||
+ dnsize = 0
|
||||
+ else:
|
||||
+ dnsize = int(dncur / dncnt)
|
||||
|
||||
# Build the backend result
|
||||
result['backends'][be_name] = {
|
||||
@@ -222,13 +228,15 @@ def db_monitor(inst, basedn, log, args):
|
||||
'entry_cache_free_percentage': entfreep,
|
||||
'entry_cache_size': convert_bytes(str(entsize)),
|
||||
'entry_cache_hit_ratio': entratio,
|
||||
- 'dn_cache_count': all_attrs['currentdncachecount'][0],
|
||||
- 'dn_cache_free': convert_bytes(str(dnfree)),
|
||||
- 'dn_cache_free_percentage': dnfreep,
|
||||
- 'dn_cache_size': convert_bytes(str(dnsize)),
|
||||
- 'dn_cache_hit_ratio': dnratio,
|
||||
'indexes': []
|
||||
}
|
||||
+ if ldbm_monitor.inst_db_impl == DB_IMPL_BDB:
|
||||
+ backend = result['backends'][be_name]
|
||||
+ backend['dn_cache_count'] = all_attrs['currentdncachecount'][0]
|
||||
+ backend['dn_cache_free'] = convert_bytes(str(dnfree))
|
||||
+ backend['dn_cache_free_percentage'] = dnfreep
|
||||
+ backend['dn_cache_size'] = convert_bytes(str(dnsize))
|
||||
+ backend['dn_cache_hit_ratio'] = dnratio
|
||||
|
||||
# Process indexes if requested
|
||||
if args.indexes:
|
||||
@@ -260,14 +268,15 @@ def db_monitor(inst, basedn, log, args):
|
||||
else:
|
||||
log.info("DB Monitor Report: " + result['date'])
|
||||
log.info("--------------------------------------------------------")
|
||||
- log.info("Database Cache:")
|
||||
- log.info(" - Cache Hit Ratio: {}%".format(result['dbcache']['hit_ratio']))
|
||||
- log.info(" - Free Space: {}".format(result['dbcache']['free']))
|
||||
- log.info(" - Free Percentage: {}%".format(result['dbcache']['free_percentage']))
|
||||
- log.info(" - RO Page Drops: {}".format(result['dbcache']['roevicts']))
|
||||
- log.info(" - Pages In: {}".format(result['dbcache']['pagein']))
|
||||
- log.info(" - Pages Out: {}".format(result['dbcache']['pageout']))
|
||||
- log.info("")
|
||||
+ if ldbm_monitor.inst_db_impl == DB_IMPL_BDB:
|
||||
+ log.info("Database Cache:")
|
||||
+ log.info(" - Cache Hit Ratio: {}%".format(result['dbcache']['hit_ratio']))
|
||||
+ log.info(" - Free Space: {}".format(result['dbcache']['free']))
|
||||
+ log.info(" - Free Percentage: {}%".format(result['dbcache']['free_percentage']))
|
||||
+ log.info(" - RO Page Drops: {}".format(result['dbcache']['roevicts']))
|
||||
+ log.info(" - Pages In: {}".format(result['dbcache']['pagein']))
|
||||
+ log.info(" - Pages Out: {}".format(result['dbcache']['pageout']))
|
||||
+ log.info("")
|
||||
log.info("Normalized DN Cache:")
|
||||
log.info(" - Cache Hit Ratio: {}%".format(result['ndncache']['hit_ratio']))
|
||||
log.info(" - Free Space: {}".format(result['ndncache']['free']))
|
||||
@@ -283,11 +292,12 @@ def db_monitor(inst, basedn, log, args):
|
||||
log.info(" - Entry Cache Free Space: {}".format(attr_dict['entry_cache_free']))
|
||||
log.info(" - Entry Cache Free Percentage: {}%".format(attr_dict['entry_cache_free_percentage']))
|
||||
log.info(" - Entry Cache Average Size: {}".format(attr_dict['entry_cache_size']))
|
||||
- log.info(" - DN Cache Hit Ratio: {}%".format(attr_dict['dn_cache_hit_ratio']))
|
||||
- log.info(" - DN Cache Count: {}".format(attr_dict['dn_cache_count']))
|
||||
- log.info(" - DN Cache Free Space: {}".format(attr_dict['dn_cache_free']))
|
||||
- log.info(" - DN Cache Free Percentage: {}%".format(attr_dict['dn_cache_free_percentage']))
|
||||
- log.info(" - DN Cache Average Size: {}".format(attr_dict['dn_cache_size']))
|
||||
+ if ldbm_monitor.inst_db_impl == DB_IMPL_BDB:
|
||||
+ log.info(" - DN Cache Hit Ratio: {}%".format(attr_dict['dn_cache_hit_ratio']))
|
||||
+ log.info(" - DN Cache Count: {}".format(attr_dict['dn_cache_count']))
|
||||
+ log.info(" - DN Cache Free Space: {}".format(attr_dict['dn_cache_free']))
|
||||
+ log.info(" - DN Cache Free Percentage: {}%".format(attr_dict['dn_cache_free_percentage']))
|
||||
+ log.info(" - DN Cache Average Size: {}".format(attr_dict['dn_cache_size']))
|
||||
if len(result['backends'][be_name]['indexes']) > 0:
|
||||
log.info(" - Indexes:")
|
||||
for index in result['backends'][be_name]['indexes']:
|
||||
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
|
||||
index ec82b0346..27b99a7e3 100644
|
||||
--- a/src/lib389/lib389/monitor.py
|
||||
+++ b/src/lib389/lib389/monitor.py
|
||||
@@ -176,68 +176,64 @@ class MonitorLDBM(DSLdapObject):
|
||||
:type instance: lib389.DirSrv
|
||||
:param dn: not used
|
||||
"""
|
||||
+ DB_KEYS = {
|
||||
+ DB_IMPL_BDB: [
|
||||
+ 'dbcachehits', 'dbcachetries', 'dbcachehitratio',
|
||||
+ 'dbcachepagein', 'dbcachepageout', 'dbcacheroevict',
|
||||
+ 'dbcacherwevict'
|
||||
+ ],
|
||||
+ DB_IMPL_MDB: [
|
||||
+ 'normalizeddncachetries', 'normalizeddncachehits',
|
||||
+ 'normalizeddncachemisses', 'normalizeddncachehitratio',
|
||||
+ 'normalizeddncacheevictions', 'currentnormalizeddncachesize',
|
||||
+ 'maxnormalizeddncachesize', 'currentnormalizeddncachecount',
|
||||
+ 'normalizeddncachethreadsize', 'normalizeddncachethreadslots'
|
||||
+ ]
|
||||
+ }
|
||||
+ DB_MONITOR_KEYS = {
|
||||
+ DB_IMPL_BDB: [
|
||||
+ 'nsslapd-db-abort-rate', 'nsslapd-db-active-txns', 'nsslapd-db-cache-hit',
|
||||
+ 'nsslapd-db-cache-try', 'nsslapd-db-cache-region-wait-rate',
|
||||
+ 'nsslapd-db-cache-size-bytes', 'nsslapd-db-clean-pages', 'nsslapd-db-commit-rate',
|
||||
+ 'nsslapd-db-deadlock-rate', 'nsslapd-db-dirty-pages', 'nsslapd-db-hash-buckets',
|
||||
+ 'nsslapd-db-hash-elements-examine-rate', 'nsslapd-db-hash-search-rate',
|
||||
+ 'nsslapd-db-lock-conflicts', 'nsslapd-db-lock-region-wait-rate',
|
||||
+ 'nsslapd-db-lock-request-rate', 'nsslapd-db-lockers', 'nsslapd-db-configured-locks',
|
||||
+ 'nsslapd-db-current-locks', 'nsslapd-db-max-locks', 'nsslapd-db-current-lock-objects',
|
||||
+ 'nsslapd-db-max-lock-objects', 'nsslapd-db-log-bytes-since-checkpoint',
|
||||
+ 'nsslapd-db-log-region-wait-rate', 'nsslapd-db-log-write-rate',
|
||||
+ 'nsslapd-db-longest-chain-length', 'nsslapd-db-page-create-rate',
|
||||
+ 'nsslapd-db-page-read-rate', 'nsslapd-db-page-ro-evict-rate',
|
||||
+ 'nsslapd-db-page-rw-evict-rate', 'nsslapd-db-page-trickle-rate',
|
||||
+ 'nsslapd-db-page-write-rate', 'nsslapd-db-pages-in-use',
|
||||
+ 'nsslapd-db-txn-region-wait-rate', 'nsslapd-db-mp-pagesize'
|
||||
+ ],
|
||||
+ DB_IMPL_MDB: [
|
||||
+ 'dbenvmapmaxsize', 'dbenvmapsize', 'dbenvlastpageno',
|
||||
+ 'dbenvlasttxnid', 'dbenvmaxreaders', 'dbenvnumreaders',
|
||||
+ 'dbenvnumdbis', 'waitingrwtxn', 'activerwtxn',
|
||||
+ 'abortrwtxn', 'commitrwtxn', 'granttimerwtxn',
|
||||
+ 'lifetimerwtxn', 'waitingrotxn', 'activerotxn',
|
||||
+ 'abortrotxn', 'commitrotxn', 'granttimerotxn',
|
||||
+ 'lifetimerotxn'
|
||||
+ ]
|
||||
+ }
|
||||
+
|
||||
def __init__(self, instance, dn=None):
|
||||
super(MonitorLDBM, self).__init__(instance=instance)
|
||||
self._dn = DN_MONITOR_LDBM
|
||||
self._db_mon = MonitorDatabase(instance)
|
||||
- self._backend_keys = [
|
||||
- 'dbcachehits',
|
||||
- 'dbcachetries',
|
||||
- 'dbcachehitratio',
|
||||
- 'dbcachepagein',
|
||||
- 'dbcachepageout',
|
||||
- 'dbcacheroevict',
|
||||
- 'dbcacherwevict',
|
||||
- ]
|
||||
- self._db_mon_keys = [
|
||||
- 'nsslapd-db-abort-rate',
|
||||
- 'nsslapd-db-active-txns',
|
||||
- 'nsslapd-db-cache-hit',
|
||||
- 'nsslapd-db-cache-try',
|
||||
- 'nsslapd-db-cache-region-wait-rate',
|
||||
- 'nsslapd-db-cache-size-bytes',
|
||||
- 'nsslapd-db-clean-pages',
|
||||
- 'nsslapd-db-commit-rate',
|
||||
- 'nsslapd-db-deadlock-rate',
|
||||
- 'nsslapd-db-dirty-pages',
|
||||
- 'nsslapd-db-hash-buckets',
|
||||
- 'nsslapd-db-hash-elements-examine-rate',
|
||||
- 'nsslapd-db-hash-search-rate',
|
||||
- 'nsslapd-db-lock-conflicts',
|
||||
- 'nsslapd-db-lock-region-wait-rate',
|
||||
- 'nsslapd-db-lock-request-rate',
|
||||
- 'nsslapd-db-lockers',
|
||||
- 'nsslapd-db-configured-locks',
|
||||
- 'nsslapd-db-current-locks',
|
||||
- 'nsslapd-db-max-locks',
|
||||
- 'nsslapd-db-current-lock-objects',
|
||||
- 'nsslapd-db-max-lock-objects',
|
||||
- 'nsslapd-db-log-bytes-since-checkpoint',
|
||||
- 'nsslapd-db-log-region-wait-rate',
|
||||
- 'nsslapd-db-log-write-rate',
|
||||
- 'nsslapd-db-longest-chain-length',
|
||||
- 'nsslapd-db-page-create-rate',
|
||||
- 'nsslapd-db-page-read-rate',
|
||||
- 'nsslapd-db-page-ro-evict-rate',
|
||||
- 'nsslapd-db-page-rw-evict-rate',
|
||||
- 'nsslapd-db-page-trickle-rate',
|
||||
- 'nsslapd-db-page-write-rate',
|
||||
- 'nsslapd-db-pages-in-use',
|
||||
- 'nsslapd-db-txn-region-wait-rate',
|
||||
- 'nsslapd-db-mp-pagesize',
|
||||
- ]
|
||||
- if not ds_is_older("1.4.0", instance=instance):
|
||||
+ self.inst_db_impl = self._instance.get_db_lib()
|
||||
+ self._backend_keys = list(self.DB_KEYS.get(self.inst_db_impl, []))
|
||||
+ self._db_mon_keys = list(self.DB_MONITOR_KEYS.get(self.inst_db_impl, []))
|
||||
+
|
||||
+ if self.inst_db_impl == DB_IMPL_BDB and not ds_is_older("1.4.0", instance=instance):
|
||||
self._backend_keys.extend([
|
||||
- 'normalizeddncachetries',
|
||||
- 'normalizeddncachehits',
|
||||
- 'normalizeddncachemisses',
|
||||
- 'normalizeddncachehitratio',
|
||||
- 'normalizeddncacheevictions',
|
||||
- 'currentnormalizeddncachesize',
|
||||
- 'maxnormalizeddncachesize',
|
||||
- 'currentnormalizeddncachecount',
|
||||
- 'normalizeddncachethreadsize',
|
||||
- 'normalizeddncachethreadslots'
|
||||
+ 'normalizeddncachetries', 'normalizeddncachehits',
|
||||
+ 'normalizeddncachemisses', 'normalizeddncachehitratio',
|
||||
+ 'normalizeddncacheevictions', 'currentnormalizeddncachesize',
|
||||
+ 'maxnormalizeddncachesize', 'currentnormalizeddncachecount',
|
||||
+ 'normalizeddncachethreadsize', 'normalizeddncachethreadslots'
|
||||
])
|
||||
|
||||
def get_status(self, use_json=False):
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
From f14c4ef145878ec0629295c7f1cb6ac34e97d7bf Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Sun, 6 Apr 2025 21:10:46 +0000
|
||||
Subject: [PATCH] Issue 6720 - Remove BDB attribute from MDB DB Monitor (#6721)
|
||||
|
||||
Bug description: Reference to a BDB attribute exists in DatabaseMonitorMDB
|
||||
class.
|
||||
|
||||
Fix description: Remove it
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6720
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6614
|
||||
|
||||
Reviewed by: @progier389 (Thank you)
|
||||
---
|
||||
.../389-console/src/lib/monitor/dbMonitor.jsx | 18 ------------------
|
||||
1 file changed, 18 deletions(-)
|
||||
|
||||
diff --git a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
|
||||
index 08aa1aaea..bb9950ccd 100644
|
||||
--- a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
|
||||
+++ b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
|
||||
@@ -605,12 +605,6 @@ export class DatabaseMonitorMDB extends React.Component {
|
||||
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;
|
||||
@@ -628,7 +622,6 @@ export class DatabaseMonitorMDB extends React.Component {
|
||||
this.setState({
|
||||
data: config.attrs,
|
||||
loading: false,
|
||||
- dbCacheList: chart_data,
|
||||
ndnCacheList: ndn_chart_data,
|
||||
ndnCacheUtilList: ndn_util_chart_data,
|
||||
count,
|
||||
@@ -651,10 +644,8 @@ export class DatabaseMonitorMDB extends React.Component {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -671,7 +662,6 @@ export class DatabaseMonitorMDB extends React.Component {
|
||||
);
|
||||
|
||||
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]);
|
||||
@@ -681,14 +671,6 @@ export class DatabaseMonitorMDB extends React.Component {
|
||||
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;
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,236 +0,0 @@
|
||||
From 4fca33eaadfc5a6ff5eb157fa1f37f688284330d Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 29 Jan 2025 17:41:55 +0000
|
||||
Subject: [PATCH] Issue 6436 - MOD on a large group slow if substring index is
|
||||
present (#6437)
|
||||
|
||||
Bug Description: If the substring index is configured for the group
|
||||
membership attribute ( member or uniqueMember ), the removal of a
|
||||
member from a large static group is pretty slow.
|
||||
|
||||
Fix Description: A solution to this issue would be to introduce
|
||||
a new index to track a membership atttribute index. In the interm,
|
||||
we add a check to healthcheck to inform the user of the implications
|
||||
of this configuration.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6436
|
||||
|
||||
Reviewed by: @Firstyear, @tbordaz, @droideck (Thanks)
|
||||
---
|
||||
.../suites/healthcheck/health_config_test.py | 89 ++++++++++++++++++-
|
||||
src/lib389/lib389/lint.py | 15 ++++
|
||||
src/lib389/lib389/plugins.py | 37 +++++++-
|
||||
3 files changed, 137 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_config_test.py b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
index 30cad94c1..168b5092e 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
@@ -171,6 +171,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)
|
||||
@@ -192,7 +193,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
|
||||
@@ -207,8 +208,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
|
||||
@@ -218,6 +219,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)
|
||||
@@ -240,6 +242,87 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
standalone.restart()
|
||||
|
||||
|
||||
+def test_healthcheck_MO_plugin_substring_index(topology_st):
|
||||
+ """Check if HealthCheck returns DSMOLE0002 code when the
|
||||
+ member, uniquemember attribute contains a substring index type
|
||||
+
|
||||
+ :id: 10954811-24ac-4886-8183-e30892f8e02d
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Configure the instance with MO Plugin
|
||||
+ 3. Change index type to substring for member attribute
|
||||
+ 4. Use HealthCheck without --json option
|
||||
+ 5. Use HealthCheck with --json option
|
||||
+ 6. Change index type back to equality for member attribute
|
||||
+ 7. Use HealthCheck without --json option
|
||||
+ 8. Use HealthCheck with --json option
|
||||
+ 9. Change index type to substring for uniquemember attribute
|
||||
+ 10. Use HealthCheck without --json option
|
||||
+ 11. Use HealthCheck with --json option
|
||||
+ 12. Change index type back to equality for uniquemember attribute
|
||||
+ 13. Use HealthCheck without --json option
|
||||
+ 14. Use HealthCheck with --json option
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 5. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 6. Success
|
||||
+ 7. Healthcheck reports no issue found
|
||||
+ 8. Healthcheck reports no issue found
|
||||
+ 9. Success
|
||||
+ 10. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 11. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 12. Success
|
||||
+ 13. Healthcheck reports no issue found
|
||||
+ 14. Healthcheck reports no issue found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = 'DSMOLE0002'
|
||||
+ MEMBER_DN = 'cn=member,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
+ UNIQUE_MEMBER_DN = 'cn=uniquemember,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
+
|
||||
+ log.info('Enable MO plugin')
|
||||
+ plugin = MemberOfPlugin(standalone)
|
||||
+ plugin.disable()
|
||||
+ plugin.enable()
|
||||
+
|
||||
+ log.info('Change the index type of the member attribute index to substring')
|
||||
+ index = Index(topology_st.standalone, MEMBER_DN)
|
||||
+ index.replace('nsIndexType', 'sub')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info('Set the index type of the member attribute index back to eq')
|
||||
+ index.replace('nsIndexType', 'eq')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+ log.info('Change the index type of the uniquemember attribute index to substring')
|
||||
+ index = Index(topology_st.standalone, UNIQUE_MEMBER_DN)
|
||||
+ index.replace('nsIndexType', 'sub')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info('Set the index type of the uniquemember attribute index back to eq')
|
||||
+ index.replace('nsIndexType', 'eq')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+ # Restart the instance after changing the plugin to avoid breaking the other tests
|
||||
+ standalone.restart()
|
||||
+
|
||||
+
|
||||
@pytest.mark.ds50873
|
||||
@pytest.mark.bz1685160
|
||||
@pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented")
|
||||
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||||
index d0747f0f4..460bf64fc 100644
|
||||
--- a/src/lib389/lib389/lint.py
|
||||
+++ b/src/lib389/lib389/lint.py
|
||||
@@ -270,6 +270,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 67af93a14..31bbfa502 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.49.0
|
||||
|
||||
@ -1,215 +0,0 @@
|
||||
From 40fc1e148db132f57cf9a556a0d70f48e5e30dc7 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 6 May 2025 16:09:36 +0200
|
||||
Subject: [PATCH] Issue 6764 - statistics about index lookup report a wrong
|
||||
duration (#6765)
|
||||
|
||||
Bug description:
|
||||
During a SRCH statistics about indexes lookup
|
||||
(when nsslapd-statlog-level=1) reports a duration.
|
||||
It is wrong because it should report a duration per filter
|
||||
component.
|
||||
|
||||
Fix description:
|
||||
Record a index lookup duration per key
|
||||
using key_lookup_start/key_lookup_end
|
||||
|
||||
fixes: #6764
|
||||
|
||||
Reviewed by: Pierre Rogier (Thanks !)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/filterindex.c | 7 +++--
|
||||
ldap/servers/slapd/back-ldbm/ldbm_search.c | 31 +++++++++++++++-------
|
||||
ldap/servers/slapd/result.c | 12 +++++----
|
||||
ldap/servers/slapd/slapi-plugin.h | 9 +++++++
|
||||
ldap/servers/slapd/slapi-private.h | 2 ++
|
||||
ldap/servers/slapd/time.c | 13 +++++++++
|
||||
6 files changed, 57 insertions(+), 17 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
index 52f51713b..778bc73e4 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
@@ -1105,11 +1105,14 @@ keys2idl(
|
||||
struct component_keys_lookup *key_stat;
|
||||
int key_len;
|
||||
|
||||
- idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
if (op_stat) {
|
||||
/* gather the index lookup statistics */
|
||||
key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
-
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(key_stat->key_lookup_start));
|
||||
+ }
|
||||
+ idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
+ if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(key_stat->key_lookup_end));
|
||||
/* indextype e.g. "eq" or "sub" (see index.c) */
|
||||
if (indextype) {
|
||||
key_stat->index_type = slapi_ch_strdup(indextype);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
index 84707a71b..2add5ec38 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
@@ -35,7 +35,7 @@ static IDList *onelevel_candidates(Slapi_PBlock *pb, backend *be, const char *ba
|
||||
static back_search_result_set *new_search_result_set(IDList *idl, int vlv, int lookthroughlimit);
|
||||
static void delete_search_result_set(Slapi_PBlock *pb, back_search_result_set **sr);
|
||||
static int can_skip_filter_test(Slapi_PBlock *pb, struct slapi_filter *f, int scope, IDList *idl);
|
||||
-static void stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
+static void stat_add_srch_lookup(Op_stat *op_stat, struct component_keys_lookup *key_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
|
||||
/* This is for performance testing, allows us to disable ACL checking altogether */
|
||||
#if defined(DISABLE_ACL_CHECK)
|
||||
@@ -1258,17 +1258,12 @@ create_subtree_filter(Slapi_Filter *filter, int managedsait)
|
||||
}
|
||||
|
||||
static void
|
||||
-stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
+stat_add_srch_lookup(Op_stat *op_stat, struct component_keys_lookup *key_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
{
|
||||
- struct component_keys_lookup *key_stat;
|
||||
-
|
||||
- if ((op_stat == NULL) || (op_stat->search_stat == NULL)) {
|
||||
+ if ((op_stat == NULL) || (op_stat->search_stat == NULL) || (key_stat == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
- /* gather the index lookup statistics */
|
||||
- key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
-
|
||||
/* indextype is "eq" */
|
||||
if (index_type) {
|
||||
key_stat->index_type = slapi_ch_strdup(index_type);
|
||||
@@ -1358,23 +1353,39 @@ subtree_candidates(
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
|
||||
if (entryrdn_get_noancestorid()) {
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if (op_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_start);
|
||||
+ }
|
||||
/* subtree-rename: on && no ancestorid */
|
||||
*err = entryrdn_get_subordinates(be,
|
||||
slapi_entry_get_sdn_const(e->ep_entry),
|
||||
e->ep_id, &descendants, &txn, 0);
|
||||
if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_end);
|
||||
/* record entryrdn lookups */
|
||||
- stat_add_srch_lookup(op_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ stat_add_srch_lookup(op_stat, key_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
}
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
idl_free(&descendants);
|
||||
} else if (!has_tombstone_filter && !is_bulk_import) {
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if (op_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_start);
|
||||
+ }
|
||||
*err = ldbm_ancestorid_read_ext(be, &txn, e->ep_id, &descendants, allidslimit);
|
||||
if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_end);
|
||||
/* records ancestorid lookups */
|
||||
- stat_add_srch_lookup(op_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ stat_add_srch_lookup(op_stat, key_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
}
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index 4e351eff5..f073bf216 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -2091,19 +2091,21 @@ log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_interna
|
||||
op_stat->search_stat) {
|
||||
struct component_keys_lookup *key_info;
|
||||
for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
+ slapi_timespec_diff(&key_info->key_lookup_end, &key_info->key_lookup_start, &duration);
|
||||
+ snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
if (internal_op) {
|
||||
slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n":
|
||||
- STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n",
|
||||
connid, op_id, op_internal_id, op_nested_count,
|
||||
key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ key_info->id_lookup_cnt, stat_etime);
|
||||
} else {
|
||||
slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n",
|
||||
connid, op_id,
|
||||
key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ key_info->id_lookup_cnt, stat_etime);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 18b4cbc67..9bf791b56 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -8329,6 +8329,15 @@ void DS_Sleep(PRIntervalTime ticks);
|
||||
* \param struct timespec c the difference.
|
||||
*/
|
||||
void slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *diff);
|
||||
+
|
||||
+/**
|
||||
+ * add 'new' timespect into 'cumul'
|
||||
+ * clock_monotonic to find time taken to perform operations.
|
||||
+ *
|
||||
+ * \param struct timespec cumul to compute total duration.
|
||||
+ * \param struct timespec new is a additional duration
|
||||
+ */
|
||||
+void slapi_timespec_add(struct timespec *cumul, struct timespec *new);
|
||||
/**
|
||||
* Given an operation, determine the time elapsed since the op
|
||||
* began.
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index a61daab75..7a3eb3fdf 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -462,6 +462,8 @@ struct component_keys_lookup
|
||||
char *attribute_type;
|
||||
char *key;
|
||||
int id_lookup_cnt;
|
||||
+ struct timespec key_lookup_start;
|
||||
+ struct timespec key_lookup_end;
|
||||
struct component_keys_lookup *next;
|
||||
};
|
||||
typedef struct op_search_stat
|
||||
diff --git a/ldap/servers/slapd/time.c b/ldap/servers/slapd/time.c
|
||||
index 5bd9279db..9c71475a7 100644
|
||||
--- a/ldap/servers/slapd/time.c
|
||||
+++ b/ldap/servers/slapd/time.c
|
||||
@@ -272,6 +272,19 @@ slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *dif
|
||||
diff->tv_nsec = nsec;
|
||||
}
|
||||
|
||||
+void
|
||||
+slapi_timespec_add(struct timespec *cumul, struct timespec *new)
|
||||
+{
|
||||
+ /* Now add the two */
|
||||
+ time_t sec = cumul->tv_sec + new->tv_sec;
|
||||
+ long nsec = cumul->tv_nsec + new->tv_nsec;
|
||||
+
|
||||
+ sec += nsec / 1000000000;
|
||||
+ nsec = nsec % 1000000000;
|
||||
+ cumul->tv_sec = sec;
|
||||
+ cumul->tv_nsec = nsec;
|
||||
+}
|
||||
+
|
||||
void
|
||||
slapi_timespec_expire_at(time_t timeout, struct timespec *expire)
|
||||
{
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
From 0ccd30b588c6824f87f3cc7610d31ecb8e6e156a Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Wed, 16 Apr 2025 18:13:47 +0200
|
||||
Subject: [PATCH] Issue 6736 - Exception thrown by dsconf instance repl get_ruv
|
||||
(#6742)
|
||||
|
||||
After issue #6715 commit dsconf instance replication get_ruv fails with error
|
||||
because by default NormalizedRidDict does not calling getitem as I expected.
|
||||
The solution is to overwrite the get function to do things properly.
|
||||
|
||||
Issue: #6736
|
||||
Relates: #6715
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
|
||||
(cherry picked from commit 64b2514d09b642913f2781ebdf8a5931f9e74eb6)
|
||||
---
|
||||
.../suites/replication/regression_m2_test.py | 42 +++++++++++++++++++
|
||||
src/lib389/lib389/replica.py | 12 +++++-
|
||||
2 files changed, 53 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index 2283b9041..ba1ffcc9c 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -1275,6 +1275,48 @@ def test_normalized_rid_dict():
|
||||
assert nrd[nkey] == val
|
||||
|
||||
|
||||
+def test_get_with_normalized_rid_dict():
|
||||
+ """Check lib389.replica NormalizedRidDict.get() function
|
||||
+
|
||||
+ :id: 4422e1be-1619-11f0-a37a-482ae39447e5
|
||||
+ :setup: None
|
||||
+ :steps:
|
||||
+ 1. Initialize a NormalizedRidDict
|
||||
+ 2. Check that normalization do something
|
||||
+ 3. Check that get() returns the expected value
|
||||
+ 4. Check get() with wrong key
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Should return the default value if it is provided
|
||||
+ otherwise it should return None
|
||||
+ """
|
||||
+
|
||||
+ sd = { '1': 'v1', '020': 'v2' }
|
||||
+ nsd = { NormalizedRidDict.normalize_rid(key): val for key,val in sd.items() }
|
||||
+ nkeys = list(nsd.keys())
|
||||
+
|
||||
+ # Initialize a NormalizedRidDict
|
||||
+ nrd = NormalizedRidDict()
|
||||
+ for key,val in sd.items():
|
||||
+ nrd[key] = val
|
||||
+
|
||||
+ # Check that get() returns the expected value
|
||||
+ for key,val in sd.items():
|
||||
+ nkey = NormalizedRidDict.normalize_rid(key)
|
||||
+ assert nrd.get(key) == val
|
||||
+ assert nrd.get(nkey) == val
|
||||
+ assert nrd.get(key, 'foo') == val
|
||||
+ assert nrd.get(nkey, 'foo') == val
|
||||
+
|
||||
+ # Check get() with wrong key
|
||||
+ assert nrd.get('99', 'foo2') == 'foo2'
|
||||
+ assert nrd.get('099', 'foo2') == 'foo2'
|
||||
+ assert nrd.get('99') is None
|
||||
+ assert nrd.get('099') is None
|
||||
+
|
||||
+
|
||||
@pytest.mark.ds49915
|
||||
@pytest.mark.bz1626375
|
||||
def test_online_reinit_may_hang(topo_with_sigkill):
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 0dc3a0f54..5183c41e9 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -841,6 +841,12 @@ class NormalizedRidDict(dict):
|
||||
nkey = NormalizedRidDict.normalize_rid(key)
|
||||
super().__setitem__(nkey, value)
|
||||
|
||||
+ def get(self, key, vdef=None):
|
||||
+ try:
|
||||
+ return self[key]
|
||||
+ except KeyError:
|
||||
+ return vdef
|
||||
+
|
||||
|
||||
class RUV(object):
|
||||
"""Represents the server in memory RUV object. The RUV contains each
|
||||
@@ -911,7 +917,7 @@ class RUV(object):
|
||||
ValueError("Wrong CSN value was supplied")
|
||||
|
||||
timestamp = int(csn[:8], 16)
|
||||
- time_str = datetime.datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
||||
+ time_str = datetime.datetime.fromtimestamp(timestamp, datetime.UTC).strftime('%Y-%m-%d %H:%M:%S')
|
||||
# We are parsing shorter CSN which contains only timestamp
|
||||
if len(csn) == 8:
|
||||
return time_str
|
||||
@@ -987,6 +993,10 @@ class RUV(object):
|
||||
return False
|
||||
return True
|
||||
|
||||
+ def __str__(self):
|
||||
+ return str(self.format_ruv())
|
||||
+
|
||||
+
|
||||
|
||||
class ChangelogLDIF(object):
|
||||
def __init__(self, file_path, output_file):
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,148 +0,0 @@
|
||||
From 44c97e185b852648fcb45c202d4ea1bfd5b281d3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 18 Jun 2025 11:12:28 +0200
|
||||
Subject: [PATCH] Issue 6819 - Incorrect pwdpolicysubentry returned for an
|
||||
entry with user password policy
|
||||
|
||||
Bug Description:
|
||||
When both subtree and user password policies exist, pwdpolicysubentry
|
||||
points to the subtree password policy instead of user password policy.
|
||||
|
||||
Fix Description:
|
||||
Update the template for CoS pointer definition to use
|
||||
`operational-default` modifier instead of `operational`.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6819
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (Thanks!)
|
||||
---
|
||||
.../suites/password/password_policy_test.py | 2 +-
|
||||
.../password/pwdPolicy_attribute_test.py | 73 +++++++++++++++++--
|
||||
src/lib389/lib389/pwpolicy.py | 2 +-
|
||||
3 files changed, 67 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/password_policy_test.py b/dirsrvtests/tests/suites/password/password_policy_test.py
|
||||
index f46f95981..528674381 100644
|
||||
--- a/dirsrvtests/tests/suites/password/password_policy_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/password_policy_test.py
|
||||
@@ -85,7 +85,7 @@ def create_subtree_policy_custom(instance, dn, properties):
|
||||
|
||||
# The CoS specification entry at the subtree level
|
||||
cos_pointer_defs = CosPointerDefinitions(instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational',
|
||||
+ cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
'cosTemplateDn': cos_template.dn,
|
||||
'cn': 'nsPwPolicy_CoS'})
|
||||
except ldap.LDAPError as e:
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
index b68b14c51..9c0983b78 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
@@ -59,17 +59,39 @@ def add_test_user(topology_st, request):
|
||||
return user
|
||||
|
||||
|
||||
-@pytest.fixture(scope="module")
|
||||
-def password_policy(topology_st, add_test_user):
|
||||
+@pytest.fixture(scope="function")
|
||||
+def password_policy(topology_st, request, add_test_user):
|
||||
"""Set up password policy for subtree and user"""
|
||||
|
||||
pwp = PwPolicyManager(topology_st.standalone)
|
||||
policy_props = {}
|
||||
- log.info('Create password policy for subtree {}'.format(OU_PEOPLE))
|
||||
- pwp.create_subtree_policy(OU_PEOPLE, policy_props)
|
||||
+ log.info(f"Create password policy for subtree {OU_PEOPLE}")
|
||||
+ try:
|
||||
+ pwp.create_subtree_policy(OU_PEOPLE, policy_props)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"Subtree password policy for {OU_PEOPLE} already exist, skipping")
|
||||
+
|
||||
+ log.info(f"Create password policy for user {TEST_USER_DN}")
|
||||
+ try:
|
||||
+ pwp.create_user_policy(TEST_USER_DN, policy_props)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"User password policy for {TEST_USER_DN} already exist, skipping")
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info(f"Delete password policy for subtree {OU_PEOPLE}")
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(OU_PEOPLE)
|
||||
+ except ValueError:
|
||||
+ log.info(f"Subtree password policy for {OU_PEOPLE} doesn't exist, skipping")
|
||||
+
|
||||
+ log.info(f"Delete password policy for user {TEST_USER_DN}")
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(TEST_USER_DN)
|
||||
+ except ValueError:
|
||||
+ log.info(f"User password policy for {TEST_USER_DN} doesn't exist, skipping")
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
|
||||
- log.info('Create password policy for user {}'.format(TEST_USER_DN))
|
||||
- pwp.create_user_policy(TEST_USER_DN, policy_props)
|
||||
|
||||
@pytest.mark.bz1845094
|
||||
@pytest.mark.skipif(ds_is_older('1.4.3.3'), reason="Not implemented")
|
||||
@@ -302,8 +324,43 @@ def test_pwd_min_age(topology_st, add_test_user, password_policy):
|
||||
log.info('Bind as DM')
|
||||
topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
user.reset_password(TEST_USER_PWD)
|
||||
- pwp.delete_local_policy(TEST_USER_DN)
|
||||
- pwp.delete_local_policy(OU_PEOPLE)
|
||||
+
|
||||
+
|
||||
+def test_pwdpolicysubentry(topology_st, password_policy):
|
||||
+ """Verify that 'pwdpolicysubentry' attr works as expected
|
||||
+ User should have a priority over a subtree.
|
||||
+
|
||||
+ :id: 4ab0c62a-623b-40b4-af67-99580c77b36c
|
||||
+ :setup: Standalone instance, a test user,
|
||||
+ password policy entries for a user and a subtree
|
||||
+ :steps:
|
||||
+ 1. Create a subtree policy
|
||||
+ 2. Create a user policy
|
||||
+ 3. Search for 'pwdpolicysubentry' in the user entry
|
||||
+ 4. Delete the user policy
|
||||
+ 5. Search for 'pwdpolicysubentry' in the user entry
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should point to the user policy entry
|
||||
+ 4. Success
|
||||
+ 5. Should point to the subtree policy entry
|
||||
+
|
||||
+ """
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, OU_PEOPLE, rdn=None)
|
||||
+ user = users.get(TEST_USER_NAME)
|
||||
+
|
||||
+ pwp_subentry = user.get_attr_vals_utf8('pwdpolicysubentry')[0]
|
||||
+ assert 'nsPwPolicyEntry_subtree' not in pwp_subentry
|
||||
+ assert 'nsPwPolicyEntry_user' in pwp_subentry
|
||||
+
|
||||
+ pwp = PwPolicyManager(topology_st.standalone)
|
||||
+ pwp.delete_local_policy(TEST_USER_DN)
|
||||
+ pwp_subentry = user.get_attr_vals_utf8('pwdpolicysubentry')[0]
|
||||
+ assert 'nsPwPolicyEntry_subtree' in pwp_subentry
|
||||
+ assert 'nsPwPolicyEntry_user' not in pwp_subentry
|
||||
+
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
|
||||
index 7ffe449cc..6a47a44fe 100644
|
||||
--- a/src/lib389/lib389/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/pwpolicy.py
|
||||
@@ -168,7 +168,7 @@ class PwPolicyManager(object):
|
||||
|
||||
# The CoS specification entry at the subtree level
|
||||
cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational',
|
||||
+ cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
'cosTemplateDn': cos_template.dn,
|
||||
'cn': 'nsPwPolicy_CoS'})
|
||||
except ldap.LDAPError as e:
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,574 +0,0 @@
|
||||
From fa4028567ee7ffd6fe280db904b3d3fc596d4ef0 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 30 May 2025 11:12:43 +0000
|
||||
Subject: [PATCH] Issue 6641 - modrdn fails when a user is member of multiple
|
||||
groups (#6643)
|
||||
|
||||
Bug description:
|
||||
Rename of a user that is member of multiple AM groups fail when MO and
|
||||
RI plugins are enabled.
|
||||
|
||||
Fix description:
|
||||
MO plugin - After updating the entry member attribute, check the return
|
||||
value. Retry the delete if the attr value exists and retry the add if the
|
||||
attr value is missing.
|
||||
|
||||
RI plugin - A previous commit checked if the attr value was not present
|
||||
before adding a mod. This commit was reverted in favour of overriding
|
||||
the internal op return value, consistent with other plugins.
|
||||
|
||||
CI test from Viktor Ashirov <vashirov@redhat.com>
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6641
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6566
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @vashirov (Thank you)
|
||||
---
|
||||
.../tests/suites/plugins/modrdn_test.py | 174 ++++++++++++++++++
|
||||
ldap/servers/plugins/automember/automember.c | 11 +-
|
||||
ldap/servers/plugins/memberof/memberof.c | 124 +++++--------
|
||||
ldap/servers/plugins/referint/referint.c | 43 +++--
|
||||
ldap/servers/slapd/modify.c | 51 +++++
|
||||
ldap/servers/slapd/slapi-plugin.h | 1 +
|
||||
6 files changed, 303 insertions(+), 101 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/modrdn_test.py b/dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
new file mode 100644
|
||||
index 000000000..be79b0c3c
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
@@ -0,0 +1,174 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import pytest
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.idm.group import Groups
|
||||
+from lib389.idm.user import nsUserAccounts
|
||||
+from lib389.plugins import (
|
||||
+ AutoMembershipDefinitions,
|
||||
+ AutoMembershipPlugin,
|
||||
+ AutoMembershipRegexRules,
|
||||
+ MemberOfPlugin,
|
||||
+ ReferentialIntegrityPlugin,
|
||||
+)
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+USER_PROPERTIES = {
|
||||
+ "uid": "userwith",
|
||||
+ "cn": "userwith",
|
||||
+ "uidNumber": "1000",
|
||||
+ "gidNumber": "2000",
|
||||
+ "homeDirectory": "/home/testuser",
|
||||
+ "displayName": "test user",
|
||||
+}
|
||||
+
|
||||
+
|
||||
+def test_modrdn_of_a_member_of_2_automember_groups(topology_st):
|
||||
+ """Test that a member of 2 automember groups can be renamed
|
||||
+
|
||||
+ :id: 0e40bdc4-a2d2-4bb8-8368-e02c8920bad2
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Enable automember plugin
|
||||
+ 2. Create definiton for users with A in the name
|
||||
+ 3. Create regex rule for users with A in the name
|
||||
+ 4. Create definiton for users with Z in the name
|
||||
+ 5. Create regex rule for users with Z in the name
|
||||
+ 6. Enable memberof plugin
|
||||
+ 7. Enable referential integrity plugin
|
||||
+ 8. Restart the instance
|
||||
+ 9. Create groups
|
||||
+ 10. Create users userwitha, userwithz, userwithaz
|
||||
+ 11. Rename userwithaz
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ 9. Success
|
||||
+ 10. Success
|
||||
+ 11. Success
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ # Enable automember plugin
|
||||
+ automember_plugin = AutoMembershipPlugin(inst)
|
||||
+ automember_plugin.enable()
|
||||
+
|
||||
+ # Create definiton for users with A in the name
|
||||
+ automembers = AutoMembershipDefinitions(inst)
|
||||
+ automember = automembers.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithA",
|
||||
+ "autoMemberScope": DEFAULT_SUFFIX,
|
||||
+ "autoMemberFilter": "objectclass=posixAccount",
|
||||
+ "autoMemberGroupingAttr": "member:dn",
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create regex rule for users with A in the name
|
||||
+ automembers_regex_rule = AutoMembershipRegexRules(inst, f"{automember.dn}")
|
||||
+ automembers_regex_rule.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithA",
|
||||
+ "autoMemberInclusiveRegex": ["cn=.*a.*"],
|
||||
+ "autoMemberTargetGroup": [f"cn=userswithA,ou=Groups,{DEFAULT_SUFFIX}"],
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create definiton for users with Z in the name
|
||||
+ automember = automembers.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithZ",
|
||||
+ "autoMemberScope": DEFAULT_SUFFIX,
|
||||
+ "autoMemberFilter": "objectclass=posixAccount",
|
||||
+ "autoMemberGroupingAttr": "member:dn",
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create regex rule for users with Z in the name
|
||||
+ automembers_regex_rule = AutoMembershipRegexRules(inst, f"{automember.dn}")
|
||||
+ automembers_regex_rule.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithZ",
|
||||
+ "autoMemberInclusiveRegex": ["cn=.*z.*"],
|
||||
+ "autoMemberTargetGroup": [f"cn=userswithZ,ou=Groups,{DEFAULT_SUFFIX}"],
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Enable memberof plugin
|
||||
+ memberof_plugin = MemberOfPlugin(inst)
|
||||
+ memberof_plugin.enable()
|
||||
+
|
||||
+ # Enable referential integrity plugin
|
||||
+ referint_plugin = ReferentialIntegrityPlugin(inst)
|
||||
+ referint_plugin.enable()
|
||||
+
|
||||
+ # Restart the instance
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Create groups
|
||||
+ groups = Groups(inst, DEFAULT_SUFFIX)
|
||||
+ groupA = groups.create(properties={"cn": "userswithA"})
|
||||
+ groupZ = groups.create(properties={"cn": "userswithZ"})
|
||||
+
|
||||
+ # Create users
|
||||
+ users = nsUserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # userwitha
|
||||
+ user_props = USER_PROPERTIES.copy()
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "a",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "a",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+
|
||||
+ # userwithz
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "z",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "z",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+
|
||||
+ # userwithaz
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "az",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "az",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+ user_orig_dn = user.dn
|
||||
+
|
||||
+ # Rename userwithaz
|
||||
+ user.rename(new_rdn="uid=userwith")
|
||||
+ user_new_dn = user.dn
|
||||
+
|
||||
+ assert user.get_attr_val_utf8("uid") != "userwithaz"
|
||||
+
|
||||
+ # Check groups contain renamed username
|
||||
+ assert groupA.is_member(user_new_dn)
|
||||
+ assert groupZ.is_member(user_new_dn)
|
||||
+
|
||||
+ # Check groups dont contain original username
|
||||
+ assert not groupA.is_member(user_orig_dn)
|
||||
+ assert not groupZ.is_member(user_orig_dn)
|
||||
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
|
||||
index 38f817e5d..f900db7f2 100644
|
||||
--- a/ldap/servers/plugins/automember/automember.c
|
||||
+++ b/ldap/servers/plugins/automember/automember.c
|
||||
@@ -1755,13 +1755,12 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
}
|
||||
|
||||
mod_pb = slapi_pblock_new();
|
||||
- slapi_modify_internal_set_pb(mod_pb, group_dn,
|
||||
- mods, 0, 0, automember_get_plugin_id(), 0);
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+ /* Do a single mod with error overrides for DEL/ADD */
|
||||
+ result = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_dn_byval(group_dn), mods,
|
||||
+ automember_get_plugin_id(), 0);
|
||||
|
||||
if(add){
|
||||
- if ((result != LDAP_SUCCESS) && (result != LDAP_TYPE_OR_VALUE_EXISTS)) {
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_update_member_value - Unable to add \"%s\" as "
|
||||
"a \"%s\" value to group \"%s\" (%s).\n",
|
||||
@@ -1771,7 +1770,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
}
|
||||
} else {
|
||||
/* delete value */
|
||||
- if ((result != LDAP_SUCCESS) && (result != LDAP_NO_SUCH_ATTRIBUTE)) {
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_update_member_value - Unable to delete \"%s\" as "
|
||||
"a \"%s\" value from group \"%s\" (%s).\n",
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 35fd1a4a0..16dae2195 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -964,7 +964,6 @@ modify_need_fixup(int set)
|
||||
mod_pb, memberof_get_config_area(),
|
||||
mods, 0, 0,
|
||||
memberof_get_plugin_id(), SLAPI_OP_FLAG_FIXUP|SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
-
|
||||
slapi_modify_internal_pb(mod_pb);
|
||||
slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
@@ -1492,18 +1491,9 @@ memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
mod.mod_op = LDAP_MOD_DELETE;
|
||||
mod.mod_type = ((memberof_del_dn_data *)callback_data)->type;
|
||||
mod.mod_values = val;
|
||||
-
|
||||
- slapi_modify_internal_set_pb_ext(
|
||||
- mod_pb, slapi_entry_get_sdn(e),
|
||||
- mods, 0, 0,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
-
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
-
|
||||
- slapi_pblock_get(mod_pb,
|
||||
- SLAPI_PLUGIN_INTOP_RESULT,
|
||||
- &rc);
|
||||
-
|
||||
+ /* Internal mod with error overrides for DEL/ADD */
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, slapi_entry_get_sdn(e), mods,
|
||||
+ memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
|
||||
if (rc == LDAP_NO_SUCH_ATTRIBUTE && val[0] == NULL) {
|
||||
@@ -1976,6 +1966,7 @@ memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
|
||||
return rc;
|
||||
}
|
||||
+
|
||||
LDAPMod **
|
||||
my_copy_mods(LDAPMod **orig_mods)
|
||||
{
|
||||
@@ -2784,33 +2775,6 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_o
|
||||
replace_mod.mod_values = replace_val;
|
||||
}
|
||||
rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- if (rc == LDAP_NO_SUCH_ATTRIBUTE || rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- /*
|
||||
- * For some reason the new modrdn value is present, so retry
|
||||
- * the delete by itself and ignore the add op by tweaking
|
||||
- * the mod array.
|
||||
- */
|
||||
- mods[1] = NULL;
|
||||
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- } else {
|
||||
- /*
|
||||
- * The memberof value to be replaced does not exist so just
|
||||
- * add the new value. Shuffle the mod array to apply only
|
||||
- * the add operation.
|
||||
- */
|
||||
- mods[0] = mods[1];
|
||||
- mods[1] = NULL;
|
||||
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- /*
|
||||
- * The entry already has the expected memberOf value, no
|
||||
- * problem just return success.
|
||||
- */
|
||||
- rc = LDAP_SUCCESS;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4461,43 +4425,57 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
|
||||
Slapi_PBlock *mod_pb = NULL;
|
||||
int added_oc = 0;
|
||||
int rc = 0;
|
||||
+ LDAPMod *single_mod[2];
|
||||
|
||||
- while (1) {
|
||||
- mod_pb = slapi_pblock_new();
|
||||
- slapi_modify_internal_set_pb(
|
||||
- mod_pb, dn, mods, 0, 0,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
-
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
- if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
- if (!add_oc || added_oc) {
|
||||
- /*
|
||||
- * We aren't auto adding an objectclass, or we already
|
||||
- * added the objectclass, and we are still failing.
|
||||
- */
|
||||
+ if (!dn || !mods) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Invalid argument: %s%s is NULL\n",
|
||||
+ !dn ? "dn " : "",
|
||||
+ !mods ? "mods " : "");
|
||||
+ return LDAP_PARAM_ERROR;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ mod_pb = slapi_pblock_new();
|
||||
+ /* Split multiple mods into individual mod operations */
|
||||
+ for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
|
||||
+ single_mod[0] = mods[i];
|
||||
+ single_mod[1] = NULL;
|
||||
+
|
||||
+ while (1) {
|
||||
+ slapi_pblock_init(mod_pb);
|
||||
+
|
||||
+ /* Internal mod with error overrides for DEL/ADD */
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_normdn_byref(dn), single_mod,
|
||||
+ memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
+ if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
+ if (!add_oc || added_oc) {
|
||||
+ /*
|
||||
+ * We aren't auto adding an objectclass, or we already
|
||||
+ * added the objectclass, and we are still failing.
|
||||
+ */
|
||||
+ break;
|
||||
+ }
|
||||
+ rc = memberof_add_objectclass(add_oc, dn);
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Entry %s - schema violation caught - repair operation %s\n",
|
||||
+ dn ? dn : "unknown",
|
||||
+ rc ? "failed" : "succeeded");
|
||||
+ if (rc) {
|
||||
+ /* Failed to add objectclass */
|
||||
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
|
||||
+ break;
|
||||
+ }
|
||||
+ added_oc = 1;
|
||||
+ } else if (rc) {
|
||||
+ /* Some other fatal error */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_add_memberof_attr - Internal modify failed. rc=%d\n", rc);
|
||||
break;
|
||||
- }
|
||||
- rc = memberof_add_objectclass(add_oc, dn);
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "Entry %s - schema violation caught - repair operation %s\n",
|
||||
- dn ? dn : "unknown",
|
||||
- rc ? "failed" : "succeeded");
|
||||
- if (rc) {
|
||||
- /* Failed to add objectclass */
|
||||
- rc = LDAP_OBJECT_CLASS_VIOLATION;
|
||||
+ } else {
|
||||
+ /* success */
|
||||
break;
|
||||
}
|
||||
- added_oc = 1;
|
||||
- slapi_pblock_destroy(mod_pb);
|
||||
- } else if (rc) {
|
||||
- /* Some other fatal error */
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_add_memberof_attr - Internal modify failed. rc=%d\n", rc);
|
||||
- break;
|
||||
- } else {
|
||||
- /* success */
|
||||
- break;
|
||||
}
|
||||
}
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
|
||||
index 218863ea5..a2f2e4706 100644
|
||||
--- a/ldap/servers/plugins/referint/referint.c
|
||||
+++ b/ldap/servers/plugins/referint/referint.c
|
||||
@@ -712,19 +712,28 @@ static int
|
||||
_do_modify(Slapi_PBlock *mod_pb, Slapi_DN *entrySDN, LDAPMod **mods)
|
||||
{
|
||||
int rc = 0;
|
||||
+ LDAPMod *mod[2];
|
||||
|
||||
- slapi_pblock_init(mod_pb);
|
||||
+ /* Split multiple modifications into individual modify operations */
|
||||
+ for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
|
||||
+ mod[0] = mods[i];
|
||||
+ mod[1] = NULL;
|
||||
|
||||
- if (allow_repl) {
|
||||
- /* Must set as a replicated operation */
|
||||
- slapi_modify_internal_set_pb_ext(mod_pb, entrySDN, mods, NULL, NULL,
|
||||
- referint_plugin_identity, OP_FLAG_REPLICATED);
|
||||
- } else {
|
||||
- slapi_modify_internal_set_pb_ext(mod_pb, entrySDN, mods, NULL, NULL,
|
||||
- referint_plugin_identity, 0);
|
||||
+ slapi_pblock_init(mod_pb);
|
||||
+
|
||||
+ /* Do a single mod with error overrides for DEL/ADD */
|
||||
+ if (allow_repl) {
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
|
||||
+ referint_plugin_identity, OP_FLAG_REPLICATED);
|
||||
+ } else {
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
|
||||
+ referint_plugin_identity, 0);
|
||||
+ }
|
||||
+
|
||||
+ if (rc != LDAP_SUCCESS) {
|
||||
+ return rc;
|
||||
+ }
|
||||
}
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -924,7 +933,6 @@ _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;
|
||||
@@ -1027,30 +1035,21 @@ _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);
|
||||
- /* 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);
|
||||
- }
|
||||
+ 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);
|
||||
- /* 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_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) {
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index fb65d58b3..58b606b2e 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -489,6 +489,57 @@ slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_IDENTITY, plugin_identity);
|
||||
}
|
||||
|
||||
+/* Performs a single LDAP modify operation with error overrides.
|
||||
+ *
|
||||
+ * If specific errors occur, such as attempting to add an existing attribute or
|
||||
+ * delete a non-existent one, the function overrides the error and returns success:
|
||||
+ * - LDAP_MOD_ADD -> LDAP_TYPE_OR_VALUE_EXISTS (ignored)
|
||||
+ * - LDAP_MOD_DELETE -> LDAP_NO_SUCH_ATTRIBUTE (ignored)
|
||||
+ *
|
||||
+ * Any other errors encountered during the operation will be returned as-is.
|
||||
+ */
|
||||
+int
|
||||
+slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_id, int op_flags)
|
||||
+{
|
||||
+ int rc = 0;
|
||||
+ int result = 0;
|
||||
+ int result_reset = 0;
|
||||
+ int mod_op = 0;
|
||||
+
|
||||
+ if (!pb || !sdn || !mod || !mod[0]) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "slapi_single_modify_internal_override",
|
||||
+ "Invalid argument: %s%s%s%s is NULL\n",
|
||||
+ !pb ? "pb " : "",
|
||||
+ !sdn ? "sdn " : "",
|
||||
+ !mod ? "mod " : "",
|
||||
+ !mod[0] ? "mod[0] " : "");
|
||||
+
|
||||
+ return LDAP_PARAM_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ slapi_modify_internal_set_pb_ext(pb, sdn, mod, NULL, NULL, plugin_id, op_flags);
|
||||
+ slapi_modify_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
+ mod_op = mod[0]->mod_op & LDAP_MOD_OP;
|
||||
+ if ((mod_op == LDAP_MOD_ADD && result == LDAP_TYPE_OR_VALUE_EXISTS) ||
|
||||
+ (mod_op == LDAP_MOD_DELETE && result == LDAP_NO_SUCH_ATTRIBUTE)) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_single_modify_internal_override",
|
||||
+ "Overriding return code - plugin:%s dn:%s mod_op:%d result:%d\n",
|
||||
+ plugin_id ? plugin_id->sci_component_name : "unknown",
|
||||
+ sdn ? sdn->udn : "unknown", mod_op, result);
|
||||
+
|
||||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_INTOP_RESULT, &result_reset);
|
||||
+ rc = LDAP_SUCCESS;
|
||||
+ } else {
|
||||
+ rc = result;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
/* Helper functions */
|
||||
|
||||
static int
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 9bf791b56..03dccf5f7 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -5955,6 +5955,7 @@ void slapi_add_entry_internal_set_pb(Slapi_PBlock *pb, Slapi_Entry *e, LDAPContr
|
||||
int slapi_add_internal_set_pb(Slapi_PBlock *pb, const char *dn, LDAPMod **attrs, LDAPControl **controls, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
void slapi_modify_internal_set_pb(Slapi_PBlock *pb, const char *dn, LDAPMod **mods, LDAPControl **controls, const char *uniqueid, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
void slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mods, LDAPControl **controls, const char *uniqueid, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
+int slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
/**
|
||||
* Set \c Slapi_PBlock to perform modrdn/rename internally
|
||||
*
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,635 +0,0 @@
|
||||
From 93d7d611a34c152924735962094a8b1c5a2c4dfe Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 27 Sep 2024 17:06:45 +0200
|
||||
Subject: [PATCH] Issue 6064 - bdb2mdb shows errors (#6341)
|
||||
|
||||
Fix several issues with dsctl dblib migration tools:
|
||||
Fix some problem related to dbscan -D bdb --import:
|
||||
|
||||
random crash when closing the database - Fixed by fully cleaning the bdb framework if open in read-write mode
|
||||
random records were missing - Fixed by fully starting the bdb framework when it is open in read-write mode
|
||||
and by using txn
|
||||
Fixed error messages displayed when trying to reopen ldap connection on restarted instance by not reopening the connection that are not needed.
|
||||
Fixed error messages displayed when there is no replication changelog by detecting the presence of replication changelog before trying to export it.
|
||||
Issue: #6064
|
||||
|
||||
Reviewed by: @tbordaz (Thans!)
|
||||
|
||||
(cherry picked from commit c1b842eafc63e0c59128369a7a02ecbb6a07a8e4)
|
||||
---
|
||||
.../tests/suites/clu/dsctl_dblib_test.py | 43 +++++--
|
||||
.../slapd/back-ldbm/db-bdb/bdb_layer.c | 80 +++++++++----
|
||||
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/dbimpl.c | 8 +-
|
||||
ldap/servers/slapd/back-ldbm/dblayer.h | 2 +-
|
||||
ldap/servers/slapd/tools/dbscan.c | 26 +++-
|
||||
src/lib389/lib389/cli_ctl/dblib.py | 111 +++++++++++-------
|
||||
7 files changed, 183 insertions(+), 89 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/clu/dsctl_dblib_test.py b/dirsrvtests/tests/suites/clu/dsctl_dblib_test.py
|
||||
index 44316d2d7..e30cabced 100644
|
||||
--- a/dirsrvtests/tests/suites/clu/dsctl_dblib_test.py
|
||||
+++ b/dirsrvtests/tests/suites/clu/dsctl_dblib_test.py
|
||||
@@ -10,22 +10,28 @@ import logging
|
||||
import pytest
|
||||
import ldap
|
||||
import os
|
||||
+import time
|
||||
from lib389._constants import DEFAULT_SUFFIX
|
||||
from lib389.backend import DatabaseConfig
|
||||
from lib389.cli_ctl.dblib import (FakeArgs, dblib_bdb2mdb, dblib_mdb2bdb, dblib_cleanup)
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.replica import ReplicationManager
|
||||
-from lib389.topologies import topology_m2 as topo_m2
|
||||
+from lib389.topologies import topology_m2 as topo_m2, topology_st as topo_st
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
-@pytest.fixture
|
||||
-def init_user(topo_m2, request):
|
||||
+@pytest.fixture(scope='function', params=['topo_st','topo_m2'])
|
||||
+def init_user(topo_m2, topo_st, request):
|
||||
"""Initialize a user - Delete and re-add test user
|
||||
"""
|
||||
- s1 = topo_m2.ms["supplier1"]
|
||||
+ if request.param == 'topo_st':
|
||||
+ s1 = topo_st.standalone
|
||||
+ s2 = None
|
||||
+ else:
|
||||
+ s1 = topo_m2.ms["supplier1"]
|
||||
+ s2 = topo_m2.ms["supplier2"]
|
||||
users = UserAccounts(s1, DEFAULT_SUFFIX)
|
||||
try:
|
||||
user_data = {'uid': 'test entry',
|
||||
@@ -48,6 +54,7 @@ def init_user(topo_m2, request):
|
||||
pass
|
||||
|
||||
request.addfinalizer(fin)
|
||||
+ return (s1, s2)
|
||||
|
||||
|
||||
def _check_db(inst, log, impl):
|
||||
@@ -59,8 +66,10 @@ def _check_db(inst, log, impl):
|
||||
db_files = os.listdir(inst.dbdir)
|
||||
if inst.ds_paths.db_home_dir is not None and inst.ds_paths.db_home_dir != inst.dbdir:
|
||||
db_files.extend(os.listdir(inst.ds_paths.db_home_dir))
|
||||
+ db_files = [ file for file in db_files if not file.startswith('log.00') ]
|
||||
+
|
||||
mdb_list = ['data.mdb', 'INFO.mdb', 'lock.mdb']
|
||||
- bdb_list = ['__db.001', 'DBVERSION', '__db.003', 'userRoot', 'log.0000000001', '__db.002']
|
||||
+ bdb_list = ['__db.001', 'DBVERSION', '__db.003', 'userRoot', '__db.002']
|
||||
mdb_list.sort()
|
||||
bdb_list.sort()
|
||||
db_files = sorted(set(db_files))
|
||||
@@ -77,7 +86,7 @@ def _check_db(inst, log, impl):
|
||||
assert db_files == mdb_list
|
||||
|
||||
|
||||
-def test_dblib_migration(topo_m2, init_user):
|
||||
+def test_dblib_migration(init_user):
|
||||
"""
|
||||
Verify dsctl dblib xxxxxxx sub commands (migration between bdb and lmdb)
|
||||
|
||||
@@ -92,28 +101,36 @@ def test_dblib_migration(topo_m2, init_user):
|
||||
2. Success
|
||||
3. Success
|
||||
"""
|
||||
- s1 = topo_m2.ms["supplier1"]
|
||||
- s2 = topo_m2.ms["supplier2"]
|
||||
+ s1, s2 = init_user
|
||||
db_lib = s1.get_db_lib()
|
||||
- repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+ if s2 is not None:
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
users = UserAccounts(s1, DEFAULT_SUFFIX)
|
||||
assert users.get('test entry')
|
||||
args = FakeArgs({'tmpdir': None})
|
||||
if db_lib == 'bdb':
|
||||
dblib_bdb2mdb(s1, log, args)
|
||||
+ s1.open()
|
||||
dblib_cleanup(s1, log, args)
|
||||
_check_db(s1, log, 'mdb')
|
||||
- repl.test_replication_topology([s1, s2])
|
||||
+ if s2 is not None:
|
||||
+ repl.test_replication_topology([s1, s2])
|
||||
dblib_mdb2bdb(s1, log, args)
|
||||
+ s1.open()
|
||||
dblib_cleanup(s1, log, args)
|
||||
_check_db(s1, log, 'bdb')
|
||||
- repl.test_replication_topology([s1, s2])
|
||||
+ if s2 is not None:
|
||||
+ repl.test_replication_topology([s1, s2])
|
||||
else:
|
||||
dblib_mdb2bdb(s1, log, args)
|
||||
+ s1.open()
|
||||
dblib_cleanup(s1, log, args)
|
||||
_check_db(s1, log, 'bdb')
|
||||
- repl.test_replication_topology([s1, s2])
|
||||
+ if s2 is not None:
|
||||
+ repl.test_replication_topology([s1, s2])
|
||||
dblib_bdb2mdb(s1, log, args)
|
||||
+ s1.open()
|
||||
dblib_cleanup(s1, log, args)
|
||||
_check_db(s1, log, 'mdb')
|
||||
- repl.test_replication_topology([s1, s2])
|
||||
+ if s2 is not None:
|
||||
+ repl.test_replication_topology([s1, s2])
|
||||
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 4b30e8e87..7c3a7dcc9 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
|
||||
@@ -6931,39 +6931,51 @@ bdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_t
|
||||
int flags;
|
||||
int rc;
|
||||
|
||||
- /* Either filename is an existing regular file
|
||||
- * or the "home" directory where txn logs are
|
||||
- */
|
||||
+ slapi_ch_free_string(&conf->bdb_dbhome_directory);
|
||||
+ if (li->li_directory == NULL) {
|
||||
+ /* Either filename is an existing regular file
|
||||
+ * or the "home" directory where txn logs are
|
||||
+ */
|
||||
|
||||
- PL_strncpyz(dbhome, db_filename, MAXPATHLEN);
|
||||
- if (stat(dbhome, &st) == 0) {
|
||||
- if (S_ISDIR(st.st_mode)) {
|
||||
- li->li_directory = slapi_ch_strdup(dbhome);
|
||||
- } else if (S_ISREG(st.st_mode)) {
|
||||
+ PL_strncpyz(dbhome, db_filename, MAXPATHLEN);
|
||||
+ if (stat(dbhome, &st) == 0) {
|
||||
+ if (S_ISDIR(st.st_mode)) {
|
||||
+ li->li_directory = slapi_ch_strdup(dbhome);
|
||||
+ } else if (S_ISREG(st.st_mode)) {
|
||||
+ getdir(dbhome, NULL);
|
||||
+ li->li_directory = slapi_ch_strdup(db_filename);
|
||||
+ getdir(dbhome, NULL);
|
||||
+ } else {
|
||||
+ fprintf(stderr, "bdb_public_private_open: Unable to determine dbhome from %s\n", db_filename);
|
||||
+ return EINVAL;
|
||||
+ }
|
||||
+ } else {
|
||||
getdir(dbhome, NULL);
|
||||
- li->li_directory = slapi_ch_strdup(db_filename);
|
||||
+ li->li_directory = slapi_ch_strdup(dbhome);
|
||||
getdir(dbhome, NULL);
|
||||
- } else {
|
||||
- fprintf(stderr, "bdb_public_private_open: Unable to determine dbhome from %s\n", db_filename);
|
||||
- return EINVAL;
|
||||
+ if (stat(dbhome, &st) || ((st.st_mode & S_IFMT) != S_IFDIR)) {
|
||||
+ fprintf(stderr, "bdb_public_private_open: Unable to determine dbhome from %s\n", db_filename);
|
||||
+ return EINVAL;
|
||||
+ }
|
||||
}
|
||||
+ conf->bdb_dbhome_directory = slapi_ch_strdup(dbhome);
|
||||
} else {
|
||||
- getdir(dbhome, NULL);
|
||||
- li->li_directory = slapi_ch_strdup(dbhome);
|
||||
- getdir(dbhome, NULL);
|
||||
- if (stat(dbhome, &st) || ((st.st_mode & S_IFMT) != S_IFDIR)) {
|
||||
- fprintf(stderr, "bdb_public_private_open: Unable to determine dbhome from %s\n", db_filename);
|
||||
- return EINVAL;
|
||||
+ conf->bdb_dbhome_directory = slapi_ch_strdup(li->li_directory);
|
||||
+ if (strcmp(li->li_directory, db_filename)) {
|
||||
+ getdir(conf->bdb_dbhome_directory, NULL);
|
||||
}
|
||||
}
|
||||
+
|
||||
li->li_config_mutex = PR_NewLock();
|
||||
- conf->bdb_dbhome_directory = slapi_ch_strdup(dbhome);
|
||||
if (rw) {
|
||||
/* Setup a fully transacted environment */
|
||||
priv->dblayer_env = NULL;
|
||||
- conf->bdb_enable_transactions = 0;
|
||||
+ conf->bdb_enable_transactions = 1;
|
||||
conf->bdb_tx_max = 50;
|
||||
rc = bdb_start(li, DBLAYER_NORMAL_MODE);
|
||||
+ if (rc == 0) {
|
||||
+ bdb_env = ((struct bdb_db_env*)(priv->dblayer_env))->bdb_DB_ENV;
|
||||
+ }
|
||||
} else {
|
||||
/* Setup minimal environment */
|
||||
rc = db_env_create(&bdb_env, 0);
|
||||
@@ -6994,18 +7006,36 @@ bdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_t
|
||||
}
|
||||
|
||||
int
|
||||
-bdb_public_private_close(dbi_env_t **env, dbi_db_t **db)
|
||||
+bdb_public_private_close(struct ldbminfo *li, dbi_env_t **env, dbi_db_t **db)
|
||||
{
|
||||
DB_ENV *bdb_env = *env;
|
||||
DB *bdb_db = *db;
|
||||
int rc = 0;
|
||||
+ int rw = 0;
|
||||
+ dblayer_private *priv = li->li_dblayer_private;
|
||||
+ bdb_config *conf = (bdb_config *)li->li_dblayer_config;
|
||||
|
||||
- if (bdb_db) {
|
||||
- rc = bdb_db->close(bdb_db, 0);
|
||||
+ if (priv) {
|
||||
+ /* Detect if db is fully set up in read write mode */
|
||||
+ bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env;
|
||||
+ if (pEnv && pEnv->bdb_thread_count>0) {
|
||||
+ rw = 1;
|
||||
+ }
|
||||
}
|
||||
- if (bdb_env) {
|
||||
- rc = bdb_env->close(bdb_env, 0);
|
||||
+ if (rw == 0) {
|
||||
+ if (bdb_db) {
|
||||
+ rc = bdb_db->close(bdb_db, 0);
|
||||
+ }
|
||||
+ if (bdb_env) {
|
||||
+ rc = bdb_env->close(bdb_env, 0);
|
||||
+ }
|
||||
+ } else {
|
||||
+ rc = bdb_close(li, DBLAYER_NORMAL_MODE);
|
||||
}
|
||||
+ slapi_ch_free_string(&conf->bdb_dbhome_directory);
|
||||
+ slapi_ch_free_string(&conf->bdb_home_directory);
|
||||
+ slapi_ch_free_string(&conf->bdb_compactdb_time);
|
||||
+ slapi_ch_free_string(&conf->bdb_log_directory);
|
||||
*db = NULL;
|
||||
*env = NULL;
|
||||
return bdb_map_error(__FUNCTION__, rc);
|
||||
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 de4161b0c..3ecc47170 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
@@ -2725,7 +2725,7 @@ dbmdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_
|
||||
|
||||
|
||||
int
|
||||
-dbmdb_public_private_close(dbi_env_t **env, dbi_db_t **db)
|
||||
+dbmdb_public_private_close(struct ldbminfo *li, dbi_env_t **env, dbi_db_t **db)
|
||||
{
|
||||
if (*db)
|
||||
dbmdb_public_db_op(*db, NULL, DBI_OP_CLOSE, NULL, NULL);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/dbimpl.c b/ldap/servers/slapd/back-ldbm/dbimpl.c
|
||||
index 134d06480..f3bf68a9f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/dbimpl.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/dbimpl.c
|
||||
@@ -476,14 +476,12 @@ int dblayer_private_close(Slapi_Backend **be, dbi_env_t **env, dbi_db_t **db)
|
||||
dblayer_private *priv = li->li_dblayer_private;
|
||||
|
||||
if (priv && priv->dblayer_private_close_fn) {
|
||||
- rc = priv->dblayer_private_close_fn(env, db);
|
||||
+ rc = priv->dblayer_private_close_fn(li, env, db);
|
||||
}
|
||||
+ slapi_ch_free_string(&li->li_directory);
|
||||
slapi_ch_free((void**)&li->li_dblayer_private);
|
||||
slapi_ch_free((void**)&li->li_dblayer_config);
|
||||
- if (dblayer_is_lmdb(*be)) {
|
||||
- /* Generate use after free and double free in bdb case */
|
||||
- ldbm_config_destroy(li);
|
||||
- }
|
||||
+ 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/back-ldbm/dblayer.h b/ldap/servers/slapd/back-ldbm/dblayer.h
|
||||
index 0876fccec..61fdf8b82 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/dblayer.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/dblayer.h
|
||||
@@ -113,7 +113,7 @@ typedef int dblayer_dbi_txn_abort_fn_t(dbi_txn_t *txn);
|
||||
typedef int dblayer_get_entries_count_fn_t(dbi_db_t *db, dbi_txn_t *txn, int *count);
|
||||
typedef int dblayer_cursor_get_count_fn_t(dbi_cursor_t *cursor, dbi_recno_t *count);
|
||||
typedef int dblayer_private_open_fn_t(backend *be, const char *db_filename, int rw, dbi_env_t **env, dbi_db_t **db);
|
||||
-typedef int dblayer_private_close_fn_t(dbi_env_t **env, dbi_db_t **db);
|
||||
+typedef int dblayer_private_close_fn_t(struct ldbminfo *li, dbi_env_t **env, dbi_db_t **db);
|
||||
typedef int ldbm_back_wire_import_fn_t(Slapi_PBlock *pb);
|
||||
typedef int dblayer_restore_file_init_fn_t(struct ldbminfo *li);
|
||||
typedef void dblayer_restore_file_update_fn_t(struct ldbminfo *li, const char *directory);
|
||||
diff --git a/ldap/servers/slapd/tools/dbscan.c b/ldap/servers/slapd/tools/dbscan.c
|
||||
index 9260c1532..1c55bc4ad 100644
|
||||
--- a/ldap/servers/slapd/tools/dbscan.c
|
||||
+++ b/ldap/servers/slapd/tools/dbscan.c
|
||||
@@ -1162,6 +1162,7 @@ importdb(const char *dbimpl_name, const char *filename, const char *dump_name)
|
||||
dbi_db_t *db = NULL;
|
||||
int keyword = 0;
|
||||
int ret = 0;
|
||||
+ int count = 0;
|
||||
|
||||
if (dump_name == NULL) {
|
||||
printf("Error: dump_name can not be NULL\n");
|
||||
@@ -1183,12 +1184,35 @@ importdb(const char *dbimpl_name, const char *filename, const char *dump_name)
|
||||
return 1;
|
||||
}
|
||||
|
||||
+ ret = dblayer_txn_begin(be, NULL, &txn);
|
||||
+ if (ret != 0) {
|
||||
+ printf("Error: failed to start the database txn. Error %d: %s\n", ret, dblayer_strerror(ret));
|
||||
+ }
|
||||
while (ret == 0 &&
|
||||
!_read_line(dump, &keyword, &key) && keyword == 'k' &&
|
||||
!_read_line(dump, &keyword, &data) && keyword == 'v') {
|
||||
ret = dblayer_db_op(be, db, txn.txn, DBI_OP_PUT, &key, &data);
|
||||
+ if (ret == 0 && count++ >= 1000) {
|
||||
+ ret = dblayer_txn_commit(be, &txn);
|
||||
+ if (ret != 0) {
|
||||
+ printf("Error: failed to commit the database txn. Error %d: %s\n", ret, dblayer_strerror(ret));
|
||||
+ } else {
|
||||
+ count = 0;
|
||||
+ ret = dblayer_txn_begin(be, NULL, &txn);
|
||||
+ if (ret != 0) {
|
||||
+ printf("Error: failed to start the database txn. Error %d: %s\n", ret, dblayer_strerror(ret));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
- if (ret !=0) {
|
||||
+ if (ret == 0) {
|
||||
+ ret = dblayer_txn_commit(be, &txn);
|
||||
+ if (ret != 0) {
|
||||
+ printf("Error: failed to commit the database txn. Error %d: %s\n", ret, dblayer_strerror(ret));
|
||||
+ }
|
||||
+ }
|
||||
+ if (ret != 0) {
|
||||
+ (void) dblayer_txn_abort(be, &txn);
|
||||
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);
|
||||
diff --git a/src/lib389/lib389/cli_ctl/dblib.py b/src/lib389/lib389/cli_ctl/dblib.py
|
||||
index 82f09c70c..ff81f0e19 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/dblib.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/dblib.py
|
||||
@@ -11,11 +11,13 @@
|
||||
#
|
||||
|
||||
import os
|
||||
+import pwd
|
||||
import re
|
||||
import glob
|
||||
import shutil
|
||||
+from pathlib import Path
|
||||
from lib389.dseldif import DSEldif
|
||||
-from lib389._constants import DEFAULT_LMDB_SIZE
|
||||
+from lib389._constants import DN_CONFIG, DEFAULT_LMDB_SIZE
|
||||
from lib389.cli_base import CustomHelpFormatter
|
||||
from lib389.utils import parse_size, format_size
|
||||
import subprocess
|
||||
@@ -32,6 +34,8 @@ MDB_LOCK = "lock.mdb"
|
||||
|
||||
LDBM_DN = "cn=config,cn=ldbm database,cn=plugins,cn=config"
|
||||
|
||||
+CL5DB='replication_changelog.db'
|
||||
+
|
||||
_log = None
|
||||
|
||||
|
||||
@@ -65,6 +69,7 @@ def get_backends(log, dse, tmpdir):
|
||||
# Lets be sure to keep config backend info
|
||||
ic2ec['config'] = 'config'
|
||||
update_dse = []
|
||||
+ dbis = None
|
||||
for entry in dse._contents:
|
||||
found = re.search(r'^nsslapd-backend: (.*)', entry)
|
||||
if found:
|
||||
@@ -74,22 +79,28 @@ def get_backends(log, dse, tmpdir):
|
||||
dn = found[0][4:]
|
||||
bename = found[1].lower()
|
||||
if not bename in ic2ec:
|
||||
- # Not a mapping tree backend
|
||||
+ # Not a mapping tree backend
|
||||
continue
|
||||
ecbename = ic2ec[bename]
|
||||
suffix = dse.get(dn, "nsslapd-suffix", True)
|
||||
dbdir = dse.get(dn, "nsslapd-directory", True)
|
||||
ecdbdir = dbdir
|
||||
- if dbdir is None and 'config' in res:
|
||||
+ dblib = dse.get(dn, "nsslapd-backend-implement", True)
|
||||
+ if dblib is None and 'config' in res:
|
||||
+ dblib = res['config']['dblib']
|
||||
+ if dbis is None:
|
||||
+ dbis = get_mdb_dbis(dbdir) if dblib == 'mdb' else []
|
||||
+ # in bdb case dbdir should be the database instance directory
|
||||
+ # in mdb case it is the database map file directory
|
||||
+ if 'config' in res and ( dbdir is None or dblib == 'mdb'):
|
||||
dbdir = f"{res['config']['dbdir']}/{bename}"
|
||||
ecdbdir = f"{res['config']['dbdir']}/{ecbename}"
|
||||
# bdb requires nsslapd-directory so lets add it once reading is done.
|
||||
update_dse.append((dn, ecdbdir))
|
||||
- dblib = dse.get(dn, "nsslapd-backend-implement", True)
|
||||
ldifname = f'{tmpdir}/{DBLIB_LDIF_PREFIX}{bename}.ldif'
|
||||
cl5name = f'{tmpdir}/{DBLIB_LDIF_PREFIX}{bename}.cl5.dbtxt'
|
||||
- cl5dbname = f'{dbdir}/replication_changelog.db'
|
||||
- eccl5dbname = f'{ecdbdir}/replication_changelog.db'
|
||||
+ cl5dbname = f'{dbdir}/{CL5DB}'
|
||||
+ eccl5dbname = f'{ecdbdir}/{CL5DB}'
|
||||
dbsize = 0
|
||||
entrysize = 0
|
||||
for f in glob.glob(f'{dbdir}/id2entry.db*'):
|
||||
@@ -99,8 +110,14 @@ def get_backends(log, dse, tmpdir):
|
||||
indexes = dse.get_indexes(bename)
|
||||
# Let estimate the number of dbis: id2entry + 1 per regular index + 2 per vlv index
|
||||
dbi = 1 + len(indexes) + len([index for index in indexes if index.startswith("vlv#")])
|
||||
-
|
||||
- res[bename] = ({
|
||||
+ if dblib == "bdb":
|
||||
+ has_changelog = os.path.isfile(eccl5dbname)
|
||||
+ elif bename in dbis:
|
||||
+ has_changelog = CL5DB in dbis[bename]
|
||||
+ else:
|
||||
+ has_changelog = False
|
||||
+
|
||||
+ res[bename] = {
|
||||
'dn': dn,
|
||||
'bename': bename,
|
||||
'ecbename': ecbename,
|
||||
@@ -116,13 +133,15 @@ def get_backends(log, dse, tmpdir):
|
||||
'dbsize': dbsize,
|
||||
'entrysize': entrysize,
|
||||
'indexes': indexes,
|
||||
+ 'has_changelog': has_changelog,
|
||||
'dbi': dbi
|
||||
- })
|
||||
+ }
|
||||
+
|
||||
# now that we finish reading the dse.ldif we may update it if needed.
|
||||
for dn, dir in update_dse:
|
||||
dse.replace(dn, 'nsslapd-directory', dir)
|
||||
log.debug(f'lib389.cli_ctl.dblib.get_backends returns: {str(res)}')
|
||||
- return res
|
||||
+ return (res, dbis)
|
||||
|
||||
|
||||
def get_mdb_dbis(dbdir):
|
||||
@@ -158,18 +177,13 @@ 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
|
||||
+ if not be['has_changelog']:
|
||||
+ return False
|
||||
try:
|
||||
cl5dbname = be['eccl5dbname'] if dblib == "bdb" else be['cl5dbname']
|
||||
+ _log.info(f'Exporting changelog {cl5dbname} to {be['cl5name']}')
|
||||
run_dbscan(['-D', dblib, '-f', cl5dbname, '-X', be['cl5name']])
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
@@ -180,10 +194,8 @@ def import_changelog(be, dblib):
|
||||
# import backend changelog
|
||||
try:
|
||||
cl5dbname = be['eccl5dbname'] if dblib == "bdb" else be['cl5dbname']
|
||||
- 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']])
|
||||
+ _log.info(f'Importing changelog {cl5dbname} from {be['cl5name']}')
|
||||
+ run_dbscan(['-D', dblib, '-f', cl5dbname, '--import', be['cl5name'], '--do-it'])
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
return False
|
||||
@@ -200,6 +212,15 @@ def set_owner(list, uid, gid):
|
||||
pass
|
||||
|
||||
|
||||
+def get_uid_gid_from_dse(dse):
|
||||
+ # For some reason inst.get_user_uid() always returns dirsrv uid
|
||||
+ # even for non root install.
|
||||
+ # Lets looks directly in the dse.ldif intead
|
||||
+ user = dse.get(DN_CONFIG, 'nsslapd-localuser', single=True)
|
||||
+ pwent = pwd.getpwnam(user)
|
||||
+ return ( pwent.pw_uid, pwent.pw_gid )
|
||||
+
|
||||
+
|
||||
def dblib_bdb2mdb(inst, log, args):
|
||||
global _log
|
||||
_log = log
|
||||
@@ -218,7 +239,8 @@ def dblib_bdb2mdb(inst, log, args):
|
||||
|
||||
inst.stop()
|
||||
dse = DSEldif(inst)
|
||||
- backends = get_backends(log, dse, tmpdir)
|
||||
+ uid, gid = get_uid_gid_from_dse(dse)
|
||||
+ backends,dbis = get_backends(log, dse, tmpdir)
|
||||
dbmapdir = backends['config']['dbdir']
|
||||
dblib = backends['config']['dblib']
|
||||
|
||||
@@ -229,6 +251,8 @@ def dblib_bdb2mdb(inst, log, args):
|
||||
# Remove ldif files and mdb files
|
||||
dblib_cleanup(inst, log, args)
|
||||
|
||||
+ # Check if changelog is present in backends
|
||||
+
|
||||
# Compute the needed space and the lmdb map configuration
|
||||
total_dbsize = 0
|
||||
total_entrysize = 0
|
||||
@@ -258,8 +282,6 @@ def dblib_bdb2mdb(inst, log, args):
|
||||
log.info(f"Required number of dbi is {nbdbis}")
|
||||
|
||||
# Generate the info file (so dbscan could generate the map)
|
||||
- uid = inst.get_user_uid()
|
||||
- gid = inst.get_group_gid()
|
||||
with open(f'{dbmapdir}/{MDB_INFO}', 'w') as f:
|
||||
f.write('LIBVERSION=9025\n')
|
||||
f.write('DATAVERSION=0\n')
|
||||
@@ -346,7 +368,7 @@ def dblib_bdb2mdb(inst, log, args):
|
||||
dbhome=backends["config"]["dbdir"]
|
||||
set_owner(glob.glob(f'{dbhome}/*.mdb'), uid, gid)
|
||||
log.info("Backends importation 100%")
|
||||
- inst.start()
|
||||
+ inst.start(post_open=False)
|
||||
log.info("Migration from Berkeley database to lmdb is done.")
|
||||
|
||||
|
||||
@@ -368,11 +390,11 @@ def dblib_mdb2bdb(inst, log, args):
|
||||
|
||||
inst.stop()
|
||||
dse = DSEldif(inst)
|
||||
- backends = get_backends(log, dse, tmpdir)
|
||||
+ uid, gid = get_uid_gid_from_dse(dse)
|
||||
+ backends,dbis = get_backends(log, dse, tmpdir)
|
||||
dbmapdir = backends['config']['dbdir']
|
||||
dbhome = inst.ds_paths.db_home_dir
|
||||
dblib = backends['config']['dblib']
|
||||
- dbis = get_mdb_dbis(dbmapdir)
|
||||
|
||||
if dblib == "bdb":
|
||||
log.error(f"Instance {inst.serverid} is already configured with bdb.")
|
||||
@@ -421,8 +443,6 @@ def dblib_mdb2bdb(inst, log, args):
|
||||
if 'has_id2entry' not in be:
|
||||
continue
|
||||
total_dbsize += 1
|
||||
- uid = inst.get_user_uid()
|
||||
- gid = inst.get_group_gid()
|
||||
for bename, be in backends.items():
|
||||
# Keep only backend associated with a db
|
||||
if 'has_id2entry' not in be:
|
||||
@@ -467,7 +487,7 @@ def dblib_mdb2bdb(inst, log, args):
|
||||
set_owner((f'{dbhome}/DBVERSION', f'{dbmapdir}/DBVERSION', f'{dbhome}/guardian', '{dbmapdir}/guardian'), uid, gid)
|
||||
|
||||
log.info("Backends importation 100%")
|
||||
- inst.start()
|
||||
+ inst.start(post_open=False)
|
||||
log.info("Migration from ldbm to Berkeley database is done.")
|
||||
|
||||
|
||||
@@ -484,7 +504,7 @@ def dblib_cleanup(inst, log, args):
|
||||
_log = log
|
||||
tmpdir = get_ldif_dir(inst)
|
||||
dse = DSEldif(inst)
|
||||
- backends = get_backends(log, dse, tmpdir)
|
||||
+ backends,dbis = get_backends(log, dse, tmpdir)
|
||||
dbmapdir = backends['config']['dbdir']
|
||||
dbhome = inst.ds_paths.db_home_dir
|
||||
dblib = backends['config']['dblib']
|
||||
@@ -495,14 +515,19 @@ def dblib_cleanup(inst, log, args):
|
||||
# Keep only backend associated with a db
|
||||
if 'has_id2entry' not in be and be['dbsize'] == 0:
|
||||
continue
|
||||
- rm(be['ldifname'])
|
||||
- rm(be['cl5name'])
|
||||
- dbdir = be['dbdir']
|
||||
- if dblib == "mdb":
|
||||
- # remove db dir
|
||||
- for f in glob.glob(f'{dbdir}/*.db*'):
|
||||
+ # rm(be['ldifname'])
|
||||
+ # rm(be['cl5name'])
|
||||
+
|
||||
+ if dblib == "mdb":
|
||||
+ # Looks for bdb database instance subdirectories
|
||||
+ for id2entry in glob.glob(f'{dbmapdir}/*/id2entry.db*'):
|
||||
+ dbdir = Path(id2entry).parent.absolute()
|
||||
+ log.info(f"cleanup removing {dbdir}")
|
||||
+ for f in glob.iglob(f'{dbdir}/*.db*'):
|
||||
rm(f)
|
||||
- rm(f'{dbdir}/DBVERSION')
|
||||
+ for f in [ 'guardian', 'DBVERSION' ]:
|
||||
+ if os.path.isfile(f'{dbdir}/{f}'):
|
||||
+ rm(f'{dbdir}/{f}')
|
||||
os.rmdir(dbdir)
|
||||
|
||||
if dblib == "bdb":
|
||||
@@ -510,13 +535,13 @@ def dblib_cleanup(inst, log, args):
|
||||
rm(f'{dbmapdir}/data.mdb')
|
||||
rm(f'{dbmapdir}/lock.mdb')
|
||||
else:
|
||||
- for f in glob.glob(f'{dbhome}/__db.*'):
|
||||
+ for f in glob.iglob(f'{dbhome}/__db.*'):
|
||||
rm(f)
|
||||
- for f in glob.glob(f'{dbmapdir}/__db.*'):
|
||||
+ for f in glob.iglob(f'{dbmapdir}/__db.*'):
|
||||
rm(f)
|
||||
- for f in glob.glob(f'{dbhome}/log.*'):
|
||||
+ for f in glob.iglob(f'{dbhome}/log.*'):
|
||||
rm(f)
|
||||
- for f in glob.glob(f'{dbmapdir}/log.*'):
|
||||
+ for f in glob.iglob(f'{dbmapdir}/log.*'):
|
||||
rm(f)
|
||||
rm(f'{dbhome}/DBVERSION')
|
||||
rm(f'{dbmapdir}/DBVERSION')
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,533 +0,0 @@
|
||||
From 63f6237b35cc1271989787247a9fe1b58518eb27 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Thu, 27 Mar 2025 12:02:19 +0100
|
||||
Subject: [PATCH] Issue 6693 - Fix error messages inconsistencies (#6694)
|
||||
|
||||
Fix missing function names and missing new lines in error message.
|
||||
Sometime the newline was moved back from message arguments to the message
|
||||
to have a better consistency and to avoid that the tool detects a false positive
|
||||
|
||||
Issue: #6693
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
|
||||
(cherry picked from commit b550579365b2dfd2ea0b057dea980111973aff12)
|
||||
---
|
||||
.../plugins/replication/repl5_inc_protocol.c | 2 +-
|
||||
.../slapd/back-ldbm/db-bdb/bdb_config.c | 4 +-
|
||||
.../back-ldbm/db-bdb/bdb_import_threads.c | 4 +-
|
||||
.../servers/slapd/back-ldbm/db-bdb/bdb_misc.c | 8 ++--
|
||||
ldap/servers/slapd/back-ldbm/idl.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_add.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_usn.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/vlv.c | 2 +-
|
||||
ldap/servers/slapd/daemon.c | 2 +-
|
||||
ldap/servers/slapd/entry.c | 4 +-
|
||||
ldap/servers/slapd/filterentry.c | 2 +-
|
||||
ldap/servers/slapd/ldaputil.c | 2 +-
|
||||
ldap/servers/slapd/libglobs.c | 4 +-
|
||||
ldap/servers/slapd/log.c | 2 +-
|
||||
ldap/servers/slapd/modrdn.c | 4 +-
|
||||
ldap/servers/slapd/passwd_extop.c | 48 +++++++++----------
|
||||
ldap/servers/slapd/pw.c | 4 +-
|
||||
ldap/servers/slapd/schema.c | 2 +-
|
||||
ldap/servers/slapd/util.c | 2 +-
|
||||
19 files changed, 51 insertions(+), 51 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c
|
||||
index 68d93e1b0..41a20c370 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_inc_protocol.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c
|
||||
@@ -1724,7 +1724,7 @@ send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *nu
|
||||
} else {
|
||||
agmt_inc_last_update_changecount(prp->agmt, csn_get_replicaid(entry.op->csn), 1 /*skipped*/);
|
||||
}
|
||||
- slapi_log_err(finished ? SLAPI_LOG_WARNING : slapi_log_urp,
|
||||
+ slapi_log_err(finished ? SLAPI_LOG_WARNING : slapi_log_urp, repl_plugin_name,
|
||||
"send_updates - %s: Failed to send update operation to receiver (uniqueid %s, CSN %s): %s. %s.\n",
|
||||
(char *)agmt_get_long_name(prp->agmt),
|
||||
entry.op->target_address.uniqueid, csn_str,
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
|
||||
index 8f847f418..a1d6c6af1 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
|
||||
@@ -284,7 +284,7 @@ bdb_config_db_lock_pause_set(void *arg, void *value, char *errorbuf, int phase _
|
||||
|
||||
if (val == 0) {
|
||||
slapi_log_err(SLAPI_LOG_NOTICE, "bdb_config_db_lock_pause_set",
|
||||
- "%s was set to '0'. The default value will be used (%s)",
|
||||
+ "%s was set to '0'. The default value will be used (%s)\n",
|
||||
CONFIG_DB_LOCKS_PAUSE, DEFAULT_DBLOCK_PAUSE_STR);
|
||||
val = DEFAULT_DBLOCK_PAUSE;
|
||||
}
|
||||
@@ -315,7 +315,7 @@ bdb_config_db_lock_threshold_set(void *arg, void *value, char *errorbuf, int pha
|
||||
"%s: \"%d\" is invalid, threshold is indicated as a percentage and it must lie in range of 70 and 95",
|
||||
CONFIG_DB_LOCKS_THRESHOLD, val);
|
||||
slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_lock_threshold_set",
|
||||
- "%s: \"%d\" is invalid, threshold is indicated as a percentage and it must lie in range of 70 and 95",
|
||||
+ "%s: \"%d\" is invalid, threshold is indicated as a percentage and it must lie in range of 70 and 95\n",
|
||||
CONFIG_DB_LOCKS_THRESHOLD, val);
|
||||
retval = LDAP_OPERATIONS_ERROR;
|
||||
return retval;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
|
||||
index ae6cd37da..762267dd9 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
|
||||
@@ -1766,7 +1766,7 @@ bdb_upgradedn_producer(void *param)
|
||||
inst->inst_name, dn_id);
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_ERR, "bdb_upgradedn_producer",
|
||||
- "%s: Error: failed to write a line \"%s\"",
|
||||
+ "%s: Error: failed to write a line \"%s\"\n",
|
||||
inst->inst_name, dn_id);
|
||||
slapi_ch_free_string(&dn_id);
|
||||
goto error;
|
||||
@@ -3723,7 +3723,7 @@ bdb_dse_conf_verify_core(struct ldbminfo *li, char *src_dir, char *file_name, ch
|
||||
slapi_ch_free_string(&estr);
|
||||
if (!e) {
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "bdb_dse_conf_verify_core",
|
||||
- "Skipping bad LDIF entry ending line %d of file \"%s\"",
|
||||
+ "Skipping bad LDIF entry ending line %d of file \"%s\"\n",
|
||||
curr_lineno, filename);
|
||||
continue;
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
|
||||
index eeafbf995..4cea7b879 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_misc.c
|
||||
@@ -192,13 +192,13 @@ bdb_start_autotune(struct ldbminfo *li)
|
||||
/* First, set our message. In the case autosize is 0, we calculate some
|
||||
* sane defaults and populate these values, but it's only on first run.
|
||||
*/
|
||||
- msg = "This can be corrected by altering the values of nsslapd-dbcachesize, nsslapd-cachememsize and nsslapd-dncachememsize\n";
|
||||
+ msg = "This can be corrected by altering the values of nsslapd-dbcachesize, nsslapd-cachememsize and nsslapd-dncachememsize";
|
||||
autosize_percentage = 25;
|
||||
} else {
|
||||
/* In this case we really are setting the values each start up, so
|
||||
* change the msg.
|
||||
*/
|
||||
- msg = "This can be corrected by altering the values of nsslapd-cache-autosize, nsslapd-cache-autosize-split and nsslapd-dncachememsize\n";
|
||||
+ msg = "This can be corrected by altering the values of nsslapd-cache-autosize, nsslapd-cache-autosize-split and nsslapd-dncachememsize";
|
||||
autosize_percentage = li->li_cache_autosize;
|
||||
}
|
||||
/* Has to be less than 0, 0 means to disable I think */
|
||||
@@ -240,7 +240,7 @@ bdb_start_autotune(struct ldbminfo *li)
|
||||
issane = util_is_cachesize_sane(mi, &zone_size);
|
||||
if (issane == UTIL_CACHESIZE_REDUCED) {
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "Your autosized cache values have been reduced. Likely your nsslapd-cache-autosize percentage is too high.\n");
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "%s", msg);
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "%s\n", msg);
|
||||
}
|
||||
/* It's valid, lets divide it up and set according to user prefs */
|
||||
db_size = (autosize_db_percentage_split * zone_size) / 100;
|
||||
@@ -382,7 +382,7 @@ bdb_start_autotune(struct ldbminfo *li)
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "In a future release this WILL prevent server start up. You MUST alter your configuration.\n");
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "Total entry cache size: %" PRIu64 " B; dbcache size: %" PRIu64 " B; available memory size: %" PRIu64 " B; \n",
|
||||
total_cache_size, (uint64_t)li->li_dbcachesize, mi->system_available_bytes);
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "%s", msg);
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, "bdb_start_autotune", "%s\n", msg);
|
||||
/* WB 2016 - This should be UNCOMMENTED in a future release */
|
||||
/* return SLAPI_FAIL_GENERAL; */
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl.c b/ldap/servers/slapd/back-ldbm/idl.c
|
||||
index f690827b5..5574f840a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl.c
|
||||
@@ -1370,7 +1370,7 @@ idl_old_delete_key(
|
||||
if ((idl = idl_fetch_one(be, db, key, txn, &rc)) == NULL) {
|
||||
idl_unlock_list(a->ai_idl, key);
|
||||
if (rc != 0 && rc != DBI_RC_NOTFOUND && rc != DBI_RC_RETRY) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "idl_old_delete_key - (%s) 0 BAD %d %s\n",
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "idl_old_delete_key", "(%s) 0 BAD %d %s\n",
|
||||
(char *)key->dptr, rc, (msg = dblayer_strerror(rc)) ? msg : "");
|
||||
}
|
||||
if (0 == rc || DBI_RC_NOTFOUND == rc)
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
index dec3a0c6d..42454c890 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
@@ -637,7 +637,7 @@ ldbm_back_add(Slapi_PBlock *pb)
|
||||
if ((addingentry->ep_id = next_id(be)) >= MAXID) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "ldbm_back_add ",
|
||||
"Maximum ID reached, cannot add entry to "
|
||||
- "backend '%s'",
|
||||
+ "backend '%s'\n",
|
||||
be->be_name);
|
||||
ldap_result_code = LDAP_OPERATIONS_ERROR;
|
||||
goto error_return;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_usn.c b/ldap/servers/slapd/back-ldbm/ldbm_usn.c
|
||||
index d002e3e4f..fd4e264fe 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_usn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_usn.c
|
||||
@@ -123,7 +123,7 @@ usn_get_last_usn(Slapi_Backend *be, PRUint64 *last_usn)
|
||||
rc = dblayer_new_cursor(be, db, NULL, &dbc);
|
||||
if (0 != rc) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "usn_get_last_usn",
|
||||
- "Failed to create a cursor: %d", rc);
|
||||
+ "Failed to create a cursor: %d\n", rc);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
index 40ca5010d..18431799c 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
@@ -1670,7 +1670,7 @@ vlv_trim_candidates_byvalue(backend *be, const IDList *candidates, const sort_sp
|
||||
slapi_attr_values2keys(&sort_control->sattr, invalue, &typedown_value, LDAP_FILTER_EQUALITY); /* JCM SLOW FUNCTION */
|
||||
if (compare_fn == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "vlv_trim_candidates_byvalue",
|
||||
- "Attempt to compare an unordered attribute");
|
||||
+ "Attempt to compare an unordered attribute\n");
|
||||
compare_fn = slapi_berval_cmp;
|
||||
}
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 76efd08d5..5d01a2526 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -582,7 +582,7 @@ disk_monitoring_thread(void *nothing __attribute__((unused)))
|
||||
{
|
||||
if (be_list_count == BE_LIST_SIZE) { /* error - too many backends */
|
||||
slapi_log_err(SLAPI_LOG_ERR, "disk_monitoring_thread",
|
||||
- "Too many backends match search request - cannot proceed");
|
||||
+ "Too many backends match search request - cannot proceed\n");
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_ALERT, "disk_monitoring_thread",
|
||||
"Putting the backend '%s' to read-only mode\n", be->be_name);
|
||||
diff --git a/ldap/servers/slapd/entry.c b/ldap/servers/slapd/entry.c
|
||||
index 658b9b279..235410e45 100644
|
||||
--- a/ldap/servers/slapd/entry.c
|
||||
+++ b/ldap/servers/slapd/entry.c
|
||||
@@ -887,8 +887,8 @@ str2entry_dupcheck(const char *rawdn, const char *s, int flags, int read_statein
|
||||
if (strcasecmp(type, "dn") == 0) {
|
||||
if (slapi_entry_get_dn_const(e) != NULL) {
|
||||
char ebuf[BUFSIZ];
|
||||
- slapi_log_err(SLAPI_LOG_TRACE, "str2entry_dupcheck"
|
||||
- "Entry has multiple dns \"%s\" and \"%s\" (second ignored)\n",
|
||||
+ slapi_log_err(SLAPI_LOG_TRACE, "str2entry_dupcheck",
|
||||
+ "Entry has multiple dns \"%s\" and \"%s\" (second ignored)\n",
|
||||
(char *)slapi_entry_get_dn_const(e),
|
||||
escape_string(valuecharptr, ebuf));
|
||||
/* the memory below was not allocated by the slapi_ch_ functions */
|
||||
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
|
||||
index f5604161d..c3737eb9e 100644
|
||||
--- a/ldap/servers/slapd/filterentry.c
|
||||
+++ b/ldap/servers/slapd/filterentry.c
|
||||
@@ -828,7 +828,7 @@ slapi_vattr_filter_test_ext(
|
||||
|
||||
if (only_check_access != 0) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "slapi_vattr_filter_test_ext",
|
||||
- "⚠️ DANGER ⚠️ - only_check_access mode is BROKEN!!! YOU MUST CHECK ACCESS WITH FILTER MATCHING");
|
||||
+ "⚠️ DANGER ⚠️ - only_check_access mode is BROKEN!!! YOU MUST CHECK ACCESS WITH FILTER MATCHING\n");
|
||||
}
|
||||
PR_ASSERT(only_check_access == 0);
|
||||
|
||||
diff --git a/ldap/servers/slapd/ldaputil.c b/ldap/servers/slapd/ldaputil.c
|
||||
index 3910bdf7f..e59d22893 100644
|
||||
--- a/ldap/servers/slapd/ldaputil.c
|
||||
+++ b/ldap/servers/slapd/ldaputil.c
|
||||
@@ -1305,7 +1305,7 @@ slapi_add_auth_response_control(Slapi_PBlock *pb, const char *binddn)
|
||||
|
||||
if (slapi_pblock_set(pb, SLAPI_ADD_RESCONTROL, &arctrl) != 0) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "slapi_add_auth_response_control",
|
||||
- "Unable to add authentication response control");
|
||||
+ "Unable to add authentication response control\n");
|
||||
}
|
||||
|
||||
if (NULL != dnbuf_dynamic) {
|
||||
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
|
||||
index 3f1fa586e..b78a69842 100644
|
||||
--- a/ldap/servers/slapd/libglobs.c
|
||||
+++ b/ldap/servers/slapd/libglobs.c
|
||||
@@ -1750,13 +1750,13 @@ FrontendConfig_init(void)
|
||||
/* initialize the read/write configuration lock */
|
||||
if ((cfg->cfg_rwlock = slapi_new_rwlock()) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_EMERG, "FrontendConfig_init",
|
||||
- "Failed to initialize cfg_rwlock. Exiting now.");
|
||||
+ "Failed to initialize cfg_rwlock. Exiting now.\n");
|
||||
exit(-1);
|
||||
}
|
||||
#else
|
||||
if ((cfg->cfg_lock = PR_NewLock()) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_EMERG, "FrontendConfig_init",
|
||||
- "Failed to initialize cfg_lock. Exiting now.");
|
||||
+ "Failed to initialize cfg_lock. Exiting now.\n");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index b5443830d..2d4144d35 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -1245,7 +1245,7 @@ log_set_numlogsperdir(const char *attrname, char *numlogs_str, int logtype, char
|
||||
default:
|
||||
rv = LDAP_OPERATIONS_ERROR;
|
||||
slapi_log_err(SLAPI_LOG_ERR, "log_set_numlogsperdir",
|
||||
- "Invalid log type %d", logtype);
|
||||
+ "Invalid log type %d\n", logtype);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
diff --git a/ldap/servers/slapd/modrdn.c b/ldap/servers/slapd/modrdn.c
|
||||
index 3a658b1e3..4a75083c5 100644
|
||||
--- a/ldap/servers/slapd/modrdn.c
|
||||
+++ b/ldap/servers/slapd/modrdn.c
|
||||
@@ -540,13 +540,13 @@ op_shared_rename(Slapi_PBlock *pb, int passin_args)
|
||||
"Syntax check of newSuperior failed\n");
|
||||
if (!internal_op) {
|
||||
slapi_log_err(SLAPI_LOG_ARGS, "op_shared_rename",
|
||||
- "conn=%" PRIu64 " op=%d MODRDN invalid new superior (\"%s\")",
|
||||
+ "conn=%" PRIu64 " op=%d MODRDN invalid new superior (\"%s\")\n",
|
||||
pb_conn->c_connid,
|
||||
operation->o_opid,
|
||||
newsuperior ? newsuperior : "(null)");
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_ARGS, "op_shared_rename",
|
||||
- "conn=%s op=%d MODRDN invalid new superior (\"%s\")",
|
||||
+ "conn=%s op=%d MODRDN invalid new superior (\"%s\")\n",
|
||||
LOG_INTERNAL_OP_CON_ID,
|
||||
LOG_INTERNAL_OP_OP_ID,
|
||||
newsuperior ? newsuperior : "(null)");
|
||||
diff --git a/ldap/servers/slapd/passwd_extop.c b/ldap/servers/slapd/passwd_extop.c
|
||||
index 8fad2ee96..f758ac018 100644
|
||||
--- a/ldap/servers/slapd/passwd_extop.c
|
||||
+++ b/ldap/servers/slapd/passwd_extop.c
|
||||
@@ -470,10 +470,10 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
* match this very plugin's OID: EXTOP_PASSWD_OID. */
|
||||
slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &oid);
|
||||
if (oid == NULL) {
|
||||
- errMesg = "Could not get OID value from request.\n";
|
||||
+ errMesg = "Could not get OID value from request.";
|
||||
rc = LDAP_OPERATIONS_ERROR;
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop",
|
||||
- "%s", errMesg);
|
||||
+ "%s\n", errMesg);
|
||||
goto free_and_return;
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop",
|
||||
@@ -481,7 +481,7 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
}
|
||||
|
||||
if (strcasecmp(oid, EXTOP_PASSWD_OID) != 0) {
|
||||
- errMesg = "Request OID does not match Passwd OID.\n";
|
||||
+ errMesg = "Request OID does not match Passwd OID.";
|
||||
rc = LDAP_OPERATIONS_ERROR;
|
||||
goto free_and_return;
|
||||
} else {
|
||||
@@ -500,24 +500,24 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
goto free_and_return;
|
||||
}
|
||||
if (slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) {
|
||||
- errMesg = "Could not get SASL SSF from connection\n";
|
||||
+ errMesg = "Could not get SASL SSF from connection";
|
||||
rc = LDAP_OPERATIONS_ERROR;
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop",
|
||||
- "%s", errMesg);
|
||||
+ "%s\n", errMesg);
|
||||
goto free_and_return;
|
||||
}
|
||||
|
||||
if (slapi_pblock_get(pb, SLAPI_CONN_LOCAL_SSF, &local_ssf) != 0) {
|
||||
- errMesg = "Could not get local SSF from connection\n";
|
||||
+ errMesg = "Could not get local SSF from connection";
|
||||
rc = LDAP_OPERATIONS_ERROR;
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop",
|
||||
- "%s", errMesg);
|
||||
+ "%s\n", errMesg);
|
||||
goto free_and_return;
|
||||
}
|
||||
|
||||
if (((conn->c_flags & CONN_FLAG_SSL) != CONN_FLAG_SSL) &&
|
||||
(sasl_ssf <= 1) && (local_ssf <= 1)) {
|
||||
- errMesg = "Operation requires a secure connection.\n";
|
||||
+ errMesg = "Operation requires a secure connection.";
|
||||
rc = LDAP_CONFIDENTIALITY_REQUIRED;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -536,7 +536,7 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
}
|
||||
|
||||
if ((ber = ber_init(extop_value)) == NULL) {
|
||||
- errMesg = "PasswdModify Request decode failed.\n";
|
||||
+ errMesg = "PasswdModify Request decode failed.";
|
||||
rc = LDAP_PROTOCOL_ERROR;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -571,7 +571,7 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
if (ber_scanf(ber, "a", &rawdn) == LBER_ERROR) {
|
||||
slapi_ch_free_string(&rawdn);
|
||||
slapi_log_err(SLAPI_LOG_ERR, "passwd_modify_extop", "ber_scanf failed :{\n");
|
||||
- errMesg = "ber_scanf failed at userID parse.\n";
|
||||
+ errMesg = "ber_scanf failed at userID parse.";
|
||||
rc = LDAP_PROTOCOL_ERROR;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -583,7 +583,7 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
if (rc) { /* syntax check failed */
|
||||
op_shared_log_error_access(pb, "EXT", rawdn ? rawdn : "",
|
||||
"strict: invalid target dn");
|
||||
- errMesg = "invalid target dn.\n";
|
||||
+ errMesg = "invalid target dn.";
|
||||
slapi_ch_free_string(&rawdn);
|
||||
rc = LDAP_INVALID_SYNTAX;
|
||||
goto free_and_return;
|
||||
@@ -597,7 +597,7 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
if (ber_scanf(ber, "a", &oldPasswd) == LBER_ERROR) {
|
||||
slapi_ch_free_string(&oldPasswd);
|
||||
slapi_log_err(SLAPI_LOG_ERR, "passwd_modify_extop", "ber_scanf failed :{\n");
|
||||
- errMesg = "ber_scanf failed at oldPasswd parse.\n";
|
||||
+ errMesg = "ber_scanf failed at oldPasswd parse.";
|
||||
rc = LDAP_PROTOCOL_ERROR;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -609,7 +609,7 @@ passwd_modify_extop(Slapi_PBlock *pb)
|
||||
if (ber_scanf(ber, "a", &newPasswd) == LBER_ERROR) {
|
||||
slapi_ch_free_string(&newPasswd);
|
||||
slapi_log_err(SLAPI_LOG_ERR, "passwd_modify_extop", "ber_scanf failed :{\n");
|
||||
- errMesg = "ber_scanf failed at newPasswd parse.\n";
|
||||
+ errMesg = "ber_scanf failed at newPasswd parse.";
|
||||
rc = LDAP_PROTOCOL_ERROR;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -626,7 +626,7 @@ parse_req_done:
|
||||
/* If the connection is bound anonymously, we must refuse to process this operation. */
|
||||
if (bindDN == NULL || *bindDN == '\0') {
|
||||
/* Refuse the operation because they're bound anonymously */
|
||||
- errMesg = "Anonymous Binds are not allowed.\n";
|
||||
+ errMesg = "Anonymous Binds are not allowed.";
|
||||
rc = LDAP_INSUFFICIENT_ACCESS;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -640,7 +640,7 @@ parse_req_done:
|
||||
dn = slapi_sdn_get_ndn(target_sdn);
|
||||
if (dn == NULL || *dn == '\0') {
|
||||
/* Refuse the operation because they're bound anonymously */
|
||||
- errMesg = "Invalid dn.\n";
|
||||
+ errMesg = "Invalid dn.";
|
||||
rc = LDAP_INVALID_DN_SYNTAX;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -657,7 +657,7 @@ parse_req_done:
|
||||
* the bind operation (or used sasl or client cert auth or OS creds) */
|
||||
slapi_pblock_get(pb, SLAPI_CONN_AUTHMETHOD, &authmethod);
|
||||
if (!authmethod || !strcmp(authmethod, SLAPD_AUTH_NONE)) {
|
||||
- errMesg = "User must be authenticated to the directory server.\n";
|
||||
+ errMesg = "User must be authenticated to the directory server.";
|
||||
rc = LDAP_INSUFFICIENT_ACCESS;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -680,14 +680,14 @@ parse_req_done:
|
||||
|
||||
if (rval != LDAP_SUCCESS) {
|
||||
if (!errMesg)
|
||||
- errMesg = "Error generating new password.\n";
|
||||
+ errMesg = "Error generating new password.";
|
||||
rc = LDAP_OPERATIONS_ERROR;
|
||||
goto free_and_return;
|
||||
}
|
||||
|
||||
/* Make sure a passwd was actually generated */
|
||||
if (newPasswd == NULL || *newPasswd == '\0') {
|
||||
- errMesg = "Error generating new password.\n";
|
||||
+ errMesg = "Error generating new password.";
|
||||
rc = LDAP_OPERATIONS_ERROR;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -723,7 +723,7 @@ parse_req_done:
|
||||
/* If we can't find the entry, then that's an error */
|
||||
if (ret) {
|
||||
/* Couldn't find the entry, fail */
|
||||
- errMesg = "No such Entry exists.\n";
|
||||
+ errMesg = "No such Entry exists.";
|
||||
rc = LDAP_NO_SUCH_OBJECT;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -767,7 +767,7 @@ parse_req_done:
|
||||
if (need_pwpolicy_ctrl) {
|
||||
slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDMODNOTALLOWED);
|
||||
}
|
||||
- errMesg = "Insufficient access rights\n";
|
||||
+ errMesg = "Insufficient access rights";
|
||||
rc = LDAP_INSUFFICIENT_ACCESS;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -780,7 +780,7 @@ parse_req_done:
|
||||
ret = passwd_check_pwd(targetEntry, oldPasswd);
|
||||
if (ret) {
|
||||
/* No, then we fail this operation */
|
||||
- errMesg = "Invalid oldPasswd value.\n";
|
||||
+ errMesg = "Invalid oldPasswd value.";
|
||||
rc = ret;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -801,7 +801,7 @@ parse_req_done:
|
||||
if (need_pwpolicy_ctrl) {
|
||||
slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDMODNOTALLOWED);
|
||||
}
|
||||
- errMesg = "User is not allowed to change password\n";
|
||||
+ errMesg = "User is not allowed to change password";
|
||||
rc = LDAP_UNWILLING_TO_PERFORM;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -823,7 +823,7 @@ parse_req_done:
|
||||
|
||||
if (ret != LDAP_SUCCESS) {
|
||||
/* Failed to modify the password, e.g. because password policy, etc. */
|
||||
- errMesg = "Failed to update password\n";
|
||||
+ errMesg = "Failed to update password";
|
||||
rc = ret;
|
||||
goto free_and_return;
|
||||
}
|
||||
@@ -838,7 +838,7 @@ parse_req_done:
|
||||
/* Free anything that we allocated above */
|
||||
free_and_return:
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop",
|
||||
- "%s", errMesg ? errMesg : "success");
|
||||
+ "%s\n", errMesg ? errMesg : "success");
|
||||
|
||||
if ((rc == LDAP_REFERRAL) && (referrals)) {
|
||||
send_referrals_from_entry(pb, referrals);
|
||||
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
|
||||
index 493907e78..65e491590 100644
|
||||
--- a/ldap/servers/slapd/pw.c
|
||||
+++ b/ldap/servers/slapd/pw.c
|
||||
@@ -243,8 +243,8 @@ slapi_encode_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, char *value, char *alg)
|
||||
slapi_ch_free((void **)&scheme_list);
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "slapi_encode_ext",
|
||||
- "Invalid scheme - %s\n"
|
||||
- "no pwdstorage scheme plugin loaded",
|
||||
+ "Invalid scheme: %s ==> "
|
||||
+ "no pwdstorage scheme plugin loaded\n",
|
||||
alg);
|
||||
}
|
||||
return NULL;
|
||||
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
|
||||
index 9dee642b9..9ef4ee4bf 100644
|
||||
--- a/ldap/servers/slapd/schema.c
|
||||
+++ b/ldap/servers/slapd/schema.c
|
||||
@@ -6563,7 +6563,7 @@ supplier_get_new_definitions(struct berval **objectclasses, struct berval **attr
|
||||
* it and look for objectclasses
|
||||
*/
|
||||
slapi_log_err(SLAPI_LOG_ERR, "supplier_get_new_definitions",
|
||||
- "Not able to build an attributes list from the consumer schema");
|
||||
+ "Not able to build an attributes list from the consumer schema\n");
|
||||
}
|
||||
schema_dse_unlock();
|
||||
*new_oc = oc2learn_list;
|
||||
diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c
|
||||
index 08072ffa9..823c90f26 100644
|
||||
--- a/ldap/servers/slapd/util.c
|
||||
+++ b/ldap/servers/slapd/util.c
|
||||
@@ -1544,7 +1544,7 @@ util_is_cachesize_sane(slapi_pal_meminfo *mi, uint64_t *cachesize)
|
||||
*/
|
||||
uint64_t adjust_cachesize = (mi->system_available_bytes * 0.5);
|
||||
if (adjust_cachesize > *cachesize) {
|
||||
- slapi_log_err(SLAPI_LOG_CRIT, "util_is_cachesize_sane", "Invalid adjusted cachesize is greater than request %" PRIu64, adjust_cachesize);
|
||||
+ slapi_log_err(SLAPI_LOG_CRIT, "util_is_cachesize_sane", "Invalid adjusted cachesize is greater than request %" PRIu64 "\n", adjust_cachesize);
|
||||
return UTIL_CACHESIZE_ERROR;
|
||||
}
|
||||
if (adjust_cachesize < (16 * mi->pagesize_bytes)) {
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,118 +0,0 @@
|
||||
From 781e909bf275df7bc972a647ee0a551453dd1b4b Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 10 Sep 2024 16:29:00 +0200
|
||||
Subject: [PATCH] Issue 6321 - lib389 get_db_lib function may returns the wrong
|
||||
db type (#6322)
|
||||
|
||||
* Issue 6321 - lib389 get_db_lib function may returns the wrong db type
|
||||
|
||||
get_db_lib returns the default db type instead of the configured one because of caught exceptions
|
||||
Fix the import issue if instance is online
|
||||
Fix the DSEldif parameter for the offline case
|
||||
|
||||
Issue: #6321
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
.../suites/lib389/config_compare_test.py | 54 +++++++++++++++++++
|
||||
src/lib389/lib389/__init__.py | 4 +-
|
||||
2 files changed, 56 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/lib389/config_compare_test.py b/dirsrvtests/tests/suites/lib389/config_compare_test.py
|
||||
index 9ed4da6c8..3eb3d0ea4 100644
|
||||
--- a/dirsrvtests/tests/suites/lib389/config_compare_test.py
|
||||
+++ b/dirsrvtests/tests/suites/lib389/config_compare_test.py
|
||||
@@ -9,11 +9,17 @@
|
||||
import os
|
||||
import pytest
|
||||
|
||||
+from contextlib import suppress
|
||||
from lib389.topologies import topology_i2
|
||||
from lib389.config import Config
|
||||
+from lib389.dseldif import DSEldif
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
+# set_db_type_and_state fixture parameters
|
||||
+db_types_and_states = [('bdb,stopped'), ('mdb,stopped'), ('bdb,started'), ('mdb,started')]
|
||||
+
|
||||
+
|
||||
def test_config_compare(topology_i2):
|
||||
"""
|
||||
Compare test between cn=config of two different Directory Server intance.
|
||||
@@ -42,6 +48,54 @@ def test_config_compare(topology_i2):
|
||||
assert Config.compare(st1_config, st2_config)
|
||||
|
||||
|
||||
+@pytest.fixture(scope="function", params=db_types_and_states)
|
||||
+def set_db_type_and_state(topology_i2, request):
|
||||
+ """
|
||||
+ Stop standalone1 instance and save its dse.ldif then restore things at teardown.
|
||||
+ """
|
||||
+ inst = topology_i2.ins.get('standalone1')
|
||||
+ dbtype,state = request.param.split(',')
|
||||
+ inst.stop()
|
||||
+ becfgdn = 'cn=config,cn=ldbm database,cn=plugins,cn=config'
|
||||
+ becfgattr = 'nsslapd-backend-implement'
|
||||
+ save_dse_ldif = DSEldif(inst)
|
||||
+ dse_ldif = DSEldif(inst)
|
||||
+ dse_ldif.replace(becfgdn, becfgattr, dbtype)
|
||||
+ if state == 'started':
|
||||
+ inst.start()
|
||||
+
|
||||
+ def fin():
|
||||
+ save_dse_ldif._update()
|
||||
+ inst.start()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ return (dbtype,state)
|
||||
+
|
||||
+
|
||||
+def test_get_db_lib(request, topology_i2, set_db_type_and_state):
|
||||
+ """
|
||||
+ Check that get_db_lib() returns the configured database type.
|
||||
+
|
||||
+ :id: 04205590-6c70-11ef-bfae-083a88554478
|
||||
+
|
||||
+ :setup: two isolated directory servers. standalone1 is set with
|
||||
+ a specified db type and instance state.
|
||||
+
|
||||
+ :steps: 1. Clear get_db_lib cache
|
||||
+ 2. Check that inst.get_db_lib() returns the expected db type
|
||||
+
|
||||
+ :expectedresults: 1. Success
|
||||
+ 2. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_i2.ins.get('standalone1')
|
||||
+ dbtype,state = set_db_type_and_state
|
||||
+
|
||||
+ with suppress(AttributeError):
|
||||
+ del inst._db_lib
|
||||
+ assert inst.get_db_lib() == dbtype
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index 1ac9770b0..65e70c1dd 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -879,11 +879,11 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
with suppress(AttributeError):
|
||||
return self._db_lib
|
||||
with suppress(Exception):
|
||||
- from backend import DatabaseConfig
|
||||
+ from lib389.backend import DatabaseConfig
|
||||
self._db_lib = DatabaseConfig(self).get_db_lib()
|
||||
return self._db_lib
|
||||
with suppress(Exception):
|
||||
- dse_ldif = DSEldif(None, self)
|
||||
+ dse_ldif = DSEldif(self)
|
||||
self._db_lib = dse_ldif.get(DN_CONFIG_LDBM, "nsslapd-backend-implement", single=True)
|
||||
return self._db_lib
|
||||
return get_default_db_lib()
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,433 +0,0 @@
|
||||
From 97975f9ab954d1a955cc203a77dfad5d2750c55f Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 25 Mar 2025 14:29:19 -0400
|
||||
Subject: [PATCH] Issue 6500 - Fix covscan and ASAN issue
|
||||
|
||||
Description:
|
||||
|
||||
- Fix memory leak in alias entry plugin
|
||||
- Fix lock issue in replication changelog ldif import
|
||||
- Fix memory leak in replication parse_changes_string()
|
||||
- Fix memory leak in views plugin
|
||||
- Remove dead code in auditlog.c
|
||||
- Fix memory leak in dbmdb_build_dbname()
|
||||
- Fix uninitialized variable warning in log.c
|
||||
- Fix several memory leaks in modify.c
|
||||
- Fix memory leak with result text
|
||||
- Fix memory leak in snmp main.c when loading config
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6500
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
.../plugins/alias_entries/alias-entries.c | 5 ++++-
|
||||
ldap/servers/plugins/memberof/memberof.c | 1 +
|
||||
.../plugins/memberof/memberof_config.c | 16 +++++++++++++-
|
||||
ldap/servers/plugins/replication/cl5_api.c | 22 +++++++++++--------
|
||||
ldap/servers/plugins/replication/replutil.c | 2 ++
|
||||
ldap/servers/plugins/views/views.c | 1 +
|
||||
ldap/servers/slapd/auditlog.c | 9 ++------
|
||||
.../slapd/back-ldbm/db-mdb/mdb_instance.c | 5 ++++-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.c | 3 ++-
|
||||
ldap/servers/slapd/log.c | 8 +++----
|
||||
ldap/servers/slapd/modify.c | 7 ++++++
|
||||
ldap/servers/slapd/operation.c | 1 +
|
||||
ldap/servers/slapd/pblock.c | 1 +
|
||||
ldap/servers/slapd/plugin.c | 5 -----
|
||||
ldap/servers/slapd/slapi-memberof.c | 4 +---
|
||||
ldap/servers/snmp/main.c | 6 ++---
|
||||
16 files changed, 61 insertions(+), 35 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/alias_entries/alias-entries.c b/ldap/servers/plugins/alias_entries/alias-entries.c
|
||||
index ddd775df4..8b5862960 100644
|
||||
--- a/ldap/servers/plugins/alias_entries/alias-entries.c
|
||||
+++ b/ldap/servers/plugins/alias_entries/alias-entries.c
|
||||
@@ -121,6 +121,7 @@ alias_entry_srch(Slapi_PBlock *pb)
|
||||
slapi_log_error(SLAPI_LOG_PLUGIN, PLUGINNAME,
|
||||
"alias_entry_srch - %s\n", errorbuf);
|
||||
|
||||
+ slapi_sdn_free(&dn1);
|
||||
slapi_send_ldap_result(pb, rc, NULL, errorbuf, 0, NULL);
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &rc);
|
||||
return SLAPI_PLUGIN_FAILURE;
|
||||
@@ -128,7 +129,8 @@ alias_entry_srch(Slapi_PBlock *pb)
|
||||
} while (dn2 != NULL && i++ < MAXALIASCHAIN);
|
||||
|
||||
if (dn1 == search_target) {
|
||||
- /* Source dn is not an alias */
|
||||
+ /* Source dn is not an alias */\
|
||||
+ slapi_sdn_free(&dn2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -138,6 +140,7 @@ alias_entry_srch(Slapi_PBlock *pb)
|
||||
slapi_pblock_set(pb, SLAPI_SEARCH_TARGET_SDN, dn1);
|
||||
} else {
|
||||
/* Here we hit an alias chain longer than MAXALIASCHAIN */
|
||||
+ slapi_sdn_free(&dn1);
|
||||
slapi_sdn_free(&dn2);
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 16dae2195..3775e52c9 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -931,6 +931,7 @@ perform_needed_fixup(void)
|
||||
}
|
||||
be = slapi_get_next_backend(cookie);
|
||||
}
|
||||
+ slapi_ch_free_string(&cookie);
|
||||
slapi_ch_free_string(&td.bind_dn);
|
||||
slapi_ch_free_string(&td.filter_str);
|
||||
memberof_free_config(&config);
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
index fca5251d0..b74c24158 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof_config.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
@@ -576,10 +576,24 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
|
||||
/* Add beginning of filter. */
|
||||
bytes_out = snprintf(filter_str, filter_str_len - bytes_out, "(|");
|
||||
+ if (bytes_out<0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "snprintf unexpectly failed in memberof_apply_config.\n");
|
||||
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
|
||||
+ goto done;
|
||||
+ }
|
||||
|
||||
/* Add filter section for each groupattr. */
|
||||
for (i = 0; theConfig.groupattrs && theConfig.groupattrs[i]; i++) {
|
||||
- bytes_out += snprintf(filter_str + bytes_out, filter_str_len - bytes_out, "(%s=*)", theConfig.groupattrs[i]);
|
||||
+ size_t bytes_read = snprintf(filter_str + bytes_out, filter_str_len - bytes_out, "(%s=*)",
|
||||
+ theConfig.groupattrs[i]);
|
||||
+ if (bytes_read<0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "snprintf unexpectly failed in memberof_apply_config.\n");
|
||||
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
|
||||
+ goto done;
|
||||
+ }
|
||||
+ bytes_out += bytes_read;
|
||||
}
|
||||
|
||||
/* Add end of filter. */
|
||||
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
|
||||
index 1b6413164..d88ce7d98 100644
|
||||
--- a/ldap/servers/plugins/replication/cl5_api.c
|
||||
+++ b/ldap/servers/plugins/replication/cl5_api.c
|
||||
@@ -540,7 +540,18 @@ cl5ImportLDIF(const char *clDir, const char *ldifFile, Replica *replica)
|
||||
return CL5_BAD_DATA;
|
||||
}
|
||||
|
||||
+ /* Set changelog state to import */
|
||||
+ pthread_mutex_lock(&(cldb->stLock));
|
||||
+
|
||||
+ if (cldb->dbState == CL5_STATE_IMPORT) {
|
||||
+ pthread_mutex_unlock(&(cldb->stLock));
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
|
||||
+ "cl5ImportLDIF - changelog import already in progress\n");
|
||||
+ return CL5_IGNORE_OP;
|
||||
+ }
|
||||
+
|
||||
if (cldb->dbState != CL5_STATE_OPEN) {
|
||||
+ pthread_mutex_unlock(&(cldb->stLock));
|
||||
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
|
||||
"cl5ImportLDIF - Changelog is not initialized\n");
|
||||
return CL5_BAD_STATE;
|
||||
@@ -549,6 +560,7 @@ cl5ImportLDIF(const char *clDir, const char *ldifFile, Replica *replica)
|
||||
/* open LDIF file */
|
||||
file = ldif_open(ldifFile, "r");
|
||||
if (file == NULL) {
|
||||
+ pthread_mutex_unlock(&(cldb->stLock));
|
||||
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
|
||||
"cl5ImportLDIF - Failed to open (%s) ldif file; system error - %d\n",
|
||||
ldifFile, errno);
|
||||
@@ -556,15 +568,6 @@ cl5ImportLDIF(const char *clDir, const char *ldifFile, Replica *replica)
|
||||
goto done;
|
||||
}
|
||||
|
||||
- /* Set changelog state to import */
|
||||
- pthread_mutex_lock(&(cldb->stLock));
|
||||
-
|
||||
- if (cldb->dbState == CL5_STATE_IMPORT) {
|
||||
- pthread_mutex_unlock(&(cldb->stLock));
|
||||
- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
|
||||
- "cl5ImportLDIF - changelog import already in progress\n");
|
||||
- return CL5_IGNORE_OP;
|
||||
- }
|
||||
cldb->dbState = CL5_STATE_IMPORT;
|
||||
|
||||
pthread_mutex_unlock(&(cldb->stLock));
|
||||
@@ -3723,6 +3726,7 @@ _cl5LDIF2Operation(char *ldifEntry, slapi_operation_parameters *op, char **replG
|
||||
}
|
||||
}
|
||||
slapi_ch_free_string(&ldifEntryWork);
|
||||
+ slapi_ch_free_string(&rawDN);
|
||||
return rval;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/replutil.c b/ldap/servers/plugins/replication/replutil.c
|
||||
index 3f40ba071..fe118c02d 100644
|
||||
--- a/ldap/servers/plugins/replication/replutil.c
|
||||
+++ b/ldap/servers/plugins/replication/replutil.c
|
||||
@@ -342,6 +342,7 @@ parse_changes_string(char *str)
|
||||
if (strcasecmp(line, "-") == 0) {
|
||||
if (slapi_mod_isvalid(&mod)) {
|
||||
slapi_mods_add_smod(mods, &mod);
|
||||
+ slapi_mod_init(&mod, 0);
|
||||
} else {
|
||||
/* need to cleanup */
|
||||
slapi_mod_done(&mod);
|
||||
@@ -382,6 +383,7 @@ parse_changes_string(char *str)
|
||||
}
|
||||
line = ldif_getline(&next);
|
||||
}
|
||||
+ slapi_mod_done(&mod);
|
||||
}
|
||||
|
||||
return mods;
|
||||
diff --git a/ldap/servers/plugins/views/views.c b/ldap/servers/plugins/views/views.c
|
||||
index 007a9d6f5..5e4499ddb 100644
|
||||
--- a/ldap/servers/plugins/views/views.c
|
||||
+++ b/ldap/servers/plugins/views/views.c
|
||||
@@ -1139,6 +1139,7 @@ views_dn_views_cb(Slapi_Entry *e, void *callback_data)
|
||||
if (attrType && !strcasecmp(attrType, VIEW_FILTER_ATTR)) {
|
||||
if (!slapi_attr_get_bervals_copy(dnAttr, &dnVals)) {
|
||||
/* add filter */
|
||||
+ slapi_ch_free_string(&pView->viewfilter);
|
||||
pView->viewfilter = slapi_ch_strdup(dnVals[0]->bv_val);
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 747448998..3a34959f6 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -643,13 +643,8 @@ write_audit_file_json(Slapi_PBlock *pb, Slapi_Entry *entry, int logtype,
|
||||
case SLAPI_OPERATION_DELETE:
|
||||
tmp = change;
|
||||
del_obj = json_object_new_object();
|
||||
- if (tmp && tmp[0]) {
|
||||
- json_object_object_add(del_obj, "dn", json_object_new_string(target_dn));
|
||||
- json_object_object_add(log_json, "delete", del_obj);
|
||||
- } else {
|
||||
- json_object_object_add(del_obj, "dn", json_object_new_string(target_dn));
|
||||
- json_object_object_add(log_json, "delete", del_obj);
|
||||
- }
|
||||
+ json_object_object_add(del_obj, "dn", json_object_new_string(target_dn));
|
||||
+ json_object_object_add(log_json, "delete", del_obj);
|
||||
break;
|
||||
|
||||
case SLAPI_OPERATION_MODDN:
|
||||
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 0315693e0..682fd70e2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c
|
||||
@@ -210,7 +210,10 @@ char *dbmdb_build_dbname(backend *be, const char *filename)
|
||||
res = slapi_ch_smprintf("%s/%s%s", inst->inst_name, filename, suffix);
|
||||
}
|
||||
pt = (char*)slapi_utf8StrToLower((unsigned char*)res);
|
||||
- slapi_ch_free_string(&res);
|
||||
+ if (pt != res) {
|
||||
+ slapi_ch_free_string(&res);
|
||||
+ }
|
||||
+
|
||||
return pt;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
index d16241df1..d7d138286 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
@@ -1294,7 +1294,8 @@ ldbm_config_search_entry_callback(Slapi_PBlock *pb __attribute__((unused)),
|
||||
if (attrs) {
|
||||
for (size_t i = 0; attrs[i]; i++) {
|
||||
if (ldbm_config_moved_attr(attrs[i])) {
|
||||
- slapi_pblock_set(pb, SLAPI_RESULT_TEXT, "at least one required attribute has been moved to the DB scecific configuration entry");
|
||||
+ char *msg = slapi_ch_strdup("at least one required attribute has been moved to the DB specific configuration entry");
|
||||
+ slapi_pblock_set(pb, SLAPI_RESULT_TEXT, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index 2d4144d35..178d29b89 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -3246,7 +3246,7 @@ log__open_accesslogfile(int logfile_state, int locked)
|
||||
char tbuf[TBUFSIZE];
|
||||
struct logfileinfo *logp;
|
||||
char buffer[BUFSIZ];
|
||||
- int rc;
|
||||
+ int rc = 0;
|
||||
|
||||
if (!locked)
|
||||
LOG_ACCESS_LOCK_WRITE();
|
||||
@@ -3410,7 +3410,7 @@ log__open_securitylogfile(int logfile_state, int locked)
|
||||
char tbuf[TBUFSIZE];
|
||||
struct logfileinfo *logp;
|
||||
char buffer[BUFSIZ];
|
||||
- int rc;
|
||||
+ int rc = 0;
|
||||
|
||||
if (!locked)
|
||||
LOG_SECURITY_LOCK_WRITE();
|
||||
@@ -6317,7 +6317,7 @@ log__open_auditlogfile(int logfile_state, int locked)
|
||||
char tbuf[TBUFSIZE];
|
||||
struct logfileinfo *logp;
|
||||
char buffer[BUFSIZ];
|
||||
- int rc;
|
||||
+ int rc = 0;
|
||||
|
||||
if (!locked)
|
||||
LOG_AUDIT_LOCK_WRITE();
|
||||
@@ -6476,7 +6476,7 @@ log__open_auditfaillogfile(int logfile_state, int locked)
|
||||
char tbuf[TBUFSIZE];
|
||||
struct logfileinfo *logp;
|
||||
char buffer[BUFSIZ];
|
||||
- int rc;
|
||||
+ int rc = 0;
|
||||
|
||||
if (!locked)
|
||||
LOG_AUDITFAIL_LOCK_WRITE();
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index 58b606b2e..b0ec1c02c 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -598,6 +598,7 @@ modify_internal_pb(Slapi_PBlock *pb)
|
||||
if (pw_change == -1) {
|
||||
/* The internal result code will already have been set by op_shared_allow_pw_change() */
|
||||
ldap_mods_free(normalized_mods, 1);
|
||||
+ slapi_ch_free_string(&old_pw);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -621,6 +622,7 @@ modify_internal_pb(Slapi_PBlock *pb)
|
||||
/* perform modify operation */
|
||||
slapi_td_internal_op_start();
|
||||
op_shared_modify(pb, pw_change, old_pw);
|
||||
+ slapi_ch_free_string(&old_pw);
|
||||
slapi_td_internal_op_finish();
|
||||
|
||||
/* free the normalized_mods don't forget to add this*/
|
||||
@@ -1196,6 +1198,7 @@ op_shared_allow_pw_change(Slapi_PBlock *pb, LDAPMod *mod, char **old_pw, Slapi_M
|
||||
char *proxystr = NULL;
|
||||
char *errtext = NULL;
|
||||
int32_t needpw = 0;
|
||||
+ bool free_bogus_entry = false;
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op);
|
||||
if (repl_op) {
|
||||
@@ -1255,6 +1258,7 @@ op_shared_allow_pw_change(Slapi_PBlock *pb, LDAPMod *mod, char **old_pw, Slapi_M
|
||||
e = slapi_entry_alloc();
|
||||
slapi_entry_init(e, NULL, NULL);
|
||||
slapi_sdn_set_dn_byref(slapi_entry_get_sdn(e), dn);
|
||||
+ free_bogus_entry = true;
|
||||
}
|
||||
|
||||
/* Set the backend in the pblock. The slapi_access_allowed function
|
||||
@@ -1400,6 +1404,9 @@ op_shared_allow_pw_change(Slapi_PBlock *pb, LDAPMod *mod, char **old_pw, Slapi_M
|
||||
valuearray_free(&values);
|
||||
|
||||
done:
|
||||
+ if (free_bogus_entry) {
|
||||
+ slapi_entry_free(e);
|
||||
+ }
|
||||
slapi_sdn_done(&sdn);
|
||||
slapi_ch_free_string(&proxydn);
|
||||
slapi_ch_free_string(&proxystr);
|
||||
diff --git a/ldap/servers/slapd/operation.c b/ldap/servers/slapd/operation.c
|
||||
index 97832db41..f70c5e035 100644
|
||||
--- a/ldap/servers/slapd/operation.c
|
||||
+++ b/ldap/servers/slapd/operation.c
|
||||
@@ -223,6 +223,7 @@ operation_done(Slapi_Operation **op, Connection *conn)
|
||||
(*op)->o_results.result_controls = NULL;
|
||||
}
|
||||
slapi_ch_free_string(&(*op)->o_results.result_matched);
|
||||
+ slapi_ch_free_string(&(*op)->o_results.result_text);
|
||||
int options = 0;
|
||||
/* save the old options */
|
||||
if ((*op)->o_ber) {
|
||||
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
|
||||
index ec820ec70..3801326c4 100644
|
||||
--- a/ldap/servers/slapd/pblock.c
|
||||
+++ b/ldap/servers/slapd/pblock.c
|
||||
@@ -4005,6 +4005,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
|
||||
pblock->pb_op->o_results.result_matched = (char *)value;
|
||||
break;
|
||||
case SLAPI_RESULT_TEXT:
|
||||
+ /* value will get freed in operation_done(), so it should be allocated */
|
||||
if (pblock->pb_op != NULL)
|
||||
pblock->pb_op->o_results.result_text = (char *)value;
|
||||
break;
|
||||
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
|
||||
index ba4a194c1..1528d5593 100644
|
||||
--- a/ldap/servers/slapd/plugin.c
|
||||
+++ b/ldap/servers/slapd/plugin.c
|
||||
@@ -708,11 +708,6 @@ slapi_send_ldap_result_from_pb(Slapi_PBlock *pb)
|
||||
if (NULL != fn) {
|
||||
(*fn)(pb, err, matched, text, 0, NULL);
|
||||
}
|
||||
-
|
||||
- slapi_pblock_set(pb, SLAPI_RESULT_TEXT, NULL);
|
||||
- slapi_pblock_set(pb, SLAPI_RESULT_MATCHED, NULL);
|
||||
- slapi_ch_free((void **)&matched);
|
||||
- slapi_ch_free((void **)&text);
|
||||
}
|
||||
|
||||
void
|
||||
diff --git a/ldap/servers/slapd/slapi-memberof.c b/ldap/servers/slapd/slapi-memberof.c
|
||||
index 0b525896a..71df0f463 100644
|
||||
--- a/ldap/servers/slapd/slapi-memberof.c
|
||||
+++ b/ldap/servers/slapd/slapi-memberof.c
|
||||
@@ -91,7 +91,7 @@ static int sm_memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
static void sm_report_error_msg(Slapi_MemberOfConfig *config, char* msg);
|
||||
static int sm_entry_get_groups(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn,
|
||||
Slapi_ValueSet *groupvals, Slapi_ValueSet *nsuniqueidvals);
|
||||
-static PRBool sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool all_backends,
|
||||
+static PRBool sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool all_backends,
|
||||
PRBool skip_nested, Slapi_DN **include_scope, Slapi_DN **exclude_scope, PRBool enabled_only);
|
||||
static void sm_add_ancestors_cbdata(sm_memberof_cached_value *ancestors, void *callback_data);
|
||||
static int sm_memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn, Slapi_MemberOfConfig *config, char **types,
|
||||
@@ -746,7 +746,6 @@ sm_entry_get_groups(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_Va
|
||||
"sm_entry_get_groups - Failed to retrieve target entry %s: %d\n",
|
||||
slapi_sdn_get_ndn(group_sdn), rc);
|
||||
slapi_sdn_free(&group_sdn);
|
||||
- slapi_ch_array_free(groups_dn);
|
||||
rc = -1;
|
||||
goto common;
|
||||
}
|
||||
@@ -767,7 +766,6 @@ sm_entry_get_groups(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_Va
|
||||
sval = slapi_value_new_string(slapi_sdn_get_ndn(group_sdn));
|
||||
slapi_valueset_add_value_ext(groupvals, sval, SLAPI_VALUE_FLAG_PASSIN);
|
||||
|
||||
-
|
||||
slapi_sdn_free(&group_sdn);
|
||||
slapi_search_get_entry_done(&group_pb);
|
||||
}
|
||||
diff --git a/ldap/servers/snmp/main.c b/ldap/servers/snmp/main.c
|
||||
index 75dc3e76b..5250a2dbc 100644
|
||||
--- a/ldap/servers/snmp/main.c
|
||||
+++ b/ldap/servers/snmp/main.c
|
||||
@@ -451,11 +451,10 @@ load_config(char *conf_path)
|
||||
got_port = 1;
|
||||
} else if (strcmp(attr, "nsslapd-rundir") == 0) {
|
||||
/* 8 = "/" + ".stats" + \0 */
|
||||
- serv_p->stats_file = malloc(vlen + (instancename ? strlen(instancename) : 0) + 8);
|
||||
+ serv_p->stats_file = calloc(1, vlen + (instancename ? strlen(instancename) : 0) + 8);
|
||||
if (serv_p->stats_file && instancename) {
|
||||
- snprintf(serv_p->stats_file, vlen + strlen(instancename) + 8,
|
||||
+ snprintf(serv_p->stats_file, vlen + strlen(instancename) + 7,
|
||||
"%s/%s.stats", val, instancename);
|
||||
- serv_p->stats_file[(vlen + strlen(instancename) + 7)] = (char)0;
|
||||
} else {
|
||||
printf("ldap-agent: malloc error processing config file\n");
|
||||
free(entry);
|
||||
@@ -466,6 +465,7 @@ load_config(char *conf_path)
|
||||
}
|
||||
got_rundir = 1;
|
||||
}
|
||||
+
|
||||
free(attr);
|
||||
free(val);
|
||||
attr = NULL;
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,404 +0,0 @@
|
||||
From 208aa03cfebd085c472f6920b8e0e38fd06ee2a8 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 23 Sep 2025 16:41:12 -0400
|
||||
Subject: [PATCH] Issue 7014 - memberOf - ignored deferred updates with LMDB
|
||||
|
||||
Description:
|
||||
|
||||
When processing the memberOf plugin conifguration simply ignore the
|
||||
deferred update settings if LMDB is in use. Log a message in the error
|
||||
log but do not reject the update because rejecting the update causes the
|
||||
server to not start up.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7014
|
||||
|
||||
Reviewed by: tbordaz & progier (Thanks!!)
|
||||
---
|
||||
.../memberof_deferred_lmdb_test.py | 128 ++++++++++++++++++
|
||||
.../memberof_deferred_repl_test.py | 2 +
|
||||
.../suites/memberof_plugin/regression_test.py | 7 +-
|
||||
ldap/servers/plugins/memberof/memberof.c | 11 +-
|
||||
ldap/servers/plugins/memberof/memberof.h | 1 +
|
||||
.../plugins/memberof/memberof_config.c | 14 +-
|
||||
.../plugins/posix-winsync/posix-wsp-ident.h | 2 -
|
||||
ldap/servers/slapd/slapi-private.h | 2 +
|
||||
ldap/servers/slapd/util.c | 43 +++++-
|
||||
9 files changed, 195 insertions(+), 15 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
|
||||
new file mode 100644
|
||||
index 000000000..0d9f793c1
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
|
||||
@@ -0,0 +1,128 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import logging
|
||||
+import pytest
|
||||
+import os
|
||||
+import time
|
||||
+import ldap
|
||||
+from lib389._constants import *
|
||||
+from lib389.topologies import topology_st as topo
|
||||
+from lib389.plugins import MemberOfPlugin
|
||||
+from lib389.config import LMDB_LDBMConfig
|
||||
+from lib389.utils import get_default_db_lib
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.group import Groups
|
||||
+
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+DEBUGGING = os.getenv('DEBUGGING', False)
|
||||
+if DEBUGGING:
|
||||
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+else:
|
||||
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(get_default_db_lib() != "mdb", reason="Not supported over mdb")
|
||||
+def test_memberof_deferred_update_lmdb_rejection(topo):
|
||||
+ """Test that memberOf plugin rejects deferred update configuration with LMDB backend
|
||||
+
|
||||
+ :id: a7f079dd-d269-41ca-95ec-91428e77626f
|
||||
+ :setup: Standalone Instance with LMDB backend
|
||||
+ :steps:
|
||||
+ 1. Enable memberOf plugin
|
||||
+ 2. Try to set deferred_update to "on"
|
||||
+ 3. Check error log for appropriate error message
|
||||
+ :expectedresults:
|
||||
+ 1. Plugin enables successfully
|
||||
+ 2. Setting deferred_update fails
|
||||
+ 3. Error log contains "deferred_update is not supported with LMDB backend"
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ # Enable memberOf plugin
|
||||
+ log.info("Step 1: Enabling memberOf plugin")
|
||||
+ memberof_plugin = MemberOfPlugin(inst)
|
||||
+ memberof_plugin.enable()
|
||||
+ log.info("✓ MemberOf plugin enabled")
|
||||
+ inst.deleteErrorLogs(restart=True)
|
||||
+
|
||||
+ # Try to set deferred_update to "on"
|
||||
+ log.info("Step 2: Attempting to set deferred_update to 'on'")
|
||||
+
|
||||
+ # Try to modify the plugin configuration
|
||||
+ plugin_dn = f"cn={PLUGIN_MEMBER_OF},cn=plugins,cn=config"
|
||||
+ memberof_plugin.set_memberofdeferredupdate('on')
|
||||
+
|
||||
+ # Check error log for appropriate error message
|
||||
+ log.info("Step 3: Checking error log for LMDB-specific error message")
|
||||
+ assert inst.ds_error_log.match(".*deferred_update is not supported with LMDB backend.*")
|
||||
+
|
||||
+ log.info("✓ Test completed successfully - memberOf plugin correctly rejects deferred update with LMDB backend")
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(get_default_db_lib() == "mdb", reason="Not supported over mdb")
|
||||
+def test_memberof_deferred_update_non_lmdb_success(topo):
|
||||
+ """Test that memberOf plugin allows deferred update configuration with non-LMDB backends
|
||||
+
|
||||
+ :id: a4b640c8-ef54-4cbf-8d1a-8b29fcdd59d1
|
||||
+ :setup: Standalone Instance with non-LMDB backend (BDB)
|
||||
+ :steps:
|
||||
+ 1. Enable memberOf plugin
|
||||
+ 2. Set deferred_update to "on"
|
||||
+ 3. Verify the operation succeeds
|
||||
+ 4. Verify deferred_update remains "on"
|
||||
+ :expectedresults:
|
||||
+ 1. Plugin enables successfully
|
||||
+ 2. Setting deferred_update succeeds
|
||||
+ 3. No error occurs
|
||||
+ 4. deferred_update is "on"
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ # Enable memberOf plugin
|
||||
+ log.info("Step 1: Enabling memberOf plugin")
|
||||
+ memberof_plugin = MemberOfPlugin(inst)
|
||||
+ memberof_plugin.enable()
|
||||
+ log.info("✓ MemberOf plugin enabled")
|
||||
+ inst.deleteErrorLogs(restart=True)
|
||||
+
|
||||
+ # Set deferred_update to "on"
|
||||
+ log.info("Step 2: Setting deferred_update to 'on'")
|
||||
+
|
||||
+ # Try to modify the plugin configuration
|
||||
+ try:
|
||||
+ memberof_plugin.set_memberofdeferredupdate('on')
|
||||
+ log.info("✓ Successfully set deferred_update to 'on'")
|
||||
+ except Exception as e:
|
||||
+ assert False, f"Expected success when setting deferred_update with non-LMDB backend, got: {type(e).__name__}: {e}"
|
||||
+
|
||||
+ # Verify no error occurred
|
||||
+ log.info("Step 3: Verifying no error occurred")
|
||||
+ assert not inst.ds_error_log.match(".*deferred_update is not supported with LMDB backend.*")
|
||||
+
|
||||
+ log.info("✓ No LMDB-related error messages found")
|
||||
+
|
||||
+ # Verify deferred_update remains "on"
|
||||
+ log.info("Step 4: Verifying deferred_update is 'on'")
|
||||
+ current_deferred = memberof_plugin.get_memberofdeferredupdate()
|
||||
+ assert current_deferred is None or current_deferred.lower() == 'on', \
|
||||
+ f"Expected deferred_update to be 'on', got: {current_deferred}"
|
||||
+ log.info("✓ deferred_update is 'on'")
|
||||
+
|
||||
+ log.info("✓ Test completed successfully - memberOf plugin correctly allows deferred update with non-LMDB backend")
|
||||
+
|
||||
+
|
||||
+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/memberof_plugin/memberof_deferred_repl_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_repl_test.py
|
||||
index e92df0661..25cbf4890 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_repl_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_repl_test.py
|
||||
@@ -17,10 +17,12 @@ from lib389.replica import Replicas
|
||||
from lib389.plugins import MemberOfPlugin
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.idm.group import Groups
|
||||
+from lib389.utils import get_default_db_lib
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+@pytest.mark.skipif(get_default_db_lib() == "mdb", reason="Not supported over mdb")
|
||||
def test_repl_deferred_updates(topo_m2):
|
||||
"""Test memberOf plugin deferred updates work in different types of
|
||||
replicated environments
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 3ffb26b01..ebca566e0 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2020 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -28,6 +28,7 @@ from lib389.idm.domain import Domain
|
||||
from lib389.dirsrv_log import DirsrvErrorLog
|
||||
from lib389.dseldif import DSEldif
|
||||
from contextlib import suppress
|
||||
+from lib389.utils import get_default_db_lib
|
||||
|
||||
|
||||
# Skip on older versions
|
||||
@@ -1282,7 +1283,8 @@ def _kill_instance(inst, sig=signal.SIGTERM, delay=None):
|
||||
time.sleep(delay)
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
|
||||
-def test_shutdown_on_deferred_memberof(topology_st):
|
||||
+@pytest.mark.skipif(get_default_db_lib() == "mdb", reason="Not supported over mdb")
|
||||
+def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
"""This test checks that shutdown is handled properly if memberof updayes are deferred.
|
||||
|
||||
:id: c5629cae-15a0-11ee-8807-482ae39447e5
|
||||
@@ -1413,4 +1415,3 @@ if __name__ == '__main__':
|
||||
# -s for DEBUG mode
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s %s" % CURRENT_FILE)
|
||||
-
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 82cb60c96..e00ad8de8 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1179,7 +1179,7 @@ memberof_postop_start(Slapi_PBlock *pb)
|
||||
memberof_rlock_config();
|
||||
mainConfig = memberof_get_config();
|
||||
/* if the update of the members is deferred then allocate mutex/cv */
|
||||
- if (mainConfig->deferred_update) {
|
||||
+ if (mainConfig->deferred_update && !mainConfig->is_lmdb) {
|
||||
MemberofDeferredList *deferred_list;
|
||||
pthread_condattr_t condAttr;
|
||||
|
||||
@@ -1362,7 +1362,7 @@ memberof_postop_del(Slapi_PBlock *pb)
|
||||
/* retrieve deferred update params that are valid until shutdown */
|
||||
memberof_rlock_config();
|
||||
mainConfig = memberof_get_config();
|
||||
- deferred_update = mainConfig->deferred_update;
|
||||
+ deferred_update = mainConfig->is_lmdb ? false : mainConfig->deferred_update;
|
||||
memberof_unlock_config();
|
||||
|
||||
if (deferred_update) {
|
||||
@@ -1738,7 +1738,7 @@ memberof_postop_modrdn(Slapi_PBlock *pb)
|
||||
/* retrieve deferred update params that are valid until shutdown */
|
||||
memberof_rlock_config();
|
||||
mainConfig = memberof_get_config();
|
||||
- deferred_update = mainConfig->deferred_update;
|
||||
+ deferred_update = mainConfig->is_lmdb ? false : mainConfig->deferred_update;
|
||||
memberof_unlock_config();
|
||||
|
||||
if (deferred_update) {
|
||||
@@ -2057,7 +2057,8 @@ memberof_postop_modify(Slapi_PBlock *pb)
|
||||
/* retrieve deferred update params that are valid until shutdown */
|
||||
memberof_rlock_config();
|
||||
mainConfig = memberof_get_config();
|
||||
- deferred_update = mainConfig->deferred_update;
|
||||
+
|
||||
+ deferred_update = mainConfig->is_lmdb ? false : mainConfig->deferred_update;
|
||||
memberof_unlock_config();
|
||||
|
||||
if (deferred_update) {
|
||||
@@ -2317,7 +2318,7 @@ memberof_postop_add(Slapi_PBlock *pb)
|
||||
/* retrieve deferred update params that are valid until shutdown */
|
||||
memberof_rlock_config();
|
||||
mainConfig = memberof_get_config();
|
||||
- deferred_update = mainConfig->deferred_update;
|
||||
+ deferred_update = mainConfig->is_lmdb ? false : mainConfig->deferred_update;
|
||||
memberof_unlock_config();
|
||||
|
||||
if (deferred_update) {
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.h b/ldap/servers/plugins/memberof/memberof.h
|
||||
index 64b17067b..1c0d2d308 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.h
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.h
|
||||
@@ -138,6 +138,7 @@ typedef struct memberofconfig
|
||||
PLHashTable *fixup_cache;
|
||||
Slapi_Task *task;
|
||||
int need_fixup;
|
||||
+ bool is_lmdb;
|
||||
} MemberOfConfig;
|
||||
|
||||
/* The key to access the hash table is the normalized DN
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
index bd7d25140..0336d8517 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof_config.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
@@ -519,6 +519,8 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
*/
|
||||
memberof_wlock_config();
|
||||
theConfig.need_fixup = (needfixup != NULL);
|
||||
+ /* DB implementation */
|
||||
+ theConfig.is_lmdb = slapi_db_is_lmdb();
|
||||
|
||||
if (groupattrs) {
|
||||
int i = 0;
|
||||
@@ -634,12 +636,16 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
}
|
||||
}
|
||||
|
||||
-
|
||||
if (deferred_update) {
|
||||
+ theConfig.deferred_update = PR_FALSE;
|
||||
if (strcasecmp(deferred_update, "on") == 0) {
|
||||
- theConfig.deferred_update = PR_TRUE;
|
||||
- } else {
|
||||
- theConfig.deferred_update = PR_FALSE;
|
||||
+ if (theConfig.is_lmdb) {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_apply_config - "
|
||||
+ "deferred_update is not supported with LMDB backend and will be ignored\n");
|
||||
+ } else {
|
||||
+ theConfig.deferred_update = PR_TRUE;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/plugins/posix-winsync/posix-wsp-ident.h b/ldap/servers/plugins/posix-winsync/posix-wsp-ident.h
|
||||
index 440bb833a..509468a6f 100644
|
||||
--- a/ldap/servers/plugins/posix-winsync/posix-wsp-ident.h
|
||||
+++ b/ldap/servers/plugins/posix-winsync/posix-wsp-ident.h
|
||||
@@ -12,8 +12,6 @@
|
||||
#define PLUGIN_MAGIC_VENDOR_STR "contac Datentechnik GmbH"
|
||||
#define PRODUCTTEXT "1.1"
|
||||
#define null NULL
|
||||
-#define true - 1
|
||||
-#define false 0
|
||||
#define POSIX_WINSYNC_MSSFU_SCHEMA "posixWinsyncMsSFUSchema"
|
||||
#define POSIX_WINSYNC_MAP_MEMBERUID "posixWinsyncMapMemberUID"
|
||||
#define POSIX_WINSYNC_CREATE_MEMBEROFTASK "posixWinsyncCreateMemberOfTask"
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index fb88488b1..ec4b540c3 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -27,6 +27,7 @@ extern "C" {
|
||||
#include "nspr.h"
|
||||
#include "portable.h"
|
||||
#include "slapi-plugin.h"
|
||||
+#include <stdbool.h>
|
||||
/*
|
||||
* XXXmcs: we can stop including slapi-plugin-compat4.h once we stop using
|
||||
* deprecated functions internally.
|
||||
@@ -1263,6 +1264,7 @@ char get_sep(char *path);
|
||||
int mkdir_p(char *dir, unsigned int mode);
|
||||
const char *ldif_getline_ro( const char **next);
|
||||
void dup_ldif_line(struct berval *copy, const char *line, const char *endline);
|
||||
+bool slapi_db_is_lmdb(void);
|
||||
|
||||
/* slapi-memberof.c */
|
||||
int slapi_memberof(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_MemberOfResult *result);
|
||||
diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c
|
||||
index 823c90f26..6d5379e11 100644
|
||||
--- a/ldap/servers/slapd/util.c
|
||||
+++ b/ldap/servers/slapd/util.c
|
||||
@@ -482,7 +482,7 @@ replace_char(char *str, char c, char c2)
|
||||
|
||||
/*
|
||||
** Break a string at the delimiter
|
||||
-** If the delimiter is not found, the string is not modified.
|
||||
+** If the delimiter is not found, the string is not modified.
|
||||
** The position immediately following the delimiter is returned.
|
||||
*/
|
||||
char *split_string_at_delim(char *str, char delim) {
|
||||
@@ -1795,3 +1795,44 @@ void dup_ldif_line(struct berval *copy, const char *line, const char *endline)
|
||||
buf[pos] = 0;
|
||||
copy->bv_len = copylen;
|
||||
}
|
||||
+
|
||||
+/*
|
||||
+ * Return true if the backend is lmdb
|
||||
+ */
|
||||
+bool
|
||||
+slapi_db_is_lmdb(void)
|
||||
+{
|
||||
+ Slapi_PBlock *search_pb;
|
||||
+ Slapi_Entry **entries = NULL;
|
||||
+ const char *config_dn = "cn=config,cn=ldbm database,cn=plugins,cn=config";
|
||||
+ int result = 0;
|
||||
+ bool is_lmdb = false;
|
||||
+
|
||||
+ search_pb = slapi_pblock_new();
|
||||
+ slapi_search_internal_set_pb(search_pb, config_dn, LDAP_SCOPE_BASE,
|
||||
+ "objectclass=*",
|
||||
+ NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(search_pb);
|
||||
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+ if (LDAP_SUCCESS != result) {
|
||||
+ /* Failed to search cn=config */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "slapi_db_is_lmdb",
|
||||
+ "Unable to search ldbm config entry, err=%d\n", result);
|
||||
+ } else {
|
||||
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
|
||||
+ if (entries && entries[0]) {
|
||||
+ Slapi_Entry *config_e = entries[0];
|
||||
+ const char *db_type = slapi_entry_attr_get_ref(config_e,
|
||||
+ "nsslapd-backend-implement");
|
||||
+ if (db_type && strcmp(db_type, "mdb") == 0) {
|
||||
+ is_lmdb = true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(search_pb);
|
||||
+ slapi_pblock_destroy(search_pb);
|
||||
+
|
||||
+ return is_lmdb;
|
||||
+}
|
||||
\ No newline at end of file
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,360 +0,0 @@
|
||||
From 708f5e4a976f255833c92fd3a141d5934f09ec48 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 1 Sep 2025 18:23:33 +0200
|
||||
Subject: [PATCH] Issue 6933 - When deferred memberof update is enabled after
|
||||
the server crashed it should not launch memberof fixup task by default
|
||||
(#6935)
|
||||
|
||||
Bug description:
|
||||
When deferred memberof update is enabled, the updates of the
|
||||
group and the members is done with different TXN.
|
||||
So there is a risk that at the time of a crash the membership
|
||||
('memberof') are invalid.
|
||||
To repair this we should run a memberof fixup task.
|
||||
The problem is that this task is resource intensive and
|
||||
should be, by default, scheduled by the administrator.
|
||||
|
||||
Fix description:
|
||||
The fix introduces a new memberof config parameter 'launchFixup'
|
||||
that is 'off' by default.
|
||||
After a crash, when it is 'on' the server launch the fixup
|
||||
task. If it is 'off' it logs a warning.
|
||||
|
||||
fixes: #6933
|
||||
|
||||
Reviewed by: Simon Pichugin (Thanks !)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 109 ++++++++++++------
|
||||
ldap/servers/plugins/memberof/memberof.c | 13 ++-
|
||||
ldap/servers/plugins/memberof/memberof.h | 2 +
|
||||
.../plugins/memberof/memberof_config.c | 11 ++
|
||||
.../lib389/cli_conf/plugins/memberof.py | 9 ++
|
||||
src/lib389/lib389/plugins.py | 30 +++++
|
||||
6 files changed, 136 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index ebca566e0..c6c03f683 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -1291,15 +1291,19 @@ def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
:setup: Standalone Instance
|
||||
:steps:
|
||||
1. Enable memberof plugin to scope SUFFIX
|
||||
- 2. create 1000 users
|
||||
- 3. Create a large groups with 500 members
|
||||
+ 2. create 500 users
|
||||
+ 3. Create a large groups with 250 members
|
||||
4. Restart the instance (using the default 2 minutes timeout)
|
||||
5. Check that users memberof and group members are in sync.
|
||||
- 6. Modify the group to have 10 members.
|
||||
+ 6. Modify the group to have 250 others members.
|
||||
7. Restart the instance with short timeout
|
||||
- 8. Check that fixup task is in progress
|
||||
- 9. Wait until fixup task is completed
|
||||
- 10. Check that users memberof and group members are in sync.
|
||||
+ 8. Check that the instance needs fixup
|
||||
+ 9. Check that deferred thread did not run fixup
|
||||
+ 10. Allow deferred thread to run fixup
|
||||
+ 11. Modify the group to have 250 others members.
|
||||
+ 12. Restart the instance with short timeout
|
||||
+ 13. Check that the instance needs fixup
|
||||
+ 14. Check that deferred thread did run fixup
|
||||
:expectedresults:
|
||||
1. should succeed
|
||||
2. should succeed
|
||||
@@ -1310,14 +1314,18 @@ def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
7. should succeed
|
||||
8. should succeed
|
||||
9. should succeed
|
||||
- 10. should succeed
|
||||
"""
|
||||
|
||||
inst = topology_st.standalone
|
||||
+ inst.stop()
|
||||
+ lpath = inst.ds_error_log._get_log_path()
|
||||
+ os.unlink(lpath)
|
||||
+ inst.start()
|
||||
inst.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
||||
errlog = DirsrvErrorLog(inst)
|
||||
test_timeout = 900
|
||||
|
||||
+
|
||||
# Step 1. Enable memberof plugin to scope SUFFIX
|
||||
memberof = MemberOfPlugin(inst)
|
||||
delay=0
|
||||
@@ -1338,8 +1346,8 @@ def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
#Creates users and groups
|
||||
users_dn = []
|
||||
|
||||
- # Step 2. create 1000 users
|
||||
- for i in range(1000):
|
||||
+ # Step 2. create 500 users
|
||||
+ for i in range(500):
|
||||
CN = '%s%d' % (USER_CN, i)
|
||||
users = UserAccounts(inst, SUFFIX)
|
||||
user_props = TEST_USER_PROPERTIES.copy()
|
||||
@@ -1349,7 +1357,7 @@ def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
|
||||
# Step 3. Create a large groups with 250 members
|
||||
groups = Groups(inst, SUFFIX)
|
||||
- testgroup = groups.create(properties={'cn': 'group500', 'member': users_dn[0:249]})
|
||||
+ testgroup = groups.create(properties={'cn': 'group50', 'member': users_dn[0:249]})
|
||||
|
||||
# Step 4. Restart the instance (using the default 2 minutes timeout)
|
||||
time.sleep(10)
|
||||
@@ -1363,7 +1371,7 @@ def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
check_memberof_consistency(inst, testgroup)
|
||||
|
||||
# Step 6. Modify the group to get another big group.
|
||||
- testgroup.replace('member', users_dn[500:999])
|
||||
+ testgroup.replace('member', users_dn[250:499])
|
||||
|
||||
# Step 7. Restart the instance with short timeout
|
||||
pattern = 'deferred_thread_func - thread has stopped'
|
||||
@@ -1376,40 +1384,71 @@ def test_shutdown_on_deferred_memberof(topology_st, request):
|
||||
nbcleanstop = len(errlog.match(pattern))
|
||||
assert nbcleanstop == original_nbcleanstop
|
||||
|
||||
- original_nbfixupmsg = count_global_fixup_message(errlog)
|
||||
log.info(f'Instance restarted after timeout at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
inst.restart()
|
||||
assert inst.status()
|
||||
log.info(f'Restart completed at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
|
||||
+ # Step 9.
|
||||
# Check that memberofneedfixup is present
|
||||
- dse = DSEldif(inst)
|
||||
- assert dse.get(memberof.dn, 'memberofneedfixup', single=True)
|
||||
-
|
||||
- # Step 8. Check that fixup task is in progress
|
||||
- # Note we have to wait as there may be some delay
|
||||
- elapsed_time = 0
|
||||
- nbfixupmsg = count_global_fixup_message(errlog)
|
||||
- while nbfixupmsg[0] == original_nbfixupmsg[0]:
|
||||
- assert elapsed_time <= test_timeout
|
||||
- assert inst.status()
|
||||
- time.sleep(5)
|
||||
- elapsed_time += 5
|
||||
- nbfixupmsg = count_global_fixup_message(errlog)
|
||||
-
|
||||
- # Step 9. Wait until fixup task is completed
|
||||
- while nbfixupmsg[1] == original_nbfixupmsg[1]:
|
||||
- assert elapsed_time <= test_timeout
|
||||
- assert inst.status()
|
||||
- time.sleep(10)
|
||||
- elapsed_time += 10
|
||||
- nbfixupmsg = count_global_fixup_message(errlog)
|
||||
-
|
||||
- # Step 10. Check that users memberof and group members are in sync.
|
||||
+ # and fixup task was not launched because by default launch_fixup is no
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.set_memberofdeferredupdate("on")
|
||||
+ if (memberof.get_memberofdeferredupdate() and memberof.get_memberofdeferredupdate().lower() != "on"):
|
||||
+ pytest.skip("Memberof deferred update not enabled or not supported.");
|
||||
+ else:
|
||||
+ delay=10
|
||||
+ value = memberof.get_memberofneedfixup()
|
||||
+ assert ((str(value).lower() == "yes") or (str(value).lower() == "true"))
|
||||
+ assert len(errlog.match('.*It is recommended to launch memberof fixup task.*')) == 1
|
||||
+
|
||||
+ # Step 10. allow the server to launch the fixup task
|
||||
+ inst.stop()
|
||||
+ inst.deleteErrorLogs()
|
||||
+ inst.start()
|
||||
+ log.info(f'set memberoflaunchfixup=ON')
|
||||
+ memberof.set_memberoflaunchfixup('on')
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Step 11. Modify the group to get another big group.
|
||||
+ testgroup.replace('member', users_dn[250:499])
|
||||
+
|
||||
+ # Step 12. then kill/reset errorlog/restart
|
||||
+ _kill_instance(inst, sig=signal.SIGKILL, delay=5)
|
||||
+ log.info(f'Instance restarted after timeout at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
+ inst.restart()
|
||||
+ assert inst.status()
|
||||
+ log.info(f'Restart completed at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
+
|
||||
+ # step 13. Check that memberofneedfixup is present
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ value = memberof.get_memberofneedfixup()
|
||||
+ assert ((str(value).lower() == "yes") or (str(value).lower() == "true"))
|
||||
+
|
||||
+ # step 14. fixup task was not launched because by default launch_fixup is no
|
||||
+ assert len(errlog.match('.*It is recommended to launch memberof fixup task.*')) == 0
|
||||
+
|
||||
+ # Check that users memberof and group members are in sync.
|
||||
time.sleep(delay)
|
||||
check_memberof_consistency(inst, testgroup)
|
||||
|
||||
|
||||
+ def fin():
|
||||
+
|
||||
+ for dn in users_dn:
|
||||
+ try:
|
||||
+ inst.delete_s(dn)
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+
|
||||
+ try:
|
||||
+ inst.delete_s(testgroup.dn)
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index e00ad8de8..7b3f55342 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1022,9 +1022,16 @@ deferred_thread_func(void *arg)
|
||||
* keep running this thread until plugin is signaled to close
|
||||
*/
|
||||
g_incr_active_threadcnt();
|
||||
- if (memberof_get_config()->need_fixup && perform_needed_fixup()) {
|
||||
- slapi_log_err(SLAPI_LOG_ALERT, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "Failure occured during global fixup task: memberof values are invalid\n");
|
||||
+ if (memberof_get_config()->need_fixup) {
|
||||
+ if (memberof_get_config()->launch_fixup) {
|
||||
+ if (perform_needed_fixup()) {
|
||||
+ slapi_log_err(SLAPI_LOG_ALERT, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Failure occurred during global fixup task: memberof values are invalid\n");
|
||||
+ }
|
||||
+ } else {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "It is recommended to launch memberof fixup task\n");
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
"deferred_thread_func - thread is starting "
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.h b/ldap/servers/plugins/memberof/memberof.h
|
||||
index 1c0d2d308..7ef97b0cd 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.h
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.h
|
||||
@@ -44,6 +44,7 @@
|
||||
#define MEMBEROF_DEFERRED_UPDATE_ATTR "memberOfDeferredUpdate"
|
||||
#define MEMBEROF_AUTO_ADD_OC "memberOfAutoAddOC"
|
||||
#define MEMBEROF_NEED_FIXUP "memberOfNeedFixup"
|
||||
+#define MEMBEROF_LAUNCH_FIXUP "memberOfLaunchFixup"
|
||||
#define NSMEMBEROF "nsMemberOf"
|
||||
#define MEMBEROF_ENTRY_SCOPE_EXCLUDE_SUBTREE "memberOfEntryScopeExcludeSubtree"
|
||||
#define DN_SYNTAX_OID "1.3.6.1.4.1.1466.115.121.1.12"
|
||||
@@ -139,6 +140,7 @@ typedef struct memberofconfig
|
||||
Slapi_Task *task;
|
||||
int need_fixup;
|
||||
bool is_lmdb;
|
||||
+ PRBool launch_fixup;
|
||||
} MemberOfConfig;
|
||||
|
||||
/* The key to access the hash table is the normalized DN
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
index 0336d8517..d1c6a78ce 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof_config.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof_config.c
|
||||
@@ -472,6 +472,7 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
const char *deferred_update = NULL;
|
||||
char *auto_add_oc = NULL;
|
||||
const char *needfixup = NULL;
|
||||
+ const char *launchfixup = NULL;
|
||||
int num_vals = 0;
|
||||
|
||||
*returncode = LDAP_SUCCESS;
|
||||
@@ -508,6 +509,7 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
deferred_update = slapi_entry_attr_get_ref(e, MEMBEROF_DEFERRED_UPDATE_ATTR);
|
||||
auto_add_oc = slapi_entry_attr_get_charptr(e, MEMBEROF_AUTO_ADD_OC);
|
||||
needfixup = slapi_entry_attr_get_ref(e, MEMBEROF_NEED_FIXUP);
|
||||
+ launchfixup = slapi_entry_attr_get_ref(e, MEMBEROF_LAUNCH_FIXUP);
|
||||
|
||||
if (auto_add_oc == NULL) {
|
||||
auto_add_oc = slapi_ch_strdup(NSMEMBEROF);
|
||||
@@ -648,6 +650,15 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
|
||||
}
|
||||
}
|
||||
}
|
||||
+ theConfig.launch_fixup = PR_FALSE;
|
||||
+ if (theConfig.deferred_update) {
|
||||
+ /* The automatic fixup task is only triggered when
|
||||
+ * deferred update is on
|
||||
+ */
|
||||
+ if (launchfixup && (strcasecmp(launchfixup, "on") == 0)) {
|
||||
+ theConfig.launch_fixup = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
if (allBackends) {
|
||||
if (strcasecmp(allBackends, "on") == 0) {
|
||||
diff --git a/src/lib389/lib389/cli_conf/plugins/memberof.py b/src/lib389/lib389/cli_conf/plugins/memberof.py
|
||||
index 90c1af2c3..598fe0bbc 100644
|
||||
--- a/src/lib389/lib389/cli_conf/plugins/memberof.py
|
||||
+++ b/src/lib389/lib389/cli_conf/plugins/memberof.py
|
||||
@@ -23,6 +23,8 @@ arg_to_attr = {
|
||||
'scope': 'memberOfEntryScope',
|
||||
'exclude': 'memberOfEntryScopeExcludeSubtree',
|
||||
'autoaddoc': 'memberOfAutoAddOC',
|
||||
+ 'deferredupdate': 'memberOfDeferredUpdate',
|
||||
+ 'launchfixup': 'memberOfLaunchFixup',
|
||||
'config_entry': 'nsslapd-pluginConfigArea'
|
||||
}
|
||||
|
||||
@@ -119,6 +121,13 @@ def _add_parser_args(parser):
|
||||
help='If an entry does not have an object class that allows the memberOf attribute '
|
||||
'then the memberOf plugin will automatically add the object class listed '
|
||||
'in the memberOfAutoAddOC parameter')
|
||||
+ parser.add_argument('--deferredupdate', choices=['on', 'off'], type=str.lower,
|
||||
+ help='Specifies that the updates of the members are done after the completion '
|
||||
+ 'of the update of the target group. In addition each update (group/members) '
|
||||
+ 'uses its own transaction')
|
||||
+ parser.add_argument('--launchfixup', choices=['on', 'off'], type=str.lower,
|
||||
+ help='Specify that if the server disorderly shutdown (crash, kill,..) then '
|
||||
+ 'at restart the memberof fixup task is launched automatically')
|
||||
|
||||
|
||||
def create_parser(subparsers):
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 31bbfa502..0878e18d1 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -962,6 +962,36 @@ class MemberOfPlugin(Plugin):
|
||||
|
||||
self.remove_all('memberofdeferredupdate')
|
||||
|
||||
+ def get_memberofneedfixup(self):
|
||||
+ """Get memberofneedfixup attribute"""
|
||||
+
|
||||
+ return self.get_attr_val_utf8_l('memberofneedfixup')
|
||||
+
|
||||
+ def get_memberofneedfixup_formatted(self):
|
||||
+ """Display memberofneedfixup attribute"""
|
||||
+
|
||||
+ return self.display_attr('memberofneedfixup')
|
||||
+
|
||||
+ def get_memberoflaunchfixup(self):
|
||||
+ """Get memberoflaunchfixup attribute"""
|
||||
+
|
||||
+ return self.get_attr_val_utf8_l('memberoflaunchfixup')
|
||||
+
|
||||
+ def get_memberoflaunchfixup_formatted(self):
|
||||
+ """Display memberoflaunchfixup attribute"""
|
||||
+
|
||||
+ return self.display_attr('memberoflaunchfixup')
|
||||
+
|
||||
+ def set_memberoflaunchfixup(self, value):
|
||||
+ """Set memberoflaunchfixup attribute"""
|
||||
+
|
||||
+ self.set('memberoflaunchfixup', value)
|
||||
+
|
||||
+ def remove_memberoflaunchfixup(self):
|
||||
+ """Remove all memberoflaunchfixup attributes"""
|
||||
+
|
||||
+ self.remove_all('memberoflaunchfixup')
|
||||
+
|
||||
def get_autoaddoc(self):
|
||||
"""Get memberofautoaddoc attribute"""
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,824 +0,0 @@
|
||||
From d5340791dea4317911351b39e0da00d086b39f99 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 18 Aug 2025 09:13:12 +0200
|
||||
Subject: [PATCH] Issue 6928 - The parentId attribute is indexed with improper
|
||||
matching rule
|
||||
|
||||
Bug Description:
|
||||
`parentId` attribute contains integer values and needs to be indexed with
|
||||
`integerOrderingMatch` matching rule. This attribute is a system attribute
|
||||
and the configuration entry for this attribute is created when a backend
|
||||
is created. The bug is that the per backend configuration entry does not
|
||||
contain `nsMatchingRule: integerOrderingMatch`.
|
||||
|
||||
Fix Description:
|
||||
* Update `ldbm_instance_create_default_indexes` to support matching rules
|
||||
and update default system index configuration for `parentId` to include
|
||||
`integerOrderingMatch` matching rule.
|
||||
* Add healthcheck linter for default system indexes and indexes created
|
||||
by RetroCL and USN plugins.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6928
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6915
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/basic/basic_test.py | 2 +-
|
||||
.../healthcheck/health_system_indexes_test.py | 456 ++++++++++++++++++
|
||||
ldap/ldif/template-dse.ldif.in | 8 +
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 32 +-
|
||||
src/lib389/lib389/backend.py | 133 ++++-
|
||||
src/lib389/lib389/lint.py | 29 ++
|
||||
6 files changed, 645 insertions(+), 15 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
index 02cb13b10..eec81043d 100644
|
||||
--- a/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
@@ -739,7 +739,7 @@ def test_basic_db2index(topology_st):
|
||||
topology_st.standalone.db2index(bename=DEFAULT_BENAME, attrs=indexes)
|
||||
log.info('Checking the server logs for %d backend indexes INFO' % numIndexes)
|
||||
for indexNum, index in enumerate(indexes):
|
||||
- if index in "entryrdn":
|
||||
+ if index in ["entryrdn", "ancestorid"]:
|
||||
assert topology_st.standalone.searchErrorsLog(
|
||||
f'INFO - {dbprefix}_db2index - {DEFAULT_BENAME}: Indexing {index}')
|
||||
else:
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
new file mode 100644
|
||||
index 000000000..61972d60c
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -0,0 +1,456 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+
|
||||
+import pytest
|
||||
+import os
|
||||
+
|
||||
+from lib389.backend import Backends
|
||||
+from lib389.index import Index
|
||||
+from lib389.plugins import (
|
||||
+ USNPlugin,
|
||||
+ RetroChangelogPlugin,
|
||||
+)
|
||||
+from lib389.utils import logging, ds_is_newer
|
||||
+from lib389.cli_base import FakeArgs
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.cli_ctl.health import health_check_run
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+CMD_OUTPUT = "No issues found."
|
||||
+JSON_OUTPUT = "[]"
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def usn_plugin_enabled(topology_st, request):
|
||||
+ """Fixture to enable USN plugin and ensure cleanup after test"""
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Enable USN plugin")
|
||||
+ usn_plugin = USNPlugin(standalone)
|
||||
+ usn_plugin.enable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Disable USN plugin")
|
||||
+ usn_plugin.disable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+ return usn_plugin
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def retrocl_plugin_enabled(topology_st, request):
|
||||
+ """Fixture to enable RetroCL plugin and ensure cleanup after test"""
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Enable RetroCL plugin")
|
||||
+ retrocl_plugin = RetroChangelogPlugin(standalone)
|
||||
+ retrocl_plugin.enable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Disable RetroCL plugin")
|
||||
+ retrocl_plugin.disable()
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+ return retrocl_plugin
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def log_buffering_enabled(topology_st, request):
|
||||
+ """Fixture to enable log buffering and restore original setting after test"""
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ original_value = standalone.config.get_attr_val_utf8("nsslapd-accesslog-logbuffering")
|
||||
+
|
||||
+ log.info("Enable log buffering")
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Restore original log buffering setting")
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", original_value)
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+ return standalone
|
||||
+
|
||||
+
|
||||
+def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searched_code2=None):
|
||||
+ args = FakeArgs()
|
||||
+ args.instance = instance.serverid
|
||||
+ args.verbose = instance.verbose
|
||||
+ args.list_errors = False
|
||||
+ args.list_checks = False
|
||||
+ args.check = [
|
||||
+ "config",
|
||||
+ "refint",
|
||||
+ "backends",
|
||||
+ "monitor-disk-space",
|
||||
+ "logs",
|
||||
+ "memberof",
|
||||
+ ]
|
||||
+ args.dry_run = False
|
||||
+
|
||||
+ # If we are using BDB as a backend, we will get error DSBLE0006 on new versions
|
||||
+ if (
|
||||
+ ds_is_newer("3.0.0")
|
||||
+ and instance.get_db_lib() == "bdb"
|
||||
+ and (searched_code is CMD_OUTPUT or searched_code is JSON_OUTPUT)
|
||||
+ ):
|
||||
+ searched_code = "DSBLE0006"
|
||||
+
|
||||
+ if json:
|
||||
+ log.info("Use healthcheck with --json option")
|
||||
+ args.json = json
|
||||
+ health_check_run(instance, topology.logcap.log, args)
|
||||
+ assert topology.logcap.contains(searched_code)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code)
|
||||
+
|
||||
+ if searched_code2 is not None:
|
||||
+ assert topology.logcap.contains(searched_code2)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code2)
|
||||
+ else:
|
||||
+ log.info("Use healthcheck without --json option")
|
||||
+ args.json = json
|
||||
+ health_check_run(instance, topology.logcap.log, args)
|
||||
+
|
||||
+ assert topology.logcap.contains(searched_code)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code)
|
||||
+
|
||||
+ if searched_code2 is not None:
|
||||
+ assert topology.logcap.contains(searched_code2)
|
||||
+ log.info("healthcheck returned searched code: %s" % searched_code2)
|
||||
+
|
||||
+ log.info("Clear the log")
|
||||
+ topology.logcap.flush()
|
||||
+
|
||||
+
|
||||
+def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when parentId system index is missing
|
||||
+
|
||||
+ :id: 2653f16f-cc9c-4fad-9d8c-86a3457c6d0d
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove parentId index
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Re-add the parentId index
|
||||
+ 6. Use healthcheck without --json option
|
||||
+ 7. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 code and related details
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. Success
|
||||
+ 6. healthcheck reports no issues found
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.delete()
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the parentId index")
|
||||
+ backend = Backends(standalone).get("userRoot")
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
|
||||
+
|
||||
+ :id: 7ffa71db-8995-430a-bed8-59bce944221c
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove integerOrderingMatch matching rule from parentId index
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Re-add the matching rule
|
||||
+ 6. Use healthcheck without --json option
|
||||
+ 7. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 code and related details
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. Success
|
||||
+ 6. healthcheck reports no issues found
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove integerOrderingMatch matching rule from parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
|
||||
+
|
||||
+ :id: 4879dfc8-cd96-43e6-9ebc-053fc8e64ad0
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable USN plugin
|
||||
+ 3. Remove entryusn index
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the entryusn index
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ ENTRYUSN_DN = "cn=entryusn,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove entryusn index")
|
||||
+ entryusn_index = Index(standalone, ENTRYUSN_DN)
|
||||
+ entryusn_index.delete()
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the entryusn index")
|
||||
+ backend = Backends(standalone).get("userRoot")
|
||||
+ backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_usn_plugin_missing_matching_rule(topology_st, usn_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing integerOrderingMatch
|
||||
+
|
||||
+ :id: b00b419f-2ca6-451f-a9b2-f22ad6b10718
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable USN plugin
|
||||
+ 3. Remove integerOrderingMatch matching rule from entryusn index
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the matching rule
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ ENTRYUSN_DN = "cn=entryusn,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Create or modify entryusn index without integerOrderingMatch")
|
||||
+ entryusn_index = Index(standalone, ENTRYUSN_DN)
|
||||
+ entryusn_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule")
|
||||
+ entryusn_index = Index(standalone, ENTRYUSN_DN)
|
||||
+ entryusn_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_retrocl_plugin_missing_changenumber(topology_st, retrocl_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when RetroCL plugin is enabled but changeNumber index is missing from changelog backend
|
||||
+
|
||||
+ :id: 3e1a3625-4e6f-4e23-868d-6f32e018ad7e
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable RetroCL plugin
|
||||
+ 3. Remove changeNumber index from changelog backend
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the changeNumber index
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove changeNumber index from changelog backend")
|
||||
+ changenumber_dn = "cn=changenumber,cn=index,cn=changelog,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ changenumber_index = Index(standalone, changenumber_dn)
|
||||
+ changenumber_index.delete()
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the changeNumber index")
|
||||
+ backends = Backends(standalone)
|
||||
+ changelog_backend = backends.get("changelog")
|
||||
+ changelog_backend.add_index("changenumber", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
+ log.info("Successfully re-added changeNumber index")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enabled, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when RetroCL plugin is enabled but changeNumber index is missing integerOrderingMatch
|
||||
+
|
||||
+ :id: 1c68b1b2-90a9-4ec0-815a-a626b20744fe
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Enable RetroCL plugin
|
||||
+ 3. Remove integerOrderingMatch matching rule from changeNumber index
|
||||
+ 4. Use healthcheck without --json option
|
||||
+ 5. Use healthcheck with --json option
|
||||
+ 6. Re-add the matching rule
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. healthcheck reports DSBLE0007 code and related details
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove integerOrderingMatch matching rule from changeNumber index")
|
||||
+ changenumber_dn = "cn=changenumber,cn=index,cn=changelog,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ changenumber_index = Index(standalone, changenumber_dn)
|
||||
+ changenumber_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule")
|
||||
+ changenumber_index = Index(standalone, changenumber_dn)
|
||||
+ changenumber_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+ log.info("Successfully re-added integerOrderingMatch to changeNumber index")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
+
|
||||
+ :id: f7cfcd6e-3c47-4ba5-bb2b-1f8e7a29c899
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove multiple system indexes (parentId, nsUniqueId)
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Re-add the missing indexes
|
||||
+ 6. Use healthcheck without --json option
|
||||
+ 7. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 code and related details
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. Success
|
||||
+ 6. healthcheck reports no issues found
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ NSUNIQUEID_DN = "cn=nsuniqueid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove multiple system indexes")
|
||||
+ for index_dn in [PARENTID_DN, NSUNIQUEID_DN]:
|
||||
+ index = Index(standalone, index_dn)
|
||||
+ index.delete()
|
||||
+ log.info(f"Successfully removed index: {index_dn}")
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the missing system indexes")
|
||||
+ backend = Backends(standalone).get("userRoot")
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
+ backend.add_index("nsuniqueid", ["eq"])
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
|
||||
index 1def62aed..75cacaa09 100644
|
||||
--- a/ldap/ldif/template-dse.ldif.in
|
||||
+++ b/ldap/ldif/template-dse.ldif.in
|
||||
@@ -990,6 +990,14 @@ cn: aci
|
||||
nssystemindex: true
|
||||
nsindextype: pres
|
||||
|
||||
+dn: cn=ancestorid,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
+objectclass: top
|
||||
+objectclass: nsIndex
|
||||
+cn: ancestorid
|
||||
+nssystemindex: true
|
||||
+nsindextype: eq
|
||||
+nsmatchingrule: integerOrderingMatch
|
||||
+
|
||||
dn: cn=cn,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
objectclass: top
|
||||
objectclass: nsIndex
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index e57962248..f9a546661 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
/* Forward declarations */
|
||||
static void ldbm_instance_destructor(void **arg);
|
||||
-Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4);
|
||||
+Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr);
|
||||
|
||||
|
||||
/* Creates and initializes a new ldbm_instance structure.
|
||||
@@ -127,7 +127,7 @@ done:
|
||||
* Take a bunch of strings, and create a index config entry
|
||||
*/
|
||||
Slapi_Entry *
|
||||
-ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4)
|
||||
+ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr)
|
||||
{
|
||||
Slapi_Entry *e = slapi_entry_alloc();
|
||||
struct berval *vals[2];
|
||||
@@ -162,6 +162,12 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
|
||||
slapi_entry_add_values(e, "nsIndexType", vals);
|
||||
}
|
||||
|
||||
+ if (mr) {
|
||||
+ val.bv_val = mr;
|
||||
+ val.bv_len = strlen(mr);
|
||||
+ slapi_entry_add_values(e, "nsMatchingRule", vals);
|
||||
+ }
|
||||
+
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -184,47 +190,47 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ACL routines.
|
||||
*/
|
||||
if (entryrdn_get_switch()) { /* subtree-rename: on */
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
} else {
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* For MMR, we need this attribute (to replace use of dncomp in delete). */
|
||||
- e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* write the dse file only on the final index */
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* ldbm_instance_config_add_index_entry(inst, 2, argv); */
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0);
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
|
||||
@@ -233,7 +239,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 5bff61c58..1d2a51290 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -34,7 +34,8 @@ from lib389.encrypted_attributes import EncryptedAttr, EncryptedAttrs
|
||||
# This is for sample entry creation.
|
||||
from lib389.configurations import get_sample_entries
|
||||
|
||||
-from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0004, DSBLE0005, DSBLE0006, DSVIRTLE0001, DSCLLE0001
|
||||
+from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0004, DSBLE0005, DSBLE0006, DSBLE0007, DSVIRTLE0001, DSCLLE0001
|
||||
+from lib389.plugins import USNPlugin
|
||||
|
||||
|
||||
class BackendLegacy(object):
|
||||
@@ -607,6 +608,136 @@ class Backend(DSLdapObject):
|
||||
self._log.debug(f"_lint_cl_trimming - backend ({suffix}) is not replicated")
|
||||
pass
|
||||
|
||||
+ def _lint_system_indexes(self):
|
||||
+ """Check that system indexes are correctly configured"""
|
||||
+ bename = self.lint_uid()
|
||||
+ suffix = self.get_attr_val_utf8('nsslapd-suffix')
|
||||
+ indexes = self.get_indexes()
|
||||
+
|
||||
+ # Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
+ expected_system_indexes = {
|
||||
+ 'entryrdn': {'types': ['subtree'], 'matching_rule': None},
|
||||
+ 'parentId': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ 'ancestorId': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ 'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
+ 'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'nsUniqueId': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'nsds5ReplConflict': {'types': ['eq', 'pres'], 'matching_rule': None}
|
||||
+ }
|
||||
+
|
||||
+ # Default system indexes taken from ldap/ldif/template-dse.ldif.in
|
||||
+ expected_system_indexes.update({
|
||||
+ 'nsCertSubjectDN': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'numsubordinates': {'types': ['pres'], 'matching_rule': None},
|
||||
+ 'nsTombstoneCSN': {'types': ['eq'], 'matching_rule': None},
|
||||
+ 'targetuniqueid': {'types': ['eq'], 'matching_rule': None}
|
||||
+ })
|
||||
+
|
||||
+
|
||||
+ # RetroCL plugin creates its own backend with an additonal index for changeNumber
|
||||
+ # See ldap/servers/plugins/retrocl/retrocl_create.c
|
||||
+ if suffix.lower() == 'cn=changelog':
|
||||
+ expected_system_indexes.update({
|
||||
+ 'changeNumber': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'}
|
||||
+ })
|
||||
+
|
||||
+ # USN plugin requires entryusn attribute indexed for equality with integerOrderingMatch rule
|
||||
+ # See ldap/ldif/template-dse.ldif.in
|
||||
+ try:
|
||||
+ usn_plugin = USNPlugin(self._instance)
|
||||
+ if usn_plugin.status():
|
||||
+ expected_system_indexes.update({
|
||||
+ 'entryusn': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'}
|
||||
+ })
|
||||
+ except Exception as e:
|
||||
+ self._log.debug(f"_lint_system_indexes - Error checking USN plugin: {e}")
|
||||
+
|
||||
+ discrepancies = []
|
||||
+ remediation_commands = []
|
||||
+ reindex_attrs = set()
|
||||
+
|
||||
+ for attr_name, expected_config in expected_system_indexes.items():
|
||||
+ try:
|
||||
+ index = indexes.get(attr_name)
|
||||
+ # Check if index exists
|
||||
+ if index is None:
|
||||
+ discrepancies.append(f"Missing system index: {attr_name}")
|
||||
+ # Generate remediation command
|
||||
+ index_types = ' '.join([f"--add-type {t}" for t in expected_config['types']])
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
|
||||
+ if expected_config['matching_rule']:
|
||||
+ cmd += f" --add-mr {expected_config['matching_rule']}"
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
+ else:
|
||||
+ # Index exists, check configuration
|
||||
+ actual_types = index.get_attr_vals_utf8('nsIndexType') or []
|
||||
+ actual_mrs = index.get_attr_vals_utf8('nsMatchingRule') or []
|
||||
+
|
||||
+ # Normalize to lowercase for comparison
|
||||
+ actual_types = [t.lower() for t in actual_types]
|
||||
+ expected_types = [t.lower() for t in expected_config['types']]
|
||||
+
|
||||
+ # Check index types
|
||||
+ missing_types = set(expected_types) - set(actual_types)
|
||||
+ if missing_types:
|
||||
+ discrepancies.append(f"Index {attr_name} missing types: {', '.join(missing_types)}")
|
||||
+ missing_type_args = ' '.join([f"--add-type {t}" for t in missing_types])
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} {missing_type_args}"
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
+
|
||||
+ # Check matching rules
|
||||
+ expected_mr = expected_config['matching_rule']
|
||||
+ if expected_mr:
|
||||
+ actual_mrs_lower = [mr.lower() for mr in actual_mrs]
|
||||
+ if expected_mr.lower() not in actual_mrs_lower:
|
||||
+ discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
|
||||
+ # Add the missing matching rule
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
+
|
||||
+ except Exception as e:
|
||||
+ self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
+ discrepancies.append(f"Unable to check index {attr_name}: {str(e)}")
|
||||
+
|
||||
+ if discrepancies:
|
||||
+ report = copy.deepcopy(DSBLE0007)
|
||||
+ report['check'] = f'backends:{bename}:system_indexes'
|
||||
+ report['items'] = [suffix]
|
||||
+
|
||||
+ expected_indexes_list = []
|
||||
+ for attr_name, config in expected_system_indexes.items():
|
||||
+ types_str = "', '".join(config['types'])
|
||||
+ index_desc = f"- {attr_name}: index type{'s' if len(config['types']) > 1 else ''} '{types_str}'"
|
||||
+ if config['matching_rule']:
|
||||
+ index_desc += f" with matching rule '{config['matching_rule']}'"
|
||||
+ expected_indexes_list.append(index_desc)
|
||||
+
|
||||
+ formatted_expected_indexes = '\n'.join(expected_indexes_list)
|
||||
+ report['detail'] = report['detail'].replace('EXPECTED_INDEXES', formatted_expected_indexes)
|
||||
+ report['detail'] = report['detail'].replace('DISCREPANCIES', '\n'.join([f"- {d}" for d in discrepancies]))
|
||||
+
|
||||
+ formatted_commands = '\n'.join([f" # {cmd}" for cmd in remediation_commands])
|
||||
+ report['fix'] = report['fix'].replace('REMEDIATION_COMMANDS', formatted_commands)
|
||||
+
|
||||
+ # Generate specific reindex commands for affected attributes
|
||||
+ if reindex_attrs:
|
||||
+ reindex_commands = []
|
||||
+ for attr in sorted(reindex_attrs):
|
||||
+ reindex_cmd = f"dsconf YOUR_INSTANCE backend index reindex {bename} --attr {attr}"
|
||||
+ reindex_commands.append(f" # {reindex_cmd}")
|
||||
+ formatted_reindex_commands = '\n'.join(reindex_commands)
|
||||
+ else:
|
||||
+ formatted_reindex_commands = " # No reindexing needed"
|
||||
+
|
||||
+ report['fix'] = report['fix'].replace('REINDEX_COMMANDS', formatted_reindex_commands)
|
||||
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
||||
+ report['fix'] = report['fix'].replace('BACKEND_NAME', bename)
|
||||
+ yield report
|
||||
+
|
||||
def create_sample_entries(self, version):
|
||||
"""Creates sample entries under nsslapd-suffix value
|
||||
|
||||
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||||
index 460bf64fc..bbfa0ca5b 100644
|
||||
--- a/src/lib389/lib389/lint.py
|
||||
+++ b/src/lib389/lib389/lint.py
|
||||
@@ -86,6 +86,35 @@ DSBLE0006 = {
|
||||
'fix': 'Migrate the backend to MDB.'
|
||||
}
|
||||
|
||||
+DSBLE0007 = {
|
||||
+ 'dsle': 'DSBLE0007',
|
||||
+ 'severity': 'HIGH',
|
||||
+ 'description': 'Missing or incorrect system indexes.',
|
||||
+ 'items': [],
|
||||
+ 'detail': """System indexes are essential for proper directory server operation. Missing or
|
||||
+incorrectly configured system indexes can lead to poor search performance, replication
|
||||
+issues, and other operational problems.
|
||||
+
|
||||
+The following system indexes should be present with correct configuration:
|
||||
+EXPECTED_INDEXES
|
||||
+
|
||||
+Current discrepancies:
|
||||
+DISCREPANCIES
|
||||
+""",
|
||||
+ 'fix': """Add the missing system indexes or fix the incorrect configurations using dsconf:
|
||||
+
|
||||
+REMEDIATION_COMMANDS
|
||||
+
|
||||
+After adding or modifying indexes, you may need to reindex the affected attributes:
|
||||
+
|
||||
+REINDEX_COMMANDS
|
||||
+
|
||||
+WARNING: Reindexing can be resource-intensive and may impact server performance on a live system.
|
||||
+Consider scheduling reindexing during maintenance windows or periods of low activity. For production
|
||||
+systems, you may want to reindex offline or use the --wait option to monitor task completion.
|
||||
+"""
|
||||
+}
|
||||
+
|
||||
# Config checks
|
||||
DSCLE0002 = {
|
||||
'dsle': 'DSCLE0002',
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,569 +0,0 @@
|
||||
From b16d404af7b473ff2da64c4ee5f27c633162a255 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 19 Aug 2025 14:30:15 -0700
|
||||
Subject: [PATCH] Issue 6936 - Make user/subtree policy creation idempotent
|
||||
(#6937)
|
||||
|
||||
Description: Correct the CLI mapping typo to use 'nsslapd-pwpolicy-local',
|
||||
rework subtree policy detection to validate CoS templates and add user-policy detection.
|
||||
Make user/subtree policy creation idempotent via ensure_state, and improve deletion
|
||||
logic to distinguish subtree vs user policies and fail if none exist.
|
||||
|
||||
Add a test suite (pwp_history_local_override_test.py) exercising global-only and local-only
|
||||
history enforcement, local overriding global counts, immediate effect of dsconf updates,
|
||||
and fallback to global after removing a user policy, ensuring reliable behavior
|
||||
and preventing regressions.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6936
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
.../pwp_history_local_override_test.py | 351 ++++++++++++++++++
|
||||
src/lib389/lib389/cli_conf/pwpolicy.py | 4 +-
|
||||
src/lib389/lib389/pwpolicy.py | 107 ++++--
|
||||
3 files changed, 424 insertions(+), 38 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
new file mode 100644
|
||||
index 000000000..6d72725fa
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
@@ -0,0 +1,351 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import os
|
||||
+import time
|
||||
+import ldap
|
||||
+import pytest
|
||||
+import subprocess
|
||||
+import logging
|
||||
+
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD, DN_CONFIG
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.domain import Domain
|
||||
+from lib389.pwpolicy import PwPolicyManager
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+if DEBUGGING:
|
||||
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+else:
|
||||
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+OU_DN = f"ou=People,{DEFAULT_SUFFIX}"
|
||||
+USER_ACI = '(targetattr="userpassword || passwordHistory")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)'
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(autouse=True, scope="function")
|
||||
+def restore_global_policy(topology_st, request):
|
||||
+ """Snapshot and restore global password policy around each test in this file."""
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ attrs = [
|
||||
+ 'nsslapd-pwpolicy-local',
|
||||
+ 'nsslapd-pwpolicy-inherit-global',
|
||||
+ 'passwordHistory',
|
||||
+ 'passwordInHistory',
|
||||
+ 'passwordChange',
|
||||
+ ]
|
||||
+
|
||||
+ entry = inst.getEntry(DN_CONFIG, ldap.SCOPE_BASE, '(objectClass=*)', attrs)
|
||||
+ saved = {attr: entry.getValue(attr) for attr in attrs}
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ for attr, value in saved.items():
|
||||
+ inst.config.replace(attr, value)
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def setup_entries(topology_st, request):
|
||||
+ """Create test OU and user, and install an ACI for self password changes."""
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ suffix = Domain(inst, DEFAULT_SUFFIX)
|
||||
+ suffix.add('aci', USER_ACI)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ try:
|
||||
+ user = users.create_test_user(uid=1)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ user = users.get("test_user_1")
|
||||
+
|
||||
+ def fin():
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(OU_DN)
|
||||
+ except Exception as e:
|
||||
+ if "No password policy" in str(e):
|
||||
+ pass
|
||||
+ else:
|
||||
+ raise e
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(user.dn)
|
||||
+ except Exception as e:
|
||||
+ if "No password policy" in str(e):
|
||||
+ pass
|
||||
+ else:
|
||||
+ raise e
|
||||
+ suffix.remove('aci', USER_ACI)
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return user
|
||||
+
|
||||
+
|
||||
+def set_user_password(inst, user, new_password, bind_as_user_password=None, expect_violation=False):
|
||||
+ if bind_as_user_password is not None:
|
||||
+ user.rebind(bind_as_user_password)
|
||||
+ try:
|
||||
+ user.reset_password(new_password)
|
||||
+ if expect_violation:
|
||||
+ pytest.fail("Password change unexpectedly succeeded")
|
||||
+ except ldap.CONSTRAINT_VIOLATION:
|
||||
+ if not expect_violation:
|
||||
+ pytest.fail("Password change unexpectedly rejected with CONSTRAINT_VIOLATION")
|
||||
+ finally:
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def set_global_history(inst, enabled: bool, count: int, inherit_global: str = 'on'):
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ inst.config.replace('nsslapd-pwpolicy-local', 'on')
|
||||
+ inst.config.replace('nsslapd-pwpolicy-inherit-global', inherit_global)
|
||||
+ inst.config.replace('passwordHistory', 'on' if enabled else 'off')
|
||||
+ inst.config.replace('passwordInHistory', str(count))
|
||||
+ inst.config.replace('passwordChange', 'on')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def ensure_local_subtree_policy(inst, count: int, track_update_time: str = 'on'):
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ pwp.create_subtree_policy(OU_DN, {
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordHistory': 'on',
|
||||
+ 'passwordInHistory': str(count),
|
||||
+ 'passwordTrackUpdateTime': track_update_time,
|
||||
+ })
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def set_local_history_via_cli(inst, count: int):
|
||||
+ sbin_dir = inst.get_sbin_dir()
|
||||
+ inst_name = inst.serverid
|
||||
+ cmd = [f"{sbin_dir}/dsconf", inst_name, "localpwp", "set", f"--pwdhistorycount={count}", OU_DN]
|
||||
+ rc = subprocess.call(cmd)
|
||||
+ assert rc == 0, f"dsconf command failed rc={rc}: {' '.join(cmd)}"
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def test_global_history_only_enforced(topology_st, setup_entries):
|
||||
+ """Global-only history enforcement with count 2
|
||||
+
|
||||
+ :id: 3d8cf35b-4a33-4587-9814-ebe18b7a1f92
|
||||
+ :setup: Standalone instance, test OU and user, ACI for self password changes
|
||||
+ :steps:
|
||||
+ 1. Remove local policies
|
||||
+ 2. Set global policy: passwordHistory=on, passwordInHistory=2
|
||||
+ 3. Set password to Alpha1, then change to Alpha2 and Alpha3 as the user
|
||||
+ 4. Attempt to change to Alpha1 and Alpha2
|
||||
+ 5. Attempt to change to Alpha4
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Changes to Welcome1 and Welcome2 are rejected with CONSTRAINT_VIOLATION
|
||||
+ 5. Change to Welcome4 is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=2)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Alpha1')
|
||||
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha1')
|
||||
+ set_user_password(inst, user, 'Alpha3', bind_as_user_password='Alpha2')
|
||||
+
|
||||
+ # Within last 2
|
||||
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha3', expect_violation=True)
|
||||
+ set_user_password(inst, user, 'Alpha1', bind_as_user_password='Alpha3', expect_violation=True)
|
||||
+
|
||||
+ # New password should be allowed
|
||||
+ set_user_password(inst, user, 'Alpha4', bind_as_user_password='Alpha3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_local_overrides_global_history(topology_st, setup_entries):
|
||||
+ """Local subtree policy (history=3) overrides global (history=1)
|
||||
+
|
||||
+ :id: 97c22f56-5ea6-40c1-8d8c-1cece3bf46fd
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Set global policy passwordInHistory=1
|
||||
+ 2. Create local subtree policy on the OU with passwordInHistory=3
|
||||
+ 3. Set password to Bravo1, then change to Bravo2 and Bravo3 as the user
|
||||
+ 4. Attempt to change to Bravo1
|
||||
+ 5. Attempt to change to Bravo5
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Change to Welcome1 is rejected (local policy wins)
|
||||
+ 5. Change to Welcome5 is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Bravo1')
|
||||
+ set_user_password(inst, user, 'Bravo2', bind_as_user_password='Bravo1')
|
||||
+ set_user_password(inst, user, 'Bravo3', bind_as_user_password='Bravo2')
|
||||
+
|
||||
+ # Third prior should be rejected under local policy count=3
|
||||
+ set_user_password(inst, user, 'Bravo1', bind_as_user_password='Bravo3', expect_violation=True)
|
||||
+
|
||||
+ # New password allowed
|
||||
+ set_user_password(inst, user, 'Bravo5', bind_as_user_password='Bravo3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_change_local_history_via_cli_affects_enforcement(topology_st, setup_entries):
|
||||
+ """Changing local policy via CLI is enforced immediately
|
||||
+
|
||||
+ :id: 5a6d0d14-4009-4bad-86e1-cde5000c43dc
|
||||
+ :setup: Standalone instance, test OU and user, dsconf available
|
||||
+ :steps:
|
||||
+ 1. Ensure local subtree policy passwordInHistory=3
|
||||
+ 2. Set password to Charlie1, then change to Charlie2 and Charlie3 as the user
|
||||
+ 3. Attempt to change to Charlie1 (within last 3)
|
||||
+ 4. Run: dsconf <inst> localpwp set --pwdhistorycount=1 "ou=product testing,<suffix>"
|
||||
+ 5. Attempt to change to Charlie1 again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Change to Welcome1 is rejected
|
||||
+ 4. CLI command succeeds
|
||||
+ 5. Change to Welcome1 now succeeds (only last 1 is disallowed)
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Charlie1')
|
||||
+ set_user_password(inst, user, 'Charlie2', bind_as_user_password='Charlie1', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Charlie3', bind_as_user_password='Charlie2', expect_violation=False)
|
||||
+
|
||||
+ # With count=3, Welcome1 is within history
|
||||
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=True)
|
||||
+
|
||||
+ # Reduce local count to 1 via CLI to exercise CLI mapping and updated code
|
||||
+ set_local_history_via_cli(inst, count=1)
|
||||
+
|
||||
+ # Now Welcome1 should be allowed
|
||||
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_history_local_only_enforced(topology_st, setup_entries):
|
||||
+ """Local-only history enforcement with count 3
|
||||
+
|
||||
+ :id: af6ff34d-ac94-4108-a7b6-2b589c960154
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Disable global password history (passwordHistory=off, passwordInHistory=0, inherit off)
|
||||
+ 2. Ensure local subtree policy with passwordInHistory=3
|
||||
+ 3. Set password to Delta1, then change to Delta2 and Delta3 as the user
|
||||
+ 4. Attempt to change to Delta1
|
||||
+ 5. Attempt to change to Delta5
|
||||
+ 6. Change once more to Delta6, then change to Delta1
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Change to Welcome1 is rejected (within last 3)
|
||||
+ 5. Change to Welcome5 is accepted
|
||||
+ 6. Welcome1 is now older than the last 3 and is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=False, count=0, inherit_global='off')
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Delta1')
|
||||
+ set_user_password(inst, user, 'Delta2', bind_as_user_password='Delta1')
|
||||
+ set_user_password(inst, user, 'Delta3', bind_as_user_password='Delta2')
|
||||
+
|
||||
+ # Within last 2
|
||||
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta3', expect_violation=True)
|
||||
+
|
||||
+ # New password allowed
|
||||
+ set_user_password(inst, user, 'Delta5', bind_as_user_password='Delta3', expect_violation=False)
|
||||
+
|
||||
+ # Now Welcome1 is older than last 2 after one more change
|
||||
+ set_user_password(inst, user, 'Delta6', bind_as_user_password='Delta5', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta6', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_user_policy_detection_and_enforcement(topology_st, setup_entries):
|
||||
+ """User local policy is detected and enforced; removal falls back to global policy
|
||||
+
|
||||
+ :id: 2213126a-1f47-468c-8337-0d2ee5d2d585
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Set global policy passwordInHistory=1
|
||||
+ 2. Create a user local password policy on the user with passwordInHistory=3
|
||||
+ 3. Verify is_user_policy(USER_DN) is True
|
||||
+ 4. Set password to Echo1, then change to Echo2 and Echo3 as the user
|
||||
+ 5. Attempt to change to Echo1 (within last 3)
|
||||
+ 6. Delete the user local policy
|
||||
+ 7. Verify is_user_policy(USER_DN) is False
|
||||
+ 8. Attempt to change to Echo1 again (now only last 1 disallowed by global)
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. is_user_policy returns True
|
||||
+ 4. Success
|
||||
+ 5. Change to Welcome1 is rejected
|
||||
+ 6. Success
|
||||
+ 7. is_user_policy returns False
|
||||
+ 8. Change to Welcome1 succeeds (two back is allowed by global=1)
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
|
||||
+
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ user = setup_entries
|
||||
+ pwp.create_user_policy(user.dn, {
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordHistory': 'on',
|
||||
+ 'passwordInHistory': '3',
|
||||
+ })
|
||||
+
|
||||
+ assert pwp.is_user_policy(user.dn) is True
|
||||
+
|
||||
+ user.reset_password('Echo1')
|
||||
+ set_user_password(inst, user, 'Echo2', bind_as_user_password='Echo1', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Echo3', bind_as_user_password='Echo2', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=True)
|
||||
+
|
||||
+ pwp.delete_local_policy(user.dn)
|
||||
+ assert pwp.is_user_policy(user.dn) is False
|
||||
+
|
||||
+ # With only global=1, Echo1 (two back) is allowed
|
||||
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
index 2d4ba9b21..a3e59a90c 100644
|
||||
--- a/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -43,7 +43,7 @@ def _get_pw_policy(inst, targetdn, log, use_json=None):
|
||||
targetdn = 'cn=config'
|
||||
policydn = targetdn
|
||||
basedn = targetdn
|
||||
- attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy_local'])
|
||||
+ attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy-local'])
|
||||
all_attrs = inst.config.get_attrs_vals_utf8(attr_list)
|
||||
attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
|
||||
else:
|
||||
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
|
||||
index 6a47a44fe..539c230a9 100644
|
||||
--- a/src/lib389/lib389/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/pwpolicy.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2018 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -7,6 +7,7 @@
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
import ldap
|
||||
+from ldap import filter as ldap_filter
|
||||
from lib389._mapped_object import DSLdapObject, DSLdapObjects
|
||||
from lib389.backend import Backends
|
||||
from lib389.config import Config
|
||||
@@ -74,19 +75,56 @@ class PwPolicyManager(object):
|
||||
}
|
||||
|
||||
def is_subtree_policy(self, dn):
|
||||
- """Check if the entry has a subtree password policy. If we can find a
|
||||
- template entry it is subtree policy
|
||||
+ """Check if a subtree password policy exists for a given entry DN.
|
||||
|
||||
- :param dn: Entry DN with PwPolicy set up
|
||||
+ A subtree policy is indicated by the presence of any CoS template
|
||||
+ (under `cn=nsPwPolicyContainer,<dn>`) that has a `pwdpolicysubentry`
|
||||
+ attribute pointing to an existing entry with objectClass `passwordpolicy`.
|
||||
+
|
||||
+ :param dn: Entry DN to check for subtree policy
|
||||
:type dn: str
|
||||
|
||||
- :returns: True if the entry has a subtree policy, False otherwise
|
||||
+ :returns: True if a subtree policy exists, False otherwise
|
||||
+ :rtype: bool
|
||||
"""
|
||||
- cos_templates = CosTemplates(self._instance, 'cn=nsPwPolicyContainer,{}'.format(dn))
|
||||
try:
|
||||
- cos_templates.get('cn=nsPwTemplateEntry,%s' % dn)
|
||||
- return True
|
||||
- except:
|
||||
+ container_basedn = 'cn=nsPwPolicyContainer,{}'.format(dn)
|
||||
+ templates = CosTemplates(self._instance, container_basedn).list()
|
||||
+ for tmpl in templates:
|
||||
+ pwp_dn = tmpl.get_attr_val_utf8('pwdpolicysubentry')
|
||||
+ if not pwp_dn:
|
||||
+ continue
|
||||
+ # Validate that the referenced entry exists and is a passwordpolicy
|
||||
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
|
||||
+ if pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy'):
|
||||
+ return True
|
||||
+ except ldap.LDAPError:
|
||||
+ pass
|
||||
+ return False
|
||||
+
|
||||
+ def is_user_policy(self, dn):
|
||||
+ """Check if the entry has a user password policy.
|
||||
+
|
||||
+ A user policy is indicated by the target entry having a
|
||||
+ `pwdpolicysubentry` attribute that points to an existing
|
||||
+ entry with objectClass `passwordpolicy`.
|
||||
+
|
||||
+ :param dn: Entry DN to check
|
||||
+ :type dn: str
|
||||
+
|
||||
+ :returns: True if the entry has a user policy, False otherwise
|
||||
+ :rtype: bool
|
||||
+ """
|
||||
+ try:
|
||||
+ entry = Account(self._instance, dn)
|
||||
+ if not entry.exists():
|
||||
+ return False
|
||||
+ pwp_dn = entry.get_attr_val_utf8('pwdpolicysubentry')
|
||||
+ if not pwp_dn:
|
||||
+ return False
|
||||
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
|
||||
+ return pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy')
|
||||
+ except ldap.LDAPError:
|
||||
return False
|
||||
|
||||
def create_user_policy(self, dn, properties):
|
||||
@@ -114,10 +152,10 @@ class PwPolicyManager(object):
|
||||
pwp_containers = nsContainers(self._instance, basedn=parentdn)
|
||||
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
|
||||
|
||||
- # Create policy entry
|
||||
+ # Create or update the policy entry
|
||||
properties['cn'] = 'cn=nsPwPolicyEntry_user,%s' % dn
|
||||
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
|
||||
- pwp_entry = pwp_entries.create(properties=properties)
|
||||
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
|
||||
try:
|
||||
# Add policy to the entry
|
||||
user_entry.replace('pwdpolicysubentry', pwp_entry.dn)
|
||||
@@ -152,32 +190,27 @@ class PwPolicyManager(object):
|
||||
pwp_containers = nsContainers(self._instance, basedn=dn)
|
||||
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
|
||||
|
||||
- # Create policy entry
|
||||
- pwp_entry = None
|
||||
+ # Create or update the policy entry
|
||||
properties['cn'] = 'cn=nsPwPolicyEntry_subtree,%s' % dn
|
||||
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
|
||||
- pwp_entry = pwp_entries.create(properties=properties)
|
||||
- try:
|
||||
- # The CoS template entry (nsPwTemplateEntry) that has the pwdpolicysubentry
|
||||
- # value pointing to the above (nsPwPolicyEntry) entry
|
||||
- cos_template = None
|
||||
- cos_templates = CosTemplates(self._instance, pwp_container.dn)
|
||||
- cos_template = cos_templates.create(properties={'cosPriority': '1',
|
||||
- 'pwdpolicysubentry': pwp_entry.dn,
|
||||
- 'cn': 'cn=nsPwTemplateEntry,%s' % dn})
|
||||
-
|
||||
- # The CoS specification entry at the subtree level
|
||||
- cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
- 'cosTemplateDn': cos_template.dn,
|
||||
- 'cn': 'nsPwPolicy_CoS'})
|
||||
- except ldap.LDAPError as e:
|
||||
- # Something went wrong, remove what we have done
|
||||
- if pwp_entry is not None:
|
||||
- pwp_entry.delete()
|
||||
- if cos_template is not None:
|
||||
- cos_template.delete()
|
||||
- raise e
|
||||
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
|
||||
+
|
||||
+ # Ensure the CoS template entry (nsPwTemplateEntry) that points to the
|
||||
+ # password policy entry
|
||||
+ cos_templates = CosTemplates(self._instance, pwp_container.dn)
|
||||
+ cos_template = cos_templates.ensure_state(properties={
|
||||
+ 'cosPriority': '1',
|
||||
+ 'pwdpolicysubentry': pwp_entry.dn,
|
||||
+ 'cn': 'cn=nsPwTemplateEntry,%s' % dn
|
||||
+ })
|
||||
+
|
||||
+ # Ensure the CoS specification entry at the subtree level
|
||||
+ cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
+ cos_pointer_defs.ensure_state(properties={
|
||||
+ 'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
+ 'cosTemplateDn': cos_template.dn,
|
||||
+ 'cn': 'nsPwPolicy_CoS'
|
||||
+ })
|
||||
|
||||
# make sure that local policies are enabled
|
||||
self.set_global_policy({'nsslapd-pwpolicy-local': 'on'})
|
||||
@@ -244,10 +277,12 @@ class PwPolicyManager(object):
|
||||
if self.is_subtree_policy(entry.dn):
|
||||
parentdn = dn
|
||||
subtree = True
|
||||
- else:
|
||||
+ elif self.is_user_policy(entry.dn):
|
||||
dn_comps = ldap.dn.explode_dn(dn)
|
||||
dn_comps.pop(0)
|
||||
parentdn = ",".join(dn_comps)
|
||||
+ else:
|
||||
+ raise ValueError('The target entry dn does not have a password policy')
|
||||
|
||||
# Starting deleting the policy, ignore the parts that might already have been removed
|
||||
pwp_container = nsContainer(self._instance, 'cn=nsPwPolicyContainer,%s' % parentdn)
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -69,9 +69,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.2"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@ -100,9 +100,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.33"
|
||||
version = "1.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@ -111,9 +111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
@ -295,9 +295,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
@ -335,7 +335,7 @@ version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
@ -358,9 +358,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "librnsslapd"
|
||||
@ -399,7 +399,7 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -449,7 +449,7 @@ version = "0.10.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@ -466,7 +466,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -526,9 +526,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -573,7 +573,7 @@ version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@ -603,14 +603,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.143"
|
||||
version = "1.0.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@ -626,9 +626,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
|
||||
[[package]]
|
||||
name = "slapd"
|
||||
@ -677,9 +677,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -756,7 +756,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -994,7 +994,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1014,5 +1014,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
@ -46,8 +46,8 @@ ExcludeArch: i686
|
||||
|
||||
Summary: 389 Directory Server (base)
|
||||
Name: 389-ds-base
|
||||
Version: 2.6.1
|
||||
Release: 12%{?dist}
|
||||
Version: 2.7.0
|
||||
Release: 7%{?dist}
|
||||
License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (0BSD OR Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT OR Zlib) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND MIT AND MPL-2.0 AND Zlib
|
||||
URL: https://www.port389.org
|
||||
Conflicts: selinux-policy-base < 3.9.8
|
||||
@ -65,11 +65,11 @@ Provides: bundled(crate(atty)) = 0.2.14
|
||||
Provides: bundled(crate(autocfg)) = 1.5.0
|
||||
Provides: bundled(crate(backtrace)) = 0.3.75
|
||||
Provides: bundled(crate(base64)) = 0.13.1
|
||||
Provides: bundled(crate(bitflags)) = 2.9.2
|
||||
Provides: bundled(crate(bitflags)) = 2.9.1
|
||||
Provides: bundled(crate(byteorder)) = 1.5.0
|
||||
Provides: bundled(crate(cbindgen)) = 0.26.0
|
||||
Provides: bundled(crate(cc)) = 1.2.33
|
||||
Provides: bundled(crate(cfg-if)) = 1.0.3
|
||||
Provides: bundled(crate(cc)) = 1.2.31
|
||||
Provides: bundled(crate(cfg-if)) = 1.0.1
|
||||
Provides: bundled(crate(clap)) = 3.2.25
|
||||
Provides: bundled(crate(clap_lex)) = 0.2.4
|
||||
Provides: bundled(crate(concread)) = 0.5.7
|
||||
@ -85,14 +85,14 @@ Provides: bundled(crate(foreign-types)) = 0.3.2
|
||||
Provides: bundled(crate(foreign-types-shared)) = 0.1.1
|
||||
Provides: bundled(crate(getrandom)) = 0.3.3
|
||||
Provides: bundled(crate(gimli)) = 0.31.1
|
||||
Provides: bundled(crate(hashbrown)) = 0.15.5
|
||||
Provides: bundled(crate(hashbrown)) = 0.15.4
|
||||
Provides: bundled(crate(heck)) = 0.4.1
|
||||
Provides: bundled(crate(hermit-abi)) = 0.1.19
|
||||
Provides: bundled(crate(indexmap)) = 1.9.3
|
||||
Provides: bundled(crate(io-uring)) = 0.7.9
|
||||
Provides: bundled(crate(itoa)) = 1.0.15
|
||||
Provides: bundled(crate(jobserver)) = 0.1.33
|
||||
Provides: bundled(crate(libc)) = 0.2.175
|
||||
Provides: bundled(crate(libc)) = 0.2.174
|
||||
Provides: bundled(crate(linux-raw-sys)) = 0.9.4
|
||||
Provides: bundled(crate(log)) = 0.4.27
|
||||
Provides: bundled(crate(lru)) = 0.13.0
|
||||
@ -110,7 +110,7 @@ Provides: bundled(crate(paste-impl)) = 0.1.18
|
||||
Provides: bundled(crate(pin-project-lite)) = 0.2.16
|
||||
Provides: bundled(crate(pkg-config)) = 0.3.32
|
||||
Provides: bundled(crate(proc-macro-hack)) = 0.5.20+deprecated
|
||||
Provides: bundled(crate(proc-macro2)) = 1.0.101
|
||||
Provides: bundled(crate(proc-macro2)) = 1.0.95
|
||||
Provides: bundled(crate(quote)) = 1.0.40
|
||||
Provides: bundled(crate(r-efi)) = 5.3.0
|
||||
Provides: bundled(crate(rustc-demangle)) = 0.1.26
|
||||
@ -118,13 +118,13 @@ Provides: bundled(crate(rustix)) = 1.0.8
|
||||
Provides: bundled(crate(ryu)) = 1.0.20
|
||||
Provides: bundled(crate(serde)) = 1.0.219
|
||||
Provides: bundled(crate(serde_derive)) = 1.0.219
|
||||
Provides: bundled(crate(serde_json)) = 1.0.143
|
||||
Provides: bundled(crate(serde_json)) = 1.0.142
|
||||
Provides: bundled(crate(shlex)) = 1.3.0
|
||||
Provides: bundled(crate(slab)) = 0.4.11
|
||||
Provides: bundled(crate(slab)) = 0.4.10
|
||||
Provides: bundled(crate(smallvec)) = 1.15.1
|
||||
Provides: bundled(crate(sptr)) = 0.3.2
|
||||
Provides: bundled(crate(strsim)) = 0.10.0
|
||||
Provides: bundled(crate(syn)) = 2.0.106
|
||||
Provides: bundled(crate(syn)) = 2.0.104
|
||||
Provides: bundled(crate(tempfile)) = 3.20.0
|
||||
Provides: bundled(crate(termcolor)) = 1.4.1
|
||||
Provides: bundled(crate(textwrap)) = 0.16.2
|
||||
@ -157,7 +157,6 @@ Provides: bundled(crate(zeroize)) = 1.8.1
|
||||
Provides: bundled(crate(zeroize_derive)) = 1.4.2
|
||||
##### Bundled cargo crates list - END #####
|
||||
|
||||
BuildRequires: git
|
||||
BuildRequires: nspr-devel >= 4.32
|
||||
BuildRequires: nss-devel >= 3.67.0-7
|
||||
|
||||
@ -287,81 +286,42 @@ Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download
|
||||
%endif
|
||||
Source4: 389-ds-base.sysusers
|
||||
|
||||
# Vendored cargo crates update
|
||||
Source5: vendor-%{version}-3.tar.gz
|
||||
Source6: Cargo-%{version}-3.lock
|
||||
Source5: vendor-%{version}-1.tar.gz
|
||||
Source6: Cargo-%{version}-1.lock
|
||||
|
||||
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
|
||||
Patch: 0027-Issue-6715-dsconf-backend-replication-monitor-fails-.patch
|
||||
Patch: 0028-Issue-6713-ns-slapd-crash-during-mdb-offline-import-.patch
|
||||
Patch: 0029-Issue-6571-Nested-group-does-not-receive-memberOf-at.patch
|
||||
Patch: 0030-Issue-6288-dsidm-crash-with-account-policy-when-alt-.patch
|
||||
Patch: 0031-Issue-6686-CLI-Re-enabling-user-accounts-that-reache.patch
|
||||
Patch: 0032-Issue-6571-2nd-Nested-group-does-not-receive-memberO.patch
|
||||
Patch: 0033-Issue-6698-NPE-after-configuring-invalid-filtered-ro.patch
|
||||
Patch: 0034-Issue-6626-Ignore-replica-busy-condition-in-healthch.patch
|
||||
Patch: 0035-Issue-6655-fix-replication-release-replica-decoding-.patch
|
||||
Patch: 0036-Issue-6787-Improve-error-message-when-bulk-import-co.patch
|
||||
Patch: 0037-Issue-6614-CLI-Error-when-trying-to-display-global-D.patch
|
||||
Patch: 0038-Issue-6720-Remove-BDB-attribute-from-MDB-DB-Monitor-.patch
|
||||
Patch: 0039-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch
|
||||
Patch: 0040-Issue-6436-MOD-on-a-large-group-slow-if-substring-in.patch
|
||||
Patch: 0041-Issue-6764-statistics-about-index-lookup-report-a-wr.patch
|
||||
Patch: 0042-Issue-6736-Exception-thrown-by-dsconf-instance-repl-.patch
|
||||
Patch: 0043-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch
|
||||
Patch: 0044-Issue-6819-Incorrect-pwdpolicysubentry-returned-for-.patch
|
||||
Patch: 0045-Issue-6641-modrdn-fails-when-a-user-is-member-of-mul.patch
|
||||
Patch: 0046-Issue-6848-AddressSanitizer-leak-in-do_search.patch
|
||||
Patch: 0047-Issue-6872-compressed-log-rotation-creates-files-wit.patch
|
||||
Patch: 0048-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch
|
||||
Patch: 0049-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch
|
||||
Patch: 0050-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch
|
||||
Patch: 0051-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch
|
||||
Patch: 0052-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch
|
||||
Patch: 0053-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch
|
||||
Patch: 0054-Issue-6859-str2filter-is-not-fully-applying-matching.patch
|
||||
Patch: 0055-Issue-6064-bdb2mdb-shows-errors-6341.patch
|
||||
Patch: 0056-Issue-6377-syntax-error-in-setup.py-6378.patch
|
||||
Patch: 0057-Issue-6693-Fix-error-messages-inconsistencies-6694.patch
|
||||
Patch: 0058-Issue-6893-Log-user-that-is-updated-during-password-.patch
|
||||
Patch: 0059-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch
|
||||
Patch: 0060-Issue-6321-lib389-get_db_lib-function-may-returns-th.patch
|
||||
Patch: 0061-Issue-6594-Add-test-for-numSubordinates-replication-.patch
|
||||
Patch: 0062-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch
|
||||
Patch: 0063-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch
|
||||
Patch: 0064-Issue-6500-Fix-covscan-and-ASAN-issue.patch
|
||||
Patch: 0065-Issue-6910-Fix-latest-coverity-issues.patch
|
||||
Patch: 0066-Issue-7014-memberOf-ignored-deferred-updates-with-LM.patch
|
||||
Patch: 0067-Issue-6933-When-deferred-memberof-update-is-enabled-.patch
|
||||
Patch: 0068-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch
|
||||
Patch: 0069-Issue-6928-The-parentId-attribute-is-indexed-with-im.patch
|
||||
Patch: 0070-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch
|
||||
Patch: 0071-Issue-6936-Make-user-subtree-policy-creation-idempot.patch
|
||||
Patch: 0001-Issue-6377-syntax-error-in-setup.py-6378.patch
|
||||
Patch: 0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch
|
||||
Patch: 0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch
|
||||
Patch: 0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch
|
||||
Patch: 0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch
|
||||
Patch: 0006-Issue-6782-Improve-paged-result-locking.patch
|
||||
Patch: 0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch
|
||||
Patch: 0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch
|
||||
Patch: 0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch
|
||||
Patch: 0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch
|
||||
Patch: 0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch
|
||||
Patch: 0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch
|
||||
Patch: 0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch
|
||||
Patch: 0014-Issue-6893-Log-user-that-is-updated-during-password-.patch
|
||||
Patch: 0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch
|
||||
Patch: 0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch
|
||||
Patch: 0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch
|
||||
Patch: 0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch
|
||||
Patch: 0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch
|
||||
Patch: 0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch
|
||||
Patch: 0021-Issue-6468-CLI-Fix-default-error-log-level.patch
|
||||
Patch: 0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch
|
||||
Patch: 0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch
|
||||
Patch: 0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch
|
||||
Patch: 0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch
|
||||
Patch: 0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch
|
||||
Patch: 0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch
|
||||
Patch: 0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch
|
||||
Patch: 0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch
|
||||
Patch: 0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch
|
||||
Patch: 0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch
|
||||
Patch: 0032-Issue-6910-Fix-latest-coverity-issues.patch
|
||||
Patch: 0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch
|
||||
|
||||
%description
|
||||
389 Directory Server is an LDAPv3 compliant server. The base package includes
|
||||
@ -442,6 +402,7 @@ Requires: python%{python3_pkgversion}-argcomplete
|
||||
Requires: python%{python3_pkgversion}-libselinux
|
||||
Requires: python%{python3_pkgversion}-setuptools
|
||||
Requires: python%{python3_pkgversion}-cryptography
|
||||
Requires: python%{python3_pkgversion}-psutil
|
||||
%{?python_provide:%python_provide python%{python3_pkgversion}-lib389}
|
||||
|
||||
%description -n python%{python3_pkgversion}-lib389
|
||||
@ -463,10 +424,11 @@ A cockpit UI Plugin for configuring and administering the 389 Directory Server
|
||||
|
||||
%prep
|
||||
|
||||
%autosetup -S git -p1 -n %{name}-%{version}
|
||||
%autosetup -p1 -n %{name}-%{version}
|
||||
rm -rf vendor
|
||||
tar xzf %{SOURCE5}
|
||||
cp %{SOURCE6} src/Cargo.lock
|
||||
|
||||
%if %{bundle_jemalloc}
|
||||
%setup -q -n %{name}-%{version} -T -D -b 3
|
||||
%endif
|
||||
@ -545,7 +507,7 @@ autoreconf -fiv
|
||||
--with-systemdgroupname=%{groupname} \
|
||||
--libexecdir=%{_libexecdir}/%{pkgname} \
|
||||
$NSSARGS $ASAN_FLAGS $RUST_FLAGS $CLANG_FLAGS $COCKPIT_FLAGS \
|
||||
--enable-cmocka --with-libldap-r=no
|
||||
--enable-cmocka --enable-new-dtags --with-libldap-r=no
|
||||
|
||||
|
||||
# lib389
|
||||
@ -806,60 +768,49 @@ exit 0
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Fri Sep 26 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-12
|
||||
- Resolves: RHEL-104590 - RHDS12: Web console doesn't show Server Version [rhel-9.6.z]
|
||||
- Resolves: RHEL-104592 - The numSubordinates value is not matching the number of direct children. [rhel-9.6.z]
|
||||
- Resolves: RHEL-111227 - Error showing local password policy on web UI [rhel-9.6.z]
|
||||
- Resolves: RHEL-112678 - Statistics about index lookup report a wrong duration [rhel-9.6.z]
|
||||
- Resolves: RHEL-112692 - Crash if repl keep alive entry can not be created [rhel-9.6.z]
|
||||
- Resolves: RHEL-112725 - Exception thrown by dsconf instance repl get_ruv [rhel-9.6.z]
|
||||
- Resolves: RHEL-113979 - AddressSanitizer: memory leak in memberof_add_memberof_attr [rhel-9.6.z]
|
||||
- Resolves: RHEL-114952 - lib389/replica.py is using unexisting datetime.UTC in python3.9 [rhel-9.6.z]
|
||||
- Resolves: RHEL-117048 - Replication online reinitialization of a large database gets stalled. [rhel-9.6.z]
|
||||
- Resolves: RHEL-117769 - When the server restarts after a crash, the RFE assumes memberof should be recomputed. It triggers a memberof fixup task, dirsrv became unresponsive. [rhel-9.6.z]
|
||||
- Resolves: RHEL-117778 - Ignore the memberOfDeferredUpdate setting when LMDB is used. [rhel-9.6.z]
|
||||
* Tue Sep 16 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-7
|
||||
- Resolves: RHEL-104591 - RHDS12: Web console doesn't show Server Version [rhel-9]
|
||||
- Resolves: RHEL-104593 - The numSubordinates value is not matching the number of direct children. [rhel-9]
|
||||
- Resolves: RHEL-109034 - Allow Uniqueness plugin to search uniqueness attributes using custom matching rules [rhel-9]
|
||||
- Resolves: RHEL-109885 - Wrong backend database name syntax causes "Red Hat Directory Server" => "Databases" menu blank in Cockpit [rhel-9]
|
||||
- Resolves: RHEL-109889 - RootDN Access Control Plugin with wildcards for IP addresses fails with an error "Invalid IP address" [rhel-9]
|
||||
- Resolves: RHEL-109892 - On RHDS 12.6 The user password policy for a user was created, but the pwdpolicysubentry attribute for this user incorrectly points to the People OU password policy instead of the specific user policy. [rhel-9]
|
||||
- Resolves: RHEL-109897 - AddressSanitizer: leak in do_search [rhel-9]
|
||||
- Resolves: RHEL-113981 - AddressSanitizer: memory leak in memberof_add_memberof_attr [rhel-9]
|
||||
|
||||
* Tue Aug 19 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-11
|
||||
- Resolves: RHEL-18333 - Can't rename users member of automember rule [rhel-9.6.z]
|
||||
- Resolves: RHEL-81140 - Healthcheck tool should warn admin about creating a substring index on membership attribute [rhel-9.6.z]
|
||||
- Resolves: RHEL-92052 - Memory leak in roles_cache_create_object_from_entry [rhel-9.6.z]
|
||||
- Resolves: RHEL-95384 - Getting error messages while migration to LMDB.
|
||||
- Resolves: RHEL-106582 - Failure to gather database statistics with LMDB.
|
||||
- Resolves: RHEL-106793 - LDAP healthcheck complains about needed parameters when using LMDB.
|
||||
- Resolves: RHEL-107004 - Failure to get Server monitoring data when NDN cache is disabled. [rhel-9.6.z]
|
||||
- Resolves: RHEL-109033 - Allow Uniqueness plugin to search uniqueness attributes using custom matching rules [rhel-9.6.z]
|
||||
- Resolves: RHEL-109884 - Wrong backend database name syntax causes "Red Hat Directory Server" => "Databases" menu blank in Cockpit [rhel-9.6.z]
|
||||
- Resolves: RHEL-109888 - RootDN Access Control Plugin with wildcards for IP addresses fails with an error "Invalid IP address" [rhel-9.6.z]
|
||||
- Resolves: RHEL-109891 - On RHDS 12.6 The user password policy for a user was created, but the pwdpolicysubentry attribute for this user incorrectly points to the People OU password policy instead of the specific user policy. [rhel-9.6.z]
|
||||
- Resolves: RHEL-109896 - AddressSanitizer: leak in do_search [rhel-9.6.z]
|
||||
- Resolves: RHEL-109904 - ns-slapd crashed when we add nsslapd-referral [rhel-9.6.z]
|
||||
- Resolves: RHEL-109945 - CWE-284 dirsrv log rotation creates files with world readable permission [rhel-9.6.z]
|
||||
- Resolves: RHEL-109954 - CWE-532 Created user password hash available to see in audit log [rhel-9.6.z]
|
||||
- Resolves: RHEL-109957 - CWE-778 Log doesn't show what user gets password changed by administrator [rhel-9.6.z]
|
||||
* Tue Aug 05 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-5
|
||||
- Resolves: RHEL-89762 - dsidm Error: float() argument must be a string or a number, not 'NoneType' [rhel-9]
|
||||
- Resolves: RHEL-92041 - Memory leak in roles_cache_create_object_from_entry
|
||||
- Resolves: RHEL-95444 - ns-slapd[xxxx]: segfault at 10d7d0d0 ip 00007ff734050cdb sp 00007ff6de9f1430 error 6 in libslapd.so.0.1.0[7ff733ec0000+1b3000] [rhel-9]
|
||||
- Resolves: RHEL-104821 - ipa-restore fails to restore SELinux contexts, causes ns-slapd AVC denials on /dev/shm after restore.
|
||||
- Resolves: RHEL-107005 - Failure to get Server monitoring data when NDN cache is disabled. [rhel-9]
|
||||
- Resolves: RHEL-107581 - segfault - error 4 in libpthread-2.28.so [rhel-9]
|
||||
- Resolves: RHEL-107585 - ns-slapd crashed when we add nsslapd-referral [rhel-9]
|
||||
- Resolves: RHEL-107586 - CWE-284 dirsrv log rotation creates files with world readable permission [rhel-9]
|
||||
- Resolves: RHEL-107587 - CWE-532 Created user password hash available to see in audit log [rhel-9]
|
||||
- Resolves: RHEL-107588 - CWE-778 Log doesn't show what user gets password changed by administrator [rhel-9]
|
||||
|
||||
* Mon Jun 09 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-10
|
||||
- Resolves: RHEL-89735 - dsconf backend replication monitor fails if replica id starts with 0 [rhel-9.6.z]
|
||||
- Resolves: RHEL-89746 - ns-slapd crash in dbmdb_import_prepare_worker_entry() [rhel-9.6.z]
|
||||
- Resolves: RHEL-89752 - Nested group does not receive memberOf attribute [rhel-9.6.z]
|
||||
- Resolves: RHEL-89761 - dsidm Error: float() argument must be a string or a number, not 'NoneType' [rhel-9.6.z]
|
||||
- Resolves: RHEL-89768 - Crash in __strlen_sse2 when using the nsRole filter rewriter. [rhel-9.6.z]
|
||||
- Resolves: RHEL-89773 - Improve the "result" field of ipa-healthcheck if replicas are busy [rhel-9.6.z]
|
||||
- Resolves: RHEL-89781 - RHDS12.2 NSMMReplicationPlugin - release_replica Unable to parse the response [rhel-9.6.z]
|
||||
- Resolves: RHEL-95767 - Improve error message when bulk import connection is closed [rhel-9.6.z]
|
||||
* Mon Jul 21 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-4
|
||||
- Resolves: RHEL-61347 - Directory Server is unavailable after a restart with nsslapd-readonly=on and consumes 100% CPU
|
||||
|
||||
* Thu Jun 05 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-9
|
||||
- Reverts: RHEL-80713 - Increased memory consumption caused by NDN cache [rhel-9.6.z]
|
||||
- Resolves: RHEL-95443 - ns-slapd[xxxx]: segfault at 10d7d0d0 ip 00007ff734050cdb sp 00007ff6de9f1430 error 6 in libslapd.so.0.1.0[7ff733ec0000+1b3000] [rhel-9.6.z]
|
||||
* Tue Jul 01 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-3
|
||||
- Resolves: RHEL-77983 - Defects found by OpenScanHub
|
||||
- Resolves: RHEL-79673 - Improve the "result" field of ipa-healthcheck if replicas are busy
|
||||
- Resolves: RHEL-80496 - Can't rename users member of automember rule [rhel-9]
|
||||
- Resolves: RHEL-81141 - Healthcheck tool should warn admin about creating a substring index on membership attribute [rhel-9]
|
||||
- Resolves: RHEL-89736 - dsconf backend replication monitor fails if replica id starts with 0 [rhel-9]
|
||||
- Resolves: RHEL-89745 - ns-slapd crash in dbmdb_import_prepare_worker_entry() [rhel-9]
|
||||
- Resolves: RHEL-89753 - Nested group does not receive memberOf attribute [rhel-9]
|
||||
- Resolves: RHEL-89769 - Crash in __strlen_sse2 when using the nsRole filter rewriter. [rhel-9]
|
||||
- Resolves: RHEL-89782 - RHDS12.2 NSMMReplicationPlugin - release_replica Unable to parse the response [rhel-9]
|
||||
- Resolves: RHEL-95768 - Improve error message when bulk import connection is closed [rhel-9]
|
||||
- Resolves: RHEL-101189 - lib389/replica.py is using unexisting datetime.UTC in python3.9
|
||||
|
||||
* 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]
|
||||
* Mon Jun 30 2025 Viktor Ashirov <vashirov@redhat.com> - 2.7.0-1
|
||||
- Resolves: RHEL-80163 - Rebase 389-ds-base to 2.7.x
|
||||
|
||||
* Wed Apr 09 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-7
|
||||
- Bump version to 2.6.1-7
|
||||
|
||||
* 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 Mar 14 2025 Viktor Ashirov <vashirov@redhat.com> - 2.6.1-6
|
||||
- Resolves: RHEL-82271 - ipa-restore is failing with "Failed to start Directory Service"
|
||||
|
||||
* 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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user