Skip to content

Commit 0d43e43

Browse files
[DPE-10663] fix(patroni): handle unreachable cluster in get_member_status (#179)
* fix(patroni): handle unreachable cluster in get_member_status get_member_status() called cluster_status() with no try/except, unlike its siblings get_member_ip / get_primary / cluster_members which all catch the RetryError cluster_status() raises when no Patroni endpoint responds. The exception then propagated uncaught into callers. During a PITR restore, Patroni is intentionally stopped while the restore runs; the ops framework re-emits the deferred database-peers-relation-changed event inside the restore action hook, which calls _was_restore_successful() -> get_member_status() -> cluster_status(), raising RetryError. That crashed the restore action hook with a non-zero exit, discarding the staged restore-status result and exhausting the integration test's 10x retry loop (test_backups_pitr_{aws,gcp}). Catch the RetryError and return "" — the value the method's docstring already promises when the status cannot be retrieved — so callers see a clean early-exit instead of a hook crash. This fixes every caller of get_member_status, not just the restore path. Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * chore: bump version to 16.3.4 The get_member_status RetryError fix is a behavioral change to the library, so it needs its own release. 16.3.3 is already released, so bump to 16.3.4 for the fix to ship under a new version. Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> --------- Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
1 parent 4b70835 commit 0d43e43

4 files changed

Lines changed: 47 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[project]
55
name = "postgresql-charms-single-kernel"
66
description = "Shared and reusable code for PostgreSQL-related charms"
7-
version = "16.3.3"
7+
version = "16.3.4"
88
readme = "README.md"
99
license = {file = "LICENSE"}
1010
authors = [

single_kernel_postgresql/managers/patroni.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,11 @@ def get_member_status(self, member_name: str) -> str:
251251
couldn't be retrieved yet.
252252
"""
253253
# Request info from cluster endpoint (which returns all members of the cluster).
254-
cluster_status = self.cluster_status()
254+
try:
255+
cluster_status = self.cluster_status()
256+
except RetryError:
257+
logger.debug("Unable to get member status. Cluster status unreachable")
258+
return ""
255259
if cluster_status:
256260
for member in cluster_status:
257261
if member["name"] == member_name:

tests/unit/test_patroni_manager.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,46 @@ def test_get_member_ip(patroni):
105105
assert patroni.get_member_ip("postgresql-0") == "1.1.1.1"
106106

107107

108+
def test_get_member_status(patroni):
109+
with (
110+
patch(
111+
"single_kernel_postgresql.managers.patroni.parallel_patroni_get_request",
112+
return_value=None,
113+
) as _parallel_patroni_get_request,
114+
patch(
115+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.patroni_password",
116+
new_callable=PropertyMock,
117+
return_value="test-pass",
118+
),
119+
patch(
120+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.endpoints",
121+
new_callable=PropertyMock,
122+
return_value=["endpoint1", "endpoint2", "endpoint3"],
123+
),
124+
patch(
125+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.members_ips",
126+
new_callable=PropertyMock,
127+
return_value=["ip1", "ip2", "ip3"],
128+
),
129+
patch(
130+
"single_kernel_postgresql.core.state.CharmState.endpoint",
131+
new_callable=PropertyMock,
132+
return_value="endpoint",
133+
),
134+
):
135+
# An unreachable cluster (cluster_status raises RetryError) yields an empty
136+
# status instead of propagating the exception.
137+
assert patroni.get_member_status("postgresql-0") == ""
138+
139+
_parallel_patroni_get_request.return_value = {
140+
"members": [
141+
{"name": "postgresql-1", "state": "running"},
142+
{"name": "postgresql-0", "state": "streaming"},
143+
]
144+
}
145+
assert patroni.get_member_status("postgresql-0") == "streaming"
146+
147+
108148
def test_get_patroni_health(patroni):
109149
with (
110150
patch(

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)