389-ds-base/SOURCES/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch

269 lines
11 KiB
Diff

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
absent server ID (#6941)
Description: The dsconf monitor server command fails when using ldapi://
protocol because the server ID is not set, preventing PID retrieval from
defaults.inf. This causes the Web console to fail displaying the "Server
Version" field and potentially other CLI/WebUI issues.
The fix attempts to derive the server ID from the LDAPI socket path when
not explicitly provided. This covers the common case where the socket name
contains the instance name (e.g., slapd-instance.socket).
If that's not possible, it also attempts to derive the server ID from the
nsslapd-instancedir configuration attribute. The derived server ID
is validated against actual system instances to ensure it exists.
Note that socket names can vary and nsslapd-instancedir can be changed.
This is a best-effort approach for the common naming pattern.
Also fixes the LDAPI socket path extraction which was incorrectly using
offset 9 instead of 8 for ldapi:// URIs.
The monitor command now handles missing PIDs gracefully, returning zero
values for process-specific stats instead of failing completely.
Fixes: https://github.com/389ds/389-ds-base/issues/6940
Reviewed by: @vashirov, @mreynolds389 (Thanks!!)
---
src/lib389/lib389/__init__.py | 93 +++++++++++++++++++++++++++---
src/lib389/lib389/cli_base/dsrc.py | 4 +-
src/lib389/lib389/monitor.py | 50 ++++++++++++----
3 files changed, 124 insertions(+), 23 deletions(-)
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 65e70c1dd..e6f9273eb 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -17,7 +17,7 @@
import sys
import os
-from urllib.parse import urlparse
+from urllib.parse import urlparse, unquote
import stat
import pwd
import grp
@@ -67,7 +67,8 @@ from lib389.utils import (
get_default_db_lib,
selinux_present,
selinux_label_port,
- get_user_is_root)
+ get_user_is_root,
+ get_instance_list)
from lib389.paths import Paths
from lib389.nss_ssl import NssSsl
from lib389.tasks import BackupTask, RestoreTask, Task
@@ -304,6 +305,57 @@ class DirSrv(SimpleLDAPObject, object):
self.dbdir = self.ds_paths.db_dir
self.changelogdir = os.path.join(os.path.dirname(self.dbdir), DEFAULT_CHANGELOG_DB)
+ def _extract_serverid_from_string(self, text):
+ """Extract serverid from a string containing 'slapd-<serverid>' pattern.
+ Returns the serverid or None if not found or validation fails.
+ Only attempts derivation if serverid is currently None.
+ """
+ if getattr(self, 'serverid', None) is not None:
+ return None
+ if not text:
+ return None
+
+ # Use regex to extract serverid from "slapd-<serverid>" or "slapd-<serverid>.socket"
+ match = re.search(r'slapd-([A-Za-z0-9._-]+?)(?:\.socket)?(?:$|/)', text)
+ if not match:
+ return None
+ candidate = match.group(1)
+
+ self.serverid = candidate
+ try:
+ insts = get_instance_list()
+ except Exception:
+ self.serverid = None
+ return None
+ if f'slapd-{candidate}' in insts or candidate in insts:
+ return candidate
+ # restore original and report failure
+ self.serverid = None
+ return None
+
+ def _derive_serverid_from_ldapi(self):
+ """Attempt to derive serverid from an LDAPI socket path or URI and
+ verify it exists on the system. Returns the serverid or None.
+ """
+ socket_path = None
+ if hasattr(self, 'ldapi_socket') and self.ldapi_socket:
+ socket_path = unquote(self.ldapi_socket)
+ elif hasattr(self, 'ldapuri') and isinstance(self.ldapuri, str) and self.ldapuri.startswith('ldapi://'):
+ socket_path = unquote(self.ldapuri[len('ldapi://'):])
+
+ return self._extract_serverid_from_string(socket_path)
+
+ def _derive_serverid_from_instancedir(self):
+ """Extract serverid from nsslapd-instancedir path like '/usr/lib64/dirsrv/slapd-<serverid>'"""
+ try:
+ from lib389.config import Config
+ config = Config(self)
+ instancedir = config.get_attr_val_utf8_l("nsslapd-instancedir")
+ except Exception:
+ return None
+
+ return self._extract_serverid_from_string(instancedir)
+
def rebind(self):
"""Reconnect to the DS
@@ -576,6 +628,15 @@ class DirSrv(SimpleLDAPObject, object):
self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off')
self.isLocal = True
self.log.debug("Allocate %s with %s", self.__class__, self.ldapi_socket)
+ elif self.ldapuri is not None and isinstance(self.ldapuri, str) and self.ldapuri.startswith('ldapi://'):
+ # Try to learn serverid from ldapi uri
+ try:
+ self.ldapi_enabled = 'on'
+ self.ldapi_socket = unquote(self.ldapuri[len('ldapi://'):])
+ self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off')
+ self.isLocal = True
+ except Exception:
+ pass
# Settings from args of server attributes
self.strict_hostname = args.get(SER_STRICT_HOSTNAME_CHECKING, False)
if self.strict_hostname is True:
@@ -596,9 +657,16 @@ class DirSrv(SimpleLDAPObject, object):
self.log.debug("Allocate %s with %s:%s", self.__class__, self.host, (self.sslport or self.port))
- if SER_SERVERID_PROP in args:
- self.ds_paths = Paths(serverid=args[SER_SERVERID_PROP], instance=self, local=self.isLocal)
+ # Try to determine serverid if not provided
+ if SER_SERVERID_PROP in args and args.get(SER_SERVERID_PROP) is not None:
self.serverid = args.get(SER_SERVERID_PROP, None)
+ elif getattr(self, 'serverid', None) is None and self.isLocal:
+ sid = self._derive_serverid_from_ldapi()
+ if sid:
+ self.serverid = sid
+
+ if getattr(self, 'serverid', None):
+ self.ds_paths = Paths(serverid=self.serverid, instance=self, local=self.isLocal)
else:
self.ds_paths = Paths(instance=self, local=self.isLocal)
@@ -1032,6 +1100,17 @@ class DirSrv(SimpleLDAPObject, object):
self.__initPart2()
self.state = DIRSRV_STATE_ONLINE
# Now that we're online, some of our methods may try to query the version online.
+
+ # After transitioning online, attempt to derive serverid if still unknown.
+ # If we find it, refresh ds_paths and rerun __initPart2
+ if getattr(self, 'serverid', None) is None and self.isLocal:
+ sid = self._derive_serverid_from_instancedir()
+ if sid:
+ self.serverid = sid
+ # Reinitialize paths with the new serverid
+ self.ds_paths = Paths(serverid=self.serverid, instance=self, local=self.isLocal)
+ if not connOnly:
+ self.__initPart2()
self.__add_brookers__()
def close(self):
@@ -3569,8 +3648,4 @@ class DirSrv(SimpleLDAPObject, object):
"""
Get the pid of the running server
"""
- pid = pid_from_file(self.pid_file())
- if pid == 0 or pid is None:
- return 0
- else:
- return pid
+ return pid_from_file(self.pid_file())
diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py
index 84567b990..498228ce0 100644
--- a/src/lib389/lib389/cli_base/dsrc.py
+++ b/src/lib389/lib389/cli_base/dsrc.py
@@ -56,7 +56,7 @@ def dsrc_arg_concat(args, dsrc_inst):
new_dsrc_inst['args'][SER_ROOT_DN] = new_dsrc_inst['binddn']
if new_dsrc_inst['uri'][0:8] == 'ldapi://':
new_dsrc_inst['args'][SER_LDAPI_ENABLED] = "on"
- new_dsrc_inst['args'][SER_LDAPI_SOCKET] = new_dsrc_inst['uri'][9:]
+ new_dsrc_inst['args'][SER_LDAPI_SOCKET] = new_dsrc_inst['uri'][8:]
new_dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"
# Make new
@@ -170,7 +170,7 @@ def dsrc_to_ldap(path, instance_name, log):
dsrc_inst['args'][SER_ROOT_DN] = dsrc_inst['binddn']
if dsrc_inst['uri'][0:8] == 'ldapi://':
dsrc_inst['args'][SER_LDAPI_ENABLED] = "on"
- dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][9:]
+ dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][8:]
dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"
# Return the dict.
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
index 27b99a7e3..bf3e1df76 100644
--- a/src/lib389/lib389/monitor.py
+++ b/src/lib389/lib389/monitor.py
@@ -92,21 +92,47 @@ class Monitor(DSLdapObject):
Get CPU and memory stats
"""
stats = {}
- pid = self._instance.get_pid()
+ try:
+ pid = self._instance.get_pid()
+ except Exception:
+ pid = None
total_mem = psutil.virtual_memory()[0]
- p = psutil.Process(pid)
- memory_stats = p.memory_full_info()
- # Get memory & CPU stats
+ # Always include total system memory
stats['total_mem'] = [str(total_mem)]
- stats['rss'] = [str(memory_stats[0])]
- stats['vms'] = [str(memory_stats[1])]
- stats['swap'] = [str(memory_stats[9])]
- stats['mem_rss_percent'] = [str(round(p.memory_percent("rss")))]
- stats['mem_vms_percent'] = [str(round(p.memory_percent("vms")))]
- stats['mem_swap_percent'] = [str(round(p.memory_percent("swap")))]
- stats['total_threads'] = [str(p.num_threads())]
- stats['cpu_usage'] = [str(round(p.cpu_percent(interval=0.1)))]
+
+ # Process-specific stats - only if process is running (pid is not None)
+ if pid is not None:
+ try:
+ p = psutil.Process(pid)
+ memory_stats = p.memory_full_info()
+
+ # Get memory & CPU stats
+ stats['rss'] = [str(memory_stats[0])]
+ stats['vms'] = [str(memory_stats[1])]
+ stats['swap'] = [str(memory_stats[9])]
+ stats['mem_rss_percent'] = [str(round(p.memory_percent("rss")))]
+ stats['mem_vms_percent'] = [str(round(p.memory_percent("vms")))]
+ stats['mem_swap_percent'] = [str(round(p.memory_percent("swap")))]
+ stats['total_threads'] = [str(p.num_threads())]
+ stats['cpu_usage'] = [str(round(p.cpu_percent(interval=0.1)))]
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ # Process exists in PID file but is not accessible or doesn't exist
+ pid = None
+
+ # If no valid PID, provide zero values for process stats
+ if pid is None:
+ stats['rss'] = ['0']
+ stats['vms'] = ['0']
+ stats['swap'] = ['0']
+ stats['mem_rss_percent'] = ['0']
+ stats['mem_vms_percent'] = ['0']
+ stats['mem_swap_percent'] = ['0']
+ stats['total_threads'] = ['0']
+ stats['cpu_usage'] = ['0']
+ stats['server_status'] = ['PID unavailable']
+ else:
+ stats['server_status'] = ['Server running']
# Connections to DS
if self._instance.port == "0":
--
2.49.0