diff --git a/features/patroni_api.feature b/features/patroni_api.feature index 624d3271..ff06f3c9 100644 --- a/features/patroni_api.feature +++ b/features/patroni_api.feature @@ -68,6 +68,7 @@ Scenario: check API requests for the primary-replica pair in the pause mode When I kill postmaster on postgres1 And I issue a GET request to http://127.0.0.1:8009/replica Then I receive a response code 503 + And "members/postgres1" key in DCS has state=stopped after 10 seconds When I run patronictl.py restart batman postgres1 --force Then I receive a response returncode 0 Then replication works from postgres0 to postgres1 after 20 seconds @@ -76,7 +77,7 @@ Scenario: check API requests for the primary-replica pair in the pause mode Then I receive a response code 200 And I receive a response state running And I receive a response role replica - When I run patronictl.py reinit batman postgres1 --force + When I run patronictl.py reinit batman postgres1 --force --wait Then I receive a response returncode 0 And I receive a response output "Success: reinitialize for member postgres1" And postgres1 role is the secondary after 30 seconds diff --git a/patroni/dcs/zookeeper.py b/patroni/dcs/zookeeper.py index def00521..b46617a4 100644 --- a/patroni/dcs/zookeeper.py +++ b/patroni/dcs/zookeeper.py @@ -393,21 +393,28 @@ class ZooKeeper(AbstractDCS): cluster = self.cluster member = cluster and cluster.get_member(self._name, fallback_to_leader=False) member_data = self.__last_member_data or member and member.data - # We want to notify leader if some important fields in the member key changed by removing ZNode - if member and (self._client.client_id is not None and member.session != self._client.client_id[0] - or not (member_data and deep_compare(member_data.get('tags', {}), data.get('tags', {})) - and (member_data.get('state') == data.get('state') - or 'running' not in (member_data.get('state'), data.get('state'))) - and member_data.get('version') == data.get('version') - and member_data.get('checkpoint_after_promote') - == data.get('checkpoint_after_promote'))): - try: - self._client.delete_async(self.member_path).get(timeout=1) - except NoNodeError: - pass - except Exception: - return False - member = None + if member and member_data: + is_leader = data.get('role') in ('master', 'primary', 'standby_leader') + checkpoint_after_promote_changed = member_data.get('checkpoint_after_promote') \ + != data.get('checkpoint_after_promote') + state_running_changed = member_data.get('state') != data.get('state') \ + and 'running' in (member_data.get('state'), data.get('state')) + tags_changed = not deep_compare(member_data.get('tags', {}), data.get('tags', {})) + + # We want delete the member ZNode if: + # - our session doesn't match with session id on our member key; or + # - we want to notify leader if some important fields in the member key changed; or + # - if we are the leader and want to notify replicas about checkpoint_after_promote; + if self._client.client_id is not None and member.session != self._client.client_id[0] \ + or is_leader and checkpoint_after_promote_changed \ + or not is_leader and (state_running_changed or tags_changed): + try: + self._client.delete_async(self.member_path).get(timeout=1) + except NoNodeError: + pass + except Exception: + return False + member = None encoded_data = json.dumps(data, separators=(',', ':')).encode('utf-8') if member and member_data: diff --git a/tests/test_zookeeper.py b/tests/test_zookeeper.py index c72fefe9..28227bc5 100644 --- a/tests/test_zookeeper.py +++ b/tests/test_zookeeper.py @@ -276,6 +276,7 @@ class TestZooKeeper(unittest.TestCase): self.assertTrue(self.zk.delete_cluster()) def test_watch(self): + self.zk.event.wait = Mock() self.zk.watch(None, 0) self.zk.event.is_set = Mock(return_value=True) self.zk._fetch_status = False