Skip to content

Commit 34b1b84

Browse files
authored
Backport #959 to 14/edge (#963)
1 parent 6c47da6 commit 34b1b84

File tree

5 files changed

+68
-58
lines changed

5 files changed

+68
-58
lines changed

lib/charms/postgresql_k8s/v0/postgresql.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
# Increment this PATCH version before using `charmcraft publish-lib` or reset
3737
# to 0 if you are raising the major API version
38-
LIBPATCH = 55
38+
LIBPATCH = 56
3939

4040
# Groups to distinguish HBA access
4141
ACCESS_GROUP_IDENTITY = "identity_access"
@@ -1078,3 +1078,21 @@ def validate_group_map(self, group_map: Optional[str]) -> bool:
10781078
return False
10791079

10801080
return True
1081+
1082+
def is_user_in_hba(self, username: str) -> bool:
1083+
"""Check if user was added in pg_hba."""
1084+
connection = None
1085+
try:
1086+
with self._connect_to_database() as connection, connection.cursor() as cursor:
1087+
cursor.execute(
1088+
SQL(
1089+
"SELECT COUNT(*) FROM pg_hba_file_rules WHERE {} = ANY(user_name);"
1090+
).format(Literal(username))
1091+
)
1092+
return cursor.fetchone()[0] > 0
1093+
except psycopg2.Error as e:
1094+
logger.debug(f"Failed to check pg_hba: {e}")
1095+
return False
1096+
finally:
1097+
if connection:
1098+
connection.close()

