|
|
b8da0b |
From d2ac7e98d53cfe6c74c99ddf3504b1072418f05a Mon Sep 17 00:00:00 2001
|
|
|
b8da0b |
From: Mark Reynolds <mreynolds@redhat.com>
|
|
|
b8da0b |
Date: Thu, 11 Mar 2021 10:12:46 -0500
|
|
|
b8da0b |
Subject: [PATCH] Issue 4656 - remove problematic language from ds-replcheck
|
|
|
b8da0b |
|
|
|
b8da0b |
Description: remove master from ds-replcheck and replace it with supplier
|
|
|
b8da0b |
|
|
|
b8da0b |
relates: https://github.com/389ds/389-ds-base/issues/4656
|
|
|
b8da0b |
|
|
|
b8da0b |
Reviewed by: mreynolds
|
|
|
b8da0b |
|
|
|
b8da0b |
e with '#' will be ignored, and an empty message aborts the commit.
|
|
|
b8da0b |
---
|
|
|
b8da0b |
ldap/admin/src/scripts/ds-replcheck | 202 ++++++++++++++--------------
|
|
|
b8da0b |
1 file changed, 101 insertions(+), 101 deletions(-)
|
|
|
b8da0b |
|
|
|
b8da0b |
diff --git a/ldap/admin/src/scripts/ds-replcheck b/ldap/admin/src/scripts/ds-replcheck
|
|
|
b8da0b |
index 169496e8f..f411f357a 100755
|
|
|
b8da0b |
--- a/ldap/admin/src/scripts/ds-replcheck
|
|
|
b8da0b |
+++ b/ldap/admin/src/scripts/ds-replcheck
|
|
|
b8da0b |
@@ -1,7 +1,7 @@
|
|
|
b8da0b |
#!/usr/bin/python3
|
|
|
b8da0b |
|
|
|
b8da0b |
# --- BEGIN COPYRIGHT BLOCK ---
|
|
|
b8da0b |
-# Copyright (C) 2020 Red Hat, Inc.
|
|
|
b8da0b |
+# Copyright (C) 2021 Red Hat, Inc.
|
|
|
b8da0b |
# All rights reserved.
|
|
|
b8da0b |
#
|
|
|
b8da0b |
# License: GPL (version 3 or any later version).
|
|
|
b8da0b |
@@ -63,7 +63,7 @@ def remove_entry(rentries, dn):
|
|
|
b8da0b |
def get_ruv_time(ruv, rid):
|
|
|
b8da0b |
"""Take a RUV element (nsds50ruv attribute) and extract the timestamp from maxcsn
|
|
|
b8da0b |
:param ruv - A lsit of RUV elements
|
|
|
b8da0b |
- :param rid - The rid of the master to extractthe maxcsn time from
|
|
|
b8da0b |
+ :param rid - The rid of the supplier to extract the maxcsn time from
|
|
|
b8da0b |
:return: The time in seconds of the maxcsn, or 0 if there is no maxcsn, or -1 if
|
|
|
b8da0b |
the rid was not found
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
@@ -213,22 +213,22 @@ def get_ruv_state(opts):
|
|
|
b8da0b |
:param opts - all the script options
|
|
|
b8da0b |
:return - A text description of the replicaton state
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
- mtime = get_ruv_time(opts['master_ruv'], opts['rid'])
|
|
|
b8da0b |
+ mtime = get_ruv_time(opts['supplier_ruv'], opts['rid'])
|
|
|
b8da0b |
rtime = get_ruv_time(opts['replica_ruv'], opts['rid'])
|
|
|
b8da0b |
if mtime == -1:
|
|
|
b8da0b |
- repl_state = "Replication State: Replica ID ({}) not found in Master's RUV".format(opts['rid'])
|
|
|
b8da0b |
+ repl_state = "Replication State: Replica ID ({}) not found in Supplier's RUV".format(opts['rid'])
|
|
|
b8da0b |
elif rtime == -1:
|
|
|
b8da0b |
repl_state = "Replication State: Replica ID ({}) not found in Replica's RUV (not initialized?)".format(opts['rid'])
|
|
|
b8da0b |
elif mtime == 0:
|
|
|
b8da0b |
- repl_state = "Replication State: Master has not seen any updates"
|
|
|
b8da0b |
+ repl_state = "Replication State: Supplier has not seen any updates"
|
|
|
b8da0b |
elif rtime == 0:
|
|
|
b8da0b |
- repl_state = "Replication State: Replica has not seen any changes from the Master"
|
|
|
b8da0b |
+ repl_state = "Replication State: Replica has not seen any changes from the Supplier"
|
|
|
b8da0b |
elif mtime > rtime:
|
|
|
b8da0b |
- repl_state = "Replication State: Replica is behind Master by: {} seconds".format(mtime - rtime)
|
|
|
b8da0b |
+ repl_state = "Replication State: Replica is behind Supplier by: {} seconds".format(mtime - rtime)
|
|
|
b8da0b |
elif mtime < rtime:
|
|
|
b8da0b |
- repl_state = "Replication State: Replica is ahead of Master by: {} seconds".format(rtime - mtime)
|
|
|
b8da0b |
+ repl_state = "Replication State: Replica is ahead of Supplier by: {} seconds".format(rtime - mtime)
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
- repl_state = "Replication State: Master and Replica are in perfect synchronization"
|
|
|
b8da0b |
+ repl_state = "Replication State: Supplier and Replica are in perfect synchronization"
|
|
|
b8da0b |
|
|
|
b8da0b |
return repl_state
|
|
|
b8da0b |
|
|
|
b8da0b |
@@ -238,11 +238,11 @@ def get_ruv_report(opts):
|
|
|
b8da0b |
:param opts - all the script options
|
|
|
b8da0b |
:return - A text blob to display in the report
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
- opts['master_ruv'].sort()
|
|
|
b8da0b |
+ opts['supplier_ruv'].sort()
|
|
|
b8da0b |
opts['replica_ruv'].sort()
|
|
|
b8da0b |
|
|
|
b8da0b |
- report = "Master RUV:\n"
|
|
|
b8da0b |
- for element in opts['master_ruv']:
|
|
|
b8da0b |
+ report = "Supplier RUV:\n"
|
|
|
b8da0b |
+ for element in opts['supplier_ruv']:
|
|
|
b8da0b |
report += " %s\n" % (element)
|
|
|
b8da0b |
report += "\nReplica RUV:\n"
|
|
|
b8da0b |
for element in opts['replica_ruv']:
|
|
|
b8da0b |
@@ -521,7 +521,7 @@ def get_ldif_ruv(LDIF, opts):
|
|
|
b8da0b |
|
|
|
b8da0b |
def cmp_entry(mentry, rentry, opts):
|
|
|
b8da0b |
"""Compare the two entries, and return a "diff map"
|
|
|
b8da0b |
- :param mentry - A Master entry
|
|
|
b8da0b |
+ :param mentry - A Supplier entry
|
|
|
b8da0b |
:param rentry - A Replica entry
|
|
|
b8da0b |
:param opts - A Dict of the scripts options
|
|
|
b8da0b |
:return - A Dict of the differences in the entry, or None
|
|
|
b8da0b |
@@ -536,7 +536,7 @@ def cmp_entry(mentry, rentry, opts):
|
|
|
b8da0b |
mlist = list(mentry.data.keys())
|
|
|
b8da0b |
|
|
|
b8da0b |
#
|
|
|
b8da0b |
- # Check master
|
|
|
b8da0b |
+ # Check Supplier
|
|
|
b8da0b |
#
|
|
|
b8da0b |
for mattr in mlist:
|
|
|
b8da0b |
if mattr in opts['ignore']:
|
|
|
b8da0b |
@@ -555,7 +555,7 @@ def cmp_entry(mentry, rentry, opts):
|
|
|
b8da0b |
if not found:
|
|
|
b8da0b |
diff['missing'].append("")
|
|
|
b8da0b |
found = True
|
|
|
b8da0b |
- diff['missing'].append(" - Master's State Info: %s" % (val))
|
|
|
b8da0b |
+ diff['missing'].append(" - Supplier's State Info: %s" % (val))
|
|
|
b8da0b |
diff['missing'].append(" - Date: %s\n" % (time.ctime(extract_time(val))))
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
# No state info, just move on
|
|
|
b8da0b |
@@ -566,18 +566,18 @@ def cmp_entry(mentry, rentry, opts):
|
|
|
b8da0b |
if report_conflict(rentry, mattr, opts) and report_conflict(mentry, mattr, opts):
|
|
|
b8da0b |
diff['diff'].append(" - Attribute '%s' is different:" % mattr)
|
|
|
b8da0b |
if 'nscpentrywsi' in mentry.data:
|
|
|
b8da0b |
- # Process Master
|
|
|
b8da0b |
+ # Process Supplier
|
|
|
b8da0b |
found = False
|
|
|
b8da0b |
for val in mentry.data['nscpentrywsi']:
|
|
|
b8da0b |
if val.lower().startswith(mattr + ';'):
|
|
|
b8da0b |
if not found:
|
|
|
b8da0b |
- diff['diff'].append(" Master:")
|
|
|
b8da0b |
+ diff['diff'].append(" Supplier:")
|
|
|
b8da0b |
diff['diff'].append(" - Value: %s" % (val.split(':')[1].lstrip()))
|
|
|
b8da0b |
diff['diff'].append(" - State Info: %s" % (val))
|
|
|
b8da0b |
diff['diff'].append(" - Date: %s\n" % (time.ctime(extract_time(val))))
|
|
|
b8da0b |
found = True
|
|
|
b8da0b |
if not found:
|
|
|
b8da0b |
- diff['diff'].append(" Master: ")
|
|
|
b8da0b |
+ diff['diff'].append(" Supplier: ")
|
|
|
b8da0b |
for val in mentry.data[mattr]:
|
|
|
b8da0b |
# This is an "origin" value which means it's never been
|
|
|
b8da0b |
# updated since replication was set up. So its the
|
|
|
b8da0b |
@@ -605,7 +605,7 @@ def cmp_entry(mentry, rentry, opts):
|
|
|
b8da0b |
diff['diff'].append("")
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
# no state info, report what we got
|
|
|
b8da0b |
- diff['diff'].append(" Master: ")
|
|
|
b8da0b |
+ diff['diff'].append(" Supplier: ")
|
|
|
b8da0b |
for val in mentry.data[mattr]:
|
|
|
b8da0b |
diff['diff'].append(" - %s: %s" % (mattr, val))
|
|
|
b8da0b |
diff['diff'].append(" Replica: ")
|
|
|
b8da0b |
@@ -622,9 +622,9 @@ def cmp_entry(mentry, rentry, opts):
|
|
|
b8da0b |
continue
|
|
|
b8da0b |
|
|
|
b8da0b |
if rattr not in mlist:
|
|
|
b8da0b |
- # Master is missing the attribute
|
|
|
b8da0b |
+ # Supplier is missing the attribute
|
|
|
b8da0b |
if report_conflict(rentry, rattr, opts):
|
|
|
b8da0b |
- diff['missing'].append(" - Master missing attribute: \"%s\"" % (rattr))
|
|
|
b8da0b |
+ diff['missing'].append(" - Supplier missing attribute: \"%s\"" % (rattr))
|
|
|
b8da0b |
diff_count += 1
|
|
|
b8da0b |
if 'nscpentrywsi' in rentry.data:
|
|
|
b8da0b |
found = False
|
|
|
b8da0b |
@@ -663,7 +663,7 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
MLDIF = open(opts['mldif'], "r")
|
|
|
b8da0b |
except Exception as e:
|
|
|
b8da0b |
- print('Failed to open Master LDIF: ' + str(e))
|
|
|
b8da0b |
+ print('Failed to open Supplier LDIF: ' + str(e))
|
|
|
b8da0b |
return
|
|
|
b8da0b |
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
@@ -676,10 +676,10 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
# Verify LDIF Files
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
- print("Validating Master ldif file ({})...".format(opts['mldif']))
|
|
|
b8da0b |
+ print("Validating Supplier ldif file ({})...".format(opts['mldif']))
|
|
|
b8da0b |
LDIFRecordList(MLDIF).parse()
|
|
|
b8da0b |
except ValueError:
|
|
|
b8da0b |
- print('Master LDIF file in invalid, aborting...')
|
|
|
b8da0b |
+ print('Supplier LDIF file in invalid, aborting...')
|
|
|
b8da0b |
MLDIF.close()
|
|
|
b8da0b |
RLDIF.close()
|
|
|
b8da0b |
return
|
|
|
b8da0b |
@@ -696,34 +696,34 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
# Get all the dn's, and entry counts
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
print ("Gathering all the DN's...")
|
|
|
b8da0b |
- master_dns = get_dns(MLDIF, opts['mldif'], opts)
|
|
|
b8da0b |
+ supplier_dns = get_dns(MLDIF, opts['mldif'], opts)
|
|
|
b8da0b |
replica_dns = get_dns(RLDIF, opts['rldif'], opts)
|
|
|
b8da0b |
- if master_dns is None or replica_dns is None:
|
|
|
b8da0b |
+ if supplier_dns is None or replica_dns is None:
|
|
|
b8da0b |
print("Aborting scan...")
|
|
|
b8da0b |
MLDIF.close()
|
|
|
b8da0b |
RLDIF.close()
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
- m_count = len(master_dns)
|
|
|
b8da0b |
+ m_count = len(supplier_dns)
|
|
|
b8da0b |
r_count = len(replica_dns)
|
|
|
b8da0b |
|
|
|
b8da0b |
# Get DB RUV
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
print ("Gathering the database RUV's...")
|
|
|
b8da0b |
- opts['master_ruv'] = get_ldif_ruv(MLDIF, opts)
|
|
|
b8da0b |
+ opts['supplier_ruv'] = get_ldif_ruv(MLDIF, opts)
|
|
|
b8da0b |
opts['replica_ruv'] = get_ldif_ruv(RLDIF, opts)
|
|
|
b8da0b |
|
|
|
b8da0b |
- """ Compare the master entries with the replica's. Take our list of dn's from
|
|
|
b8da0b |
- the master ldif and get that entry( dn) from the master and replica ldif. In
|
|
|
b8da0b |
+ """ Compare the Supplier entries with the replica's. Take our list of dn's from
|
|
|
b8da0b |
+ the Supplier ldif and get that entry( dn) from the Supplier and replica ldif. In
|
|
|
b8da0b |
this phase we keep keep track of conflict/tombstone counts, and we check for
|
|
|
b8da0b |
missing entries and entry differences. We only need to do the entry diff
|
|
|
b8da0b |
checking in this phase - we do not need to do it when process the replica dn's
|
|
|
b8da0b |
because if the entry exists in both LDIF's then we already checked or diffs
|
|
|
b8da0b |
- while processing the master dn's.
|
|
|
b8da0b |
+ while processing the Supplier dn's.
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
- print ("Comparing Master to Replica...")
|
|
|
b8da0b |
+ print ("Comparing Supplier to Replica...")
|
|
|
b8da0b |
missing = False
|
|
|
b8da0b |
- for dn in master_dns:
|
|
|
b8da0b |
+ for dn in supplier_dns:
|
|
|
b8da0b |
mresult = ldif_search(MLDIF, dn)
|
|
|
b8da0b |
if mresult['entry'] is None and mresult['conflict'] is None and not mresult['tombstone']:
|
|
|
b8da0b |
# Try from the beginning
|
|
|
b8da0b |
@@ -736,7 +736,7 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
rresult['conflict'] is not None or rresult['tombstone']):
|
|
|
b8da0b |
""" We can safely remove this DN from the replica dn list as it
|
|
|
b8da0b |
does not need to be checked again. This also speeds things up
|
|
|
b8da0b |
- when doing the replica vs master phase.
|
|
|
b8da0b |
+ when doing the replica vs Supplier phase.
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
replica_dns.remove(dn)
|
|
|
b8da0b |
|
|
|
b8da0b |
@@ -766,7 +766,7 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
missing_report += (' Entries missing on Replica:\n')
|
|
|
b8da0b |
missing = True
|
|
|
b8da0b |
if mresult['entry'] and 'createtimestamp' in mresult['entry'].data:
|
|
|
b8da0b |
- missing_report += (' - %s (Created on Master at: %s)\n' %
|
|
|
b8da0b |
+ missing_report += (' - %s (Created on Supplier at: %s)\n' %
|
|
|
b8da0b |
(dn, convert_timestamp(mresult['entry'].data['createtimestamp'][0])))
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
missing_report += (' - %s\n' % dn)
|
|
|
b8da0b |
@@ -791,7 +791,7 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
remaining conflict & tombstone entries as well.
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
- print ("Comparing Replica to Master...")
|
|
|
b8da0b |
+ print ("Comparing Replica to Supplier...")
|
|
|
b8da0b |
MLDIF.seek(0)
|
|
|
b8da0b |
RLDIF.seek(0)
|
|
|
b8da0b |
missing = False
|
|
|
b8da0b |
@@ -811,7 +811,7 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
if mresult['entry'] is None and mresult['glue'] is None:
|
|
|
b8da0b |
MLDIF.seek(rresult['idx']) # Set the LDIF cursor/index to the last good line
|
|
|
b8da0b |
if not missing:
|
|
|
b8da0b |
- missing_report += (' Entries missing on Master:\n')
|
|
|
b8da0b |
+ missing_report += (' Entries missing on Supplier:\n')
|
|
|
b8da0b |
missing = True
|
|
|
b8da0b |
if rresult['entry'] and 'createtimestamp' in rresult['entry'].data:
|
|
|
b8da0b |
missing_report += (' - %s (Created on Replica at: %s)\n' %
|
|
|
b8da0b |
@@ -837,12 +837,12 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
final_report += get_ruv_report(opts)
|
|
|
b8da0b |
final_report += ('Entry Counts\n')
|
|
|
b8da0b |
final_report += ('=====================================================\n\n')
|
|
|
b8da0b |
- final_report += ('Master: %d\n' % (m_count))
|
|
|
b8da0b |
+ final_report += ('Supplier: %d\n' % (m_count))
|
|
|
b8da0b |
final_report += ('Replica: %d\n\n' % (r_count))
|
|
|
b8da0b |
|
|
|
b8da0b |
final_report += ('\nTombstones\n')
|
|
|
b8da0b |
final_report += ('=====================================================\n\n')
|
|
|
b8da0b |
- final_report += ('Master: %d\n' % (mtombstones))
|
|
|
b8da0b |
+ final_report += ('Supplier: %d\n' % (mtombstones))
|
|
|
b8da0b |
final_report += ('Replica: %d\n' % (rtombstones))
|
|
|
b8da0b |
|
|
|
b8da0b |
final_report += get_conflict_report(mconflicts, rconflicts, opts['conflicts'])
|
|
|
b8da0b |
@@ -859,9 +859,9 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
final_report += ('\nResult\n')
|
|
|
b8da0b |
final_report += ('=====================================================\n\n')
|
|
|
b8da0b |
if missing_report == "" and len(diff_report) == 0:
|
|
|
b8da0b |
- final_report += ('No replication differences between Master and Replica\n')
|
|
|
b8da0b |
+ final_report += ('No replication differences between Supplier and Replica\n')
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
- final_report += ('There are replication differences between Master and Replica\n')
|
|
|
b8da0b |
+ final_report += ('There are replication differences between Supplier and Replica\n')
|
|
|
b8da0b |
|
|
|
b8da0b |
if output_file:
|
|
|
b8da0b |
output_file.write(final_report)
|
|
|
b8da0b |
@@ -871,8 +871,8 @@ def do_offline_report(opts, output_file=None):
|
|
|
b8da0b |
|
|
|
b8da0b |
def check_for_diffs(mentries, mglue, rentries, rglue, report, opts):
|
|
|
b8da0b |
"""Online mode only - Check for diffs, return the updated report
|
|
|
b8da0b |
- :param mentries - Master entries
|
|
|
b8da0b |
- :param mglue - Master glue entries
|
|
|
b8da0b |
+ :param mentries - Supplier entries
|
|
|
b8da0b |
+ :param mglue - Supplier glue entries
|
|
|
b8da0b |
:param rentries - Replica entries
|
|
|
b8da0b |
:param rglue - Replica glue entries
|
|
|
b8da0b |
:param report - A Dict of the entire report
|
|
|
b8da0b |
@@ -947,8 +947,8 @@ def validate_suffix(ldapnode, suffix, hostname):
|
|
|
b8da0b |
# Check suffix is replicated
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
replica_filter = "(&(objectclass=nsds5replica)(nsDS5ReplicaRoot=%s))" % suffix
|
|
|
b8da0b |
- master_replica = ldapnode.search_s("cn=config",ldap.SCOPE_SUBTREE,replica_filter)
|
|
|
b8da0b |
- if (len(master_replica) != 1):
|
|
|
b8da0b |
+ supplier_replica = ldapnode.search_s("cn=config",ldap.SCOPE_SUBTREE,replica_filter)
|
|
|
b8da0b |
+ if (len(supplier_replica) != 1):
|
|
|
b8da0b |
print("Error: Failed to validate suffix in {}. {} is not replicated.".format(hostname, suffix))
|
|
|
b8da0b |
return False
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
@@ -969,7 +969,7 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
muri = "%s://%s" % (opts['mprotocol'], opts['mhost'].replace("/", "%2f"))
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
muri = "%s://%s:%s/" % (opts['mprotocol'], opts['mhost'], opts['mport'])
|
|
|
b8da0b |
- master = SimpleLDAPObject(muri)
|
|
|
b8da0b |
+ supplier = SimpleLDAPObject(muri)
|
|
|
b8da0b |
|
|
|
b8da0b |
if opts['rprotocol'].lower() == 'ldapi':
|
|
|
b8da0b |
ruri = "%s://%s" % (opts['rprotocol'], opts['rhost'].replace("/", "%2f"))
|
|
|
b8da0b |
@@ -978,23 +978,23 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
replica = SimpleLDAPObject(ruri)
|
|
|
b8da0b |
|
|
|
b8da0b |
# Set timeouts
|
|
|
b8da0b |
- master.set_option(ldap.OPT_NETWORK_TIMEOUT, opts['timeout'])
|
|
|
b8da0b |
- master.set_option(ldap.OPT_TIMEOUT, opts['timeout'])
|
|
|
b8da0b |
+ supplier.set_option(ldap.OPT_NETWORK_TIMEOUT, opts['timeout'])
|
|
|
b8da0b |
+ supplier.set_option(ldap.OPT_TIMEOUT, opts['timeout'])
|
|
|
b8da0b |
replica.set_option(ldap.OPT_NETWORK_TIMEOUT, opts['timeout'])
|
|
|
b8da0b |
replica.set_option(ldap.OPT_TIMEOUT, opts['timeout'])
|
|
|
b8da0b |
|
|
|
b8da0b |
# Setup Secure Connection
|
|
|
b8da0b |
if opts['certdir'] is not None:
|
|
|
b8da0b |
- # Setup Master
|
|
|
b8da0b |
+ # Setup Supplier
|
|
|
b8da0b |
if opts['mprotocol'] != LDAPI:
|
|
|
b8da0b |
- master.set_option(ldap.OPT_X_TLS_CACERTDIR, opts['certdir'])
|
|
|
b8da0b |
- master.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD)
|
|
|
b8da0b |
+ supplier.set_option(ldap.OPT_X_TLS_CACERTDIR, opts['certdir'])
|
|
|
b8da0b |
+ supplier.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD)
|
|
|
b8da0b |
if opts['mprotocol'] == LDAP:
|
|
|
b8da0b |
# Do StartTLS
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
- master.start_tls_s()
|
|
|
b8da0b |
+ supplier.start_tls_s()
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print('TLS negotiation failed on Master: {}'.format(str(e)))
|
|
|
b8da0b |
+ print('TLS negotiation failed on Supplier: {}'.format(str(e)))
|
|
|
b8da0b |
exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
# Setup Replica
|
|
|
b8da0b |
@@ -1006,17 +1006,17 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
replica.start_tls_s()
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print('TLS negotiation failed on Master: {}'.format(str(e)))
|
|
|
b8da0b |
+ print('TLS negotiation failed on Supplier: {}'.format(str(e)))
|
|
|
b8da0b |
exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
- # Open connection to master
|
|
|
b8da0b |
+ # Open connection to Supplier
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
- master.simple_bind_s(opts['binddn'], opts['bindpw'])
|
|
|
b8da0b |
+ supplier.simple_bind_s(opts['binddn'], opts['bindpw'])
|
|
|
b8da0b |
except ldap.SERVER_DOWN as e:
|
|
|
b8da0b |
print(f"Cannot connect to {muri} ({str(e)})")
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print("Error: Failed to authenticate to Master: ({}). "
|
|
|
b8da0b |
+ print("Error: Failed to authenticate to Supplier: ({}). "
|
|
|
b8da0b |
"Please check your credentials and LDAP urls are correct.".format(str(e)))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
@@ -1034,7 +1034,7 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
# Validate suffix
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
print ("Validating suffix ...")
|
|
|
b8da0b |
- if not validate_suffix(master, opts['suffix'], opts['mhost']):
|
|
|
b8da0b |
+ if not validate_suffix(supplier, opts['suffix'], opts['mhost']):
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
if not validate_suffix(replica,opts['suffix'], opts['rhost']):
|
|
|
b8da0b |
@@ -1042,16 +1042,16 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
|
|
|
b8da0b |
# Get the RUVs
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
- print ("Gathering Master's RUV...")
|
|
|
b8da0b |
+ print ("Gathering Supplier's RUV...")
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
- master_ruv = master.search_s(opts['suffix'], ldap.SCOPE_SUBTREE, RUV_FILTER, ['nsds50ruv'])
|
|
|
b8da0b |
- if len(master_ruv) > 0:
|
|
|
b8da0b |
- opts['master_ruv'] = ensure_list_str(master_ruv[0][1]['nsds50ruv'])
|
|
|
b8da0b |
+ supplier_ruv = supplier.search_s(opts['suffix'], ldap.SCOPE_SUBTREE, RUV_FILTER, ['nsds50ruv'])
|
|
|
b8da0b |
+ if len(supplier_ruv) > 0:
|
|
|
b8da0b |
+ opts['supplier_ruv'] = ensure_list_str(supplier_ruv[0][1]['nsds50ruv'])
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
- print("Error: Master does not have an RUV entry")
|
|
|
b8da0b |
+ print("Error: Supplier does not have an RUV entry")
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print("Error: Failed to get Master RUV entry: {}".format(str(e)))
|
|
|
b8da0b |
+ print("Error: Failed to get Supplier RUV entry: {}".format(str(e)))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
@@ -1067,12 +1067,12 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
print("Error: Failed to get Replica RUV entry: {}".format(str(e)))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
- # Get the master RID
|
|
|
b8da0b |
+ # Get the Supplier RID
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
- print("Getting Master's replica ID")
|
|
|
b8da0b |
+ print("Getting Supplier's replica ID")
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
search_filter = "(&(objectclass=nsds5Replica)(nsDS5ReplicaRoot={})(nsDS5ReplicaId=*))".format(opts['suffix'])
|
|
|
b8da0b |
- replica_entry = master.search_s("cn=config", ldap.SCOPE_SUBTREE, search_filter)
|
|
|
b8da0b |
+ replica_entry = supplier.search_s("cn=config", ldap.SCOPE_SUBTREE, search_filter)
|
|
|
b8da0b |
if len(replica_entry) > 0:
|
|
|
b8da0b |
opts['rid'] = ensure_int(replica_entry[0][1]['nsDS5ReplicaId'][0])
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
@@ -1081,7 +1081,7 @@ def connect_to_replicas(opts):
|
|
|
b8da0b |
print("Error: Failed to get Replica entry: {}".format(str(e)))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
- return (master, replica, opts)
|
|
|
b8da0b |
+ return (supplier, replica, opts)
|
|
|
b8da0b |
|
|
|
b8da0b |
|
|
|
b8da0b |
def print_online_report(report, opts, output_file):
|
|
|
b8da0b |
@@ -1104,11 +1104,11 @@ def print_online_report(report, opts, output_file):
|
|
|
b8da0b |
final_report += get_ruv_report(opts)
|
|
|
b8da0b |
final_report += ('Entry Counts\n')
|
|
|
b8da0b |
final_report += ('=====================================================\n\n')
|
|
|
b8da0b |
- final_report += ('Master: %d\n' % (report['m_count']))
|
|
|
b8da0b |
+ final_report += ('Supplier: %d\n' % (report['m_count']))
|
|
|
b8da0b |
final_report += ('Replica: %d\n\n' % (report['r_count']))
|
|
|
b8da0b |
final_report += ('\nTombstones\n')
|
|
|
b8da0b |
final_report += ('=====================================================\n\n')
|
|
|
b8da0b |
- final_report += ('Master: %d\n' % (report['mtombstones']))
|
|
|
b8da0b |
+ final_report += ('Supplier: %d\n' % (report['mtombstones']))
|
|
|
b8da0b |
final_report += ('Replica: %d\n' % (report['rtombstones']))
|
|
|
b8da0b |
final_report += report['conflict']
|
|
|
b8da0b |
missing = False
|
|
|
b8da0b |
@@ -1121,7 +1121,7 @@ def print_online_report(report, opts, output_file):
|
|
|
b8da0b |
final_report += (' Entries missing on Replica:\n')
|
|
|
b8da0b |
for entry in report['r_missing']:
|
|
|
b8da0b |
if 'createtimestamp' in entry.data:
|
|
|
b8da0b |
- final_report += (' - %s (Created on Master at: %s)\n' %
|
|
|
b8da0b |
+ final_report += (' - %s (Created on Supplier at: %s)\n' %
|
|
|
b8da0b |
(entry.dn, convert_timestamp(entry.data['createtimestamp'][0])))
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
final_report += (' - %s\n' % (entry.dn))
|
|
|
b8da0b |
@@ -1129,7 +1129,7 @@ def print_online_report(report, opts, output_file):
|
|
|
b8da0b |
if m_missing > 0:
|
|
|
b8da0b |
if r_missing > 0:
|
|
|
b8da0b |
final_report += ('\n')
|
|
|
b8da0b |
- final_report += (' Entries missing on Master:\n')
|
|
|
b8da0b |
+ final_report += (' Entries missing on Supplier:\n')
|
|
|
b8da0b |
for entry in report['m_missing']:
|
|
|
b8da0b |
if 'createtimestamp' in entry.data:
|
|
|
b8da0b |
final_report += (' - %s (Created on Replica at: %s)\n' %
|
|
|
b8da0b |
@@ -1146,9 +1146,9 @@ def print_online_report(report, opts, output_file):
|
|
|
b8da0b |
final_report += ('\nResult\n')
|
|
|
b8da0b |
final_report += ('=====================================================\n\n')
|
|
|
b8da0b |
if not missing and len(report['diff']) == 0:
|
|
|
b8da0b |
- final_report += ('No replication differences between Master and Replica\n')
|
|
|
b8da0b |
+ final_report += ('No replication differences between Supplier and Replica\n')
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
- final_report += ('There are replication differences between Master and Replica\n')
|
|
|
b8da0b |
+ final_report += ('There are replication differences between Supplier and Replica\n')
|
|
|
b8da0b |
|
|
|
b8da0b |
if output_file:
|
|
|
b8da0b |
output_file.write(final_report)
|
|
|
b8da0b |
@@ -1170,7 +1170,7 @@ def remove_state_info(entry):
|
|
|
b8da0b |
|
|
|
b8da0b |
def get_conflict_report(mentries, rentries, verbose):
|
|
|
b8da0b |
"""Gather the conflict entry dn's for each replica
|
|
|
b8da0b |
- :param mentries - Master entries
|
|
|
b8da0b |
+ :param mentries - Supplier entries
|
|
|
b8da0b |
:param rentries - Replica entries
|
|
|
b8da0b |
:param verbose - verbose logging
|
|
|
b8da0b |
:return - A text blob to dispaly in the report
|
|
|
b8da0b |
@@ -1197,7 +1197,7 @@ def get_conflict_report(mentries, rentries, verbose):
|
|
|
b8da0b |
report = "\n\nConflict Entries\n"
|
|
|
b8da0b |
report += "=====================================================\n\n"
|
|
|
b8da0b |
if len(m_conflicts) > 0:
|
|
|
b8da0b |
- report += ('Master Conflict Entries: %d\n' % (len(m_conflicts)))
|
|
|
b8da0b |
+ report += ('Supplier Conflict Entries: %d\n' % (len(m_conflicts)))
|
|
|
b8da0b |
if verbose:
|
|
|
b8da0b |
for entry in m_conflicts:
|
|
|
b8da0b |
report += ('\n - %s\n' % (entry['dn']))
|
|
|
b8da0b |
@@ -1239,8 +1239,8 @@ def do_online_report(opts, output_file=None):
|
|
|
b8da0b |
rconflicts = []
|
|
|
b8da0b |
mconflicts = []
|
|
|
b8da0b |
|
|
|
b8da0b |
- # Fire off paged searches on Master and Replica
|
|
|
b8da0b |
- master, replica, opts = connect_to_replicas(opts)
|
|
|
b8da0b |
+ # Fire off paged searches on Supplier and Replica
|
|
|
b8da0b |
+ supplier, replica, opts = connect_to_replicas(opts)
|
|
|
b8da0b |
|
|
|
b8da0b |
if opts['verbose']:
|
|
|
b8da0b |
print('Start searching and comparing...')
|
|
|
b8da0b |
@@ -1248,12 +1248,12 @@ def do_online_report(opts, output_file=None):
|
|
|
b8da0b |
controls = [paged_ctrl]
|
|
|
b8da0b |
req_pr_ctrl = controls[0]
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
- master_msgid = master.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE,
|
|
|
b8da0b |
- "(|(objectclass=*)(objectclass=ldapsubentry)(objectclass=nstombstone))",
|
|
|
b8da0b |
- ['*', 'createtimestamp', 'nscpentrywsi', 'nsds5replconflict'],
|
|
|
b8da0b |
- serverctrls=controls)
|
|
|
b8da0b |
+ supplier_msgid = supplier.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE,
|
|
|
b8da0b |
+ "(|(objectclass=*)(objectclass=ldapsubentry)(objectclass=nstombstone))",
|
|
|
b8da0b |
+ ['*', 'createtimestamp', 'nscpentrywsi', 'nsds5replconflict'],
|
|
|
b8da0b |
+ serverctrls=controls)
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print("Error: Failed to get Master entries: %s", str(e))
|
|
|
b8da0b |
+ print("Error: Failed to get Supplier entries: %s", str(e))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
replica_msgid = replica.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE,
|
|
|
b8da0b |
@@ -1268,11 +1268,11 @@ def do_online_report(opts, output_file=None):
|
|
|
b8da0b |
while not m_done or not r_done:
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
if not m_done:
|
|
|
b8da0b |
- m_rtype, m_rdata, m_rmsgid, m_rctrls = master.result3(master_msgid)
|
|
|
b8da0b |
+ m_rtype, m_rdata, m_rmsgid, m_rctrls = supplier.result3(supplier_msgid)
|
|
|
b8da0b |
elif not r_done:
|
|
|
b8da0b |
m_rdata = []
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print("Error: Problem getting the results from the master: %s", str(e))
|
|
|
b8da0b |
+ print("Error: Problem getting the results from the Supplier: %s", str(e))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
if not r_done:
|
|
|
b8da0b |
@@ -1299,7 +1299,7 @@ def do_online_report(opts, output_file=None):
|
|
|
b8da0b |
report, opts)
|
|
|
b8da0b |
|
|
|
b8da0b |
if not m_done:
|
|
|
b8da0b |
- # Master
|
|
|
b8da0b |
+ # Supplier
|
|
|
b8da0b |
m_pctrls = [
|
|
|
b8da0b |
c
|
|
|
b8da0b |
for c in m_rctrls
|
|
|
b8da0b |
@@ -1310,11 +1310,11 @@ def do_online_report(opts, output_file=None):
|
|
|
b8da0b |
try:
|
|
|
b8da0b |
# Copy cookie from response control to request control
|
|
|
b8da0b |
req_pr_ctrl.cookie = m_pctrls[0].cookie
|
|
|
b8da0b |
- master_msgid = master.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE,
|
|
|
b8da0b |
+ supplier_msgid = supplier.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE,
|
|
|
b8da0b |
"(|(objectclass=*)(objectclass=ldapsubentry))",
|
|
|
b8da0b |
['*', 'createtimestamp', 'nscpentrywsi', 'conflictcsn', 'nsds5replconflict'], serverctrls=controls)
|
|
|
b8da0b |
except ldap.LDAPError as e:
|
|
|
b8da0b |
- print("Error: Problem searching the master: %s", str(e))
|
|
|
b8da0b |
+ print("Error: Problem searching the Supplier: %s", str(e))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
m_done = True # No more pages available
|
|
|
b8da0b |
@@ -1354,7 +1354,7 @@ def do_online_report(opts, output_file=None):
|
|
|
b8da0b |
print_online_report(report, opts, output_file)
|
|
|
b8da0b |
|
|
|
b8da0b |
# unbind
|
|
|
b8da0b |
- master.unbind_s()
|
|
|
b8da0b |
+ supplier.unbind_s()
|
|
|
b8da0b |
replica.unbind_s()
|
|
|
b8da0b |
|
|
|
b8da0b |
|
|
|
b8da0b |
@@ -1367,18 +1367,18 @@ def init_online_params(args):
|
|
|
b8da0b |
|
|
|
b8da0b |
# Make sure the URLs are different
|
|
|
b8da0b |
if args.murl == args.rurl:
|
|
|
b8da0b |
- print("Master and Replica LDAP URLs are the same, they must be different")
|
|
|
b8da0b |
+ print("Supplier and Replica LDAP URLs are the same, they must be different")
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
- # Parse Master url
|
|
|
b8da0b |
+ # Parse Supplier url
|
|
|
b8da0b |
if not ldapurl.isLDAPUrl(args.murl):
|
|
|
b8da0b |
- print("Master LDAP URL is invalid")
|
|
|
b8da0b |
+ print("Supplier LDAP URL is invalid")
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
murl = ldapurl.LDAPUrl(args.murl)
|
|
|
b8da0b |
if murl.urlscheme in VALID_PROTOCOLS:
|
|
|
b8da0b |
opts['mprotocol'] = murl.urlscheme
|
|
|
b8da0b |
else:
|
|
|
b8da0b |
- print('Unsupported ldap url protocol (%s) for Master, please use "ldaps" or "ldap"' %
|
|
|
b8da0b |
+ print('Unsupported ldap url protocol (%s) for Supplier, please use "ldaps" or "ldap"' %
|
|
|
b8da0b |
murl.urlscheme)
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
@@ -1520,7 +1520,7 @@ def offline_report(args):
|
|
|
b8da0b |
print ("LDIF file ({}) is empty".format(ldif_dir))
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
if opts['mldif'] == opts['rldif']:
|
|
|
b8da0b |
- print("The Master and Replica LDIF files must be different")
|
|
|
b8da0b |
+ print("The Supplier and Replica LDIF files must be different")
|
|
|
b8da0b |
sys.exit(1)
|
|
|
b8da0b |
|
|
|
b8da0b |
OUTPUT_FILE = None
|
|
|
b8da0b |
@@ -1547,7 +1547,7 @@ def get_state(args):
|
|
|
b8da0b |
"""Just do the RUV comparision
|
|
|
b8da0b |
"""
|
|
|
b8da0b |
opts = init_online_params(args)
|
|
|
b8da0b |
- master, replica, opts = connect_to_replicas(opts)
|
|
|
b8da0b |
+ supplier, replica, opts = connect_to_replicas(opts)
|
|
|
b8da0b |
print(get_ruv_state(opts))
|
|
|
b8da0b |
|
|
|
b8da0b |
|
|
|
b8da0b |
@@ -1569,10 +1569,10 @@ def main():
|
|
|
b8da0b |
# Get state
|
|
|
b8da0b |
state_parser = subparsers.add_parser('state', help="Get the current replicaton state between two replicas")
|
|
|
b8da0b |
state_parser.set_defaults(func=get_state)
|
|
|
b8da0b |
- state_parser.add_argument('-m', '--master-url', help='The LDAP URL for the Master server',
|
|
|
b8da0b |
- dest='murl', default=None, required=True)
|
|
|
b8da0b |
+ state_parser.add_argument('-m', '--supplier-url', help='The LDAP URL for the Supplier server',
|
|
|
b8da0b |
+ dest='murl', default=None, required=True)
|
|
|
b8da0b |
state_parser.add_argument('-r', '--replica-url', help='The LDAP URL for the Replica server',
|
|
|
b8da0b |
- dest='rurl', required=True, default=None)
|
|
|
b8da0b |
+ dest='rurl', required=True, default=None)
|
|
|
b8da0b |
state_parser.add_argument('-b', '--suffix', help='Replicated suffix', dest='suffix', required=True)
|
|
|
b8da0b |
state_parser.add_argument('-D', '--bind-dn', help='The Bind DN', required=True, dest='binddn', default=None)
|
|
|
b8da0b |
state_parser.add_argument('-w', '--bind-pw', help='The Bind password', dest='bindpw', default=None)
|
|
|
b8da0b |
@@ -1586,7 +1586,7 @@ def main():
|
|
|
b8da0b |
# Online mode
|
|
|
b8da0b |
online_parser = subparsers.add_parser('online', help="Compare two online replicas for differences")
|
|
|
b8da0b |
online_parser.set_defaults(func=online_report)
|
|
|
b8da0b |
- online_parser.add_argument('-m', '--master-url', help='The LDAP URL for the Master server (REQUIRED)',
|
|
|
b8da0b |
+ online_parser.add_argument('-m', '--supplier-url', help='The LDAP URL for the Supplier server (REQUIRED)',
|
|
|
b8da0b |
dest='murl', default=None, required=True)
|
|
|
b8da0b |
online_parser.add_argument('-r', '--replica-url', help='The LDAP URL for the Replica server (REQUIRED)',
|
|
|
b8da0b |
dest='rurl', required=True, default=None)
|
|
|
b8da0b |
@@ -1612,12 +1612,12 @@ def main():
|
|
|
b8da0b |
# Offline LDIF mode
|
|
|
b8da0b |
offline_parser = subparsers.add_parser('offline', help="Compare two replication LDIF files for differences (LDIF file generated by 'db2ldif -r')")
|
|
|
b8da0b |
offline_parser.set_defaults(func=offline_report)
|
|
|
b8da0b |
- offline_parser.add_argument('-m', '--master-ldif', help='Master LDIF file',
|
|
|
b8da0b |
+ offline_parser.add_argument('-m', '--supplier-ldif', help='Supplier LDIF file',
|
|
|
b8da0b |
dest='mldif', default=None, required=True)
|
|
|
b8da0b |
offline_parser.add_argument('-r', '--replica-ldif', help='Replica LDIF file',
|
|
|
b8da0b |
dest='rldif', default=None, required=True)
|
|
|
b8da0b |
offline_parser.add_argument('--rid', dest='rid', default=None, required=True,
|
|
|
b8da0b |
- help='The Replica Identifer (rid) for the "Master" server')
|
|
|
b8da0b |
+ help='The Replica Identifier (rid) for the "Supplier" server')
|
|
|
b8da0b |
offline_parser.add_argument('-b', '--suffix', help='Replicated suffix', dest='suffix', required=True)
|
|
|
b8da0b |
offline_parser.add_argument('-c', '--conflicts', help='Display verbose conflict information', action='store_true',
|
|
|
b8da0b |
dest='conflicts', default=False)
|
|
|
b8da0b |
--
|
|
|
b8da0b |
2.31.1
|
|
|
b8da0b |
|