Hi,

Attached are the patches for auto-find and clean of dangling (cs)ruvs. Currently, the cleaning of an RUV waits for all replicas to be online, even on --force. If that were an issue, I can make the command fail before trying to clean any of RUVs. However, the user is shown a replica is offline and is prompted to confirm the cleaning so the possible wait should not be a problem I believe.

Standa L.
From 8e5aff9aa4b121fa5c623aceee5c9bf055111d42 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 18 Dec 2015 10:30:44 +0100
Subject: [PATCH 1/2] Listing and cleaning RUV extended for CA suffix

https://fedorahosted.org/freeipa/ticket/5411
---
 install/tools/ipa-replica-manage | 36 +++++++++++++++++++++++-------------
 ipaserver/install/replication.py |  2 +-
 2 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index 5124731255807287a7857b1a4cdc2e6799cd7278..9276597d9d883afbc124a29d0080197aa769fd23 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -347,7 +347,7 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False):
 
     return True
 
-def get_ruv(realm, host, dirman_passwd, nolookup=False):
+def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False):
     """
     Return the RUV entries as a list of tuples: (hostname, rid)
     """
@@ -356,7 +356,10 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False):
         enforce_host_existence(host)
 
     try:
-        thisrepl = replication.ReplicationManager(realm, host, dirman_passwd)
+        if ca:
+            thisrepl = replication.get_cs_replication_manager(realm, host, dirman_passwd)
+        else:
+            thisrepl = replication.ReplicationManager(realm, host, dirman_passwd)
     except Exception as e:
         print("Failed to connect to server %s: %s" % (host, e))
         sys.exit(1)
@@ -364,7 +367,7 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False):
     search_filter = '(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectclass=nstombstone))'
     try:
         entries = thisrepl.conn.get_entries(
-            api.env.basedn, thisrepl.conn.SCOPE_SUBTREE, search_filter,
+            thisrepl.db_suffix, thisrepl.conn.SCOPE_SUBTREE, search_filter,
             ['nsds50ruv'])
     except errors.NotFound:
         print("No RUV records found.")
@@ -404,7 +407,7 @@ def get_rid_by_host(realm, sourcehost, host, dirman_passwd, nolookup=False):
         if '%s:389' % host == netloc:
             return int(rid)
 
-def clean_ruv(realm, ruv, options):
+def clean_ruv(realm, ruv, options, ca=False):
     """
     Given an RID create a CLEANALLRUV task to clean it up.
     """
@@ -414,7 +417,7 @@ def clean_ruv(realm, ruv, options):
         sys.exit("Replica ID must be an integer: %s" % ruv)
 
     servers = get_ruv(realm, options.host, options.dirman_passwd,
-                      options.nolookup)
+                      options.nolookup, ca=ca)
     found = False
     for (netloc, rid) in servers:
         if ruv == int(rid):
@@ -426,14 +429,21 @@ def clean_ruv(realm, ruv, options):
         sys.exit("Replica ID %s not found" % ruv)
 
     print("Clean the Replication Update Vector for %s" % hostname)
-    print()
-    print("Cleaning the wrong replica ID will cause that server to no")
-    print("longer replicate so it may miss updates while the process")
-    print("is running. It would need to be re-initialized to maintain")
-    print("consistency. Be very careful.")
-    if not options.force and not ipautil.user_input("Continue to clean?", False):
-        sys.exit("Aborted")
-    thisrepl = replication.ReplicationManager(realm, options.host,
+
+    if not options.force:
+        print()
+        print("Cleaning the wrong replica ID will cause that server to no")
+        print("longer replicate so it may miss updates while the process")
+        print("is running. It would need to be re-initialized to maintain")
+        print("consistency. Be very careful.")
+        if not ipautil.user_input("Continue to clean?", False):
+            sys.exit("Aborted")
+
+    if ca:
+        thisrepl = replication.get_cs_replication_manager(realm, options.host,
+                                                        options.dirman_passwd)
+    else:
+        thisrepl = replication.ReplicationManager(realm, options.host,
                                               options.dirman_passwd)
     thisrepl.cleanallruv(ruv)
     print("Cleanup task created")
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index b20842bb72fb8a6ebbd04afd163928c258c3ff73..e3265f93fe1620948397a379e77dcf68c945a0a9 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -1344,7 +1344,7 @@ class ReplicationManager(object):
             {
                 'objectclass': ['top', 'extensibleObject'],
                 'cn': ['clean %d' % replicaId],
-                'replica-base-dn': [api.env.basedn],
+                'replica-base-dn': [self.db_suffix],
                 'replica-id': [replicaId],
             }
         )
