From ac24da8c9ae78092dcbef8ab806f1cddb7d1a4ad Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Tue, 13 Feb 2024 17:17:29 +0200 Subject: [PATCH 01/19] Reenable landscape tests --- tests/integration/test_db_admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index e081c54576..eb6fc14275 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -35,7 +35,6 @@ @pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) -@pytest.mark.skip(reason="DB Admin tests are currently broken") async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> None: """Deploy Landscape Scalable Bundle to test the 'db-admin' relation.""" await ops_test.model.deploy( From 98e151ea7877e0fd6620c8fef05f8c208805cafc Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Tue, 13 Feb 2024 19:01:22 +0200 Subject: [PATCH 02/19] Try GH runners --- tests/integration/test_db_admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index eb6fc14275..4c7868035b 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -33,7 +33,6 @@ RELATION_NAME = "db-admin" -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> None: """Deploy Landscape Scalable Bundle to test the 'db-admin' relation.""" From 5d9da4ca19eb03946198d82f2ac9135741f5ec42 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 21 Feb 2024 13:09:12 +0200 Subject: [PATCH 03/19] Back on self-hosted runner --- tests/integration/test_db_admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index 4c7868035b..eb6fc14275 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -33,6 +33,7 @@ RELATION_NAME = "db-admin" +@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> None: """Deploy Landscape Scalable Bundle to test the 'db-admin' relation.""" From 375c95799f7ecf835b72ec6aced5a9fe0ef05685 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 21 Feb 2024 15:53:17 +0200 Subject: [PATCH 04/19] Don't check for primary if peer didn't join yet --- src/charm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/charm.py b/src/charm.py index 7cf803742c..a0fc041ff7 100755 --- a/src/charm.py +++ b/src/charm.py @@ -299,6 +299,9 @@ def postgresql(self) -> PostgreSQL: @property def primary_endpoint(self) -> Optional[str]: """Returns the endpoint of the primary instance or None when no primary available.""" + if not self._peers: + logger.debug("primary endpoint early exit: Peer relation not joined yet.") + return None try: for attempt in Retrying(stop=stop_after_delay(60), wait=wait_fixed(3)): with attempt: From 8c476690c3b8c071b0e003d4e026dd5e0250629c Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 21 Feb 2024 17:17:38 +0200 Subject: [PATCH 05/19] Don't recheck missing primary ip when looking for primary endpoint --- src/charm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/charm.py b/src/charm.py index a0fc041ff7..edf6f1e6cd 100755 --- a/src/charm.py +++ b/src/charm.py @@ -311,7 +311,10 @@ def primary_endpoint(self) -> Optional[str]: # returned is not in the list of the current cluster members # (like when the cluster was not updated yet after a failed switchover). if not primary_endpoint or primary_endpoint not in self._units_ips: - raise ValueError() + logger.debug( + "primary endpoint early exit: Primary IP not in cached peer list." + ) + primary_endpoint = None except RetryError: return None else: From 1e0b514f909cb7dacc59c75a89667c9d8a5be718 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 21 Feb 2024 17:45:33 +0200 Subject: [PATCH 06/19] Catch list user exception --- src/charm.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/charm.py b/src/charm.py index edf6f1e6cd..fc38f11365 100755 --- a/src/charm.py +++ b/src/charm.py @@ -19,6 +19,7 @@ PostgreSQL, PostgreSQLCreateUserError, PostgreSQLEnableDisableExtensionError, + PostgreSQLListUsersError, PostgreSQLUpdateUserPasswordError, ) from charms.postgresql_k8s.v0.postgresql_tls import PostgreSQLTLS @@ -1042,6 +1043,10 @@ def _start_primary(self, event: StartEvent) -> None: logger.exception(e) self.unit.status = BlockedStatus("Failed to create postgres user") return + except PostgreSQLListUsersError: + logger.warning("Deferriing on_start: Unable to list users") + event.defer() + return self.postgresql.set_up_database() From fa4479067235474f27b9edbfbdbe8ec827b52582 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 21 Feb 2024 18:48:34 +0200 Subject: [PATCH 07/19] Catch Psycopg2 error when trying to set config --- src/charm.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/charm.py b/src/charm.py index fc38f11365..d10f547fcb 100755 --- a/src/charm.py +++ b/src/charm.py @@ -44,6 +44,7 @@ Unit, WaitingStatus, ) +from psycopg2 import OperationalError from tenacity import RetryError, Retrying, retry, stop_after_attempt, stop_after_delay, wait_fixed from backups import PostgreSQLBackups @@ -1435,7 +1436,11 @@ def update_config(self, is_creating_backup: bool = False) -> bool: logger.debug("Early exit update_config: Patroni not started yet") return False - self._validate_config_options() + try: + self._validate_config_options() + except OperationalError: + logger.debug("Early exit update_config: Postgresql not yet available") + return False self._patroni.bulk_update_parameters_controller_by_patroni( { From 7fce7858740a9877e7d99843f0b2e0fcc042d34b Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 21 Feb 2024 21:10:26 +0200 Subject: [PATCH 08/19] Try to preflight connection on config validation --- src/charm.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/charm.py b/src/charm.py index d10f547fcb..3eb3e6a85c 100755 --- a/src/charm.py +++ b/src/charm.py @@ -44,7 +44,6 @@ Unit, WaitingStatus, ) -from psycopg2 import OperationalError from tenacity import RetryError, Retrying, retry, stop_after_attempt, stop_after_delay, wait_fixed from backups import PostgreSQLBackups @@ -1393,6 +1392,17 @@ def _is_workload_running(self) -> bool: return charmed_postgresql_snap.services["patroni"]["active"] + @property + def _can_connect_to_postgresql(self) -> bool: + try: + for attempt in Retrying(stop=stop_after_delay(30), wait=wait_fixed(3)): + with attempt: + assert self.postgresql.get_postgresql_timezones() + except RetryError: + logger.debug("Cannot connect to database") + return False + return True + def update_config(self, is_creating_backup: bool = False) -> bool: """Updates Patroni config file based on the existence of the TLS files.""" if ( @@ -1436,11 +1446,11 @@ def update_config(self, is_creating_backup: bool = False) -> bool: logger.debug("Early exit update_config: Patroni not started yet") return False - try: - self._validate_config_options() - except OperationalError: - logger.debug("Early exit update_config: Postgresql not yet available") + # Try to connect + if not self._can_connect_to_postgresql: + logger.debug("Early exit update_config: Cannot connect to Postgresql") return False + self._validate_config_options() self._patroni.bulk_update_parameters_controller_by_patroni( { From d50d66934fba5b4af266e5191ab9327b7efbe81a Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 22 Feb 2024 13:30:31 +0200 Subject: [PATCH 09/19] Don't retry db connection check --- src/charm.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/charm.py b/src/charm.py index 3eb3e6a85c..1b9c2dceab 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1395,10 +1395,8 @@ def _is_workload_running(self) -> bool: @property def _can_connect_to_postgresql(self) -> bool: try: - for attempt in Retrying(stop=stop_after_delay(30), wait=wait_fixed(3)): - with attempt: - assert self.postgresql.get_postgresql_timezones() - except RetryError: + assert self.postgresql.get_postgresql_timezones() + except Exception: logger.debug("Cannot connect to database") return False return True From 8184dfae1f9995e8c8957d996ae29205de09e416 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 22 Feb 2024 15:14:21 +0200 Subject: [PATCH 10/19] Set the primary endpoint before starting the primary --- src/charm.py | 4 ++++ tests/unit/test_charm.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/charm.py b/src/charm.py index 1b9c2dceab..98423029ac 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1021,6 +1021,10 @@ def _start_primary(self, event: StartEvent) -> None: event.defer() return + if not self.primary_endpoint: + logger.debug("Primary endpoint not generated") + self.postgresql.primary_host = self.postgresql.current_host + # Create the default postgres database user that is needed for some # applications (not charms) like Landscape Server. try: diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index a02e4cb1e6..7f76f91a89 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -504,6 +504,11 @@ def test_enable_disable_extensions(self, _): harness.charm.enable_disable_extensions() self.assertEqual(postgresql_mock.enable_disable_extensions.call_count, 1) + @patch( + "charm.PostgresqlOperatorCharm.primary_endpoint", + new_callable=PropertyMock, + return_value="127.0.0.1", + ) @patch("charm.PostgresqlOperatorCharm.enable_disable_extensions") @patch("charm.snap.SnapCache") @patch("charm.Patroni.get_postgresql_version") @@ -543,6 +548,7 @@ def test_on_start( _get_postgresql_version, _snap_cache, _enable_disable_extensions, + ___, ): _get_postgresql_version.return_value = "14.0" From 5a09131b43ad5581e48f9a632f9065f2673dc9f1 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 22 Feb 2024 19:53:47 +0200 Subject: [PATCH 11/19] Revert "Set the primary endpoint before starting the primary" This reverts commit 8184dfae1f9995e8c8957d996ae29205de09e416. --- src/charm.py | 4 ---- tests/unit/test_charm.py | 6 ------ 2 files changed, 10 deletions(-) diff --git a/src/charm.py b/src/charm.py index 98423029ac..1b9c2dceab 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1021,10 +1021,6 @@ def _start_primary(self, event: StartEvent) -> None: event.defer() return - if not self.primary_endpoint: - logger.debug("Primary endpoint not generated") - self.postgresql.primary_host = self.postgresql.current_host - # Create the default postgres database user that is needed for some # applications (not charms) like Landscape Server. try: diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 7f76f91a89..a02e4cb1e6 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -504,11 +504,6 @@ def test_enable_disable_extensions(self, _): harness.charm.enable_disable_extensions() self.assertEqual(postgresql_mock.enable_disable_extensions.call_count, 1) - @patch( - "charm.PostgresqlOperatorCharm.primary_endpoint", - new_callable=PropertyMock, - return_value="127.0.0.1", - ) @patch("charm.PostgresqlOperatorCharm.enable_disable_extensions") @patch("charm.snap.SnapCache") @patch("charm.Patroni.get_postgresql_version") @@ -548,7 +543,6 @@ def test_on_start( _get_postgresql_version, _snap_cache, _enable_disable_extensions, - ___, ): _get_postgresql_version.return_value = "14.0" From 1eb5529722c5b36c745e15334dd79d9c7b9223c1 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 22 Feb 2024 19:55:55 +0200 Subject: [PATCH 12/19] Wait for peer before starting primary --- src/charm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/charm.py b/src/charm.py index 1b9c2dceab..a55a8d7abb 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1015,7 +1015,7 @@ def _start_primary(self, event: StartEvent) -> None: return # Assert the member is up and running before marking it as initialised. - if not self._patroni.member_started: + if not self._peers or not self._patroni.member_started: logger.debug("Deferring on_start: awaiting for member to start") self.unit.status = WaitingStatus("awaiting for member to start") event.defer() From e14d5a10ca51bce252fd213d66554c394cbf7b6c Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 22 Feb 2024 21:58:27 +0200 Subject: [PATCH 13/19] Switch to GH runners --- tests/integration/ha_tests/test_replication.py | 1 - tests/integration/ha_tests/test_restore_cluster.py | 1 - tests/integration/ha_tests/test_self_healing.py | 1 - tests/integration/ha_tests/test_upgrade.py | 1 - tests/integration/ha_tests/test_upgrade_from_stable.py | 1 - tests/integration/new_relations/test_new_relations.py | 1 - tests/integration/test_backups.py | 1 - tests/integration/test_charm.py | 1 - tests/integration/test_db.py | 1 - tests/integration/test_db_admin.py | 1 - tests/integration/test_password_rotation.py | 1 - tests/integration/test_plugins.py | 1 - tests/integration/test_tls.py | 1 - 13 files changed, 13 deletions(-) diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index 8706dbea06..600e2997d4 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -18,7 +18,6 @@ ) -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest) -> None: diff --git a/tests/integration/ha_tests/test_restore_cluster.py b/tests/integration/ha_tests/test_restore_cluster.py index a61d5cc058..e55f967337 100644 --- a/tests/integration/ha_tests/test_restore_cluster.py +++ b/tests/integration/ha_tests/test_restore_cluster.py @@ -29,7 +29,6 @@ charm = None -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest) -> None: diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index d73ffa2b21..0dcc43be02 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -60,7 +60,6 @@ DB_PROCESSES = [POSTGRESQL_PROCESS, PATRONI_PROCESS] -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest) -> None: diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index f25363815c..608986eeca 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -30,7 +30,6 @@ TIMEOUT = 600 -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_latest(ops_test: OpsTest) -> None: diff --git a/tests/integration/ha_tests/test_upgrade_from_stable.py b/tests/integration/ha_tests/test_upgrade_from_stable.py index 325ae0b6b2..515f1b701e 100644 --- a/tests/integration/ha_tests/test_upgrade_from_stable.py +++ b/tests/integration/ha_tests/test_upgrade_from_stable.py @@ -25,7 +25,6 @@ TIMEOUT = 600 -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_stable(ops_test: OpsTest) -> None: diff --git a/tests/integration/new_relations/test_new_relations.py b/tests/integration/new_relations/test_new_relations.py index 509c18c123..f982e0547d 100644 --- a/tests/integration/new_relations/test_new_relations.py +++ b/tests/integration/new_relations/test_new_relations.py @@ -36,7 +36,6 @@ INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_charms(ops_test: OpsTest, charm): diff --git a/tests/integration/test_backups.py b/tests/integration/test_backups.py index 334743727e..5719ce0d84 100644 --- a/tests/integration/test_backups.py +++ b/tests/integration/test_backups.py @@ -90,7 +90,6 @@ async def cloud_configs(ops_test: OpsTest, github_secrets) -> None: bucket_object.delete() -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) async def test_none() -> None: """Empty test so that the suite will not fail if all tests are skippedi.""" diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 39ca7b0882..78a30680b0 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -32,7 +32,6 @@ UNIT_IDS = [0, 1, 2] -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 4ee3fe705b..4d10cd24ef 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -39,7 +39,6 @@ ) -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: """Deploy Mailman3 Core to test the 'db' relation.""" diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index e081c54576..eb0d2291db 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -33,7 +33,6 @@ RELATION_NAME = "db-admin" -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.skip(reason="DB Admin tests are currently broken") async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> None: diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index fb9fec9ed6..c05b3ddaf0 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -21,7 +21,6 @@ APP_NAME = METADATA["name"] -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index 6f7e7cc7ab..ce395469a9 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -85,7 +85,6 @@ ) -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_plugins(ops_test: OpsTest) -> None: diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index b27692954b..4d4ad97f50 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -38,7 +38,6 @@ TLS_CONFIG = {"ca-common-name": "Test CA"} -@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"]) @pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed From dc4588f125c2629c5350293fd20d33d3225998b3 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 22 Feb 2024 22:57:33 +0200 Subject: [PATCH 14/19] Recheck DB connection --- src/charm.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/charm.py b/src/charm.py index a55a8d7abb..fbf5c52c17 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1395,8 +1395,10 @@ def _is_workload_running(self) -> bool: @property def _can_connect_to_postgresql(self) -> bool: try: - assert self.postgresql.get_postgresql_timezones() - except Exception: + for attempt in Retrying(stop=stop_after_delay(30), wait=wait_fixed(3)): + with attempt: + assert self.postgresql.get_postgresql_timezones() + except RetryError: logger.debug("Cannot connect to database") return False return True From e7e896527ec935a0eebe19b5a5949ef287045897 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Fri, 23 Feb 2024 00:06:17 +0200 Subject: [PATCH 15/19] Don't check peer on primary startup --- src/charm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/charm.py b/src/charm.py index fbf5c52c17..3eb3e6a85c 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1015,7 +1015,7 @@ def _start_primary(self, event: StartEvent) -> None: return # Assert the member is up and running before marking it as initialised. - if not self._peers or not self._patroni.member_started: + if not self._patroni.member_started: logger.debug("Deferring on_start: awaiting for member to start") self.unit.status = WaitingStatus("awaiting for member to start") event.defer() From 5c78ca8962cff0e0f450d36e12d9a3fbc6bbd432 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Fri, 23 Feb 2024 23:28:35 +0200 Subject: [PATCH 16/19] Try to skip primary IP validation if not all cluster IPs seem to be available --- src/charm.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/charm.py b/src/charm.py index 3eb3e6a85c..1a2b9787d6 100755 --- a/src/charm.py +++ b/src/charm.py @@ -312,6 +312,15 @@ def primary_endpoint(self) -> Optional[str]: # returned is not in the list of the current cluster members # (like when the cluster was not updated yet after a failed switchover). if not primary_endpoint or primary_endpoint not in self._units_ips: + if ( + primary_endpoint + and len(self._units_ips) == 1 + and len(self._peers.units) > 1 + ): + logger.warning( + "Possibly incoplete peer data: Will not map primary IP to unit IP" + ) + return primary_endpoint logger.debug( "primary endpoint early exit: Primary IP not in cached peer list." ) From beea41d906212086544f1a0951f16911d5831b30 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Sat, 24 Feb 2024 01:13:05 +0200 Subject: [PATCH 17/19] Comment --- src/charm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/charm.py b/src/charm.py index 1a2b9787d6..b33b21de8e 100755 --- a/src/charm.py +++ b/src/charm.py @@ -312,6 +312,7 @@ def primary_endpoint(self) -> Optional[str]: # returned is not in the list of the current cluster members # (like when the cluster was not updated yet after a failed switchover). if not primary_endpoint or primary_endpoint not in self._units_ips: + # TODO figure out why peer data is not available if ( primary_endpoint and len(self._units_ips) == 1 From e520923fcb1ab89dc086a1f0fe209ba8f2016ae6 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Mon, 26 Feb 2024 13:56:30 +0200 Subject: [PATCH 18/19] Review comments --- src/charm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/charm.py b/src/charm.py index b33b21de8e..b5a4bec8bb 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1458,7 +1458,7 @@ def update_config(self, is_creating_backup: bool = False) -> bool: # Try to connect if not self._can_connect_to_postgresql: - logger.debug("Early exit update_config: Cannot connect to Postgresql") + logger.warning("Early exit update_config: Cannot connect to Postgresql") return False self._validate_config_options() From f3e3d3389f7fc8886b5063f5c9380aaf84d2ad0a Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Mon, 26 Feb 2024 23:05:18 +0200 Subject: [PATCH 19/19] Unit tests --- tests/unit/test_charm.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index a02e4cb1e6..c8e2ba912d 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -5,7 +5,7 @@ import platform import subprocess import unittest -from unittest.mock import MagicMock, Mock, PropertyMock, call, mock_open, patch +from unittest.mock import MagicMock, Mock, PropertyMock, call, mock_open, patch, sentinel import pytest from charms.operator_libs_linux.v2 import snap @@ -179,6 +179,33 @@ def test_patroni_scrape_config_tls(self, _): }, ] + @patch( + "charm.PostgresqlOperatorCharm._units_ips", + new_callable=PropertyMock, + return_value={"1.1.1.1", "1.1.1.2"}, + ) + @patch("charm.PostgresqlOperatorCharm._patroni", new_callable=PropertyMock) + def test_primary_endpoint(self, _patroni, _): + _patroni.return_value.get_member_ip.return_value = "1.1.1.1" + _patroni.return_value.get_primary.return_value = sentinel.primary + assert self.charm.primary_endpoint == "1.1.1.1" + + _patroni.return_value.get_member_ip.assert_called_once_with(sentinel.primary) + _patroni.return_value.get_primary.assert_called_once_with() + + @patch("charm.PostgresqlOperatorCharm._peers", new_callable=PropertyMock, return_value=None) + @patch( + "charm.PostgresqlOperatorCharm._units_ips", + new_callable=PropertyMock, + return_value={"1.1.1.1", "1.1.1.2"}, + ) + @patch("charm.PostgresqlOperatorCharm._patroni", new_callable=PropertyMock) + def test_primary_endpoint_no_peers(self, _patroni, _, __): + assert self.charm.primary_endpoint is None + + assert not _patroni.return_value.get_member_ip.called + assert not _patroni.return_value.get_primary.called + @patch("charm.PostgresqlOperatorCharm._update_relation_endpoints", new_callable=PropertyMock) @patch( "charm.PostgresqlOperatorCharm.primary_endpoint",