From a0b32379e5f51e675267647a7d6eabce595e3b24 Mon Sep 17 00:00:00 2001 From: Alexander Kukushkin Date: Mon, 15 Aug 2022 15:11:59 +0200 Subject: [PATCH] Handle the case when data dir storage disappeared (#2381) The `os.listdir()` is raising the OSError exception, breaking the heart-beat loop. Close https://github.com/zalando/patroni/issues/2380 --- patroni/ha.py | 14 ++++++++++++-- tests/test_ha.py | 12 +++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/patroni/ha.py b/patroni/ha.py index 54f2c0ab..45cf538d 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -1407,7 +1407,14 @@ class Ha(object): return 'started as a secondary' # is data directory empty? - if self.state_handler.data_directory_empty(): + try: + data_directory_is_empty = self.state_handler.data_directory_empty() + data_directory_is_accessible = True + except OSError as e: + data_directory_is_accessible = False + data_directory_error = e + + if not data_directory_is_accessible or data_directory_is_empty: self.state_handler.set_role('uninitialized') self.state_handler.stop('immediate', stop_timeout=self.patroni.config['retry_timeout']) # In case datadir went away while we were master. @@ -1416,8 +1423,11 @@ class Ha(object): # is this instance the leader? if self.has_lock(): self.release_leader_key_voluntarily() - return 'released leader key voluntarily as data dir empty and currently leader' + return 'released leader key voluntarily as data dir {0} and currently leader'.format( + 'empty' if data_directory_is_accessible else 'not accessible') + if not data_directory_is_accessible: + return 'data directory is not accessible: {0}'.format(data_directory_error) if self.is_paused(): return 'running with empty data directory' return self.bootstrap() # new node diff --git a/tests/test_ha.py b/tests/test_ha.py index 5ed4503d..1d6e2904 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -1133,17 +1133,19 @@ class TestHa(PostgresInit): self.ha.shutdown() @patch('time.sleep', Mock()) - def test_leader_with_empty_directory(self): + def test_leader_with_not_accessible_data_directory(self): self.ha.cluster = get_cluster_initialized_with_leader() self.ha.has_lock = true - self.p.data_directory_empty = true - self.assertEqual(self.ha.run_cycle(), 'released leader key voluntarily as data dir empty and currently leader') + self.p.data_directory_empty = Mock(side_effect=OSError(5, "Input/output error: '{}'".format(self.p.data_dir))) + self.assertEqual(self.ha.run_cycle(), + 'released leader key voluntarily as data dir not accessible and currently leader') self.assertEqual(self.p.role, 'uninitialized') # as has_lock is mocked out, we need to fake the leader key release self.ha.has_lock = false - # will not say bootstrap from leader as replica can't self elect - self.assertEqual(self.ha.run_cycle(), "trying to bootstrap from replica 'other'") + # will not say bootstrap because data directory is not accessible + self.assertEqual(self.ha.run_cycle(), + "data directory is not accessible: [Errno 5] Input/output error: '{}'".format(self.p.data_dir)) @patch('patroni.postgresql.mtime', Mock(return_value=1588316884)) @patch.object(builtins, 'open', mock_open(read_data=('1\t0/40159C0\tno recovery target specified\n\n'