-- 
2.5.0

From bde7147b6253d9d1b2a3f3a0aff07c2f7a588d5a Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 18 Dec 2015 10:34:52 +0100
Subject: [PATCH 2/2] Automatically detect and remove dangling RUVs

https://fedorahosted.org/freeipa/ticket/5411
---
 install/tools/ipa-replica-manage | 160 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 159 insertions(+), 1 deletion(-)

diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index 9276597d9d883afbc124a29d0080197aa769fd23..e5287fd036b17a3e286dfe77307e0d870f78a563 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -37,7 +37,7 @@ from ipaserver.install import bindinstance, cainstance, certs
 from ipaserver.install import opendnssecinstance, dnskeysyncinstance
 from ipapython import version, ipaldap
 from ipalib import api, errors, util
-from ipalib.constants import CACERT
+from ipalib.constants import CACERT, CA_SUFFIX_NAME
 from ipalib.util import (create_topology_graph,
     get_topology_connection_errors, has_managed_topology)
 from ipapython.ipa_log_manager import *
@@ -62,6 +62,7 @@ commands = {
     "clean-ruv":(1, 1, "Replica ID of to clean", "must provide replica ID to clean"),
     "abort-clean-ruv":(1, 1, "Replica ID to abort cleaning", "must provide replica ID to abort cleaning"),
     "list-clean-ruv":(0, 0, "", ""),
+    "clean-dangling-ruv":(0, 0, "", ""),
     "dnarange-show":(0, 1, "[master fqdn]", ""),
     "dnanextrange-show":(0, 1, "", ""),
     "dnarange-set":(2, 2, "<master fqdn> <range>", "must provide a master and ID range"),
@@ -530,6 +531,161 @@ def list_clean_ruv(realm, host, dirman_passwd, verbose, nolookup=False):
                 print(str(dn))
                 print(entry.single_value.get('nstasklog'))
 
+
+def clean_dangling_ruvs(realm, host, options):
+    """
+    Cleans all RUVs that are left in the system from uninstalled replicas
+    """
+    # get the Directory Manager password
+    if options.dirman_passwd:
+        dirman_passwd = options.dirman_passwd
+    else:
+        dirman_passwd = installutils.read_password('Directory Manager',
+            confirm=False, validate=False, retry=False)
+        if dirman_passwd is None:
+            sys.exit('Directory Manager password is required')
+
+    options.dirman_passwd = dirman_passwd
+
+    try:
+        conn = ipaldap.IPAdmin(host, 636, cacert=CACERT)
+        conn.do_simple_bind(bindpw=dirman_passwd)
+
+        # get all masters
+        masters_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+                        ipautil.realm_to_suffix(realm))
+        masters = conn.get_entries(masters_dn, conn.SCOPE_ONELEVEL)
+        info = dict()
+
+        # check whether CAs are configured on those masters
+        for master in masters:
+            info[master.single_value['cn']] = {
+                    'online': False, 'ca': False, 'ruvs': list(),
+                    'csruvs': list(), 'clean_ruv': list(),
+                    'clean_csruv': list()
+                    }
+            try:
+                ca_dn = DN(('cn', 'ca'), DN(master.dn))
+                entry = conn.get_entry(ca_dn)
+                info[master.single_value['cn']]['ca'] = True
+            except errors.NotFound:
+                continue
+
+    except Exception as e:
+        sys.exit(
+            "Failed to get data from '%s' while trying to list replicas: %s" %
+            (host, e)
+        )
+    finally:
+        conn.unbind()
+
+    # Get realm string for config tree
+    s = realm.split('.')
+    s = ['dc={dc},'.format(dc=x.lower()) for x in s]
+    realm_config = DN(('cn', ''.join(s)[0:-1]))
+
+    replica_dn = DN(('cn', 'replica'), realm_config,
+                    ('cn', 'mapping tree'), ('cn', 'config'))
+
+    csreplica_dn = DN(('cn', 'replica'), ('cn', 'o=ipaca'),
+                      ('cn', 'mapping tree'), ('cn', 'config'))
+
+    masters = [x.single_value['cn'] for x in masters]
+
+    ruvs = list()
+    csruvs = list()
+    offlines = list()
+    for master in masters:
+        try:
+            conn = ipaldap.IPAdmin(master, 636, cacert=CACERT)
+            conn.do_simple_bind(bindpw=dirman_passwd)
+            info[master]['online'] = True
+        except:
+            print("The server '%s' appears to be offline." % master)
+            offlines.append(master)
+            continue
+
+        try:
+            entry = conn.get_entry(replica_dn)
+            ruv = (master, entry.single_value.get('nsDS5ReplicaID'))
+            if ruv not in ruvs:
+                ruvs.append(ruv)
+
+            if(info[master]['ca']):
+                entry = conn.get_entry(csreplica_dn)
+                csruv = (master, entry.single_value.get('nsDS5ReplicaID'))
+                if csruv not in csruvs:
+                    csruvs.append(csruv)
+
+            # get_ruv returns server names with :port which needs to be split off
+            ruv_list = get_ruv(realm, master, dirman_passwd, options.nolookup)
+            info[master]['ruvs'] = [
+                (re.sub(':\d+', '', x), y)
+                for (x, y) in ruv_list
+                ]
+
+            ruv_list = get_ruv(realm, master, dirman_passwd, options.nolookup,
+                               ca=True)
+            info[master]['csruvs'] = [
+                (re.sub(':\d+', '', x), y)
+                for (x, y) in ruv_list
+                ]
+        except Exception as e:
+            sys.exit("Failed to obtain information from '%s': %s" %
+                     (master, str(e)))
+        finally:
+            conn.unbind()
+
+    clean_list = list()
+    dangles = False
+    # get the dangling RUVs
+    for master in masters:
+        if info[master]['online']:
+            for ruv in info[master]['ruvs']:
+                if (ruv not in ruvs) and (ruv[0] not in offlines):
+                    info[master]['clean_ruv'].append(ruv)
+                    dangles = True
+
+            if info[master]['ca']:
+                for csruv in info[master]['csruvs']:
+                    if (csruv not in csruvs) and (csruv[0] not in offlines):
+                        info[master]['clean_csruv'].append(csruv)
+                        dangles = True
+
+    if not dangles:
+        print('No dangling RUVs found')
+        sys.exit(0)
+
+    print('These RUVs are dangling and will be removed:')
+    for master in masters:
+        if info[master]['online'] and (info[master]['clean_ruv'] or
+                                       info[master]['clean_csruv']):
+            print('Host: {m}'.format(m=master))
+            print('\tRUVs:')
+            for ruv in info[master]['clean_ruv']:
+                print('\t\tid: {id}, hostname: {host}'.format(id=ruv[1], host=ruv[0]))
+
+            print('\tCS-RUVs:')
+            for csruv in info[master]['clean_csruv']:
+                print('\t\tid: {id}, hostname: {host}'.format(id=csruv[1], host=csruv[0]))
+
+    if not options.force and not ipautil.user_input("Proceed with cleaning?", False):
+        sys.exit("Aborted")
+
+    options.force = True
+    cleaned = list()
+    for master in masters:
+        options.host = master
+        for ruv in info[master]['clean_ruv']:
+            if ruv[1] not in cleaned:
+                cleaned.append(ruv[1])
+                clean_ruv(realm, ruv[1], options)
+        for csruv in info[master]['clean_csruv']:
+            if csruv[1] not in cleaned:
+                cleaned.append(csruv[1])
+                clean_ruv(realm, csruv[1], options, ca=True)
+
+
 def check_last_link(delrepl, realm, dirman_passwd, force):
     """
     We don't want to orphan a server when deleting another one. If you have
@@ -1464,6 +1620,8 @@ def main():
     elif args[0] == "list-clean-ruv":
         list_clean_ruv(realm, host, dirman_passwd, options.verbose,
                        options.nolookup)
+    elif args[0] == "clean-dangling-ruv":
+        clean_dangling_ruvs(realm, host, options)
     elif args[0] == "dnarange-show":
         if len(args) == 2:
             master = args[1]
-- 
2.5.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to