diff --git a/patroni/ha.py b/patroni/ha.py index b50884f1..e72768f8 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -634,11 +634,20 @@ class Ha(object): promoting standbys that were guaranteed to be replicating synchronously. """ if self.is_synchronous_mode(): - current = CaseInsensitiveSet(self.cluster.sync.members) + sync = self.cluster.sync + if sync.is_empty: + # corner case: we need to explicitly enable synchronous mode by updating the + # ``/sync`` key with the current leader name and empty members. In opposite case + # it will never be automatically enabled if there are not eligible candidates. + sync = self.dcs.write_sync_state(self.state_handler.name, None, version=sync.version) + if not sync: + return logger.warning("Updating sync state failed") + logger.info("Enabled synchronous replication") + + current = CaseInsensitiveSet(sync.members) picked, allow_promote = self.state_handler.sync_handler.current_state(self.cluster) if picked != current: - sync = self.cluster.sync # update synchronous standby list in dcs temporarily to point to common nodes in current and picked sync_common = current & allow_promote if sync_common != current: diff --git a/tests/test_ha.py b/tests/test_ha.py index d3a75ed3..29c4ca11 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -1298,6 +1298,21 @@ class TestHa(PostgresInit): mock_restart.assert_called_once() self.ha.dcs.get_cluster.assert_not_called() + @patch.object(Cluster, 'is_unlocked', Mock(return_value=False)) + def test_enable_synchronous_mode(self): + self.ha.is_synchronous_mode = true + self.ha.has_lock = true + self.p.name = 'leader' + self.p.sync_handler.current_state = Mock(return_value=(CaseInsensitiveSet(), CaseInsensitiveSet())) + self.ha.dcs.write_sync_state = Mock(return_value=SyncState.empty()) + with patch('patroni.ha.logger.info') as mock_logger: + self.ha.run_cycle() + self.assertEqual(mock_logger.call_args_list[0][0][0], 'Enabled synchronous replication') + self.ha.dcs.write_sync_state = Mock(return_value=None) + with patch('patroni.ha.logger.warning') as mock_logger: + self.ha.run_cycle() + self.assertEqual(mock_logger.call_args[0][0], 'Updating sync state failed') + def test_effective_tags(self): self.ha._disable_sync = True self.assertEqual(self.ha.get_effective_tags(), {'foo': 'bar', 'nosync': True})