Improve and fix timeout bug in wait_for_entry()

replication.wait_for_entry() now can wait for an attribute value to
appear on a replica.

Fixed timeout handling caused by bad rounding and comparison. For small
timeouts, the actual time was rounded down. For example for 60 seconds
timeout and fast replica, the query accumulated to about 0.45 seconds
plus 60 seconds sleep. 60.45 is large enough to terminate the loop
"while int(time.time()) < timeout", but not large enough to trigger the
exception in "if int(time.time()) > timeout", because int(60.65) == 60.

See: https://pagure.io/freeipa/issue/7593
Fixes: https://pagure.io/freeipa/issue/7595
Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
This commit is contained in:
Christian Heimes 2018-06-22 09:39:26 +02:00
parent de8d308196
commit 14c869b347

View File

@ -20,6 +20,7 @@
from __future__ import print_function, absolute_import from __future__ import print_function, absolute_import
import logging import logging
import itertools
import six import six
import time import time
@ -160,40 +161,43 @@ def wait_for_task(conn, dn):
return exit_code return exit_code
def wait_for_entry(connection, dn, timeout=7200, attr='', quiet=True): def wait_for_entry(connection, dn, timeout=7200, attr=None, attrvalue='*',
"""Wait for entry and/or attr to show up""" quiet=True):
"""Wait for entry and/or attr to show up
filter = "(objectclass=*)" """
log = logger.debug if quiet else logger.info
attrlist = [] attrlist = []
if attr: if attr is not None:
filter = "(%s=*)" % attr filterstr = ipaldap.LDAPClient.make_filter_from_attr(attr, attrvalue)
attrlist.append(attr) attrlist.append(attr)
timeout += int(time.time()) else:
filterstr = "(objectclass=*)"
if not quiet: log("Waiting for replication (%s) %s %s", connection, dn, filterstr)
sys.stdout.write("Waiting for %s %s:%s " % (connection, dn, attr)) entry = []
sys.stdout.flush() deadline = time.time() + timeout
entry = None for i in itertools.count(start=1):
while not entry and int(time.time()) < timeout:
try: try:
[entry] = connection.get_entries( entry = connection.get_entries(
dn, ldap.SCOPE_BASE, filter, attrlist) dn, ldap.SCOPE_BASE, filterstr, attrlist)
except errors.NotFound: except errors.NotFound:
pass # no entry yet pass # no entry yet
except Exception as e: # badness except Exception as e: # badness
logger.error("Error reading entry %s: %s", dn, e) logger.error("Error reading entry %s: %s", dn, e)
raise raise
if not entry:
if not quiet:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1)
if not entry and int(time.time()) > timeout: if entry:
raise errors.NotFound( log("Entry found %r", entry)
reason="wait_for_entry timeout for %s for %s" % (connection, dn)) return
elif entry and not quiet: elif time.time() > deadline:
logger.error("The waited for entry is: %s", entry) raise errors.NotFound(
reason="wait_for_entry timeout on {} for {}".format(
connection, dn
)
)
else:
if i % 10 == 0:
logger.debug("Still waiting for replication of %s", dn)
time.sleep(1)
class ReplicationManager(object): class ReplicationManager(object):