From 5ff1f59c470e5c4ac7875c58c7bddcea63ee1e0e Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: May 22 2024 09:16:32 +0000 Subject: Issue 9591 - Allow get_ruv() to handle incomplete RUV elements Sometimes RUV's are missing the LDAP Url and max/min csns. This prevents cleanallruv task from running. However, cleanallruv doesn't need to know the LDAP URL or min/max csns. Added a new paramter to get_run() called "strict", and when set to False it will still process and include incomplete RUVs. Fixes: https://pagure.io/freeipa/issue/9591 Signed-off-by: Mark Reynolds Reviewed-By: Rob Crittenden Reviewed-By: Rob Crittenden --- diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in index e0235e0..18c9ad4 100644 --- a/install/tools/ipa-replica-manage.in +++ b/install/tools/ipa-replica-manage.in @@ -26,7 +26,6 @@ import os import re import socket import traceback -from urllib.parse import urlparse from xmlrpc.client import MAXINT import ldap @@ -138,6 +137,7 @@ def parse_options(): return options, args + def test_connection(realm, host, nolookup=False): """ Make a GSSAPI connection to the remote LDAP server to test out credentials. @@ -163,6 +163,7 @@ def test_connection(realm, host, nolookup=False): # more than likely a GSSAPI error return False + def list_replicas(realm, host, replica, dirman_passwd, verbose, nolookup=False): if not nolookup: @@ -358,9 +359,14 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False): return True -def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False): + +def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False, + strict=True): """ Return the RUV entries as a list of tuples: (hostname, rid) + + If strict is True then the RUV must contain the ldap url, otherwise it is + ok to proceed with just the rid """ if not nolookup: @@ -371,10 +377,9 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False): thisrepl = replication.get_cs_replication_manager(realm, host, dirman_passwd) else: thisrepl = replication.ReplicationManager(realm, host, dirman_passwd) - except Exception as e: + except Exception as ex: logger.debug("%s", traceback.format_exc()) - raise RuntimeError("Failed to connect to server {host}: {err}" - .format(host=host, err=e)) + raise RuntimeError(f"Failed to connect to server {host}: {ex}") search_filter = '(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectclass=nstombstone))' try: @@ -386,24 +391,42 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False): raise NoRUVsFound("No RUV records found.") servers = [] - for e in entries: - for ruv in e['nsds50ruv']: + for entry in entries: + for ruv in entry['nsds50ruv']: if ruv.startswith('{replicageneration'): continue - data = re.match('\{replica (\d+) (ldap://.*:\d+)\}(\s+\w+\s+\w*){0,1}', ruv) - if data: - rid = data.group(1) - ( - _scheme, netloc, _path, _params, _query, _fragment - ) = urlparse(data.group(2)) - servers.append((netloc, rid)) + + # Get the RID, this is required in all cases + rid_data = re.match( + r'\{replica (\d+)', + ruv + ) + if rid_data: + rid = rid_data.group(1) else: - print("unable to decode: %s" % ruv) + print(f"unable to decode: {ruv} --> missing replica ID") + continue + + # Attempt to extract ldap url from ruv (it's not always present) + netloc = "unknown host" + host_data = re.match( + r'(\{\w+\s+\d+\s+)ldap://(\w+)', + ruv + ) + if host_data: + netloc = host_data.group(2) + elif strict: + print(f"unable to decode: {ruv} --> missing LDAP url") + continue + + # Ok update server list + servers.append((netloc, rid)) return servers -def get_ruv_both_suffixes(realm, host, dirman_passwd, verbose, nolookup=False): +def get_ruv_both_suffixes(realm, host, dirman_passwd, verbose, nolookup=False, + strict=True): """ Get RUVs for both domain and ipaca suffixes """ @@ -411,19 +434,20 @@ def get_ruv_both_suffixes(realm, host, dirman_passwd, verbose, nolookup=False): fail_gracefully = True try: - ruvs['ca'] = get_ruv(realm, host, dirman_passwd, nolookup, True) + ruvs['ca'] = get_ruv(realm, host, dirman_passwd, nolookup, True, + strict) except (NoRUVsFound, RuntimeError) as e: - err = "Failed to get CS-RUVs from {host}: {err}".format(host=host, - err=e) + err = f"Failed to get CS-RUVs from {host}: {e}" if isinstance(e, RuntimeError): fail_gracefully = False if verbose: print(err) logger.debug('%s', err) try: - ruvs['domain'] = get_ruv(realm, host, dirman_passwd, nolookup) + ruvs['domain'] = get_ruv(realm, host, dirman_passwd, nolookup, False, + strict) except (NoRUVsFound, RuntimeError) as e: - err = "Failed to get RUVs from {host}: {err}".format(host=host, err=e) + err = f"Failed to get RUVs from {host}: {e}" if isinstance(e, RuntimeError): if not fail_gracefully: raise @@ -495,7 +519,8 @@ def clean_ruv(realm, ruv, options): servers = get_ruv_both_suffixes(realm, options.host, options.dirman_passwd, options.verbose, - options.nolookup) + options.nolookup, + strict=False) except (NoRUVsFound, RuntimeError) as e: print(e) sys.exit(0 if isinstance(e, NoRUVsFound) else 1) @@ -551,7 +576,8 @@ def abort_clean_ruv(realm, ruv, options): servers = get_ruv_both_suffixes(realm, options.host, options.dirman_passwd, options.verbose, - options.nolookup) + options.nolookup, + strict=False) except (NoRUVsFound, RuntimeError) as e: print(e) sys.exit(0 if isinstance(e, NoRUVsFound) else 1) @@ -710,7 +736,8 @@ def clean_dangling_ruvs(realm, host, options): ruv_dict = get_ruv_both_suffixes(realm, master_cn, options.dirman_passwd, options.verbose, - options.nolookup) + options.nolookup, + strict=False) except (RuntimeError, NoRUVsFound): continue @@ -845,6 +872,7 @@ def enforce_host_existence(host, message=None): message = "Unknown host %s: %s" % (host, ex) sys.exit(message) + def ensure_last_services(conn, hostname, masters, options): """ 1. When deleting master, check if there will be at least one remaining @@ -919,6 +947,7 @@ def del_master(realm, hostname, options): else: del_master_direct(realm, hostname, options) + def del_master_managed(realm, hostname, options): """ Removing of master in managed_topology @@ -1085,6 +1114,7 @@ def del_master_direct(realm, hostname, options): # 7. And clean up the removed replica DNS entries if any. cleanup_server_dns_entries(realm, hostname, thisrepl.suffix, options) + def add_link(realm, replica1, replica2, dirman_passwd, options): if not options.nolookup: @@ -1116,7 +1146,7 @@ def add_link(realm, replica1, replica2, dirman_passwd, options): if repl.get_agreement_type(replica2) == replication.WINSYNC: agreement = repl.get_replication_agreement(replica2) sys.exit("winsync agreement already exists on subtree %s" % - agreement.single_value.get('nsds7WindowsReplicaSubtree')) + agreement.single_value.get('nsds7WindowsReplicaSubtree')) else: sys.exit("A replication agreement to %s already exists" % replica2) except errors.NotFound: @@ -1183,6 +1213,7 @@ def add_link(realm, replica1, replica2, dirman_passwd, options): repl1.setup_gssapi_replication(replica2, DN(('cn', 'Directory Manager')), dirman_passwd) print("Connected '%s' to '%s'" % (replica1, replica2)) + def re_initialize(realm, thishost, fromhost, dirman_passwd, nolookup=False): if not nolookup: @@ -1220,6 +1251,7 @@ def re_initialize(realm, thishost, fromhost, dirman_passwd, nolookup=False): ds = dsinstance.DsInstance(realm_name=realm) ds.init_memberof() + def force_sync(realm, thishost, fromhost, dirman_passwd, nolookup=False): if not nolookup: @@ -1244,6 +1276,7 @@ def force_sync(realm, thishost, fromhost, dirman_passwd, nolookup=False): repl.wait_for_repl_update(repl.conn, agreement.dn) ds.replica_manage_time_skew(prevent=True) + def show_DNA_ranges(hostname, master, realm, dirman_passwd, nextrange=False, nolookup=False): """ @@ -1503,6 +1536,7 @@ def exit_on_managed_topology(what): "Please use `ipa topologysegment-*` commands to manage " "the topology.".format(what)) + def main(options, args): if os.getegid() == 0: installutils.check_server_configuration() @@ -1611,6 +1645,7 @@ def main(options, args): api.Backend.ldap2.disconnect() + try: options, args = parse_options() main(options, args)