95ea96
From b17a85bded822c4078a9b817720652807a939bc8 Mon Sep 17 00:00:00 2001
2737e7
From: Christian Heimes <cheimes@redhat.com>
2737e7
Date: Fri, 22 Jun 2018 09:39:26 +0200
2737e7
Subject: [PATCH] Improve and fix timeout bug in wait_for_entry()
2737e7
2737e7
replication.wait_for_entry() now can wait for an attribute value to
2737e7
appear on a replica.
2737e7
2737e7
Fixed timeout handling caused by bad rounding and comparison. For small
2737e7
timeouts, the actual time was rounded down. For example for 60 seconds
2737e7
timeout and fast replica, the query accumulated to about 0.45 seconds
2737e7
plus 60 seconds sleep. 60.45 is large enough to terminate the loop
2737e7
"while int(time.time()) < timeout", but not large enough to trigger the
2737e7
exception in "if int(time.time()) > timeout", because int(60.65) == 60.
2737e7
2737e7
See: https://pagure.io/freeipa/issue/7593
2737e7
Fixes: https://pagure.io/freeipa/issue/7595
2737e7
Signed-off-by: Christian Heimes <cheimes@redhat.com>
2737e7
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
2737e7
---
95ea96
 ipaserver/install/replication.py | 54 +++++++++++++++++++++-------------------
95ea96
 1 file changed, 29 insertions(+), 25 deletions(-)
2737e7
2737e7
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
95ea96
index 2e7ce53fef89093a7f661d846aa138ee330961e6..6d9878e16faab1b88a5ab79649571de6802c8536 100644
2737e7
--- a/ipaserver/install/replication.py
2737e7
+++ b/ipaserver/install/replication.py
95ea96
@@ -20,6 +20,7 @@
95ea96
 from __future__ import print_function, absolute_import
2737e7
 
95ea96
 import logging
2737e7
+import itertools
2737e7
 
2737e7
 import six
2737e7
 import time
95ea96
@@ -160,40 +161,43 @@ def wait_for_task(conn, dn):
2737e7
     return exit_code
2737e7
 
2737e7
 
2737e7
-def wait_for_entry(connection, dn, timeout=7200, attr='', quiet=True):
2737e7
-    """Wait for entry and/or attr to show up"""
2737e7
-
2737e7
-    filter = "(objectclass=*)"
2737e7
+def wait_for_entry(connection, dn, timeout=7200, attr=None, attrvalue='*',
2737e7
+                   quiet=True):
2737e7
+    """Wait for entry and/or attr to show up
2737e7
+    """
95ea96
+    log = logger.debug if quiet else logger.info
2737e7
     attrlist = []
2737e7
-    if attr:
2737e7
-        filter = "(%s=*)" % attr
2737e7
+    if attr is not None:
2737e7
+        filterstr = ipaldap.LDAPClient.make_filter_from_attr(attr, attrvalue)
2737e7
         attrlist.append(attr)
2737e7
-    timeout += int(time.time())
2737e7
-
2737e7
-    if not quiet:
2737e7
-        sys.stdout.write("Waiting for %s %s:%s " % (connection, dn, attr))
2737e7
-        sys.stdout.flush()
2737e7
-    entry = None
2737e7
-    while not entry and int(time.time()) < timeout:
2737e7
+    else:
2737e7
+        filterstr = "(objectclass=*)"
2737e7
+    log("Waiting for replication (%s) %s %s", connection, dn, filterstr)
2737e7
+    entry = []
2737e7
+    deadline = time.time() + timeout
2737e7
+    for i in itertools.count(start=1):
2737e7
         try:
2737e7
-            [entry] = connection.get_entries(
2737e7
-                dn, ldap.SCOPE_BASE, filter, attrlist)
2737e7
+            entry = connection.get_entries(
2737e7
+                dn, ldap.SCOPE_BASE, filterstr, attrlist)
2737e7
         except errors.NotFound:
2737e7
             pass  # no entry yet
2737e7
         except Exception as e:  # badness
95ea96
             logger.error("Error reading entry %s: %s", dn, e)
2737e7
             raise
2737e7
-        if not entry:
2737e7
-            if not quiet:
2737e7
-                sys.stdout.write(".")
2737e7
-                sys.stdout.flush()
2737e7
-            time.sleep(1)
2737e7
 
2737e7
-    if not entry and int(time.time()) > timeout:
2737e7
-        raise errors.NotFound(
2737e7
-            reason="wait_for_entry timeout for %s for %s" % (connection, dn))
2737e7
-    elif entry and not quiet:
95ea96
-        logger.error("The waited for entry is: %s", entry)
2737e7
+        if entry:
2737e7
+            log("Entry found %r", entry)
2737e7
+            return
2737e7
+        elif time.time() > deadline:
2737e7
+            raise errors.NotFound(
2737e7
+                reason="wait_for_entry timeout on {} for {}".format(
2737e7
+                    connection, dn
2737e7
+                )
2737e7
+            )
2737e7
+        else:
2737e7
+            if i % 10 == 0:
95ea96
+                logger.debug("Still waiting for replication of %s", dn)
2737e7
+            time.sleep(1)
2737e7
 
2737e7
 
2737e7
 class ReplicationManager(object):
2737e7
-- 
95ea96
2.14.4
2737e7