Skip to content

Commit ca8f525

Browse files
committed
Merge branch 'main' into password-rotation
2 parents 4cf20fd + 3e1ece8 commit ca8f525

File tree

5 files changed

+4
-141
lines changed

5 files changed

+4
-141
lines changed

src/charm.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -453,36 +453,13 @@ def _on_get_primary(self, event: ActionEvent) -> None:
453453
logger.error(f"failed to get primary with error {e}")
454454

455455
def _on_update_status(self, _) -> None:
456-
# Until https://github.com/canonical/pebble/issues/6 is fixed,
457-
# we need to use the logic below to restart the leader
458-
# and a stuck replica after a failover/switchover.
459-
try:
460-
state = self._patroni.get_postgresql_state()
461-
if state == "restarting":
462-
# Restart the stuck replica.
463-
self._restart_postgresql_service()
464-
elif state == "starting" or state == "stopping":
465-
# Force a primary change when the current primary is stuck.
466-
self.force_primary_change()
467-
except RetryError as e:
468-
logger.error("failed to check PostgreSQL state")
469-
self.unit.status = BlockedStatus(f"failed to check PostgreSQL state with error {e}")
470-
return
471-
472456
# Display an active status message if the current unit is the primary.
473457
try:
474458
if self._patroni.get_primary(unit_name_pattern=True) == self.unit.name:
475459
self.unit.status = ActiveStatus("Primary")
476460
except (RetryError, ConnectionError) as e:
477461
logger.error(f"failed to get primary with error {e}")
478462

479-
def _restart_postgresql_service(self) -> None:
480-
"""Restart PostgreSQL and Patroni."""
481-
self.unit.status = MaintenanceStatus(f"restarting {self._postgresql_service} service")
482-
container = self.unit.get_container("postgresql")
483-
container.restart(self._postgresql_service)
484-
self.unit.status = ActiveStatus()
485-
486463
@property
487464
def _patroni(self):
488465
"""Returns an instance of the Patroni object."""
@@ -594,21 +571,6 @@ def _unit_name_to_pod_name(self, unit_name: str) -> str:
594571
"""
595572
return unit_name.replace("/", "-")
596573

597-
def force_primary_change(self) -> None:
598-
"""Force primary changes immediately.
599-
600-
This function is needed to handle cases related to
601-
https://github.com/canonical/pebble/issues/6 .
602-
When a fail-over is started, Patroni gets stuck on the primary
603-
change because of some zombie process that are not cleaned by Pebble.
604-
"""
605-
# Change needed to force an immediate failover.
606-
self._patroni.change_master_start_timeout(0)
607-
# Restart the stuck previous leader (will trigger an immediate failover).
608-
self._restart_postgresql_service()
609-
# Revert configuration to default.
610-
self._patroni.change_master_start_timeout(None)
611-
612574

613575
if __name__ == "__main__":
614576
main(PostgresqlOperatorCharm, use_juju_for_storage=True)

src/patroni.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,6 @@ def __init__(
4040
self._storage_path = storage_path
4141
self._planned_units = planned_units
4242

43-
def change_master_start_timeout(self, seconds: int) -> None:
44-
"""Change master start timeout configuration.
45-
46-
Args:
47-
seconds: number of seconds to set in master_start_timeout configuration.
48-
"""
49-
requests.patch(
50-
f"http://{self._endpoint}:8008/config",
51-
json={"master_start_timeout": seconds},
52-
)
53-
5443
def get_primary(self, unit_name_pattern=False) -> str:
5544
"""Get primary instance.
5645
@@ -72,16 +61,6 @@ def get_primary(self, unit_name_pattern=False) -> str:
7261
break
7362
return primary
7463

75-
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
76-
def get_postgresql_state(self) -> str:
77-
"""Get PostgreSQL state.
78-
79-
Returns:
80-
running, restarting or stopping.
81-
"""
82-
r = requests.get(f"http://{self._endpoint}:8008/health")
83-
return r.json()["state"]
84-
8564
@property
8665
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
8766
def cluster_members(self) -> set:

tests/integration/test_charm.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ async def test_build_and_deploy(ops_test: OpsTest):
4545
await ops_test.model.deploy(
4646
charm, resources=resources, application_name=APP_NAME, num_units=len(UNIT_IDS), trust=True
4747
)
48-
# Change update status hook interval to be triggered more often
49-
# (it's required to handle https://github.com/canonical/postgresql-k8s-operator/issues/3).
5048
await ops_test.model.set_config({"update-status-hook-interval": "5s"})
5149

5250
await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000)

tests/unit/test_charm.py

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
# See LICENSE file for licensing details.
33

44
import unittest
5-
from unittest.mock import Mock, call, patch
5+
from unittest.mock import Mock, patch
66

77
from lightkube import codecs
88
from lightkube.resources.core_v1 import Pod
9-
from ops.model import ActiveStatus, BlockedStatus
9+
from ops.model import ActiveStatus
1010
from ops.testing import Harness
1111
from tenacity import RetryError
1212

