From 4c16b43c714b0b3d1d7ba6cb65be00bd42a27f3f Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Tue, 6 Jun 2023 12:49:50 -0400 Subject: [PATCH] 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 f411f357aa..efa13ffe81 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 9387af3258..d592995f88 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 = -
+
There is no local RUV, the database might not have been initialized yet. diff --git a/src/cockpit/389-console/src/replication.jsx b/src/cockpit/389-console/src/replication.jsx index c2598c1181..76b8da486a 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 8a919da989..2c9501cdef 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 c178eb02f0..00d38463a0 100644 --- a/src/lib389/lib389/config.py +++ b/src/lib389/lib389/config.py @@ -209,7 +209,7 @@ def _lint_hr_timestamp(self): 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 ce23d5c128..0219801f48 100644 --- a/src/lib389/lib389/lint.py +++ b/src/lib389/lib389/lint.py @@ -310,6 +310,16 @@ '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 f0c71cbebd..8b02433454 100644 --- a/src/lib389/lib389/replica.py +++ b/src/lib389/lib389/replica.py @@ -45,7 +45,7 @@ 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 @@ def _lint_conflicts(self): 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 @@ def get_ruv(self): 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)