Blame SOURCES/0045-Issue-4443-Internal-unindexed-searches-in-syncrepl-r.patch

a458d3
From 98caa0c0ddf48db791a26764aa695fa2345584ce Mon Sep 17 00:00:00 2001
a458d3
From: Mark Reynolds <mreynolds@redhat.com>
a458d3
Date: Tue, 13 Jul 2021 14:18:03 -0400
a458d3
Subject: [PATCH] Issue 4443 - Internal unindexed searches in syncrepl/retro
a458d3
 changelog
a458d3
a458d3
Bug Description:
a458d3
a458d3
When a non-system index is added to a backend it is
a458d3
disabled until the database is initialized or reindexed.
a458d3
So in the case of the retro changelog the changenumber index
a458d3
is alway disabled by default since it is never initialized.
a458d3
This leads to unexpected unindexed searches of the retro
a458d3
changelog.
a458d3
a458d3
Fix Description:
a458d3
a458d3
If an index has "nsSystemIndex" set to "true" then enable it
a458d3
immediately.
a458d3
a458d3
relates:  https://github.com/389ds/389-ds-base/issues/4443
a458d3
a458d3
Reviewed by: spichugi & tbordaz(Thanks!!)
a458d3
---
a458d3
 .../suites/retrocl/retrocl_indexing_test.py   | 68 +++++++++++++++++++
a458d3
 ldap/servers/plugins/retrocl/retrocl_create.c |  2 +-
a458d3
 .../slapd/back-ldbm/ldbm_index_config.c       | 25 +++++--
a458d3
 src/lib389/lib389/_mapped_object.py           | 13 ++++
a458d3
 4 files changed, 102 insertions(+), 6 deletions(-)
a458d3
 create mode 100644 dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py
a458d3
a458d3
diff --git a/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py b/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py
a458d3
new file mode 100644
a458d3
index 000000000..b1dfe962c
a458d3
--- /dev/null
a458d3
+++ b/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py
a458d3
@@ -0,0 +1,68 @@
a458d3
+import logging
a458d3
+import pytest
a458d3
+import os
a458d3
+from lib389._constants import RETROCL_SUFFIX, DEFAULT_SUFFIX
a458d3
+from lib389.topologies import topology_st as topo
a458d3
+from lib389.plugins import RetroChangelogPlugin
a458d3
+from lib389.idm.user import UserAccounts
a458d3
+from lib389._mapped_object import DSLdapObjects
a458d3
+log = logging.getLogger(__name__)
a458d3
+
a458d3
+
a458d3
+def test_indexing_is_online(topo):
a458d3
+    """Test that the changenmumber index is online right after enabling the plugin
a458d3
+
a458d3
+    :id: 16f4c001-9e0c-4448-a2b3-08ac1e85d40f
a458d3
+    :setup: Standalone Instance
a458d3
+    :steps:
a458d3
+        1. Enable retro cl
a458d3
+        2. Perform some updates
a458d3
+        3. Search for "(changenumber>=-1)", and it is not partially unindexed
a458d3
+        4. Search for "(&(changenumber>=-1)(targetuniqueid=*))", and it is not partially unindexed
a458d3
+    :expectedresults:
a458d3
+        1. Success
a458d3
+        2. Success
a458d3
+        3. Success
a458d3
+        4. Success
a458d3
+    """
a458d3
+
a458d3
+    # Enable plugin
a458d3
+    topo.standalone.config.set('nsslapd-accesslog-logbuffering',  'off')
a458d3
+    plugin = RetroChangelogPlugin(topo.standalone)
a458d3
+    plugin.enable()
a458d3
+    topo.standalone.restart()
a458d3
+
a458d3
+    # Do a bunch of updates
a458d3
+    users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
a458d3
+    user_entry = users.create(properties={
a458d3
+        'sn': '1',
a458d3
+        'cn': 'user 1',
a458d3
+        'uid': 'user1',
a458d3
+        'uidNumber': '11',
a458d3
+        'gidNumber': '111',
a458d3
+        'givenname': 'user1',
a458d3
+        'homePhone': '0861234567',
a458d3
+        'carLicense': '131D16674',
a458d3
+        'mail': 'user1@whereever.com',
a458d3
+        'homeDirectory': '/home'
a458d3
+    })
a458d3
+    for count in range(0, 10):
a458d3
+        user_entry.replace('mail', f'test{count}@test.com')
a458d3
+
a458d3
+    # Search the retro cl, and check for error messages
a458d3
+    filter_simple = '(changenumber>=-1)'
a458d3
+    filter_compound = '(&(changenumber>=-1)(targetuniqueid=*))'
a458d3
+    retro_changelog_suffix = DSLdapObjects(topo.standalone, basedn=RETROCL_SUFFIX)
a458d3
+    retro_changelog_suffix.filter(filter_simple)
a458d3
+    assert not topo.standalone.searchAccessLog('Partially Unindexed Filter')
a458d3
+
a458d3
+    # Search the retro cl again with compound filter
a458d3
+    retro_changelog_suffix.filter(filter_compound)
a458d3
+    assert not topo.standalone.searchAccessLog('Partially Unindexed Filter')
a458d3
+
a458d3
+
a458d3
+if __name__ == '__main__':
a458d3
+    # Run isolated
a458d3
+    # -s for DEBUG mode
a458d3
+    CURRENT_FILE = os.path.realpath(__file__)
a458d3
+    pytest.main(["-s", CURRENT_FILE])
a458d3
diff --git a/ldap/servers/plugins/retrocl/retrocl_create.c b/ldap/servers/plugins/retrocl/retrocl_create.c
a458d3
index 571e6899f..5bfde7831 100644
a458d3
--- a/ldap/servers/plugins/retrocl/retrocl_create.c
a458d3
+++ b/ldap/servers/plugins/retrocl/retrocl_create.c
a458d3
@@ -133,7 +133,7 @@ retrocl_create_be(const char *bedir)
a458d3
     val.bv_len = strlen(val.bv_val);
