Skip to content

Commit e509ced

Browse files
[DPE-3258] Check system identifier in stanza (#318)
* Check system identifier in stanza and update tests Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix integration test Signed-off-by: Marcelo Henrique Neppel <[email protected]> --------- Signed-off-by: Marcelo Henrique Neppel <[email protected]>
1 parent 9d9a14e commit e509ced

File tree

4 files changed

+124
-9
lines changed

4 files changed

+124
-9
lines changed

poetry.lock

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

src/backups.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,23 @@ def can_use_s3_repository(self) -> Tuple[bool, Optional[str]]:
147147

148148
if self.charm.unit.is_leader():
149149
for stanza in json.loads(stdout):
150-
if stanza.get("name") != self.charm.app_peer_data.get("stanza", self.stanza_name):
150+
return_code, system_identifier_from_instance, error = self._execute_command(
151+
[
152+
f'/snap/charmed-postgresql/current/usr/lib/postgresql/{self.charm._patroni.get_postgresql_version().split(".")[0]}/bin/pg_controldata',
153+
POSTGRESQL_DATA_PATH,
154+
]
155+
)
156+
if return_code != 0:
157+
raise Exception(error)
158+
system_identifier_from_instance = [
159+
line
160+
for line in system_identifier_from_instance.splitlines()
161+
if "Database system identifier" in line
162+
][0].split(" ")[-1]
163+
system_identifier_from_stanza = str(stanza.get("db")[0]["system-id"])
164+
if system_identifier_from_instance != system_identifier_from_stanza or stanza.get(
165+
"name"
166+
) != self.charm.app_peer_data.get("stanza", self.stanza_name):
151167
# Prevent archiving of WAL files.
152168
self.charm.app_peer_data.update({"stanza": ""})
153169
self.charm.update_config()

tests/integration/test_backups.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,27 @@ async def test_backup(ops_test: OpsTest, cloud_configs: Tuple[Dict, Dict]) -> No
227227
async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets) -> None:
228228
"""Test that is possible to restore a backup to another PostgreSQL cluster."""
229229
charm = await ops_test.build_charm(".")
230+
previous_database_app_name = f"{DATABASE_APP_NAME}-gcp"
230231
database_app_name = f"new-{DATABASE_APP_NAME}"
232+
await ops_test.model.deploy(charm, application_name=previous_database_app_name)
231233
await ops_test.model.deploy(
232234
charm,
233235
application_name=database_app_name,
234236
series=CHARM_SERIES,
235237
)
238+
await ops_test.model.relate(previous_database_app_name, S3_INTEGRATOR_APP_NAME)
236239
await ops_test.model.relate(database_app_name, S3_INTEGRATOR_APP_NAME)
237240
async with ops_test.fast_forward():
241+
logger.info(
242+
"waiting for the database charm to become blocked due to existing backups from another cluster in the repository"
243+
)
244+
await wait_for_idle_on_blocked(
245+
ops_test,
246+
previous_database_app_name,
247+
2,
248+
S3_INTEGRATOR_APP_NAME,
249+
ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE,
250+
)
238251
logger.info(
239252
"waiting for the database charm to become blocked due to existing backups from another cluster in the repository"
240253
)
@@ -246,6 +259,10 @@ async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets) -> None
246259
ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE,
247260
)
248261

262+
# Remove the database app with the same name as the previous one (that was used only to test
263+
# that the cluster becomes blocked).
264+
await ops_test.model.remove_application(previous_database_app_name, block_until_done=True)
265+
249266
# Run the "list backups" action.
250267
unit_name = f"{database_app_name}/0"
251268
logger.info("listing the available backups")

