Run on_role_change cb after a failed primary recovery (#3198)

Additionally run on_role_change callback in post_recover() for a primary
that failed to start after a crash to increase chances the callback is executed,
even if the further start as a replica fails

---------

Co-authored-by: Alexander Kukushkin <[email protected]>
This commit is contained in:
Polina Bungina
2024-10-31 09:22:51 +01:00
committed by GitHub
co-authored by Alexander Kukushkin
parent e8a8bfe42f
commit 7dcb9b9840
4 changed files with 24 additions and 1 deletions
+9
View File
@@ -24,3 +24,12 @@ Feature: recovery
When I kill postmaster on postgres-0 When I kill postmaster on postgres-0
Then postgres-1 is a leader after 10 seconds Then postgres-1 is a leader after 10 seconds
And postgres-1 role is the primary after 10 seconds And postgres-1 role is the primary after 10 seconds
Scenario: check crashed primary demotes after failed attempt to start
Given I issue a PATCH request to http://127.0.0.1:8009/config with {"master_start_timeout": null}
Then I receive a response code 200
And postgres-0 role is the replica after 10 seconds
When I ensure postgres-1 fails to start after a failure
When I kill postmaster on postgres-1
Then postgres-0 is a leader after 10 seconds
And there is a postgres-1_cb.log with "on_role_change demoted batman" in postgres-1 data directory
+9
View File
@@ -0,0 +1,9 @@
import os
from behave import step
@step('I ensure {name:name} fails to start after a failure')
def spoil_autoconf(context, name):
with open(os.path.join(context.pctl._processes[name]._data_dir, 'postgresql.auto.conf'), 'w') as f:
f.write('foo=bar')
+1
View File
@@ -1964,6 +1964,7 @@ class Ha(object):
if self.has_lock(): if self.has_lock():
if self.state_handler.role in ('primary', 'standby_leader'): if self.state_handler.role in ('primary', 'standby_leader'):
self.state_handler.set_role('demoted') self.state_handler.set_role('demoted')
self.state_handler.call_nowait(CallbackAction.ON_ROLE_CHANGE)
self._delete_leader() self._delete_leader()
return 'removed leader key after trying and failing to start postgres' return 'removed leader key after trying and failing to start postgres'
return 'failed to start postgres' return 'failed to start postgres'
+5 -1
View File
@@ -15,6 +15,7 @@ from patroni.exceptions import DCSError, PatroniFatalException, PostgresConnecti
from patroni.ha import _MemberStatus, Ha from patroni.ha import _MemberStatus, Ha
from patroni.postgresql import Postgresql from patroni.postgresql import Postgresql
from patroni.postgresql.bootstrap import Bootstrap from patroni.postgresql.bootstrap import Bootstrap
from patroni.postgresql.callback_executor import CallbackAction
from patroni.postgresql.cancellable import CancellableSubprocess from patroni.postgresql.cancellable import CancellableSubprocess
from patroni.postgresql.config import ConfigHandler from patroni.postgresql.config import ConfigHandler
from patroni.postgresql.postmaster import PostmasterProcess from patroni.postgresql.postmaster import PostmasterProcess
@@ -1105,11 +1106,14 @@ class TestHa(PostgresInit):
@patch.object(Rewind, 'check_leader_is_not_in_recovery', true) @patch.object(Rewind, 'check_leader_is_not_in_recovery', true)
@patch('os.listdir', Mock(return_value=[])) @patch('os.listdir', Mock(return_value=[]))
@patch('patroni.postgresql.rewind.fsync_dir', Mock()) @patch('patroni.postgresql.rewind.fsync_dir', Mock())
def test_post_recover(self): @patch.object(Postgresql, 'call_nowait')
def test_post_recover(self, mock_call_nowait):
self.p.is_running = false self.p.is_running = false
self.ha.has_lock = true self.ha.has_lock = true
self.p.set_role('primary') self.p.set_role('primary')
self.assertEqual(self.ha.post_recover(), 'removed leader key after trying and failing to start postgres') self.assertEqual(self.ha.post_recover(), 'removed leader key after trying and failing to start postgres')
self.assertEqual(self.p.role, 'demoted')
mock_call_nowait.assert_called_once_with(CallbackAction.ON_ROLE_CHANGE)
self.ha.has_lock = false self.ha.has_lock = false
self.assertEqual(self.ha.post_recover(), 'failed to start postgres') self.assertEqual(self.ha.post_recover(), 'failed to start postgres')
leader = Leader(0, 0, Member(0, 'l', 2, {"version": "1.6", "conn_url": "postgres://a", "role": "primary"})) leader = Leader(0, 0, Member(0, 'l', 2, {"version": "1.6", "conn_url": "postgres://a", "role": "primary"}))