a458d3
     slapi_entry_add_values(e, "cn", vals);
a458d3
 
a458d3
-    val.bv_val = "false";
a458d3
+    val.bv_val = "true"; /* enables the index */
a458d3
     val.bv_len = strlen(val.bv_val);
a458d3
     slapi_entry_add_values(e, "nssystemindex", vals);
a458d3
 
a458d3
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
a458d3
index 9722d0ce7..38e7368e1 100644
a458d3
--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
a458d3
+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
a458d3
@@ -25,7 +25,7 @@ int ldbm_instance_index_config_delete_callback(Slapi_PBlock *pb, Slapi_Entry *en
a458d3
 #define INDEXTYPE_NONE 1
a458d3
 
a458d3
 static int
a458d3
-ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_string, char **index_name, char *err_buf)
a458d3
+ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_string, char **index_name, PRBool *is_system_index, char *err_buf)
a458d3
 {
a458d3
     Slapi_Attr *attr;
a458d3
     const struct berval *attrValue;
a458d3
@@ -78,6 +78,15 @@ ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_st
a458d3
         }
a458d3
     }
a458d3
 
a458d3
+    *is_system_index = PR_FALSE;
a458d3
+    if (0 == slapi_entry_attr_find(e, "nsSystemIndex", &attr)) {
a458d3
+        slapi_attr_first_value(attr, &sval);
a458d3
+        attrValue = slapi_value_get_berval(sval);
a458d3
+        if (strcasecmp(attrValue->bv_val, "true") == 0) {
a458d3
+            *is_system_index = PR_TRUE;
a458d3
+        }
a458d3
+    }
a458d3
+
a458d3
     /* ok the entry is good to process, pass it to attr_index_config */
