Skip to content

Commit

Permalink
Issue 6436 - MOD on a large group slow if substring index is present
Browse files Browse the repository at this point in the history
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: 389ds#6436

Reviewed by:
  • Loading branch information
jchapma committed Dec 4, 2024
1 parent fd62700 commit 5495af7
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 4 deletions.
86 changes: 83 additions & 3 deletions dirsrvtests/tests/suites/healthcheck/health_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,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
Expand All @@ -203,8 +203,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
Expand Down Expand Up @@ -236,6 +236,86 @@ 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

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.xfail(ds_is_older("1.4.1"), reason="Not implemented")
def test_healthcheck_virtual_attr_incorrectly_indexed(topology_st):
"""Check if HealthCheck returns DSVIRTLE0001 code
Expand Down
15 changes: 15 additions & 0 deletions src/lib389/lib389/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,21 @@
"""
}

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',
Expand Down
52 changes: 51 additions & 1 deletion src/lib389/lib389/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
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 (
Expand Down Expand Up @@ -827,6 +827,56 @@ def _lint_member_attr_indexes(self):
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()
attrs = self.get_attr_vals_utf8_l("memberofgroupattr")
container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
#import pdb; pdb.set_trace()
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()
membership_attrs = ['member', 'uniquemember']
for attr in membership_attrs:
report = copy.deepcopy(DSMOLE0002)
try:
index = indexes.get(attr)
types = index.get_attr_vals_utf8_l("nsIndexType")
valid = True
if "sub" in types:
valid = False

if not valid:
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'memberof:substring_index'
yield report
except:
# No index at all, bad
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'memberof:substring_index'
yield report

def get_attr(self):
"""Get memberofattr attribute"""

Expand Down

0 comments on commit 5495af7

Please sign in to comment.