Blob Blame Raw
From 2a2773d4bf8553ba64b396d567fe05506b22c94c Mon Sep 17 00:00:00 2001
From: progier389 <72748589+progier389@users.noreply.github.com>
Date: Tue, 24 Nov 2020 19:22:49 +0100
Subject: [PATCH] Issue 4449 - dsconf replication monitor fails to retrieve
 database RUV - consumer (Unavailable) (#4451)

Bug Description:

"dsconf replication monitor" fails to retrieve database RUV entry from consumer and this
appears into the Cockpit web UI too.
The problem is that the bind credentials are not rightly propagated when trying to get
the consumers agreement status.  Then supplier credntials are used instead  and RUV
is searched anonymously because there is no bind dn in ldapi case.

Fix Description:

- Propagates the bind credentials when computing agreement status
- Add a credential cache because now a replica password could get asked several times:
    when discovering the topology and
    when getting the agreement maxcsn
- No testcase in 1.4.3 branch as the file modfied in master does not exists

- Add a comment about nonlocal keyword

Relates: #4449

Reviewers:
  firstyear
  droideck
  mreynolds

Issue 4449: Add a comment about nonlocal keyword

(cherry picked from commit 73ee04fa12cd1de3a5e47c109e79e31c1aaaa2ab)
---
 src/lib389/lib389/cli_conf/replication.py | 13 +++++++++++--
 src/lib389/lib389/replica.py              | 16 ++++++++++++----
 2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index 9dbaa320a..248972cba 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -369,9 +369,16 @@ def set_repl_config(inst, basedn, log, args):
 
 def get_repl_monitor_info(inst, basedn, log, args):
     connection_data = dsrc_to_repl_monitor(DSRC_HOME, log)
+    credentials_cache = {}
 
     # Additional details for the connections to the topology
     def get_credentials(host, port):
+        # credentials_cache is nonlocal to refer to the instance
+        # from enclosing function (get_repl_monitor_info)`
+        nonlocal credentials_cache
+        key = f'{host}:{port}'
+        if key in credentials_cache:
+            return credentials_cache[key]
         found = False
         if args.connections:
             connections = args.connections
@@ -406,8 +413,10 @@ def get_repl_monitor_info(inst, basedn, log, args):
             binddn = input(f'\nEnter a bind DN for {host}:{port}: ').rstrip()
             bindpw = getpass(f"Enter a password for {binddn} on {host}:{port}: ").rstrip()
 
-        return {"binddn": binddn,
-                "bindpw": bindpw}
+        credentials = {"binddn": binddn,
+                       "bindpw": bindpw}
+        credentials_cache[key] = credentials
+        return credentials
 
     repl_monitor = ReplicationMonitor(inst)
     report_dict = repl_monitor.generate_report(get_credentials, args.json)
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index c2ad2104d..3d89e61fb 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -2487,9 +2487,10 @@ class ReplicationMonitor(object):
         else:
             self._log = logging.getLogger(__name__)
 
-    def _get_replica_status(self, instance, report_data, use_json):
+    def _get_replica_status(self, instance, report_data, use_json, get_credentials=None):
         """Load all of the status data to report
         and add new hostname:port pairs for future processing
+        :type get_credentials: function
         """
 
         replicas_status = []
@@ -2503,6 +2504,13 @@ class ReplicationMonitor(object):
             for agmt in agmts.list():
                 host = agmt.get_attr_val_utf8_l("nsds5replicahost")
                 port = agmt.get_attr_val_utf8_l("nsds5replicaport")
+                if get_credentials is not None:
+                    credentials = get_credentials(host, port)
+                    binddn = credentials["binddn"]
+                    bindpw = credentials["bindpw"]
+                else:
+                    binddn = instance.binddn
+                    bindpw = instance.bindpw
                 protocol = agmt.get_attr_val_utf8_l('nsds5replicatransportinfo')
                 # Supply protocol here because we need it only for connection
                 # and agreement status is already preformatted for the user output
@@ -2510,9 +2518,9 @@ class ReplicationMonitor(object):
                 if consumer not in report_data:
                     report_data[f"{consumer}:{protocol}"] = None
                 if use_json:
-                    agmts_status.append(json.loads(agmt.status(use_json=True)))
+                    agmts_status.append(json.loads(agmt.status(use_json=True, binddn=binddn, bindpw=bindpw)))
                 else:
-                    agmts_status.append(agmt.status())
+                    agmts_status.append(agmt.status(binddn=binddn, bindpw=bindpw))
             replicas_status.append({"replica_id": replica_id,
                                     "replica_root": replica_root,
                                     "replica_status": "Available",
@@ -2535,7 +2543,7 @@ class ReplicationMonitor(object):
         initial_inst_key = f"{self._instance.config.get_attr_val_utf8_l('nsslapd-localhost')}:{self._instance.config.get_attr_val_utf8_l('nsslapd-port')}"
         # Do this on an initial instance to get the agreements to other instances
         try:
-            report_data[initial_inst_key] = self._get_replica_status(self._instance, report_data, use_json)
+            report_data[initial_inst_key] = self._get_replica_status(self._instance, report_data, use_json, get_credentials)
         except ldap.LDAPError as e:
             self._log.debug(f"Connection to consumer ({supplier_hostname}:{supplier_port}) failed, error: {e}")
             report_data[initial_inst_key] = [{"replica_status": f"Unavailable - {e.args[0]['desc']}"}]
-- 
2.26.2