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