269 lines
11 KiB
Diff
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
|
|
|