@@ -130,41 +130,19 @@ def test_fail_to_get_primary(self, _get_primary):
130130
_get_primary.assert_called_once()
131131
mock_event.set_results.assert_not_called()
132132

133-
@patch("ops.model.Container.restart")
134-
def test_restart_postgresql_service(self, _restart):
135-
self.charm._restart_postgresql_service()
136-
_restart.assert_called_once_with(self._postgresql_service)
137-
self.assertEqual(
138-
self.harness.model.unit.status,
139-
ActiveStatus(),
140-
)
141-
142133
@patch_network_get(private_address="1.1.1.1")
143134
@patch("charm.Patroni.get_primary")
144-
@patch("charm.PostgresqlOperatorCharm._restart_postgresql_service")
145-
@patch("charm.Patroni.change_master_start_timeout")
146-
@patch("charm.Patroni.get_postgresql_state")
147135
def test_on_update_status(
148136
self,
149-
_get_postgresql_state,
150-
_change_master_start_timeout,
151-
_restart_postgresql_service,
152137
_get_primary,
153138
):
154-
_get_postgresql_state.side_effect = ["running", "running", "restarting", "stopping"]
155139
_get_primary.side_effect = [
156140
"postgresql-k8s/1",
157141
self.charm.unit.name,
158-
self.charm.unit.name,
159-
self.charm.unit.name,
160142
]
161143

162-
# Test running status.
163-
self.charm.on.update_status.emit()
164-
_change_master_start_timeout.assert_not_called()
165-
_restart_postgresql_service.assert_not_called()
166-
167144
# Check primary message not being set (current unit is not the primary).
145+
self.charm.on.update_status.emit()
168146
_get_primary.assert_called_once()
169147
self.assertNotEqual(
170148
self.harness.model.unit.status,
@@ -178,41 +156,9 @@ def test_on_update_status(
178156
ActiveStatus("Primary"),
179157
)
180158

181-
# Test restarting status.
182-
self.charm.on.update_status.emit()
183-
_change_master_start_timeout.assert_not_called()
184-
_restart_postgresql_service.assert_called_once()
185-
186-
# Create a manager mock to check the correct order of the calls.
187-
manager = Mock()
188-
manager.attach_mock(_change_master_start_timeout, "c")
189-
manager.attach_mock(_restart_postgresql_service, "r")
190-
191-
# Test stopping status.
192-
_restart_postgresql_service.reset_mock()
193-
self.charm.on.update_status.emit()
194-
expected_calls = [call.c(0), call.r(), call.c(None)]
195-
self.assertEqual(manager.mock_calls, expected_calls)
196-
197-
@patch_network_get(private_address="1.1.1.1")
198-
@patch("charm.Patroni.get_primary")
199-
@patch("charm.PostgresqlOperatorCharm._restart_postgresql_service")
200-
@patch("charm.Patroni.get_postgresql_state")
201-
def test_on_update_status_with_error_on_postgresql_status_check(
202-
self, _get_postgresql_state, _restart_postgresql_service, _
203-
):
204-
_get_postgresql_state.side_effect = [RetryError("fake error")]
205-
self.charm.on.update_status.emit()
206-
_restart_postgresql_service.assert_not_called()
207-
self.assertEqual(
208-
self.harness.model.unit.status,
209-
BlockedStatus("failed to check PostgreSQL state with error RetryError[fake error]"),
210-
)
211-
212159
@patch_network_get(private_address="1.1.1.1")
213160
@patch("charm.Patroni.get_primary")
214-
@patch("charm.Patroni.get_postgresql_state")
215-
def test_on_update_status_with_error_on_get_primary(self, _, _get_primary):
161+
def test_on_update_status_with_error_on_get_primary(self, _get_primary):
216162
_get_primary.side_effect = [RetryError("fake error")]
217163

218164
with self.assertLogs("charm", "ERROR") as logs:

tests/unit/test_patroni.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,6 @@ def setUp(self):
1818
"postgresql-k8s-0", "postgresql-k8s-0", "test-model", 1, STORAGE_PATH
1919
)
2020

21-
@patch("requests.patch")
22-
def test_change_master_start_timeout(self, _patch):
23-
# Test with an initial timeout value.
24-
self.patroni.change_master_start_timeout(0)
25-
_patch.assert_called_once_with(
26-
"http://postgresql-k8s-0:8008/config", json={"master_start_timeout": 0}
27-
)
28-
29-
# Test with another timeout value.
30-
_patch.reset_mock()
31-
self.patroni.change_master_start_timeout(300)
32-
_patch.assert_called_once_with(
33-
"http://postgresql-k8s-0:8008/config", json={"master_start_timeout": 300}
34-
)
35-
36-
@patch("requests.get")
37-
def test_get_postgresql_state(self, _get):
38-
_get.return_value.json.return_value = {"state": "running"}
39-
state = self.patroni.get_postgresql_state()
40-
self.assertEqual(state, "running")
41-
_get.assert_called_once_with("http://postgresql-k8s-0:8008/health")
42-
4321
@patch("requests.get")
4422
def test_get_primary(self, _get):
4523
# Mock Patroni cluster API.

0 commit comments

Comments
 (0)