src/relations/postgresql_provider.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""Postgres client relation hooks & helpers."""
55

66
import logging
7+
from datetime import datetime
78

89
from charms.data_platform_libs.v0.data_interfaces import (
910
DatabaseProvides,
@@ -22,6 +23,7 @@
2223
from ops.charm import CharmBase, RelationBrokenEvent, RelationChangedEvent
2324
from ops.framework import Object
2425
from ops.model import ActiveStatus, BlockedStatus, Relation
26+
from tenacity import RetryError, Retrying, stop_after_attempt, wait_fixed
2527

2628
from constants import (
2729
ALL_CLIENT_RELATIONS,
@@ -66,9 +68,6 @@ def __init__(self, charm: CharmBase, relation_name: str = "database") -> None:
6668
self.framework.observe(
6769
self.database_provides.on.database_requested, self._on_database_requested
6870
)
69-
self.framework.observe(
70-
charm.on[self.relation_name].relation_changed, self._on_relation_changed
71-
)
7271

7372
@staticmethod
7473
def _sanitize_extra_roles(extra_roles: str | None) -> list[str]:
@@ -145,27 +144,16 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
145144
else f"Failed to initialize {self.relation_name} relation"
146145
)
147146

148-
def _on_relation_changed(self, event: RelationChangedEvent) -> None:
149-
# Check for some conditions before trying to access the PostgreSQL instance.
150-
if not self.charm.is_cluster_initialised:
151-
logger.debug(
152-
"Deferring on_relation_changed: Cluster must be initialized before configuration can be updated with relation users"
153-
)
154-
event.defer()
155-
return
156-
157-
user = f"relation-{event.relation.id}"
147+
# Try to wait for pg_hba trigger
158148
try:
159-
if user not in self.charm.postgresql.list_users():
160-
logger.debug("Deferring on_relation_changed: user was not created yet")
161-
event.defer()
162-
return
163-
except PostgreSQLListUsersError:
164-
logger.debug("Deferring on_relation_changed: failed to list users")
165-
event.defer()
166-
return
167-
168-
self.charm.update_config()
149+
for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(1)):
150+
with attempt:
151+
self.charm.postgresql.is_user_in_hba(user)
152+
self.charm.unit_peer_data.update({
153+
"pg_hba_needs_update_timestamp": str(datetime.now())
154+
})
155+
except RetryError:
156+
logger.warning("database requested: Unable to check pg_hba rule update")
169157

170158
def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
171159
"""Correctly update the status."""

tests/integration/ha_tests/test_async_replication.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,12 @@ async def test_deploy_async_replication_setup(
125125
num_units=CLUSTER_SIZE,
126126
config={"profile": "testing"},
127127
)
128-
await ops_test.model.deploy(APPLICATION_NAME, channel="latest/edge", num_units=1)
129-
await second_model.deploy(APPLICATION_NAME, channel="latest/edge", num_units=1)
128+
await ops_test.model.deploy(
129+
APPLICATION_NAME, channel="latest/edge", num_units=1, config={"sleep_interval": 1000}
130+
)
131+
await second_model.deploy(
132+
APPLICATION_NAME, channel="latest/edge", num_units=1, config={"sleep_interval": 1000}
133+
)
130134

131135
async with ops_test.fast_forward(), fast_forward(second_model):
132136
await gather(
@@ -535,36 +539,32 @@ async def test_scaling(
535539
logger.info("checking whether writes are increasing")
536540
await are_writes_increasing(ops_test)
537541

538-
async with ops_test.fast_forward(FAST_INTERVAL), fast_forward(second_model, FAST_INTERVAL):
539-
logger.info("scaling out the first cluster")
540-
first_cluster_original_size = len(first_model.applications[DATABASE_APP_NAME].units)
541-
await scale_application(ops_test, DATABASE_APP_NAME, first_cluster_original_size + 1)
542-
543-
logger.info("checking whether writes are increasing")
544-
await are_writes_increasing(ops_test, extra_model=second_model)
545-
546-
logger.info("scaling out the second cluster")
547-
second_cluster_original_size = len(second_model.applications[DATABASE_APP_NAME].units)
548-
await scale_application(
549-
ops_test, DATABASE_APP_NAME, second_cluster_original_size + 1, model=second_model
550-
)
551-
552-
logger.info("checking whether writes are increasing")
553-
await are_writes_increasing(ops_test, extra_model=second_model)
554-
555-
logger.info("scaling in the first cluster")
556-
await scale_application(ops_test, DATABASE_APP_NAME, first_cluster_original_size)
542+
logger.info("scaling out the clusters")
543+
first_cluster_original_size = len(first_model.applications[DATABASE_APP_NAME].units)
544+
second_cluster_original_size = len(second_model.applications[DATABASE_APP_NAME].units)
545+
await gather(
546+
scale_application(ops_test, DATABASE_APP_NAME, first_cluster_original_size + 1),
547+
scale_application(
548+
ops_test,
549+
DATABASE_APP_NAME,
550+
second_cluster_original_size + 1,
551+
model=second_model,
552+
),
553+
)
557554

558-
logger.info("checking whether writes are increasing")
559-
await are_writes_increasing(ops_test, extra_model=second_model)
555+
logger.info("checking whether writes are increasing")
556+
await are_writes_increasing(ops_test, extra_model=second_model)
560557

561-
logger.info("scaling in the second cluster")
562-
await scale_application(
558+
logger.info("scaling in the clusters")
559+
await gather(
560+
scale_application(ops_test, DATABASE_APP_NAME, first_cluster_original_size),
561+
scale_application(
563562
ops_test, DATABASE_APP_NAME, second_cluster_original_size, model=second_model
564-
)
563+
),
564+
)
565565

566-
logger.info("checking whether writes are increasing")
567-
await are_writes_increasing(ops_test, extra_model=second_model)
566+
logger.info("checking whether writes are increasing")
567+
await are_writes_increasing(ops_test, extra_model=second_model)
568568

569569
# Verify that no writes to the database were missed after stopping the writes
570570
# (check that all the units have all the writes).

tests/integration/helpers.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,12 @@ async def run_command_on_unit(ops_test: OpsTest, unit_name: str, command: str) -
950950

951951

952952
async def scale_application(
953-
ops_test: OpsTest, application_name: str, count: int, model: Model = None
953+
ops_test: OpsTest,
954+
application_name: str,
955+
count: int,
956+
model: Model = None,
957+
timeout=2000,
958+
idle_period: int = 30,
954959
) -> None:
955960
"""Scale a given application to a specific unit count.
956961
@@ -959,6 +964,8 @@ async def scale_application(
959964
application_name: The name of the application
960965
count: The desired number of units to scale to
961966
model: The model to scale the application in
967+
timeout: timeout period
968+
idle_period: idle period
962969
"""
963970
if model is None:
964971
model = ops_test.model
@@ -971,8 +978,8 @@ async def scale_application(
971978
await model.wait_for_idle(
972979
apps=[application_name],
973980
status="active",
974-
timeout=2000,
975-
idle_period=30,
981+
timeout=timeout,
982+
idle_period=idle_period,
976983
wait_for_exact_units=count,
977984
)
978985

tests/unit/test_postgresql_provider.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@ def request_database(_harness):
7676
def test_on_database_requested(harness):
7777
with (
7878
patch("charm.PostgresqlOperatorCharm.update_config"),
79-
patch(
80-
"relations.postgresql_provider.PostgreSQLProvider._on_relation_changed"
81-
) as _on_relation_changed,
8279
patch.object(PostgresqlOperatorCharm, "postgresql", Mock()) as postgresql_mock,
8380
patch("subprocess.check_output", return_value=b"C"),
8481
patch("charm.PostgreSQLProvider.update_endpoints") as _update_endpoints,

0 commit comments

Comments
 (0)