You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
12 KiB
263 lines
12 KiB
9 months ago
|
From 30fb67855d7b3da6ed98d177e416145d5767a7d4 Mon Sep 17 00:00:00 2001
|
||
|
From: Mark Reynolds <mreynolds@redhat.com>
|
||
|
Date: Tue, 6 Jun 2023 12:49:50 -0400
|
||
|
Subject: [PATCH 01/11] Issue 5789 - Improve ds-replcheck error handling
|
||
|
|
||
|
Description: When replication is not fully configured the tool outputs vague
|
||
|
messages. These should be cleaned up to indicate that
|
||
|
replication was not initialized. Also added healthcheck.
|
||
|
|
||
|
Relates: https://github.com/389ds/389-ds-base/issues/5789
|
||
|
|
||
|
Reviewed by: tbordaz, spichugi, progier (Thanks!!!)
|
||
|
---
|
||
|
ldap/admin/src/scripts/ds-replcheck | 35 +++++++++++--------
|
||
|
.../src/lib/replication/replTasks.jsx | 2 +-
|
||
|
src/cockpit/389-console/src/replication.jsx | 3 +-
|
||
|
src/lib389/lib389/cli_conf/replication.py | 7 +++-
|
||
|
src/lib389/lib389/config.py | 2 +-
|
||
|
src/lib389/lib389/lint.py | 10 ++++++
|
||
|
src/lib389/lib389/replica.py | 20 +++++++++--
|
||
|
7 files changed, 58 insertions(+), 21 deletions(-)
|
||
|
|
||
|
diff --git a/ldap/admin/src/scripts/ds-replcheck b/ldap/admin/src/scripts/ds-replcheck
|
||
|
index f411f357a..efa13ffe8 100755
|
||
|
--- a/ldap/admin/src/scripts/ds-replcheck
|
||
|
+++ b/ldap/admin/src/scripts/ds-replcheck
|
||
|
@@ -1,7 +1,7 @@
|
||
|
#!/usr/bin/python3
|
||
|
|
||
|
# --- BEGIN COPYRIGHT BLOCK ---
|
||
|
-# Copyright (C) 2021 Red Hat, Inc.
|
||
|
+# Copyright (C) 2023 Red Hat, Inc.
|
||
|
# All rights reserved.
|
||
|
#
|
||
|
# License: GPL (version 3 or any later version).
|
||
|
@@ -216,17 +216,17 @@ def get_ruv_state(opts):
|
||
|
mtime = get_ruv_time(opts['supplier_ruv'], opts['rid'])
|
||
|
rtime = get_ruv_time(opts['replica_ruv'], opts['rid'])
|
||
|
if mtime == -1:
|
||
|
- repl_state = "Replication State: Replica ID ({}) not found in Supplier's RUV".format(opts['rid'])
|
||
|
+ repl_state = f"Replication State: Replica ID ({opts['rid']}) not found in Supplier's RUV"
|
||
|
elif rtime == -1:
|
||
|
- repl_state = "Replication State: Replica ID ({}) not found in Replica's RUV (not initialized?)".format(opts['rid'])
|
||
|
+ repl_state = f"Replication State: Replica ID ({opts['rid']}) not found in Replica's RUV (not initialized?)"
|
||
|
elif mtime == 0:
|
||
|
repl_state = "Replication State: Supplier has not seen any updates"
|
||
|
elif rtime == 0:
|
||
|
repl_state = "Replication State: Replica has not seen any changes from the Supplier"
|
||
|
elif mtime > rtime:
|
||
|
- repl_state = "Replication State: Replica is behind Supplier by: {} seconds".format(mtime - rtime)
|
||
|
+ repl_state = f"Replication State: Replica is behind Supplier by: {mtime - rtime} seconds"
|
||
|
elif mtime < rtime:
|
||
|
- repl_state = "Replication State: Replica is ahead of Supplier by: {} seconds".format(rtime - mtime)
|
||
|
+ repl_state = f"Replication State: Replica is ahead of Supplier by: {rtime - mtime} seconds"
|
||
|
else:
|
||
|
repl_state = "Replication State: Supplier and Replica are in perfect synchronization"
|
||
|
|
||
|
@@ -928,7 +928,7 @@ def check_for_diffs(mentries, mglue, rentries, rglue, report, opts):
|
||
|
|
||
|
return report
|
||
|
|
||
|
-def validate_suffix(ldapnode, suffix, hostname):
|
||
|
+def validate_suffix(ldapnode, suffix, hostname, port):
|
||
|
"""Validate that the suffix exists
|
||
|
:param ldapnode - The LDAP object
|
||
|
:param suffix - The suffix to validate
|
||
|
@@ -938,10 +938,11 @@ def validate_suffix(ldapnode, suffix, hostname):
|
||
|
try:
|
||
|
ldapnode.search_s(suffix, ldap.SCOPE_BASE)
|
||
|
except ldap.NO_SUCH_OBJECT:
|
||
|
- print("Error: Failed to validate suffix in {}. {} does not exist.".format(hostname, suffix))
|
||
|
+ print(f"Error: Failed to validate suffix in {hostname}:{port}. {suffix} " +
|
||
|
+ "does not exist. Replica might need to be initialized.")
|
||
|
return False
|
||
|
except ldap.LDAPError as e:
|
||
|
- print("Error: failed to validate suffix in {} ({}). ".format(hostname, str(e)))
|
||
|
+ print(f"Error: failed to validate suffix in {hostname}:{port} ({str(e)}). ")
|
||
|
return False
|
||
|
|
||
|
# Check suffix is replicated
|
||
|
@@ -949,10 +950,10 @@ def validate_suffix(ldapnode, suffix, hostname):
|
||
|
replica_filter = "(&(objectclass=nsds5replica)(nsDS5ReplicaRoot=%s))" % suffix
|
||
|
supplier_replica = ldapnode.search_s("cn=config",ldap.SCOPE_SUBTREE,replica_filter)
|
||
|
if (len(supplier_replica) != 1):
|
||
|
- print("Error: Failed to validate suffix in {}. {} is not replicated.".format(hostname, suffix))
|
||
|
+ print(f"Error: Failed to validate suffix in {hostname}:{port}. {suffix} is not replicated.")
|
||
|
return False
|
||
|
except ldap.LDAPError as e:
|
||
|
- print("Error: failed to validate suffix in {} ({}). ".format(hostname, str(e)))
|
||
|
+ print(f"Error: failed to validate suffix in {hostname}:{port} ({str(e)}). ")
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
@@ -1034,10 +1035,10 @@ def connect_to_replicas(opts):
|
||
|
# Validate suffix
|
||
|
if opts['verbose']:
|
||
|
print ("Validating suffix ...")
|
||
|
- if not validate_suffix(supplier, opts['suffix'], opts['mhost']):
|
||
|
+ if not validate_suffix(supplier, opts['suffix'], opts['mhost'], opts['mport']):
|
||
|
sys.exit(1)
|
||
|
|
||
|
- if not validate_suffix(replica,opts['suffix'], opts['rhost']):
|
||
|
+ if not validate_suffix(replica,opts['suffix'], opts['rhost'], opts['rport']):
|
||
|
sys.exit(1)
|
||
|
|
||
|
# Get the RUVs
|
||
|
@@ -1048,8 +1049,11 @@ def connect_to_replicas(opts):
|
||
|
if len(supplier_ruv) > 0:
|
||
|
opts['supplier_ruv'] = ensure_list_str(supplier_ruv[0][1]['nsds50ruv'])
|
||
|
else:
|
||
|
- print("Error: Supplier does not have an RUV entry")
|
||
|
+ print("Error: Supplier does not have an RUV entry. It might need to be initialized.")
|
||
|
sys.exit(1)
|
||
|
+ except ldap.NO_SUCH_OBJECT:
|
||
|
+ print("Error: Supplier does not have an RUV entry. It might need to be initialized.")
|
||
|
+ sys.exit(1)
|
||
|
except ldap.LDAPError as e:
|
||
|
print("Error: Failed to get Supplier RUV entry: {}".format(str(e)))
|
||
|
sys.exit(1)
|
||
|
@@ -1061,8 +1065,11 @@ def connect_to_replicas(opts):
|
||
|
if len(replica_ruv) > 0:
|
||
|
opts['replica_ruv'] = ensure_list_str(replica_ruv[0][1]['nsds50ruv'])
|
||
|
else:
|
||
|
- print("Error: Replica does not have an RUV entry")
|
||
|
+ print("Error: Replica does not have an RUV entry. It might need to be initialized.")
|
||
|
sys.exit(1)
|
||
|
+ except ldap.NO_SUCH_OBJECT:
|
||
|
+ print("Error: Replica does not have an RUV entry. It might need to be initialized.")
|
||
|
+ sys.exit(1)
|
||
|
except ldap.LDAPError as e:
|
||
|
print("Error: Failed to get Replica RUV entry: {}".format(str(e)))
|
||
|
sys.exit(1)
|
||
|
diff --git a/src/cockpit/389-console/src/lib/replication/replTasks.jsx b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
|
||
|
index 9387af325..d592995f8 100644
|
||
|
--- a/src/cockpit/389-console/src/lib/replication/replTasks.jsx
|
||
|
+++ b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
|
||
|
@@ -345,7 +345,7 @@ export class ReplRUV extends React.Component {
|
||
|
|
||
|
if (localRID == "") {
|
||
|
localRUV =
|
||
|
- <div className="ds-indent">
|
||
|
+ <div className="ds-indent ds-margin-top">
|
||
|
<i>
|
||
|
There is no local RUV, the database might not have been initialized yet.
|
||
|
</i>
|
||
|
diff --git a/src/cockpit/389-console/src/replication.jsx b/src/cockpit/389-console/src/replication.jsx
|
||
|
index c2598c118..76b8da486 100644
|
||
|
--- a/src/cockpit/389-console/src/replication.jsx
|
||
|
+++ b/src/cockpit/389-console/src/replication.jsx
|
||
|
@@ -880,7 +880,8 @@ export class Replication extends React.Component {
|
||
|
})
|
||
|
.fail(err => {
|
||
|
const errMsg = JSON.parse(err);
|
||
|
- if (errMsg.desc !== "No such object") {
|
||
|
+ if (errMsg.desc !== "No such object" &&
|
||
|
+ !errMsg.desc.includes('There is no RUV for suffix')) {
|
||
|
this.props.addNotification(
|
||
|
"error",
|
||
|
`Error loading suffix RUV - ${errMsg.desc}`
|
||
|
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
|
||
|
index 8a919da98..2c9501cde 100644
|
||
|
--- a/src/lib389/lib389/cli_conf/replication.py
|
||
|
+++ b/src/lib389/lib389/cli_conf/replication.py
|
||
|
@@ -115,10 +115,15 @@ def _args_to_attrs(args):
|
||
|
#
|
||
|
def get_ruv(inst, basedn, log, args):
|
||
|
replicas = Replicas(inst)
|
||
|
- replica = replicas.get(args.suffix)
|
||
|
+ try:
|
||
|
+ replica = replicas.get(args.suffix)
|
||
|
+ except ldap.NO_SUCH_OBJECT:
|
||
|
+ raise ValueError(f"Suffix '{args.suffix}' is not configured for replication.")
|
||
|
ruv = replica.get_ruv()
|
||
|
ruv_dict = ruv.format_ruv()
|
||
|
ruvs = ruv_dict['ruvs']
|
||
|
+ if len(ruvs) == 0:
|
||
|
+ raise ValueError(f"There is no RUV for suffix {args.suffix}. Replica is not initialized.")
|
||
|
if args and args.json:
|
||
|
log.info(json.dumps({"type": "list", "items": ruvs}, indent=4))
|
||
|
else:
|
||
|
diff --git a/src/lib389/lib389/config.py b/src/lib389/lib389/config.py
|
||
|
index c178eb02f..00d38463a 100644
|
||
|
--- a/src/lib389/lib389/config.py
|
||
|
+++ b/src/lib389/lib389/config.py
|
||
|
@@ -209,7 +209,7 @@ class Config(DSLdapObject):
|
||
|
yield report
|
||
|
|
||
|
def _lint_passwordscheme(self):
|
||
|
- allowed_schemes = ['SSHA512', 'PBKDF2_SHA256', 'GOST_YESCRYPT']
|
||
|
+ allowed_schemes = ['PBKDF2-SHA512', 'PBKDF2_SHA256', 'PBKDF2_SHA512', 'GOST_YESCRYPT']
|
||
|
u_password_scheme = self.get_attr_val_utf8('passwordStorageScheme')
|
||
|
u_root_scheme = self.get_attr_val_utf8('nsslapd-rootpwstoragescheme')
|
||
|
if u_root_scheme not in allowed_schemes or u_password_scheme not in allowed_schemes:
|
||
|
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||
|
index ce23d5c12..0219801f4 100644
|
||
|
--- a/src/lib389/lib389/lint.py
|
||
|
+++ b/src/lib389/lib389/lint.py
|
||
|
@@ -310,6 +310,16 @@ because the consumer server is not reachable.""",
|
||
|
'fix': """Check if the consumer is running, and also check the errors log for more information."""
|
||
|
}
|
||
|
|
||
|
+DSREPLLE0006 = {
|
||
|
+ 'dsle': 'DSREPLLE0006',
|
||
|
+ 'severity': 'MEDIUM',
|
||
|
+ 'description': 'Replication has not been initilaized',
|
||
|
+ 'items': ['Replication'],
|
||
|
+ 'detail': """The replication for "SUFFIX" does not appear to be initialzied,
|
||
|
+because there is no RUV found for the suffix.""",
|
||
|
+ 'fix': """Initialize this replica from a primary supplier replica"""
|
||
|
+}
|
||
|
+
|
||
|
# Replication changelog
|
||
|
DSCLLE0001 = {
|
||
|
'dsle': 'DSCLLE0001',
|
||
|
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||
|
index f0c71cbeb..8b0243345 100644
|
||
|
--- a/src/lib389/lib389/replica.py
|
||
|
+++ b/src/lib389/lib389/replica.py
|
||
|
@@ -45,7 +45,7 @@ from lib389.idm.services import ServiceAccounts
|
||
|
from lib389.idm.organizationalunit import OrganizationalUnits
|
||
|
from lib389.conflicts import ConflictEntries
|
||
|
from lib389.lint import (DSREPLLE0001, DSREPLLE0002, DSREPLLE0003, DSREPLLE0004,
|
||
|
- DSREPLLE0005, DSCLLE0001)
|
||
|
+ DSREPLLE0005, DSREPLLE0006, DSCLLE0001)
|
||
|
|
||
|
|
||
|
class ReplicaLegacy(object):
|
||
|
@@ -1207,6 +1207,20 @@ class Replica(DSLdapObject):
|
||
|
report['check'] = f'replication:conflicts'
|
||
|
yield report
|
||
|
|
||
|
+ def _lint_no_ruv(self):
|
||
|
+ # No RUV means replica has not been initialized
|
||
|
+ replicas = Replicas(self._instance).list()
|
||
|
+ for replica in replicas:
|
||
|
+ ruv = replica.get_ruv()
|
||
|
+ ruv_dict = ruv.format_ruv()
|
||
|
+ ruvs = ruv_dict['ruvs']
|
||
|
+ suffix = replica.get_suffix()
|
||
|
+ if len(ruvs) == 0:
|
||
|
+ report = copy.deepcopy(DSREPLLE0006)
|
||
|
+ report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
||
|
+ report['check'] = 'replication'
|
||
|
+ yield report
|
||
|
+
|
||
|
def _validate(self, rdn, properties, basedn):
|
||
|
(tdn, str_props) = super(Replica, self)._validate(rdn, properties, basedn)
|
||
|
# We override the tdn here. We use the MT for the suffix.
|
||
|
@@ -1587,8 +1601,8 @@ class Replica(DSLdapObject):
|
||
|
serverctrls=self._server_controls, clientctrls=self._client_controls,
|
||
|
escapehatch='i am sure')[0]
|
||
|
data = ensure_list_str(ent.getValues('nsds50ruv'))
|
||
|
- except IndexError:
|
||
|
- # There is no ruv entry, it's okay
|
||
|
+ except (IndexError, ldap.NO_SUCH_OBJECT):
|
||
|
+ # There are no ruv elements, it's okay
|
||
|
pass
|
||
|
|
||
|
return RUV(data)
|
||
|
--
|
||
|
2.41.0
|
||
|
|