a458d3
     if (attr_index_config(inst->inst_be, (char *)trace_string, 0, e, 0, 0, err_buf)) {
a458d3
         slapi_ch_free_string(index_name);
a458d3
@@ -101,9 +110,10 @@ ldbm_index_init_entry_callback(Slapi_PBlock *pb __attribute__((unused)),
a458d3
                                void *arg)
a458d3
 {
a458d3
     ldbm_instance *inst = (ldbm_instance *)arg;
a458d3
+    PRBool is_system_index = PR_FALSE;
a458d3
 
a458d3
     returntext[0] = '\0';
a458d3
-    *returncode = ldbm_index_parse_entry(inst, e, "from ldbm instance init", NULL, NULL);
a458d3
+    *returncode = ldbm_index_parse_entry(inst, e, "from ldbm instance init", NULL, &is_system_index /* not used */, NULL);
a458d3
     if (*returncode == LDAP_SUCCESS) {
a458d3
         return SLAPI_DSE_CALLBACK_OK;
a458d3
     } else {
a458d3
@@ -126,17 +136,21 @@ ldbm_instance_index_config_add_callback(Slapi_PBlock *pb __attribute__((unused))
a458d3
 {
a458d3
     ldbm_instance *inst = (ldbm_instance *)arg;
a458d3
     char *index_name = NULL;
a458d3
+    PRBool is_system_index = PR_FALSE;
a458d3
 
a458d3
     returntext[0] = '\0';
a458d3
-    *returncode = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, returntext);
a458d3
+    *returncode = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, &is_system_index, returntext);
a458d3
     if (*returncode == LDAP_SUCCESS) {
a458d3
         struct attrinfo *ai = NULL;
a458d3
         /* if the index is a "system" index, we assume it's being added by
a458d3
          * by the server, and it's okay for the index to go online immediately.
a458d3
          * if not, we set the index "offline" so it won't actually be used
a458d3
          * until someone runs db2index on it.
a458d3
+         * If caller wants to add an index that they want to be online
a458d3
+         * immediately they can also set "nsSystemIndex" to "true" in the
a458d3
+         * index config entry (e.g. is_system_index).
a458d3
          */
a458d3
-        if (!ldbm_attribute_always_indexed(index_name)) {
a458d3
+        if (!is_system_index && !ldbm_attribute_always_indexed(index_name)) {
a458d3
             ainfo_get(inst->inst_be, index_name, &ai;;
a458d3
             PR_ASSERT(ai != NULL);
a458d3
             ai->ai_indexmask |= INDEX_OFFLINE;
a458d3
@@ -386,13 +400,14 @@ ldbm_instance_index_config_enable_index(ldbm_instance *inst, Slapi_Entry *e)
a458d3
     char *index_name = NULL;
a458d3
     int rc = LDAP_SUCCESS;
a458d3
     struct attrinfo *ai = NULL;
a458d3
+    PRBool is_system_index = PR_FALSE;
a458d3
 
a458d3
     index_name = slapi_entry_attr_get_charptr(e, "cn");
a458d3
     if (index_name) {
a458d3
         ainfo_get(inst->inst_be, index_name, &ai;;
a458d3
     }
a458d3
     if (!ai) {
a458d3
-        rc = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, NULL);
a458d3
+        rc = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, &is_system_index /* not used */, NULL);
a458d3
     }
a458d3
     if (rc == LDAP_SUCCESS) {
a458d3
         /* Assume the caller knows if it is OK to go online immediately */
a458d3
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
a458d3
index ca6ea6ef8..6cdcb0dc7 100644
a458d3
--- a/src/lib389/lib389/_mapped_object.py
a458d3
+++ b/src/lib389/lib389/_mapped_object.py
a458d3
@@ -147,6 +147,19 @@ class DSLdapObject(DSLogging, DSLint):
a458d3
 
a458d3
         return True
a458d3
 
a458d3
+    def search(self, scope="subtree", filter='objectclass=*'):
a458d3
+        search_scope = ldap.SCOPE_SUBTREE
a458d3
+        if scope == 'base':
a458d3
+            search_scope = ldap.SCOPE_BASE
a458d3
+        elif scope == 'one':
a458d3
+            search_scope = ldap.SCOPE_ONE
a458d3
+        elif scope == 'subtree':
a458d3
+            search_scope = ldap.SCOPE_SUBTREE
a458d3
+        return self._instance.search_ext_s(self._dn, search_scope, filter,
a458d3
+                                           serverctrls=self._server_controls,
a458d3
+                                           clientctrls=self._client_controls,
a458d3
+                                           escapehatch='i am sure')
a458d3
+
a458d3
     def display(self, attrlist=['*']):
a458d3
         """Get an entry but represent it as a string LDIF
a458d3
 
a458d3
-- 
a458d3
2.31.1
a458d3