This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push:
new 8c896dc Issue 50545 - Port repl-monitor.pl to lib389 CLI
8c896dc is described below
commit 8c896dc89e39b4017b2c4d709fb743b87795ebc4
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Wed Sep 11 20:21:17 2019 +0200
Issue 50545 - Port repl-monitor.pl to lib389 CLI
Description: Add a new command to 'dsconf replication' CLI.
'dsconf replication monitor' generates a report which
shows the replication topology to which the instance does belong.
Additional arguments:
-c or --connections [CONNECTION [CONNECTION ...]]
The connection values for monitoring other not
connected topologies. The format:
'host:port:binddn:bindpwd'. You can use regex for host
and port. You can set bindpwd to * and it will be
requested at the runtime or you can include the path
to the password file in square brackets - [~/pwd.txt]
-a or --aliases [ALIAS [ALIAS ...]]
If a host:port is assigned an alias, then the alias
instead of host:port will be displayed in the output.
The format: alias=host:port
Also, ~/.dsrc can be used for specifying the connections and aliases.
[repl-monitor-connections]
connection1 = server1.example.com:38901:cn=Directory manager:*
connection2 = server2.example.com:38902:cn=Directory manager:[~/pwd.txt]
connection3 = hub1.example.com:.*:cn=Directory manager:password
[repl-monitor-aliases]
M1 = server1.example.com:38901
M2 = server2.example.com:38902
https://pagure.io/389-ds-base/issue/50545
Reviewed by: mreynolds (Thanks!)
---
src/lib389/cli/dsconf | 3 +-
src/lib389/cli/dsidm | 3 +-
src/lib389/lib389/_constants.py | 1 +
src/lib389/lib389/agreement.py | 2 +-
src/lib389/lib389/cli_base/dsrc.py | 75 +++++++++---
src/lib389/lib389/cli_conf/replication.py | 105 ++++++++++++++++-
src/lib389/lib389/replica.py | 186 +++++++++++++++++++++++++++++-
7 files changed, 353 insertions(+), 22 deletions(-)
diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf
index 22635ca..1940b8e 100755
--- a/src/lib389/cli/dsconf
+++ b/src/lib389/cli/dsconf
@@ -16,6 +16,7 @@ import sys
import signal
import json
import ast
+from lib389._constants import DSRC_HOME
from lib389.cli_conf import config as cli_config
from lib389.cli_conf import backend as cli_backend
from lib389.cli_conf import directory_manager as cli_directory_manager
@@ -109,7 +110,7 @@ if __name__ == '__main__':
log.debug("Inspired by works of: ITS, The University of Adelaide")
# Now that we have our args, see how they relate with our instance.
- dsrc_inst = dsrc_to_ldap("~/.dsrc", args.instance,
log.getChild('dsrc'))
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
# Now combine this with our arguments
dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
index aab6f6f..7fd77b6 100755
--- a/src/lib389/cli/dsidm
+++ b/src/lib389/cli/dsidm
@@ -15,6 +15,7 @@ import argparse
import argcomplete
import sys
import signal
+from lib389._constants import DSRC_HOME
from lib389.cli_idm import account as cli_account
from lib389.cli_idm import initialise as cli_init
from lib389.cli_idm import organizationalunit as cli_ou
@@ -96,7 +97,7 @@ if __name__ == '__main__':
log.debug("Inspired by works of: ITS, The University of Adelaide")
# Now that we have our args, see how they relate with our instance.
- dsrc_inst = dsrc_to_ldap("~/.dsrc", args.instance,
log.getChild('dsrc'))
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
# Now combine this with our arguments
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
index a1fa6ee..1ad72b5 100644
--- a/src/lib389/lib389/_constants.py
+++ b/src/lib389/lib389/_constants.py
@@ -346,4 +346,5 @@ args_instance = {SER_DEPLOYED_DIR: os.environ.get('PREFIX',
None),
# Helper for linking dse.ldif values to the parse_config function
args_dse_keys = SER_PROPNAME_TO_ATTRNAME
+DSRC_HOME = '~/.dsrc'
DSRC_CONTAINER = '/data/config/container.inf'
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
index 84e2f8c..a9ee68c 100644
--- a/src/lib389/lib389/agreement.py
+++ b/src/lib389/lib389/agreement.py
@@ -366,7 +366,7 @@ class Agreement(DSLdapObject):
return (json.dumps(result))
else:
retstr = (
- "Status for agreement: \"%(cn)s\"
(%(nsDS5ReplicaHost)s:"
+ "Status For Agreement: \"%(cn)s\"
(%(nsDS5ReplicaHost)s:"
"%(nsDS5ReplicaPort)s)" "\n"
"Replica Enabled: %(nsds5ReplicaEnabled)s" "\n"
"Update In Progress: %(nsds5replicaUpdateInProgress)s"
"\n"
diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py
index 5056514..bbd160e 100644
--- a/src/lib389/lib389/cli_base/dsrc.py
+++ b/src/lib389/lib389/cli_base/dsrc.py
@@ -8,8 +8,10 @@
import sys
import os
+import json
import ldap
-from lib389.properties import *
+from lib389.properties import (SER_LDAP_URL, SER_ROOT_DN, SER_LDAPI_ENABLED,
+ SER_LDAPI_SOCKET, SER_LDAPI_AUTOBIND)
from lib389._constants import DSRC_CONTAINER
MAJOR, MINOR, _, _, _ = sys.version_info
@@ -17,6 +19,7 @@ MAJOR, MINOR, _, _, _ = sys.version_info
if MAJOR >= 3:
import configparser
+
def dsrc_arg_concat(args, dsrc_inst):
"""
Given a set of argparse args containing:
@@ -65,6 +68,23 @@ def dsrc_arg_concat(args, dsrc_inst):
dsrc_inst['starttls'] = True
return dsrc_inst
+
+def _read_dsrc(path, log, case_sensetive=False):
+ path = os.path.expanduser(path)
+ log.debug("dsrc path: %s" % path)
+ log.debug("dsrc container path: %s" % DSRC_CONTAINER)
+ config = configparser.ConfigParser()
+ if case_sensetive:
+ config.optionxform = str
+ # First read our container config if it exists
+ # Then overlap the user config.
+ config.read([DSRC_CONTAINER, path])
+
+ log.debug("dsrc instances: %s" % config.sections())
+
+ return config
+
+
def dsrc_to_ldap(path, instance_name, log):
"""
Given a path to a file, return the required details for an instance.
@@ -84,15 +104,7 @@ def dsrc_to_ldap(path, instance_name, log):
tls_reqcert = [never, hard, allow]
starttls = [true, false]
"""
- path = os.path.expanduser(path)
- log.debug("dsrc path: %s" % path)
- log.debug("dsrc container path: %s" % DSRC_CONTAINER)
- config = configparser.ConfigParser()
- # First read our container config if it exists
- # Then overlap the user config.
- config.read([DSRC_CONTAINER, path])
-
- log.debug("dsrc instances: %s" % config.sections())
+ config = _read_dsrc(path, log)
# Does our section exist?
if not config.has_section(instance_name):
@@ -109,14 +121,15 @@ def dsrc_to_ldap(path, instance_name, log):
dsrc_inst['binddn'] = config.get(instance_name, 'binddn',
fallback=None)
dsrc_inst['saslmech'] = config.get(instance_name, 'saslmech',
fallback=None)
if dsrc_inst['saslmech'] is not None and dsrc_inst['saslmech'] not in
['EXTERNAL', 'PLAIN']:
- raise Exception("~/.dsrc [%s] saslmech must be one of EXTERNAL or
PLAIN" % instance_name)
+ raise Exception("%s [%s] saslmech must be one of EXTERNAL or PLAIN" %
(path, instance_name))
dsrc_inst['tls_cacertdir'] = config.get(instance_name,
'tls_cacertdir', fallback=None)
dsrc_inst['tls_cert'] = config.get(instance_name, 'tls_cert',
fallback=None)
dsrc_inst['tls_key'] = config.get(instance_name, 'tls_key',
fallback=None)
dsrc_inst['tls_reqcert'] = config.get(instance_name, 'tls_reqcert',
fallback='hard')
if dsrc_inst['tls_reqcert'] not in ['never', 'allow',
'hard']:
- raise Exception("dsrc tls_reqcert value invalid. ~/.dsrc [%s] tls_reqcert
should be one of never, allow or hard" % instance_name)
+ raise Exception("dsrc tls_reqcert value invalid. %s [%s] tls_reqcert should
be one of never, allow or hard" % (instance_name,
+
path))
if dsrc_inst['tls_reqcert'] == 'never':
dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_NEVER
elif dsrc_inst['tls_reqcert'] == 'allow':
@@ -133,9 +146,45 @@ def dsrc_to_ldap(path, instance_name, log):
dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][9:]
dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"
-
# Return the dict.
log.debug("dsrc completed with %s" % dsrc_inst)
return dsrc_inst
+def dsrc_to_repl_monitor(path, log):
+ """
+ Given a path to a file, return the required details for an instance.
+
+ The connection values for monitoring other not connected topologies. The format:
+ 'host:port:binddn:bindpwd'. You can use regex for host and port. You can set
bindpwd
+ to * and it will be requested at the runtime.
+
+ If a host:port is assigned an alias, then the alias instead of host:port will be
+ displayed in the output. The format: "alias=host:port".
+
+ The file should be an ini file, and instance should identify a section.
+
+ The ini fileshould have the content:
+
+ [repl-monitor-connections]
+ connection1 = server1.example.com:38901:cn=Directory manager:*
+ connection2 = server2.example.com:38902:cn=Directory manager:[~/pwd.txt]
+ connection3 = hub1.example.com:.*:cn=Directory manager:password
+
+ [repl-monitor-aliases]
+ M1 = server1.example.com:38901
+ M2 = server2.example.com:38902
+ """
+
+ config = _read_dsrc(path, log, case_sensetive=True)
+ dsrc_repl_monitor = {"connections": None,
+ "aliases": None}
+
+ if config.has_section("repl-monitor-connections"):
+ dsrc_repl_monitor["connections"] = [conn for _, conn in
config.items("repl-monitor-connections")]
+
+ if config.has_section("repl-monitor-aliases"):
+ dsrc_repl_monitor["aliases"] = {alias: inst for alias, inst in
config.items("repl-monitor-aliases")}
+
+ log.debug(f"dsrc completed with {dsrc_repl_monitor}")
+ return dsrc_repl_monitor
diff --git a/src/lib389/lib389/cli_conf/replication.py
b/src/lib389/lib389/cli_conf/replication.py
index eb8297d..de2099f 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -6,16 +6,16 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import re
import logging
-import time
-import base64
import os
import json
import ldap
from getpass import getpass
-from lib389._constants import *
-from lib389.utils import is_a_dn, ensure_str
-from lib389.replica import Replicas, BootstrapReplicationManager, RUV, Changelog5,
ChangelogLDIF
+from lib389._constants import ReplicaRole, DSRC_HOME
+from lib389.cli_base.dsrc import dsrc_to_repl_monitor
+from lib389.utils import is_a_dn
+from lib389.replica import Replicas, ReplicationMonitor, BootstrapReplicationManager,
Changelog5, ChangelogLDIF
from lib389.tasks import CleanAllRUVTask, AbortCleanAllRUVTask
from lib389._mapped_object import DSLdapObjects
@@ -338,6 +338,90 @@ def set_repl_config(inst, basedn, log, args):
log.info("Successfully updated replication configuration")
+def get_repl_monitor_info(inst, basedn, log, args):
+ connection_data = dsrc_to_repl_monitor(DSRC_HOME, log)
+
+ # Additional details for the connections to the topology
+ def get_credentials(host, port):
+ found = False
+ if args.connections:
+ connections = args.connections
+ elif connection_data["connections"]:
+ connections = connection_data["connections"]
+ else:
+ connections = []
+
+ if connections:
+ for connection_str in connections:
+ if len(connection_str.split(":")) != 4:
+ raise ValueError(f"Connection string {connection_str} is in
wrong format."
+ "It should be host:port:binddn:bindpw")
+ host_regex = connection_str.split(":")[0]
+ port_regex = connection_str.split(":")[1]
+ if re.match(host_regex, host) and re.match(port_regex, port):
+ found = True
+ binddn = connection_str.split(":")[2]
+ bindpw = connection_str.split(":")[3]
+ # Search for the password file or ask the user to write it
+ if bindpw.startswith("[") and
bindpw.endswith("]"):
+ pwd_file_path = os.path.expanduser(bindpw[1:][:-1])
+ try:
+ with open(pwd_file_path) as f:
+ bindpw = f.readline().strip()
+ except FileNotFoundError:
+ bindpw = getpass(f"File '{pwd_file_path}' was
not found. Please, enter "
+ f"a password for {binddn} on
{host}:{port}: ").rstrip()
+ if bindpw == "*":
+ bindpw = getpass(f"Enter a password for {binddn} on
{host}:{port}: ").rstrip()
+ if not found:
+ 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}
+
+ repl_monitor = ReplicationMonitor(inst)
+ report_dict = repl_monitor.generate_report(get_credentials)
+
+ if args.json:
+ log.info(json.dumps({"type": "list", "items":
report_dict}))
+ else:
+ for instance, report_data in report_dict.items():
+ found_alias = False
+ if args.aliases:
+ aliases = {al.split("=")[0]: al.split("=")[1] for al
in args.aliases}
+ elif connection_data["aliases"]:
+ aliases = connection_data["aliases"]
+ else:
+ aliases = {}
+ if aliases:
+ for alias_name, alias_host_port in aliases.items():
+ if alias_host_port.lower() == instance.lower():
+ supplier_header = f"Supplier: {alias_name}
({instance})"
+ found_alias = True
+ break
+ if not found_alias:
+ supplier_header = f"Supplier: {instance}"
+ log.info(supplier_header)
+ # Draw a line with the same length as the header
+ log.info("-".join(["" for _ in range(0,
len(supplier_header)+1)]))
+ if "status" in report_data and report_data["status"] ==
"Unavailable":
+ status = report_data["status"]
+ reason = report_data["reason"]
+ log.info(f"Status: {status}")
+ log.info(f"Reason: {reason}\n")
+ else:
+ for replica in report_data:
+ replica_root = replica["replica_root"]
+ replica_id = replica["replica_id"]
+ maxcsn = replica["maxcsn"]
+ log.info(f"Replica Root: {replica_root}")
+ log.info(f"Replica ID: {replica_id}")
+ log.info(f"Max CSN: {maxcsn}\n")
+ for agreement_status in replica["agmts_status"]:
+ log.info(agreement_status)
+
+
def create_cl(inst, basedn, log, args):
cl = Changelog5(inst)
try:
@@ -1033,6 +1117,17 @@ def create_parser(subparsers):
repl_set_parser.add_argument('--repl-release-timeout', help="A timeout
in seconds a replication master should send "
"updates before it
yields its replication session")
+ repl_monitor_parser = repl_subcommands.add_parser('monitor', help='Get
the full replication topology report')
+ repl_monitor_parser.set_defaults(func=get_repl_monitor_info)
+ repl_monitor_parser.add_argument('-c', '--connections',
nargs="*",
+ help="The connection values for monitoring
other not connected topologies. "
+ "The format:
'host:port:binddn:bindpwd'. You can use regex for host and port. "
+ "You can set bindpwd to * and it will be
requested at the runtime or "
+ "you can include the path to the password
file in square brackets - [~/pwd.txt]")
+ repl_monitor_parser.add_argument('-a', '--aliases',
nargs="*",
+ help="If a host:port is assigned an alias, then
the alias instead of "
+ "host:port will be displayed in the
output. The format: alias=host:port")
+#
############################################
# Replication Agmts
############################################
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index 48a14cf..7f2fd5e 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -1381,10 +1381,62 @@ class Replica(DSLdapObject):
"""Return the set of agreements related to this suffix replica
:param: winsync: If True then return winsync replication agreements,
otherwise return teh standard replication agreements.
- :returns: Agreements object
+ :returns: A list Replicas objects
"""
return Agreements(self._instance, self.dn, winsync=winsync)
+ def get_consumer_replicas(self, get_credentials):
+ """Return the set of consumer replicas related to this suffix
replica through its agreements
+
+ :param get_credentials: A user-defined callback function which returns the
binding credentials
+ using given host and port data - {"binddn":
"cn=Directory Manager",
+ "bindpw": "password"}
+ :returns: Replicas object
+ """
+
+ agmts = self.get_agreements()
+ result_replicas = []
+ connections = []
+
+ try:
+ for agmt in agmts:
+ host = agmt.get_attr_val_utf8("nsDS5ReplicaHost")
+ port = agmt.get_attr_val_utf8("nsDS5ReplicaPort")
+ protocol =
agmt.get_attr_val_utf8("nsDS5ReplicaTransportInfo").lower()
+
+ # The function should be defined outside and
+ # it should have all the logic for figuring out the credentials
+ credentials = get_credentials(host, port)
+ if not credentials["binddn"]:
+ report_data[supplier] = {"status":
"Unavailable",
+ "reason": "Bind DN was not
specified"}
+ continue
+
+ # Open a connection to the consumer
+ consumer = DirSrv(verbose=self._instance.verbose)
+ args_instance[SER_HOST] = host
+ if protocol == "ssl" or protocol == "ldaps":
+ args_instance[SER_SECURE_PORT] = int(port)
+ else:
+ args_instance[SER_PORT] = int(port)
+ args_instance[SER_ROOT_DN] = credentials["binddn"]
+ args_instance[SER_ROOT_PW] = credentials["bindpw"]
+ args_standalone = args_instance.copy()
+ consumer.allocate(args_standalone)
+ try:
+ consumer.open()
+ except ldap.LDAPError as e:
+ self._log.debug(f"Connection to consumer ({host}:{port}) failed,
error: {e}")
+ raise
+ connections.append(consumer)
+ result_replicas.append(Replicas(consumer))
+ except:
+ for conn in connections:
+ conn.close()
+ raise
+
+ return result_replicas
+
def get_rid(self):
"""Return the current replicas RID for this suffix
@@ -1412,6 +1464,15 @@ class Replica(DSLdapObject):
return RUV(data)
+ def get_maxcsn(self):
+ """Return the current replica's maxcsn for this suffix
+
+ :returns: str
+ """
+ replica_id = self.get_rid()
+ replica_ruvs = self.get_ruv()
+ return replica_ruvs._rid_maxcsn.get(replica_id, '00000000000000000000')
+
def get_ruv_agmt_maxcsns(self):
"""Return the in memory ruv of this replica suffix.
@@ -2232,3 +2293,126 @@ class ReplicationManager(object):
replicas = Replicas(instance)
replica = replicas.get(self._suffix)
return replica.get_rid()
+
+
+class ReplicationMonitor(object):
+ """The lib389 replication monitor. This is used to check the status
+ of many instances at once.
+ It also allows to monitor independent topologies and get them into
+ the one combined report.
+
+ :param instance: A supplier or hub for replication topology monitoring
+ :type instance: list of DirSrv objects
+ :param logger: A logging interface
+ :type logger: python logging
+ """
+
+ def __init__(self, instance, logger=None):
+ self._instance = instance
+ if logger is not None:
+ self._log = logger
+ else:
+ self._log = logging.getLogger(__name__)
+
+ def _get_replica_status(self, instance, report_data, use_json):
+ """Load all of the status data to report
+ and add new hostname:port pairs for future processing
+ """
+
+ replicas_status = []
+ replicas = Replicas(instance)
+ for replica in replicas.list():
+ replica_id = replica.get_rid()
+ replica_root = replica.get_suffix()
+ replica_maxcsn = replica.get_maxcsn()
+ agmts_status = []
+ agmts = replica.get_agreements()
+ for agmt in agmts.list():
+ host = agmt.get_attr_val_utf8_l("nsds5replicahost")
+ port = agmt.get_attr_val_utf8_l("nsds5replicaport")
+ 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
+ consumer = f"{host}:{port}:{protocol}"
+ if consumer not in report_data:
+ report_data[consumer] = None
+ agmts_status.append(agmt.status(use_json))
+ replicas_status.append({"replica_id": replica_id,
+ "replica_root": replica_root,
+ "maxcsn": replica_maxcsn,
+ "agmts_status": agmts_status})
+ return replicas_status
+
+ def generate_report(self, get_credentials, use_json=False):
+ """Generate a replication report for each supplier or hub and the
instances
+ that are connected with it by agreements.
+
+ :param get_credentials: A user-defined callback function with parameters (host,
port) which returns
+ a dictionary with binddn and bindpw keys -
+ example values "cn=Directory Manager" and
"password"
+ :type get_credentials: function
+ :returns: dict
+ """
+ report_data = {}
+
+ initial_inst_key =
f"{self._instance.host.lower()}:{str(self._instance.port).lower()}"
+ # Do this on an initial instance to get the agreements to other instances
+ report_data[initial_inst_key] = self._get_replica_status(self._instance,
report_data, use_json)
+
+ # Check if at least some replica report on other instances was generated
+ repl_exists = False
+
+ # While we have unprocessed instances - continue
+ while True:
+ try:
+ supplier = [host_port for host_port, processed_data in
report_data.items() if processed_data is None][0]
+ except IndexError:
+ break
+
+ s_splitted = supplier.split(":")
+ supplier_hostname = s_splitted[0]
+ supplier_port = s_splitted[1]
+ supplier_protocol = s_splitted[2]
+
+ # The function should be defined outside and
+ # it should have all the logic for figuring out the credentials.
+ # It is done for flexibility purpuses between CLI, WebUI and lib389 API
applications
+ credentials = get_credentials(supplier_hostname, supplier_port)
+ if not credentials["binddn"]:
+ report_data[supplier] = {"status": "Unavailable",
+ "reason": "Bind DN was not
specified"}
+ continue
+
+ # Open a connection to the consumer
+ supplier_inst = DirSrv(verbose=self._instance.verbose)
+ args_instance[SER_HOST] = supplier_hostname
+ if supplier_protocol == "ssl" or supplier_protocol ==
"ldaps":
+ args_instance[SER_SECURE_PORT] = int(supplier_port)
+ else:
+ args_instance[SER_PORT] = int(supplier_port)
+ args_instance[SER_ROOT_DN] = credentials["binddn"]
+ args_instance[SER_ROOT_PW] = credentials["bindpw"]
+ args_standalone = args_instance.copy()
+ supplier_inst.allocate(args_standalone)
+ try:
+ supplier_inst.open()
+ except ldap.LDAPError as e:
+ self._log.debug(f"Connection to consumer
({supplier_hostname}:{supplier_port}) failed, error: {e}")
+ report_data[supplier] = {"status": "Unavailable",
+ "reason": e.args[0]['desc']}
+ continue
+
+ report_data[supplier] = self._get_replica_status(supplier_inst, report_data,
use_json)
+ repl_exists = True
+
+ # Now remove the protocol from the name
+ report_data_final = {}
+ for key, value in report_data.items():
+ # We take the initial instance only if it is the only existing part of the
report
+ if key != initial_inst_key or not repl_exists:
+ if not value:
+ value = {"status": "Unavailable",
+ "reason": "No replicas were found"}
+ report_data_final[":".join(key.split(":")[:2])] =
value
+
+ return report_data_final
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.