diff -pruN 2.3.1-1.1/AUTHORS 2.4.1-0ubuntu3/AUTHORS
--- 2.3.1-1.1/AUTHORS	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/AUTHORS	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1,30 @@
+98k <18552437190@163.com>
+Andreas Jaeger <aj@suse.com>
+Charles Duffy <charles@dyfis.net>
+Charles Duffy <duffy@indeed.com>
+Chris McDonough <chrism@plope.com>
+Colleen Murphy <colleen.murphy@suse.de>
+Colleen Murphy <colleen@gazlene.net>
+Colleen Murphy <comurphy@suse.com>
+Corey Bryant <corey.bryant@canonical.com>
+Dirk Mueller <dirk@dmllr.de>
+Doug Hellmann <doug@doughellmann.com>
+Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
+Monty Taylor <mordred@inaugust.com>
+Morgan Fainberg <morgan.fainberg@gmail.com>
+Nathan Kinder <nkinder@redhat.com>
+Nick Wilburn <senior.crepe@gmail.com>
+Samriddhi Jain <j.samriddhi13@gmail.com>
+Steve Martinelli <s.martinelli@gmail.com>
+Tarek Ziade <tarek@ziade.org>
+Tarek Ziade <ziade.tarek@gmail.com>
+Tarek Ziadé <tarek@mozilla.com>
+Tony Breeds <tony@bakeyournoodle.com>
+Van Hung Pham <hungpv@vn.fujitsu.com>
+Vieri <15050873171@163.com>
+Vu Cong Tuan <tuanvc@vn.fujitsu.com>
+huang.zhiping <huang.zhiping@99cloud.net>
+pallavi <pallavi.s@nectechnologies.in>
+qingszhao <zhao.daqing@99cloud.net>
+ricolin <rico.lin@easystack.cn>
+zhouxinyong <zhouxinyong@inspur.com>
diff -pruN 2.3.1-1.1/ChangeLog 2.4.1-0ubuntu3/ChangeLog
--- 2.3.1-1.1/ChangeLog	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ChangeLog	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1,109 @@
+CHANGES
+=======
+
+2.4.1
+-----
+
+* Add py37 tox env
+* add python 3.7 unit test job
+* Use template for lower-constraints
+* Fix releasenotes build
+* Change openstack-dev to openstack-discuss
+* Replacing the HTTP protocal with HTTPS in index.rst
+* Add py36 tox environment
+* Add release notes jobs
+
+2.4.0
+-----
+
+* Allow pool status to be printed as a table
+* Add plumbing to support reno release notes
+* Handle retry logic for timeouts with multiple LDAP servers
+* Improve connection retry logging
+
+2.3.1
+-----
+
+* PY3: switch to using unicode text values
+* Don't quote {posargs} in tox.ini
+* Removed older version of python added 3.5
+* add python 3.6 unit test job
+* import zuul job settings from project-config
+* fix tox python3 overrides
+* fix ldappool bad password retry logic
+
+2.3.0
+-----
+
+* Add author email to setup.cfg
+* Switch to python-ldap again
+* Bump to hacking 1.1.x
+* Switch to stestr
+* add lower-constraints job
+* Updated from global requirements
+* Updated from global requirements
+
+2.2.0
+-----
+
+* Updated from global requirements
+* Avoid tox\_install.sh for constraints support
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+* Turn on warning-is-error for sphinx build
+* Switch from oslosphinx to openstackdocstheme
+* Fix html\_last\_updated\_fmt for Python3
+
+2.1.0
+-----
+
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+* Don't call start\_tls\_s() twice
+* [Fix gate]Update test requirement
+* Add Constraints support
+* update README to reflect actual ldap dependency
+* Expose SERVER\_DOWN if connection fails
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+
+2.0.0
+-----
+
+* Add py3 info to setup.cfg
+* Updated from global requirements
+* make ldappool py3 compatible
+* use standard docstring convention for parameters
+* Use standard-library logging to record errors
+* Raise an explicit BackendError on TLS failures
+* Fix pool\_full race condition
+* additional files to ignore in .gitignore
+* Fix license in setup.py
+* add .gitreview and fix ldappool gate
+* Add test-requirements for py27 testing
+* PEP8 fixes
+* Add support for tox unit testing
+* Initialize conn in \_create\_connector (fixes: #7)
+* Use setuptools when available
+* #4: UTF-8 encode passwd only when set
+* starting 1.1
+
+1.0
+---
+
+* raised version
+* preparing 1.0
+* fix use\_tls flag
+* packaging tweaks
+* added a MANIFEST template
+* added a few keywords for pypi indexation
+* simplified setup
+* more docs
+* initial import of server-core's ldappool
+* first commit
diff -pruN 2.3.1-1.1/debian/changelog 2.4.1-0ubuntu3/debian/changelog
--- 2.3.1-1.1/debian/changelog	2021-01-06 17:49:11.000000000 +0000
+++ 2.4.1-0ubuntu3/debian/changelog	2020-03-01 20:28:28.000000000 +0000
@@ -1,21 +1,37 @@
-python-ldappool (2.3.1-1.1) unstable; urgency=medium
+python-ldappool (2.4.1-0ubuntu3) focal; urgency=medium
 
-  * Non maintainer upload by the Reproducible Builds team.
-  * No source change upload to rebuild on buildd with .buildinfo files.
+  * d/rules: Use pybuild instead of python-distutils, fixing FTBFS.
 
- -- Holger Levsen <holger@debian.org>  Wed, 06 Jan 2021 18:49:11 +0100
+ -- Logan Rosen <logan@ubuntu.com>  Sun, 01 Mar 2020 15:28:28 -0500
 
-python-ldappool (2.3.1-1) unstable; urgency=medium
+python-ldappool (2.4.1-0ubuntu2) eoan; urgency=medium
 
-  [ Ondřej Nový ]
-  * Running wrap-and-sort -bast
-  * d/control: Use team+openstack@tracker.debian.org as maintainer
+  * Drop python2 support.
 
-  [ Thomas Goirand ]
-  * New upstream release, fixing Python 3.x issues.
-  * Switch to python3-stestr, just like upstream did.
+ -- Steve Langasek <steve.langasek@ubuntu.com>  Fri, 06 Sep 2019 13:54:00 -0700
+
+python-ldappool (2.4.1-0ubuntu1) disco; urgency=medium
+
+  * New upstream point release for OpenStack Stein.
+
+ -- James Page <james.page@ubuntu.com>  Fri, 22 Mar 2019 14:29:56 +0000
+
+python-ldappool (2.4.0-0ubuntu1) disco; urgency=medium
+
+  * New upstream release for OpenStack Stein.
+  * d/control: Align (Build-)Depends with upstream.
+
+ -- Corey Bryant <corey.bryant@canonical.com>  Wed, 30 Jan 2019 13:28:05 -0500
+
+python-ldappool (2.2.0-3ubuntu1) cosmic; urgency=low
+
+  * Merge from Debian unstable.  Remaining changes:
+    - d/gbp.conf: Retain for pristine-tar.
+    - d/watch: Get tarball from tarballs.openstack.org.
+    - d/control, d/rules: Retain python2 support, including python-ldap binary
+      package. Also update Vcs-Browser/Vcs-Git to point at lp:~ubuntu-server-dev.
 
- -- Thomas Goirand <zigo@debian.org>  Fri, 08 Mar 2019 12:12:42 +0100
+ -- Corey Bryant <corey.bryant@canonical.com>  Fri, 11 May 2018 10:48:57 -0400
 
 python-ldappool (2.2.0-3) unstable; urgency=medium
 
@@ -53,6 +69,27 @@ python-ldappool (2.2.0-1) unstable; urge
 
  -- Thomas Goirand <zigo@debian.org>  Sat, 03 Mar 2018 18:12:01 +0100
 
+python-ldappool (2.2.0-0ubuntu1) bionic; urgency=medium
+
+  * New upstream release.
+  * d/control: Bump debhelper compat to 10.
+  * d/control: Align (Build-)Depends with upstream.
+
+ -- Corey Bryant <corey.bryant@canonical.com>  Tue, 13 Feb 2018 13:16:32 -0500
+
+python-ldappool (2.1.0-0ubuntu1) bionic; urgency=medium
+
+  * d/gbp.conf: Update gbp configuration file.
+  * d/control: Update Vcs-* links and maintainers.
+  * d/watch: Get tarball from tarballs.openstack.org.
+  * New upstream release.
+  * d/control: Udpate to new upstream homepage.
+  * d/control: Align (Build-)Depends with upstream.
+  * d/*: wrap-and-sort -bast.
+  * d/control: Update Standards-Version to 4.1.2.
+
+ -- Corey Bryant <corey.bryant@canonical.com>  Fri, 08 Dec 2017 13:28:13 -0500
+
 python-ldappool (2.0.0-1) unstable; urgency=medium
 
   [ Ondřej Nový ]
@@ -75,3 +112,4 @@ python-ldappool (1.0-1) unstable; urgenc
   * Initial release. (Closes: #752753)
 
  -- Thomas Goirand <zigo@debian.org>  Thu, 26 Jun 2014 16:10:51 +0800
+
diff -pruN 2.3.1-1.1/debian/control 2.4.1-0ubuntu3/debian/control
--- 2.3.1-1.1/debian/control	2019-03-08 11:12:42.000000000 +0000
+++ 2.4.1-0ubuntu3/debian/control	2019-09-06 20:54:00.000000000 +0000
@@ -1,7 +1,8 @@
 Source: python-ldappool
 Section: python
 Priority: optional
-Maintainer: Debian OpenStack <team+openstack@tracker.debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian OpenStack <openstack-devel@lists.alioth.debian.org>
 Uploaders:
  Thomas Goirand <zigo@debian.org>,
 Build-Depends:
@@ -12,25 +13,31 @@ Build-Depends:
  python3-setuptools,
  python3-sphinx (>= 1.6.2),
 Build-Depends-Indep:
- python3-coverage,
- python3-fixtures,
- python3-hacking,
- python3-ldap (>= 3.0.0~b4),
- python3-stestr,
+ python3-coverage (>= 4.0),
+ python3-hacking (>= 1.1.0),
+ python3-flake8-docstrings (>= 0.2.1.post1),
+ python3-fixtures (>= 3.0.0),
+ python3-ldap (>= 3.0.0),
+ python3-openstackdocstheme (>= 1.18.1),
+ python3-prettytable (>= 0.7.2),
+ python3-reno (>= 2.5.0),
+ python3-stestr (>= 2.0.0),
  python3-subunit,
- python3-testresources,
- python3-testtools (>= 1.4.0),
+ python3-testresources (>= 2.0.0),
+ python3-testtools (>= 2.2.0),
  subunit,
+ testrepository,
 Standards-Version: 4.1.3
-Vcs-Browser: https://salsa.debian.org/openstack-team/python/python-ldappool
-Vcs-Git: https://salsa.debian.org/openstack-team/python/python-ldappool.git
+Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-ldappool
+Vcs-Git: git://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-ldappool
 Homepage: https://git.openstack.org/cgit/openstack/ldappool
 
 Package: python3-ldappool
 Architecture: all
 Depends:
- python3-ldap (>= 3.0.0~b4),
  python3-six,
+ python3-ldap (>= 3.0.0),
+ python3-prettytable (>= 0.7.2),
  ${misc:Depends},
  ${python3:Depends},
 Description: connection pool for python-ldap - Python 3.x
diff -pruN 2.3.1-1.1/debian/gbp.conf 2.4.1-0ubuntu3/debian/gbp.conf
--- 2.3.1-1.1/debian/gbp.conf	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/debian/gbp.conf	2019-03-22 14:29:56.000000000 +0000
@@ -0,0 +1,7 @@
+[DEFAULT]
+debian-branch = master
+upstream-tag = %(version)s
+pristine-tar = True
+
+[buildpackage]
+export-dir = ../build-area
diff -pruN 2.3.1-1.1/debian/rules 2.4.1-0ubuntu3/debian/rules
--- 2.3.1-1.1/debian/rules	2019-03-08 11:12:42.000000000 +0000
+++ 2.4.1-0ubuntu3/debian/rules	2020-03-01 20:28:27.000000000 +0000
@@ -4,15 +4,12 @@ UPSTREAM_GIT := https://git.openstack.or
 include /usr/share/openstack-pkg-tools/pkgos.make
 
 %:
-	dh $@ --buildsystem=python_distutils --with python3
+	dh $@ --buildsystem=pybuild --with python3
 
 override_dh_clean:
-	dh_clean -O--buildsystem=python_distutils
+	dh_clean -O--buildsystem=pybuild
 	rm -rf build
 
-override_dh_auto_clean:
-	python3 setup.py clean
-
 override_dh_auto_build:
 	echo "Do not build..."
 
@@ -24,3 +21,11 @@ override_dh_auto_install:
 ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))
 	pkgos-dh_auto_test --no-py2
 endif
+
+# Commands not to run
+override_dh_installcatalogs:
+override_dh_installemacsen override_dh_installifupdown:
+override_dh_installinfo override_dh_installmenu override_dh_installmime:
+override_dh_installmodules override_dh_installlogcheck:
+override_dh_installpam override_dh_installppp override_dh_installudev override_dh_installwm:
+override_dh_installxfonts override_dh_gconf override_dh_icons override_dh_perl override_dh_usrlocal:
diff -pruN 2.3.1-1.1/debian/watch 2.4.1-0ubuntu3/debian/watch
--- 2.3.1-1.1/debian/watch	2019-03-08 11:12:42.000000000 +0000
+++ 2.4.1-0ubuntu3/debian/watch	2019-03-22 14:29:56.000000000 +0000
@@ -1,3 +1,4 @@
 version=3
-http://pypi.python.org/packages/source/l/ldappool ldappool-(.*).tar.gz
+opts="uversionmangle=s/\.(b|rc)/~$1/" \
+    http://tarballs.openstack.org/ldappool/ ldappool-(\d.*)\.tar\.gz
 
diff -pruN 2.3.1-1.1/doc/requirements.txt 2.4.1-0ubuntu3/doc/requirements.txt
--- 2.3.1-1.1/doc/requirements.txt	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/doc/requirements.txt	2019-02-25 21:38:50.000000000 +0000
@@ -0,0 +1,3 @@
+openstackdocstheme>=1.18.1 # Apache-2.0
+reno>=2.5.0 # Apache-2.0
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
diff -pruN 2.3.1-1.1/doc/source/index.rst 2.4.1-0ubuntu3/doc/source/index.rst
--- 2.3.1-1.1/doc/source/index.rst	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/doc/source/index.rst	2019-02-25 21:38:50.000000000 +0000
@@ -25,7 +25,7 @@ using `Gerrit`_.
 
 .. _on GitHub: https://github.com/openstack/ldappool
 .. _Launchpad: https://launchpad.net/ldappool
-.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow
+.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow
 
 Run tests with ``tox``.
 
diff -pruN 2.3.1-1.1/.gitignore 2.4.1-0ubuntu3/.gitignore
--- 2.3.1-1.1/.gitignore	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/.gitignore	1970-01-01 00:00:00.000000000 +0000
@@ -1,27 +0,0 @@
-*.pyc
-*.sw?
-*.egg/
-vendor
-.ksl-venv
-.venv
-.update-venv/
-.tox
-ldappool.egg-info/
-*.log
-.coverage
-coverage.xml
-cover/*
-covhtml
-pep8.txt
-doc/build
-.DS_Store
-doc/source/api
-doc/source/modules.rst
-ChangeLog
-AUTHORS
-build/
-dist/
-.project
-.pydevproject
-.stestr/
-*.db
diff -pruN 2.3.1-1.1/.gitreview 2.4.1-0ubuntu3/.gitreview
--- 2.3.1-1.1/.gitreview	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/.gitreview	1970-01-01 00:00:00.000000000 +0000
@@ -1,4 +0,0 @@
-[gerrit]
-host=review.openstack.org
-port=29418
-project=openstack/ldappool.git
diff -pruN 2.3.1-1.1/ldappool/__init__.py 2.4.1-0ubuntu3/ldappool/__init__.py
--- 2.3.1-1.1/ldappool/__init__.py	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool/__init__.py	2019-02-25 21:38:50.000000000 +0000
@@ -43,6 +43,8 @@ import time
 
 import ldap
 from ldap.ldapobject import ReconnectLDAPObject
+from prettytable import PrettyTable
+import re
 import six
 from six import PY2
 
@@ -239,48 +241,69 @@ class ConnectionManager(object):
         :returns: StateConnector
         :raises BackendError: If unable to connect to LDAP
         """
-        tries = 0
         connected = False
         if passwd is not None:
             if PY2:
                 passwd = utf8_encode(passwd)
-        exc = None
-        conn = None
 
-        # trying retry_max times in a row with a fresh connector
-        while tries < self.retry_max and not connected:
-            try:
-                conn = self.connector_cls(self.uri, retry_max=self.retry_max,
-                                          retry_delay=self.retry_delay)
-                conn.timeout = self.timeout
-                self._bind(conn, bind, passwd)
-                connected = True
-            except ldap.INVALID_CREDENTIALS as error:
-                exc = error
-                log.error('Invalid credentials. Cancelling retry',
-                          exc_info=True)
-                break
-            except ldap.LDAPError as error:
-                exc = error
-                time.sleep(self.retry_delay)
-                if tries < self.retry_max:
-                    log.info('Failure attempting to create and bind '
-                             'connector; will retry after %r seconds',
-                             self.retry_delay, exc_info=True)
-                else:
-                    log.error('Failure attempting to create and bind '
-                              'connector', exc_info=True)
-                tries += 1
+        # If multiple server URIs have been provided, loop through
+        # each one in turn in case of connection failures (server down,
+        # timeout, etc.).  URIs can be delimited by either commas or
+        # whitespace.
+        for server in re.split('[\s,]+', self.uri):
+            tries = 0
+            exc = None
+            conn = None
 
+            # trying retry_max times in a row with a fresh connector
+            while tries < self.retry_max and not connected:
+                try:
+                    log.debug('Attempting to create a new connector '
+                              'to %s (attempt %d)', server, tries + 1)
+                    conn = self.connector_cls(server, retry_max=self.retry_max,
+                                              retry_delay=self.retry_delay)
+                    conn.timeout = self.timeout
+                    self._bind(conn, bind, passwd)
+                    connected = True
+                except ldap.INVALID_CREDENTIALS as error:
+                    # Treat this as a hard failure instead of retrying to
+                    # avoid locking out the LDAP account due to successive
+                    # failed bind attempts.  We also don't want to try
+                    # connecting to additional servers if multiple URIs were
+                    # provide, as failed bind attempts may be replicated
+                    # across multiple LDAP servers.
+                    exc = error
+                    log.error('Invalid credentials. Cancelling retry',
+                              exc_info=True)
+                    raise exc
+                except ldap.LDAPError as error:
+                    exc = error
+                    tries += 1
+                    if tries < self.retry_max:
+                        log.info('Failure attempting to create and bind '
+                                 'connector; will retry after %r seconds',
+                                 self.retry_delay, exc_info=True)
+                        time.sleep(self.retry_delay)
+                    else:
+                        log.error('Failure attempting to create and bind '
+                                  'connector', exc_info=True)
+
+            # We successfully connected to one of the servers, so
+            # we can just return the connection and stop processing
+            # any additional URIs.
+            if connected:
+                return conn
+
+        # We failed to connect to any of the servers,
+        # so raise an appropriate exception.
         if not connected:
             if isinstance(exc, (ldap.NO_SUCH_OBJECT,
-                                ldap.INVALID_CREDENTIALS,
-                                ldap.SERVER_DOWN)):
+                                ldap.SERVER_DOWN,
+                                ldap.TIMEOUT)):
                 raise exc
 
-            # that's something else
-            raise BackendError(str(exc), backend=conn)
-        return conn
+        # that's something else
+        raise BackendError(str(exc), backend=conn)
 
     def _get_connection(self, bind=None, passwd=None):
         if bind is None:
@@ -401,3 +424,20 @@ class ConnectionManager(object):
                     log.debug('Failure attempting to unbind on purge; '
                               'should be harmless', exc_info=True)
                 self._pool.remove(conn)
+
+    def __str__(self):
+        table = PrettyTable()
+        table.field_names = ['Slot (%d max)' % self.size,
+                             'Connected', 'Active', 'URI',
+                             'Lifetime (%d max)' % self.max_lifetime,
+                             'Bind DN']
+
+        with self._pool_lock:
+            for slot, conn in enumerate(self._pool):
+                table.add_row([
+                    slot + 1,
+                    'connected' if conn.connected else 'not connected',
+                    'active' if conn.active else 'inactive',
+                    conn._uri, conn.get_lifetime(), conn.who])
+
+        return str(table)
diff -pruN 2.3.1-1.1/ldappool/tests/test_ldapconnection.py 2.4.1-0ubuntu3/ldappool/tests/test_ldapconnection.py
--- 2.3.1-1.1/ldappool/tests/test_ldapconnection.py	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool/tests/test_ldapconnection.py	2019-02-25 21:38:50.000000000 +0000
@@ -51,14 +51,51 @@ def _bind_fails(self, who='', cred='', *
     raise ldap.LDAPError('LDAP connection invalid')
 
 
-def _bind_fails2(self, who='', cred='', **kw):
+def _bind_fails_server_down(self, who='', cred='', **kw):
     raise ldap.SERVER_DOWN('LDAP connection invalid')
 
 
+def _bind_fails_server_down_failover(self, who='', cred='', **kw):
+    # Raise a server down error unless the URI is 'ldap://GOOD'
+    if self._uri == 'ldap://GOOD':
+        self.connected = True
+        self.who = who
+        self.cred = cred
+        return 1
+    else:
+        raise ldap.SERVER_DOWN('LDAP connection invalid')
+
+
+def _bind_fails_timeout(self, who='', cred='', **kw):
+    raise ldap.TIMEOUT('LDAP connection timeout')
+
+
+def _bind_fails_timeout_failover(self, who='', cred='', **kw):
+    # Raise a timeout error unless the URI is 'ldap://GOOD'
+    if self._uri == 'ldap://GOOD':
+        self.connected = True
+        self.who = who
+        self.cred = cred
+        return 1
+    else:
+        raise ldap.TIMEOUT('LDAP connection timeout')
+
+
 def _bind_fails_invalid_credentials(self, who='', cred='', **kw):
     raise ldap.INVALID_CREDENTIALS('LDAP connection invalid')
 
 
+def _bind_fails_invalid_credentials_failover(self, who='', cred='', **kw):
+    # Raise invalid credentials erorr unless the URI is 'ldap://GOOD'
+    if self._uri == 'ldap://GOOD':
+        self.connected = True
+        self.who = who
+        self.cred = cred
+        return 1
+    else:
+        raise ldap.INVALID_CREDENTIALS('LDAP connection invalid')
+
+
 def _start_tls_s(self):
     if self.start_tls_already_called_flag:
         raise ldap.LOCAL_ERROR
@@ -146,7 +183,7 @@ class TestLDAPConnection(unittest.TestCa
             unbinds.append(1)
 
         # the binding fails with an LDAPError
-        ldappool.StateConnector.simple_bind_s = _bind_fails2
+        ldappool.StateConnector.simple_bind_s = _bind_fails_server_down
         ldappool.StateConnector.unbind_s = _unbind
         uri = ''
         dn = 'uid=adminuser,ou=logins,dc=mozilla'
@@ -162,6 +199,80 @@ class TestLDAPConnection(unittest.TestCa
         else:
             raise AssertionError()
 
+    def test_simple_bind_fails_failover(self):
+        unbinds = []
+
+        def _unbind(self):
+            unbinds.append(1)
+
+        # the binding to any server other than 'ldap://GOOD' fails
+        # with ldap.SERVER_DOWN
+        ldappool.StateConnector.simple_bind_s = \
+            _bind_fails_server_down_failover
+        ldappool.StateConnector.unbind_s = _unbind
+        uri = 'ldap://BAD,ldap://GOOD'
+        dn = 'uid=adminuser,ou=logins,dc=mozilla'
+        passwd = 'adminuser'
+        cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
+        self.assertEqual(len(cm), 0)
+
+        try:
+            with cm.connection('dn', 'pass') as conn:
+                # Ensure we failed over to the second URI
+                self.assertTrue(conn.active)
+                self.assertEqual(conn._uri, 'ldap://GOOD')
+                pass
+        except Exception:
+            raise AssertionError()
+
+    def test_simple_bind_fails_timeout(self):
+        unbinds = []
+
+        def _unbind(self):
+            unbinds.append(1)
+
+        # the binding fails with ldap.TIMEOUT
+        ldappool.StateConnector.simple_bind_s = _bind_fails_timeout
+        ldappool.StateConnector.unbind_s = _unbind
+        uri = ''
+        dn = 'uid=adminuser,ou=logins,dc=mozilla'
+        passwd = 'adminuser'
+        cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
+        self.assertEqual(len(cm), 0)
+
+        try:
+            with cm.connection('dn', 'pass'):
+                pass
+        except ldap.TIMEOUT:
+            pass
+        else:
+            raise AssertionError()
+
+    def test_simple_bind_fails_timeout_failover(self):
+        unbinds = []
+
+        def _unbind(self):
+            unbinds.append(1)
+
+        # the binding to any server other than 'ldap://GOOD' fails
+        # with ldap.TIMEOUT
+        ldappool.StateConnector.simple_bind_s = _bind_fails_timeout_failover
+        ldappool.StateConnector.unbind_s = _unbind
+        uri = 'ldap://BAD,ldap://GOOD'
+        dn = 'uid=adminuser,ou=logins,dc=mozilla'
+        passwd = 'adminuser'
+        cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
+        self.assertEqual(len(cm), 0)
+
+        try:
+            with cm.connection('dn', 'pass') as conn:
+                # Ensure we failed over to the second URI
+                self.assertTrue(conn.active)
+                self.assertEqual(conn._uri, 'ldap://GOOD')
+                pass
+        except Exception:
+            raise AssertionError()
+
     def test_simple_bind_fails_invalid_credentials(self):
         unbinds = []
 
@@ -181,6 +292,34 @@ class TestLDAPConnection(unittest.TestCa
             with cm.connection('dn', 'pass'):
                 pass
         except ldap.INVALID_CREDENTIALS:
+            pass
+        else:
+            raise AssertionError()
+
+    def test_simple_bind_fails_invalid_credentials_failover(self):
+        unbinds = []
+
+        def _unbind(self):
+            unbinds.append(1)
+
+        # the binding to any server other than 'ldap://GOOD' fails
+        # with ldap.INVALID_CREDENTIALS
+        ldappool.StateConnector.simple_bind_s = \
+            _bind_fails_invalid_credentials_failover
+        ldappool.StateConnector.unbind_s = _unbind
+        uri = 'ldap://BAD,ldap://GOOD'
+        dn = 'uid=adminuser,ou=logins,dc=mozilla'
+        passwd = 'adminuser'
+        cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
+        self.assertEqual(len(cm), 0)
+
+        try:
+            # We expect this to throw an INVALID_CREDENTIALS exception for the
+            # first URI, as this is a hard-failure where we don't want failover
+            # to occur to subsequent URIs.
+            with cm.connection('dn', 'pass'):
+                pass
+        except ldap.INVALID_CREDENTIALS:
             pass
         else:
             raise AssertionError()
diff -pruN 2.3.1-1.1/ldappool.egg-info/dependency_links.txt 2.4.1-0ubuntu3/ldappool.egg-info/dependency_links.txt
--- 2.3.1-1.1/ldappool.egg-info/dependency_links.txt	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/dependency_links.txt	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1 @@
+
diff -pruN 2.3.1-1.1/ldappool.egg-info/not-zip-safe 2.4.1-0ubuntu3/ldappool.egg-info/not-zip-safe
--- 2.3.1-1.1/ldappool.egg-info/not-zip-safe	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/not-zip-safe	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1 @@
+
diff -pruN 2.3.1-1.1/ldappool.egg-info/pbr.json 2.4.1-0ubuntu3/ldappool.egg-info/pbr.json
--- 2.3.1-1.1/ldappool.egg-info/pbr.json	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/pbr.json	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1 @@
+{"git_version": "01d0eb3", "is_release": true}
\ No newline at end of file
diff -pruN 2.3.1-1.1/ldappool.egg-info/PKG-INFO 2.4.1-0ubuntu3/ldappool.egg-info/PKG-INFO
--- 2.3.1-1.1/ldappool.egg-info/PKG-INFO	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/PKG-INFO	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1,102 @@
+Metadata-Version: 1.1
+Name: ldappool
+Version: 2.4.1
+Summary: A simple connector pool for python-ldap.
+Home-page: https://git.openstack.org/cgit/openstack/ldappool
+Author: OpenStack
+Author-email: openstack-discuss@lists.openstack.org
+License: UNKNOWN
+Description: ldappool
+        ========
+        
+        A simple connector pool for python-ldap.
+        
+        The pool keeps LDAP connectors alive and let you reuse them,
+        drastically reducing the time spent to initiate a ldap connection.
+        
+        The pool has useful features like:
+        
+        - transparent reconnection on failures or server restarts
+        - configurable pool size and connectors timeouts
+        - configurable max lifetime for connectors
+        - a context manager to simplify acquiring and releasing a connector
+        
+        **You need python-ldap in order to use this library**
+        
+        Quickstart
+        ::::::::::
+        
+        To work with the pool, you just need to create it, then use it as a
+        context manager with the *connection* method::
+        
+            from ldappool import ConnectionManager
+        
+            cm = ConnectionManager('ldap://localhost')
+        
+            with cm.connection('uid=adminuser,ou=logins,dc=mozilla', 'password') as conn:
+                .. do something with conn ..
+        
+        
+        The connector returned by *connection* is a LDAPObject, that's binded to the
+        server. See https://pypi.org/project/python-ldap/ for details on how to use a connector.
+        
+        It is possible to check the state of the pool by representing the pool as a string::
+        
+            from ldappool import ConnectionManager
+        
+            cm = ConnectionManager('ldap://localhost', size=2)
+        
+            .. do something with cm ..
+        
+            print(cm)
+        
+        This will result in output similar to this table::
+        
+            +--------------+-----------+----------+------------------+--------------------+------------------------------+
+            | Slot (2 max) | Connected |  Active  |       URI        | Lifetime (600 max) |           Bind DN            |
+            +--------------+-----------+----------+------------------+--------------------+------------------------------+
+            |      1       | connected | inactive | ldap://localhost |  0.00496101379395  | uid=tuser,dc=example,dc=test |
+            |      2       | connected | inactive | ldap://localhost |  0.00532603263855  | uid=tuser,dc=example,dc=test |
+            +--------------+-----------+----------+------------------+--------------------+------------------------------+
+        
+        
+        ConnectionManager options
+        :::::::::::::::::::::::::
+        
+        Here are the options you can use when instanciating the pool:
+        
+        - **uri**: ldap server uri **[mandatory]**
+        - **bind**: default bind that will be used to bind a connector.
+          **default: None**
+        - **passwd**: default password that will be used to bind a connector.
+          **default: None**
+        - **size**: pool size. **default: 10**
+        - **retry_max**: number of attempts when a server is down. **default: 3**
+        - **retry_delay**: delay in seconds before a retry. **default: .1**
+        - **use_tls**: activate TLS when connecting. **default: False**
+        - **timeout**: connector timeout. **default: -1**
+        - **use_pool**: activates the pool. If False, will recreate a connector
+          each time. **default: True**
+        
+        The **uri** option will accept a comma or whitespace separated list of LDAP
+        server URIs to allow for failover behavior when connection errors are
+        encountered.  Connections will be attempted against the servers in order,
+        with **retry_max** attempts per URI before failing over to the next server.
+        
+        The **connection** method takes two options:
+        
+        - **bind**: bind used to connect. If None, uses the pool default's.
+          **default: None**
+        - **passwd**: password used to connect. If None, uses the pool default's.
+          **default: None**
+        
+        
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
diff -pruN 2.3.1-1.1/ldappool.egg-info/requires.txt 2.4.1-0ubuntu3/ldappool.egg-info/requires.txt
--- 2.3.1-1.1/ldappool.egg-info/requires.txt	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/requires.txt	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1,2 @@
+python-ldap>=3.0.0
+PrettyTable<0.8,>=0.7.2
diff -pruN 2.3.1-1.1/ldappool.egg-info/SOURCES.txt 2.4.1-0ubuntu3/ldappool.egg-info/SOURCES.txt
--- 2.3.1-1.1/ldappool.egg-info/SOURCES.txt	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/SOURCES.txt	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1,34 @@
+.stestr.conf
+.zuul.yaml
+AUTHORS
+CHANGES.rst
+CONTRIBUTORS
+ChangeLog
+MANIFEST.in
+README.rst
+lower-constraints.txt
+requirements.txt
+setup.cfg
+setup.py
+test-requirements.txt
+tox.ini
+doc/Makefile
+doc/requirements.txt
+doc/source/conf.py
+doc/source/history.rst
+doc/source/index.rst
+ldappool/__init__.py
+ldappool.egg-info/PKG-INFO
+ldappool.egg-info/SOURCES.txt
+ldappool.egg-info/dependency_links.txt
+ldappool.egg-info/not-zip-safe
+ldappool.egg-info/pbr.json
+ldappool.egg-info/requires.txt
+ldappool.egg-info/top_level.txt
+ldappool/tests/__init__.py
+ldappool/tests/test_ldapconnection.py
+ldappool/tests/test_ldappool.py
+releasenotes/source/conf.py
+releasenotes/source/index.rst
+releasenotes/source/unreleased.rst
+releasenotes/source/_static/.placeholder
\ No newline at end of file
diff -pruN 2.3.1-1.1/ldappool.egg-info/top_level.txt 2.4.1-0ubuntu3/ldappool.egg-info/top_level.txt
--- 2.3.1-1.1/ldappool.egg-info/top_level.txt	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/ldappool.egg-info/top_level.txt	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1 @@
+ldappool
diff -pruN 2.3.1-1.1/lower-constraints.txt 2.4.1-0ubuntu3/lower-constraints.txt
--- 2.3.1-1.1/lower-constraints.txt	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/lower-constraints.txt	2019-02-25 21:38:50.000000000 +0000
@@ -17,6 +17,7 @@ openstackdocstheme==1.18.1
 pbr==2.0.0
 pep257==0.7.0
 pep8==1.5.7
+prettytable==0.7.2
 pyflakes==0.8.1
 Pygments==2.2.0
 python-ldap==3.0.0
diff -pruN 2.3.1-1.1/PKG-INFO 2.4.1-0ubuntu3/PKG-INFO
--- 2.3.1-1.1/PKG-INFO	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/PKG-INFO	2019-02-25 21:41:02.000000000 +0000
@@ -0,0 +1,102 @@
+Metadata-Version: 1.1
+Name: ldappool
+Version: 2.4.1
+Summary: A simple connector pool for python-ldap.
+Home-page: https://git.openstack.org/cgit/openstack/ldappool
+Author: OpenStack
+Author-email: openstack-discuss@lists.openstack.org
+License: UNKNOWN
+Description: ldappool
+        ========
+        
+        A simple connector pool for python-ldap.
+        
+        The pool keeps LDAP connectors alive and let you reuse them,
+        drastically reducing the time spent to initiate a ldap connection.
+        
+        The pool has useful features like:
+        
+        - transparent reconnection on failures or server restarts
+        - configurable pool size and connectors timeouts
+        - configurable max lifetime for connectors
+        - a context manager to simplify acquiring and releasing a connector
+        
+        **You need python-ldap in order to use this library**
+        
+        Quickstart
+        ::::::::::
+        
+        To work with the pool, you just need to create it, then use it as a
+        context manager with the *connection* method::
+        
+            from ldappool import ConnectionManager
+        
+            cm = ConnectionManager('ldap://localhost')
+        
+            with cm.connection('uid=adminuser,ou=logins,dc=mozilla', 'password') as conn:
+                .. do something with conn ..
+        
+        
+        The connector returned by *connection* is a LDAPObject, that's binded to the
+        server. See https://pypi.org/project/python-ldap/ for details on how to use a connector.
+        
+        It is possible to check the state of the pool by representing the pool as a string::
+        
+            from ldappool import ConnectionManager
+        
+            cm = ConnectionManager('ldap://localhost', size=2)
+        
+            .. do something with cm ..
+        
+            print(cm)
+        
+        This will result in output similar to this table::
+        
+            +--------------+-----------+----------+------------------+--------------------+------------------------------+
+            | Slot (2 max) | Connected |  Active  |       URI        | Lifetime (600 max) |           Bind DN            |
+            +--------------+-----------+----------+------------------+--------------------+------------------------------+
+            |      1       | connected | inactive | ldap://localhost |  0.00496101379395  | uid=tuser,dc=example,dc=test |
+            |      2       | connected | inactive | ldap://localhost |  0.00532603263855  | uid=tuser,dc=example,dc=test |
+            +--------------+-----------+----------+------------------+--------------------+------------------------------+
+        
+        
+        ConnectionManager options
+        :::::::::::::::::::::::::
+        
+        Here are the options you can use when instanciating the pool:
+        
+        - **uri**: ldap server uri **[mandatory]**
+        - **bind**: default bind that will be used to bind a connector.
+          **default: None**
+        - **passwd**: default password that will be used to bind a connector.
+          **default: None**
+        - **size**: pool size. **default: 10**
+        - **retry_max**: number of attempts when a server is down. **default: 3**
+        - **retry_delay**: delay in seconds before a retry. **default: .1**
+        - **use_tls**: activate TLS when connecting. **default: False**
+        - **timeout**: connector timeout. **default: -1**
+        - **use_pool**: activates the pool. If False, will recreate a connector
+          each time. **default: True**
+        
+        The **uri** option will accept a comma or whitespace separated list of LDAP
+        server URIs to allow for failover behavior when connection errors are
+        encountered.  Connections will be attempted against the servers in order,
+        with **retry_max** attempts per URI before failing over to the next server.
+        
+        The **connection** method takes two options:
+        
+        - **bind**: bind used to connect. If None, uses the pool default's.
+          **default: None**
+        - **passwd**: password used to connect. If None, uses the pool default's.
+          **default: None**
+        
+        
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
diff -pruN 2.3.1-1.1/README.rst 2.4.1-0ubuntu3/README.rst
--- 2.3.1-1.1/README.rst	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/README.rst	2019-02-25 21:38:50.000000000 +0000
@@ -32,6 +32,25 @@ context manager with the *connection* me
 The connector returned by *connection* is a LDAPObject, that's binded to the
 server. See https://pypi.org/project/python-ldap/ for details on how to use a connector.
 
+It is possible to check the state of the pool by representing the pool as a string::
+
+    from ldappool import ConnectionManager
+
+    cm = ConnectionManager('ldap://localhost', size=2)
+
+    .. do something with cm ..
+
+    print(cm)
+
+This will result in output similar to this table::
+
+    +--------------+-----------+----------+------------------+--------------------+------------------------------+
+    | Slot (2 max) | Connected |  Active  |       URI        | Lifetime (600 max) |           Bind DN            |
+    +--------------+-----------+----------+------------------+--------------------+------------------------------+
+    |      1       | connected | inactive | ldap://localhost |  0.00496101379395  | uid=tuser,dc=example,dc=test |
+    |      2       | connected | inactive | ldap://localhost |  0.00532603263855  | uid=tuser,dc=example,dc=test |
+    +--------------+-----------+----------+------------------+--------------------+------------------------------+
+
 
 ConnectionManager options
 :::::::::::::::::::::::::
@@ -51,6 +70,10 @@ Here are the options you can use when in
 - **use_pool**: activates the pool. If False, will recreate a connector
   each time. **default: True**
 
+The **uri** option will accept a comma or whitespace separated list of LDAP
+server URIs to allow for failover behavior when connection errors are
+encountered.  Connections will be attempted against the servers in order,
+with **retry_max** attempts per URI before failing over to the next server.
 
 The **connection** method takes two options:
 
diff -pruN 2.3.1-1.1/releasenotes/source/conf.py 2.4.1-0ubuntu3/releasenotes/source/conf.py
--- 2.3.1-1.1/releasenotes/source/conf.py	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/releasenotes/source/conf.py	2019-02-25 21:38:50.000000000 +0000
@@ -0,0 +1,283 @@
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ldappool Release Notes documentation build configuration file, created
+# by sphinx-quickstart on Tue Nov  3 17:40:50 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'openstackdocstheme',
+    'reno.sphinxext',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'ldappool Release Notes'
+copyright = u'2018, ldappool Developers'
+
+# Release notes are version independent.
+
+# The short X.Y version.
+
+# The full version, including alpha/beta/rc tags.
+release = ''
+# The short X.Y version.
+version = ''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'openstackdocs'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ldappoolReleaseNotesdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+    # The paper size ('letterpaper' or 'a4paper').
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    ('index', 'ldappoolReleaseNotes.tex',
+     u'ldappool Release Notes Documentation',
+     u'ldappool Developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'ldappoolreleasenotes',
+     u'ldappool Release Notes Documentation',
+     [u'ldappool Developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    ('index', 'ldappoolReleaseNotes',
+     u'ldappool Release Notes Documentation',
+     u'ldappool Developers', 'ldappoolReleaseNotes',
+     'Authentication plugins for the OpenStack Identity service.',
+     'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# texinfo_no_detailmenu = False
+
+# -- Options for Internationalization output ------------------------------
+locale_dirs = ['locale/']
+
+# -- Options for openstackdocstheme -------------------------------------------
+repository_name = 'openstack/ldappool'
+bug_project = 'ldappool'
+bug_tag = 'doc'
diff -pruN 2.3.1-1.1/releasenotes/source/index.rst 2.4.1-0ubuntu3/releasenotes/source/index.rst
--- 2.3.1-1.1/releasenotes/source/index.rst	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/releasenotes/source/index.rst	2019-02-25 21:38:50.000000000 +0000
@@ -0,0 +1,8 @@
+======================
+ldappool Release Notes
+======================
+
+.. toctree::
+   :maxdepth: 1
+
+   unreleased
diff -pruN 2.3.1-1.1/releasenotes/source/unreleased.rst 2.4.1-0ubuntu3/releasenotes/source/unreleased.rst
--- 2.3.1-1.1/releasenotes/source/unreleased.rst	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.1-0ubuntu3/releasenotes/source/unreleased.rst	2019-02-25 21:38:50.000000000 +0000
@@ -0,0 +1,5 @@
+==============================
+ Current Series Release Notes
+==============================
+
+.. release-notes::
diff -pruN 2.3.1-1.1/requirements.txt 2.4.1-0ubuntu3/requirements.txt
--- 2.3.1-1.1/requirements.txt	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/requirements.txt	2019-02-25 21:38:50.000000000 +0000
@@ -2,3 +2,4 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 python-ldap>=3.0.0 # PSF
+PrettyTable<0.8,>=0.7.2
diff -pruN 2.3.1-1.1/setup.cfg 2.4.1-0ubuntu3/setup.cfg
--- 2.3.1-1.1/setup.cfg	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/setup.cfg	2019-02-25 21:41:02.000000000 +0000
@@ -1,24 +1,24 @@
 [metadata]
 name = ldappool
 summary = A simple connector pool for python-ldap.
-description-file =
-    README.rst
+description-file = 
+	README.rst
 author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
 home-page = https://git.openstack.org/cgit/openstack/ldappool
-classifier =
-    Intended Audience :: Developers
-    License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
-    Operating System :: POSIX :: Linux
-    Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
-    Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.5
+classifier = 
+	Intended Audience :: Developers
+	License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
+	Operating System :: POSIX :: Linux
+	Programming Language :: Python
+	Programming Language :: Python :: 2
+	Programming Language :: Python :: 2.7
+	Programming Language :: Python :: 3
+	Programming Language :: Python :: 3.5
 
 [files]
-packages =
-    ldappool
+packages = 
+	ldappool
 
 [build_sphinx]
 source-dir = doc/source
@@ -31,9 +31,14 @@ upload-dir = doc/build/html
 
 [pbr]
 autodoc_tree_index_modules = True
-autodoc_tree_excludes =
-  setup.py
-  ldappool/tests/
+autodoc_tree_excludes = 
+	setup.py
+	ldappool/tests/
 
 [wheel]
 universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff -pruN 2.3.1-1.1/tox.ini 2.4.1-0ubuntu3/tox.ini
--- 2.3.1-1.1/tox.ini	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/tox.ini	2019-02-25 21:38:54.000000000 +0000
@@ -1,7 +1,7 @@
 [tox]
 minversion = 2.0
 skipsdist = True
-envlist = py27,py35,pep8,cover,docs
+envlist = py27,py35,py36,py37,pep8,cover,docs,releasenotes
 
 [testenv]
 usedevelop = True
@@ -32,7 +32,7 @@ basepython = python3
 setenv =
     PYTHON=coverage run --source ldappool --parallel-mode
 commands =
-    stestr run '{posargs}'
+    stestr run {posargs}
     coverage combine
     coverage html -d cover
     coverage xml -o cover/coverage.xml
@@ -56,6 +56,13 @@ basepython = python3
 commands=
     python setup.py build_sphinx
 
+[testenv:releasenotes]
+basepython = python3
+deps =
+    -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+    -r{toxinidir}/doc/requirements.txt
+commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+
 [testenv:lower-constraints]
 basepython = python3
 deps =
diff -pruN 2.3.1-1.1/.zuul.yaml 2.4.1-0ubuntu3/.zuul.yaml
--- 2.3.1-1.1/.zuul.yaml	2018-10-19 13:53:41.000000000 +0000
+++ 2.4.1-0ubuntu3/.zuul.yaml	2019-02-25 21:38:50.000000000 +0000
@@ -1,12 +1,9 @@
 - project:
     templates:
       - check-requirements
+      - openstack-lower-constraints-jobs
       - openstack-python-jobs
       - openstack-python35-jobs
       - openstack-python36-jobs
-    check:
-      jobs:
-        - openstack-tox-lower-constraints
-    gate:
-      jobs:
-        - openstack-tox-lower-constraints
+      - openstack-python37-jobs
+      - release-notes-jobs-python3