tests/unit/test_backups.py

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,15 @@ def test_can_unit_perform_backup(
154154
@patch("charm.Patroni.reload_patroni_configuration")
155155
@patch("charm.Patroni.member_started", new_callable=PropertyMock)
156156
@patch("charm.PostgresqlOperatorCharm.update_config")
157+
@patch("charm.Patroni.get_postgresql_version", return_value="14.10")
157158
@patch("charm.PostgreSQLBackups._execute_command")
158159
def test_can_use_s3_repository(
159-
self, _execute_command, _update_config, _member_started, _reload_patroni_configuration
160+
self,
161+
_execute_command,
162+
_get_postgresql_version,
163+
_update_config,
164+
_member_started,
165+
_reload_patroni_configuration,
160166
):
161167
# Define the stanza name inside the unit relation data.
162168
with self.harness.hooks_disabled():
@@ -178,13 +184,13 @@ def test_can_use_s3_repository(
178184
(False, FAILED_TO_INITIALIZE_STANZA_ERROR_MESSAGE),
179185
)
180186

181-
# Test when the unit is a replica and there is a backup from another cluster
182-
# in the S3 repository.
183-
_execute_command.return_value = (
187+
# Test when the unit is a replica.
188+
pgbackrest_info_same_cluster_backup_output = (
184189
0,
185-
f'[{{"name": "another-model.{self.charm.cluster_name}"}}]',
190+
f'[{{"db": [{{"system-id": "12345"}}], "name": "{self.charm.backup.stanza_name}"}}]',
186191
"",
187192
)
193+
_execute_command.return_value = pgbackrest_info_same_cluster_backup_output
188194
self.assertEqual(
189195
self.charm.backup.can_use_s3_repository(),
190196
(True, None),
@@ -196,10 +202,80 @@ def test_can_use_s3_repository(
196202
{"stanza": self.charm.backup.stanza_name},
197203
)
198204

199-
# Test when the unit is the leader and the workload is running.
205+
# Test when the unit is the leader and the workload is running,
206+
# but an exception happens when retrieving the cluster system id.
200207
_member_started.return_value = True
208+
_execute_command.side_effect = [
209+
pgbackrest_info_same_cluster_backup_output,
210+
(1, "", "fake error"),
211+
]
201212
with self.harness.hooks_disabled():
202213
self.harness.set_leader()
214+
with self.assertRaises(Exception):
215+
self.assertEqual(
216+
self.charm.backup.can_use_s3_repository(),
217+
(False, ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE),
218+
)
219+
_update_config.assert_not_called()
220+
_member_started.assert_not_called()
221+
_reload_patroni_configuration.assert_not_called()
222+
223+
# Test when the cluster system id can be retrieved, but it's different from the stanza system id.
224+
pgbackrest_info_other_cluster_system_id_backup_output = (
225+
0,
226+
f'[{{"db": [{{"system-id": "12345"}}], "name": "{self.charm.backup.stanza_name}"}}]',
227+
"",
228+
)
229+
other_instance_system_identifier_output = (
230+
0,
231+
"Database system identifier: 67890",
232+
"",
233+
)
234+
_execute_command.side_effect = [
235+
pgbackrest_info_other_cluster_system_id_backup_output,
236+
other_instance_system_identifier_output,
237+
]
238+
with self.harness.hooks_disabled():
239+
self.harness.update_relation_data(
240+
self.peer_rel_id,
241+
self.charm.app.name,
242+
{"stanza": self.charm.backup.stanza_name},
243+
)
244+
self.assertEqual(
245+
self.charm.backup.can_use_s3_repository(),
246+
(False, ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE),
247+
)
248+
_update_config.assert_called_once()
249+
_member_started.assert_called_once()
250+
_reload_patroni_configuration.assert_called_once()
251+
252+
# Assert that the stanza name is not present in the unit relation data anymore.
253+
self.assertEqual(self.harness.get_relation_data(self.peer_rel_id, self.charm.app), {})
254+
255+
# Test when the cluster system id can be retrieved, but it's different from the stanza system id.
256+
_update_config.reset_mock()
257+
_member_started.reset_mock()
258+
_reload_patroni_configuration.reset_mock()
259+
pgbackrest_info_other_cluster_name_backup_output = (
260+
0,
261+
f'[{{"db": [{{"system-id": "12345"}}], "name": "another-model.{self.charm.cluster_name}"}}]',
262+
"",
263+
)
264+
same_instance_system_identifier_output = (
265+
0,
266+
"Database system identifier: 12345",
267+
"",
268+
)
269+
_execute_command.side_effect = [
270+
pgbackrest_info_other_cluster_name_backup_output,
271+
same_instance_system_identifier_output,
272+
]
273+
with self.harness.hooks_disabled():
274+
self.harness.update_relation_data(
275+
self.peer_rel_id,
276+
self.charm.app.name,
277+
{"stanza": self.charm.backup.stanza_name},
278+
)
203279
self.assertEqual(
204280
self.charm.backup.can_use_s3_repository(),
205281
(False, ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE),
@@ -222,6 +298,10 @@ def test_can_use_s3_repository(
222298
self.charm.app.name,
223299
{"stanza": self.charm.backup.stanza_name},
224300
)
301+
_execute_command.side_effect = [
302+
pgbackrest_info_same_cluster_backup_output,
303+
other_instance_system_identifier_output,
304+
]
225305
self.assertEqual(
226306
self.charm.backup.can_use_s3_repository(),
227307
(False, ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE),
@@ -240,7 +320,10 @@ def test_can_use_s3_repository(
240320
self.charm.app.name,
241321
{"stanza": self.charm.backup.stanza_name},
242322
)
243-
_execute_command.return_value = (0, f'[{{"name": "{self.charm.backup.stanza_name}"}}]', "")
323+
_execute_command.side_effect = [
324+
pgbackrest_info_same_cluster_backup_output,
325+
same_instance_system_identifier_output,
326+
]
244327
self.assertEqual(
245328
self.charm.backup.can_use_s3_repository(),
246329
(True, None),

0 commit comments

Comments
 (0)