374 lines
15 KiB
Diff
374 lines
15 KiB
Diff
From 55a47c1bfe1ce1c27e470384c4f1d50895db25f7 Mon Sep 17 00:00:00 2001
|
|
From: Mark Reynolds <mreynolds@redhat.com>
|
|
Date: Tue, 13 Jul 2021 14:18:03 -0400
|
|
Subject: [PATCH] Issue 4443 - Internal unindexed searches in syncrepl/retro
|
|
changelog
|
|
|
|
Bug Description:
|
|
|
|
When a non-system index is added to a backend it is
|
|
disabled until the database is initialized or reindexed.
|
|
So in the case of the retro changelog the changenumber index
|
|
is alway disabled by default since it is never initialized.
|
|
This leads to unexpected unindexed searches of the retro
|
|
changelog.
|
|
|
|
Fix Description:
|
|
|
|
If an index has "nsSystemIndex" set to "true" then enable it
|
|
immediately.
|
|
|
|
relates: https://github.com/389ds/389-ds-base/issues/4443
|
|
|
|
Reviewed by: spichugi & tbordaz(Thanks!!)
|
|
---
|
|
.../tests/suites/retrocl/basic_test.py | 53 ++++++++-------
|
|
.../suites/retrocl/retrocl_indexing_test.py | 68 +++++++++++++++++++
|
|
ldap/servers/plugins/retrocl/retrocl_create.c | 2 +-
|
|
.../slapd/back-ldbm/ldbm_index_config.c | 25 +++++--
|
|
src/lib389/lib389/_mapped_object.py | 13 ++++
|
|
5 files changed, 130 insertions(+), 31 deletions(-)
|
|
create mode 100644 dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py
|
|
|
|
diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py
|
|
index f3bc50f29..84d513829 100644
|
|
--- a/dirsrvtests/tests/suites/retrocl/basic_test.py
|
|
+++ b/dirsrvtests/tests/suites/retrocl/basic_test.py
|
|
@@ -8,7 +8,6 @@
|
|
|
|
import logging
|
|
import ldap
|
|
-import time
|
|
import pytest
|
|
from lib389.topologies import topology_st
|
|
from lib389.plugins import RetroChangelogPlugin
|
|
@@ -18,7 +17,8 @@ from lib389.tasks import *
|
|
from lib389.cli_base import FakeArgs, connect_instance, disconnect_instance
|
|
from lib389.cli_base.dsrc import dsrc_arg_concat
|
|
from lib389.cli_conf.plugins.retrochangelog import retrochangelog_add_attr
|
|
-from lib389.idm.user import UserAccount, UserAccounts, nsUserAccounts
|
|
+from lib389.idm.user import UserAccount, UserAccounts
|
|
+from lib389._mapped_object import DSLdapObjects
|
|
|
|
pytestmark = pytest.mark.tier1
|
|
|
|
@@ -82,7 +82,7 @@ def test_retrocl_exclude_attr_add(topology_st):
|
|
|
|
log.info('Adding user1')
|
|
try:
|
|
- user1 = users.create(properties={
|
|
+ users.create(properties={
|
|
'sn': '1',
|
|
'cn': 'user 1',
|
|
'uid': 'user1',
|
|
@@ -97,17 +97,18 @@ def test_retrocl_exclude_attr_add(topology_st):
|
|
except ldap.ALREADY_EXISTS:
|
|
pass
|
|
except ldap.LDAPError as e:
|
|
- log.error("Failed to add user1")
|
|
+ log.error("Failed to add user1: " + str(e))
|
|
|
|
log.info('Verify homePhone and carLicense attrs are in the changelog changestring')
|
|
try:
|
|
- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN)
|
|
+ retro_changelog_suffix = DSLdapObjects(st, basedn=RETROCL_SUFFIX)
|
|
+ cllist = retro_changelog_suffix.filter(f'(targetDn={USER1_DN})')
|
|
except ldap.LDAPError as e:
|
|
- log.fatal("Changelog search failed, error: " +str(e))
|
|
+ log.fatal("Changelog search failed, error: " + str(e))
|
|
assert False
|
|
assert len(cllist) > 0
|
|
- if cllist[0].hasAttr('changes'):
|
|
- clstr = (cllist[0].getValue('changes')).decode()
|
|
+ if cllist[0].present('changes'):
|
|
+ clstr = str(cllist[0].get_attr_vals_utf8('changes'))
|
|
assert ATTR_HOMEPHONE in clstr
|
|
assert ATTR_CARLICENSE in clstr
|
|
|
|
@@ -134,7 +135,7 @@ def test_retrocl_exclude_attr_add(topology_st):
|
|
|
|
log.info('Adding user2')
|
|
try:
|
|
- user2 = users.create(properties={
|
|
+ users.create(properties={
|
|
'sn': '2',
|
|
'cn': 'user 2',
|
|
'uid': 'user2',
|
|
@@ -149,18 +150,18 @@ def test_retrocl_exclude_attr_add(topology_st):
|
|
except ldap.ALREADY_EXISTS:
|
|
pass
|
|
except ldap.LDAPError as e:
|
|
- log.error("Failed to add user2")
|
|
+ log.error("Failed to add user2: " + str(e))
|
|
|
|
log.info('Verify homePhone attr is not in the changelog changestring')
|
|
try:
|
|
- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER2_DN)
|
|
+ cllist = retro_changelog_suffix.filter(f'(targetDn={USER2_DN})')
|
|
assert len(cllist) > 0
|
|
- if cllist[0].hasAttr('changes'):
|
|
- clstr = (cllist[0].getValue('changes')).decode()
|
|
+ if cllist[0].present('changes'):
|
|
+ clstr = str(cllist[0].get_attr_vals_utf8('changes'))
|
|
assert ATTR_HOMEPHONE not in clstr
|
|
assert ATTR_CARLICENSE in clstr
|
|
except ldap.LDAPError as e:
|
|
- log.fatal("Changelog search failed, error: " +str(e))
|
|
+ log.fatal("Changelog search failed, error: " + str(e))
|
|
assert False
|
|
|
|
def test_retrocl_exclude_attr_mod(topology_st):
|
|
@@ -228,19 +229,20 @@ def test_retrocl_exclude_attr_mod(topology_st):
|
|
'homeDirectory': '/home/user1',
|
|
'userpassword': USER_PW})
|
|
except ldap.ALREADY_EXISTS:
|
|
- pass
|
|
+ user1 = UserAccount(st, dn=USER1_DN)
|
|
except ldap.LDAPError as e:
|
|
- log.error("Failed to add user1")
|
|
+ log.error("Failed to add user1: " + str(e))
|
|
|
|
log.info('Verify homePhone and carLicense attrs are in the changelog changestring')
|
|
try:
|
|
- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN)
|
|
+ retro_changelog_suffix = DSLdapObjects(st, basedn=RETROCL_SUFFIX)
|
|
+ cllist = retro_changelog_suffix.filter(f'(targetDn={USER1_DN})')
|
|
except ldap.LDAPError as e:
|
|
- log.fatal("Changelog search failed, error: " +str(e))
|
|
+ log.fatal("Changelog search failed, error: " + str(e))
|
|
assert False
|
|
assert len(cllist) > 0
|
|
- if cllist[0].hasAttr('changes'):
|
|
- clstr = (cllist[0].getValue('changes')).decode()
|
|
+ if cllist[0].present('changes'):
|
|
+ clstr = str(cllist[0].get_attr_vals_utf8('changes'))
|
|
assert ATTR_HOMEPHONE in clstr
|
|
assert ATTR_CARLICENSE in clstr
|
|
|
|
@@ -267,24 +269,25 @@ def test_retrocl_exclude_attr_mod(topology_st):
|
|
|
|
log.info('Modify user1 carLicense attribute')
|
|
try:
|
|
- st.modify_s(USER1_DN, [(ldap.MOD_REPLACE, ATTR_CARLICENSE, b"123WX321")])
|
|
+ user1.replace(ATTR_CARLICENSE, "123WX321")
|
|
except ldap.LDAPError as e:
|
|
log.fatal('test_retrocl_exclude_attr_mod: Failed to update user1 attribute: error ' + e.message['desc'])
|
|
assert False
|
|
|
|
log.info('Verify carLicense attr is not in the changelog changestring')
|
|
try:
|
|
- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN)
|
|
+ cllist = retro_changelog_suffix.filter(f'(targetDn={USER1_DN})')
|
|
assert len(cllist) > 0
|
|
# There will be 2 entries in the changelog for this user, we are only
|
|
#interested in the second one, the modify operation.
|
|
- if cllist[1].hasAttr('changes'):
|
|
- clstr = (cllist[1].getValue('changes')).decode()
|
|
+ if cllist[1].present('changes'):
|
|
+ clstr = str(cllist[1].get_attr_vals_utf8('changes'))
|
|
assert ATTR_CARLICENSE not in clstr
|
|
except ldap.LDAPError as e:
|
|
- log.fatal("Changelog search failed, error: " +str(e))
|
|
+ log.fatal("Changelog search failed, error: " + str(e))
|
|
assert False
|
|
|
|
+
|
|
if __name__ == '__main__':
|
|
# Run isolated
|
|
# -s for DEBUG mode
|
|
diff --git a/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py b/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py
|
|
new file mode 100644
|
|
index 000000000..b1dfe962c
|
|
--- /dev/null
|
|
+++ b/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py
|
|
@@ -0,0 +1,68 @@
|
|
+import logging
|
|
+import pytest
|
|
+import os
|
|
+from lib389._constants import RETROCL_SUFFIX, DEFAULT_SUFFIX
|
|
+from lib389.topologies import topology_st as topo
|
|
+from lib389.plugins import RetroChangelogPlugin
|
|
+from lib389.idm.user import UserAccounts
|
|
+from lib389._mapped_object import DSLdapObjects
|
|
+log = logging.getLogger(__name__)
|
|
+
|
|
+
|
|
+def test_indexing_is_online(topo):
|
|
+ """Test that the changenmumber index is online right after enabling the plugin
|
|
+
|
|
+ :id: 16f4c001-9e0c-4448-a2b3-08ac1e85d40f
|
|
+ :setup: Standalone Instance
|
|
+ :steps:
|
|
+ 1. Enable retro cl
|
|
+ 2. Perform some updates
|
|
+ 3. Search for "(changenumber>=-1)", and it is not partially unindexed
|
|
+ 4. Search for "(&(changenumber>=-1)(targetuniqueid=*))", and it is not partially unindexed
|
|
+ :expectedresults:
|
|
+ 1. Success
|
|
+ 2. Success
|
|
+ 3. Success
|
|
+ 4. Success
|
|
+ """
|
|
+
|
|
+ # Enable plugin
|
|
+ topo.standalone.config.set('nsslapd-accesslog-logbuffering', 'off')
|
|
+ plugin = RetroChangelogPlugin(topo.standalone)
|
|
+ plugin.enable()
|
|
+ topo.standalone.restart()
|
|
+
|
|
+ # Do a bunch of updates
|
|
+ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
|
+ user_entry = users.create(properties={
|
|
+ 'sn': '1',
|
|
+ 'cn': 'user 1',
|
|
+ 'uid': 'user1',
|
|
+ 'uidNumber': '11',
|
|
+ 'gidNumber': '111',
|
|
+ 'givenname': 'user1',
|
|
+ 'homePhone': '0861234567',
|
|
+ 'carLicense': '131D16674',
|
|
+ 'mail': 'user1@whereever.com',
|
|
+ 'homeDirectory': '/home'
|
|
+ })
|
|
+ for count in range(0, 10):
|
|
+ user_entry.replace('mail', f'test{count}@test.com')
|
|
+
|
|
+ # Search the retro cl, and check for error messages
|
|
+ filter_simple = '(changenumber>=-1)'
|
|
+ filter_compound = '(&(changenumber>=-1)(targetuniqueid=*))'
|
|
+ retro_changelog_suffix = DSLdapObjects(topo.standalone, basedn=RETROCL_SUFFIX)
|
|
+ retro_changelog_suffix.filter(filter_simple)
|
|
+ assert not topo.standalone.searchAccessLog('Partially Unindexed Filter')
|
|
+
|
|
+ # Search the retro cl again with compound filter
|
|
+ retro_changelog_suffix.filter(filter_compound)
|
|
+ assert not topo.standalone.searchAccessLog('Partially Unindexed Filter')
|
|
+
|
|
+
|
|
+if __name__ == '__main__':
|
|
+ # Run isolated
|
|
+ # -s for DEBUG mode
|
|
+ CURRENT_FILE = os.path.realpath(__file__)
|
|
+ pytest.main(["-s", CURRENT_FILE])
|
|
diff --git a/ldap/servers/plugins/retrocl/retrocl_create.c b/ldap/servers/plugins/retrocl/retrocl_create.c
|
|
index 571e6899f..5bfde7831 100644
|
|
--- a/ldap/servers/plugins/retrocl/retrocl_create.c
|
|
+++ b/ldap/servers/plugins/retrocl/retrocl_create.c
|
|
@@ -133,7 +133,7 @@ retrocl_create_be(const char *bedir)
|
|
val.bv_len = strlen(val.bv_val);
|
|
slapi_entry_add_values(e, "cn", vals);
|
|
|
|
- val.bv_val = "false";
|
|
+ val.bv_val = "true"; /* enables the index */
|
|
val.bv_len = strlen(val.bv_val);
|
|
slapi_entry_add_values(e, "nssystemindex", vals);
|
|
|
|
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
|
index 9722d0ce7..38e7368e1 100644
|
|
--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
|
+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
|
@@ -25,7 +25,7 @@ int ldbm_instance_index_config_delete_callback(Slapi_PBlock *pb, Slapi_Entry *en
|
|
#define INDEXTYPE_NONE 1
|
|
|
|
static int
|
|
-ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_string, char **index_name, char *err_buf)
|
|
+ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_string, char **index_name, PRBool *is_system_index, char *err_buf)
|
|
{
|
|
Slapi_Attr *attr;
|
|
const struct berval *attrValue;
|
|
@@ -78,6 +78,15 @@ ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_st
|
|
}
|
|
}
|
|
|
|
+ *is_system_index = PR_FALSE;
|
|
+ if (0 == slapi_entry_attr_find(e, "nsSystemIndex", &attr)) {
|
|
+ slapi_attr_first_value(attr, &sval);
|
|
+ attrValue = slapi_value_get_berval(sval);
|
|
+ if (strcasecmp(attrValue->bv_val, "true") == 0) {
|
|
+ *is_system_index = PR_TRUE;
|
|
+ }
|
|
+ }
|
|
+
|
|
/* ok the entry is good to process, pass it to attr_index_config */
|
|
if (attr_index_config(inst->inst_be, (char *)trace_string, 0, e, 0, 0, err_buf)) {
|
|
slapi_ch_free_string(index_name);
|
|
@@ -101,9 +110,10 @@ ldbm_index_init_entry_callback(Slapi_PBlock *pb __attribute__((unused)),
|
|
void *arg)
|
|
{
|
|
ldbm_instance *inst = (ldbm_instance *)arg;
|
|
+ PRBool is_system_index = PR_FALSE;
|
|
|
|
returntext[0] = '\0';
|
|
- *returncode = ldbm_index_parse_entry(inst, e, "from ldbm instance init", NULL, NULL);
|
|
+ *returncode = ldbm_index_parse_entry(inst, e, "from ldbm instance init", NULL, &is_system_index /* not used */, NULL);
|
|
if (*returncode == LDAP_SUCCESS) {
|
|
return SLAPI_DSE_CALLBACK_OK;
|
|
} else {
|
|
@@ -126,17 +136,21 @@ ldbm_instance_index_config_add_callback(Slapi_PBlock *pb __attribute__((unused))
|
|
{
|
|
ldbm_instance *inst = (ldbm_instance *)arg;
|
|
char *index_name = NULL;
|
|
+ PRBool is_system_index = PR_FALSE;
|
|
|
|
returntext[0] = '\0';
|
|
- *returncode = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, returntext);
|
|
+ *returncode = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, &is_system_index, returntext);
|
|
if (*returncode == LDAP_SUCCESS) {
|
|
struct attrinfo *ai = NULL;
|
|
/* if the index is a "system" index, we assume it's being added by
|
|
* by the server, and it's okay for the index to go online immediately.
|
|
* if not, we set the index "offline" so it won't actually be used
|
|
* until someone runs db2index on it.
|
|
+ * If caller wants to add an index that they want to be online
|
|
+ * immediately they can also set "nsSystemIndex" to "true" in the
|
|
+ * index config entry (e.g. is_system_index).
|
|
*/
|
|
- if (!ldbm_attribute_always_indexed(index_name)) {
|
|
+ if (!is_system_index && !ldbm_attribute_always_indexed(index_name)) {
|
|
ainfo_get(inst->inst_be, index_name, &ai);
|
|
PR_ASSERT(ai != NULL);
|
|
ai->ai_indexmask |= INDEX_OFFLINE;
|
|
@@ -386,13 +400,14 @@ ldbm_instance_index_config_enable_index(ldbm_instance *inst, Slapi_Entry *e)
|
|
char *index_name = NULL;
|
|
int rc = LDAP_SUCCESS;
|
|
struct attrinfo *ai = NULL;
|
|
+ PRBool is_system_index = PR_FALSE;
|
|
|
|
index_name = slapi_entry_attr_get_charptr(e, "cn");
|
|
if (index_name) {
|
|
ainfo_get(inst->inst_be, index_name, &ai);
|
|
}
|
|
if (!ai) {
|
|
- rc = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, NULL);
|
|
+ rc = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, &is_system_index /* not used */, NULL);
|
|
}
|
|
if (rc == LDAP_SUCCESS) {
|
|
/* Assume the caller knows if it is OK to go online immediately */
|
|
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
|
|
index b6d778b01..fe610d175 100644
|
|
--- a/src/lib389/lib389/_mapped_object.py
|
|
+++ b/src/lib389/lib389/_mapped_object.py
|
|
@@ -148,6 +148,19 @@ class DSLdapObject(DSLogging, DSLint):
|
|
|
|
return True
|
|
|
|
+ def search(self, scope="subtree", filter='objectclass=*'):
|
|
+ search_scope = ldap.SCOPE_SUBTREE
|
|
+ if scope == 'base':
|
|
+ search_scope = ldap.SCOPE_BASE
|
|
+ elif scope == 'one':
|
|
+ search_scope = ldap.SCOPE_ONE
|
|
+ elif scope == 'subtree':
|
|
+ search_scope = ldap.SCOPE_SUBTREE
|
|
+ return self._instance.search_ext_s(self._dn, search_scope, filter,
|
|
+ serverctrls=self._server_controls,
|
|
+ clientctrls=self._client_controls,
|
|
+ escapehatch='i am sure')
|
|
+
|
|
def display(self, attrlist=['*']):
|
|
"""Get an entry but represent it as a string LDIF
|
|
|
|
--
|
|
2.31.1
|
|
|