diff --git a/docs/releases.rst b/docs/releases.rst index 98eaa4aa..fce7805c 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -3,6 +3,72 @@ Release notes ============= +Version 2.1.3 +------------- + +**New features** + +- Added support for encrypted TLS keys for ``patronictl`` (Alexander Kukushkin) + + It could be configured via ``ctl.keyfile_password`` or the ``PATRONI_CTL_KEYFILE_PASSWORD`` environment variable. + +- Added more metrics to the /metrics endpoint (Alexandre Pereira) + + Specifically, ``patroni_pending_restart`` and ``patroni_is_paused``. + +- Make it possible to specify multiple hosts in the standby cluster configuration (Michael Banck) + + If the standby cluster is replicating from the Patroni cluster it might be nice to rely on client-side failover which is available in ``libpq`` since PostgreSQL v10. That is, the ``primary_conninfo`` on the standby leader and ``pg_rewind`` setting ``target_session_attrs=read-write`` in the connection string. The ``pgpass`` file will be generated with multiple lines (one line per host), and instead of calling ``CHECKPOINT`` on the primary cluster nodes the standby cluster will wait for ``pg_control`` to be updated. + +**Stability improvements** + +- Compatibility with legacy ``psycopg2`` (Alexander) + + For example, the ``psycopg2`` installed from Ubuntu 18.04 packages doesn't have the ``UndefinedFile`` exception yet. + +- Restart ``etcd3`` watcher if all Etcd nodes don't respond (Alexander) + + If the watcher is alive the ``get_cluster()`` method continues returning stale information even if all Etcd nodes are failing. + +- Don't remove the leader lock in the standby cluster while paused (Alexander) + + Previously the lock was maintained only by the node that was running as a primary and not a standby leader. + +**Bugfixes** + +- Fixed bug in the standby-leader bootstrap (Alexander) + + Patroni was considering bootstrap as failed if Postgres didn't start accepting connections after 60 seconds. The bug was introduced in the 2.1.2 release. + +- Fixed bug with failover to a cascading standby (Alexander) + + When figuring out which slots should be created on cascading standby we forgot to take into account that the leader might be absent. + +- Fixed small issues in Postgres config validator (Alexander) + + Integer parameters introduced in PostgreSQL v14 were failing to validate because min and max values were quoted in the validator.py + +- Use replication credentials when checking leader status (Alexander) + + It could be that the ``remove_data_directory_on_diverged_timelines`` is set, but there is no ``rewind_credentials`` defined and superuser access between nodes is not allowed. + +- Fixed "port in use" error on REST API certificate replacement (Ants Aasma) + + When switching certificates there was a race condition with a concurrent API request. If there is one active during the replacement period then the replacement will error out with a port in use error and Patroni gets stuck in a state without an active API server. + +- Fixed a bug in cluster bootstrap if passwords contain ``%`` characters (Bastien Wirtz) + + The bootstrap method executes the ``DO`` block, with all parameters properly quoted, but the ``cursor.execute()`` method didn't like an empty list with parameters passed. + +- Fixed the "AttributeError: no attribute 'leader'" exception (Hrvoje Milković) + + It could happen if the synchronous mode is enabled and the DCS content was wiped out. + +- Fix bug in divergence timeline check (Alexander) + + Patroni was falsely assuming that timelines have diverged. For pg_rewind it didn't create any problem, but if pg_rewind is not allowed and the ``remove_data_directory_on_diverged_timelines`` is set, it resulted in reinitializing the former leader. + + Version 2.1.2 ------------- diff --git a/patroni/config.py b/patroni/config.py index 2161a7c6..8633d447 100644 --- a/patroni/config.py +++ b/patroni/config.py @@ -270,7 +270,7 @@ class Config(object): _set_section_values('restapi', ['listen', 'connect_address', 'certfile', 'keyfile', 'keyfile_password', 'cafile', 'ciphers', 'verify_client', 'http_extra_headers', 'https_extra_headers', 'allowlist', 'allowlist_include_members']) - _set_section_values('ctl', ['insecure', 'cacert', 'certfile', 'keyfile']) + _set_section_values('ctl', ['insecure', 'cacert', 'certfile', 'keyfile', 'keyfile_password']) _set_section_values('postgresql', ['listen', 'connect_address', 'config_dir', 'data_dir', 'pgpass', 'bin_dir']) _set_section_values('log', ['level', 'traceback_level', 'format', 'dateformat', 'max_queue_size', 'dir', 'file_size', 'file_num', 'loggers']) diff --git a/patroni/version.py b/patroni/version.py index f8115612..2d31b1c3 100644 --- a/patroni/version.py +++ b/patroni/version.py @@ -1 +1 @@ -__version__ = '2.1.2' +__version__ = '2.1.3' diff --git a/tests/__init__.py b/tests/__init__.py index b4d6002d..2e867b57 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -190,7 +190,8 @@ class PostgresInit(unittest.TestCase): 'krbsrvname': 'postgres', 'pgpass': os.path.join(data_dir, 'pgpass0'), 'listen': '127.0.0.2, 127.0.0.3:5432', 'connect_address': '127.0.0.2:5432', 'authentication': {'superuser': {'username': 'foo', 'password': 'test'}, - 'replication': {'username': '', 'password': 'rep-pass'}}, + 'replication': {'username': '', 'password': 'rep-pass'}, + 'rewind': {'username': 'rewind', 'password': 'test'}}, 'remove_data_directory_on_rewind_failure': True, 'use_pg_rewind': True, 'pg_ctl_timeout': 'bla', 'parameters': self._PARAMETERS, diff --git a/tests/test_rewind.py b/tests/test_rewind.py index 54fdae91..33b8149e 100644 --- a/tests/test_rewind.py +++ b/tests/test_rewind.py @@ -102,6 +102,11 @@ class TestRewind(BaseTestPostgresql): @patch.object(Postgresql, 'start', Mock()) def test_execute(self, mock_checkpoint): self.r.execute(self.leader) + with patch.object(Postgresql, 'major_version', PropertyMock(return_value=130000)): + self.r.execute(self.leader) + with patch.object(MockCursor, 'fetchone', Mock(side_effect=Exception)): + self.r.execute(self.leader) + with patch.object(Rewind, 'pg_rewind', Mock(return_value=False)): mock_checkpoint.side_effect = ['1', '', '', ''] self.r.execute(self.leader)