Skip to content

Commit a1fac25

Browse files
committed
Test
1 parent 3ae2e3b commit a1fac25

File tree

3 files changed

+186
-12
lines changed

3 files changed

+186
-12
lines changed

src/relations/postgresql_provider.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,25 @@ def _get_credentials(self, event: DatabaseRequestedEvent) -> tuple[str, str] | N
197197
return
198198
return f"relation-{event.relation.id}", new_password()
199199

200+
def _collect_databases(
201+
self, user: str, event: DatabaseRequestedEvent
202+
) -> tuple[str, list[str]] | None:
203+
# Retrieve the database name and extra user roles using the charm library.
204+
database = event.database or ""
205+
if database and database[-1] == "*":
206+
if len(database) < 4:
207+
self.charm.unit.status = BlockedStatus(PREFIX_TOO_SHORT_MSG)
208+
return
209+
if event.prefix_matching and event.prefix_matching != "all":
210+
logger.warning("Only all prefix matching is supported")
211+
databases = sorted(self.charm.postgresql.list_databases(database[:-1]))
212+
self.set_databases_prefix_mapping(event.relation.id, user, database[:-1], databases)
213+
else:
214+
databases = [database]
215+
# Add to cached field to be able to generate hba rules
216+
self.add_database_to_prefix_mapping(database)
217+
return database, databases
218+
200219
def _are_units_in_sync(self) -> bool:
201220
for key in self.charm.all_peer_data:
202221
# We skip the leader so we don't have to wait on the defer
@@ -231,18 +250,10 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
231250
else:
232251
return
233252

234-
# Retrieve the database name and extra user roles using the charm library.
235-
database = event.database or ""
236-
if database and database[-1] == "*":
237-
if len(database) < 4:
238-
self.charm.unit.status = BlockedStatus(PREFIX_TOO_SHORT_MSG)
239-
return
240-
databases = sorted(self.charm.postgresql.list_databases(database[:-1]))
241-
self.set_databases_prefix_mapping(event.relation.id, user, database[:-1], databases)
253+
if databases_setup := self._collect_databases(user, event):
254+
database, databases = databases_setup
242255
else:
243-
databases = [database]
244-
# Add to cached field to be able to generate hba rules
245-
self.add_database_to_prefix_mapping(database)
256+
return
246257

247258
self.update_username_mapping(event.relation.id, user)
248259
self.charm.update_config()
@@ -523,7 +534,7 @@ def _update_unit_status(self, relation: Relation) -> None:
523534
NO_ACCESS_TO_SECRET_MSG,
524535
FORBIDDEN_USER_MSG,
525536
]:
526-
self._unblock_custom_user_errors(self, relation)
537+
self._unblock_custom_user_errors(relation)
527538

528539
def check_for_invalid_extra_user_roles(self, relation_id: int) -> bool:
529540
"""Checks if there are relations with invalid extra user roles.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2025 Canonical Ltd.
3+
# See LICENSE file for licensing details.
4+
import logging
5+
6+
import jubilant
7+
import psycopg2
8+
9+
from .helpers import DATA_INTEGRATOR_APP_NAME, DATABASE_APP_NAME
10+
from .high_availability.high_availability_helpers_new import (
11+
get_app_leader,
12+
get_unit_ip,
13+
wait_for_apps_status,
14+
)
15+
16+
MINUTE_SECS = 60
17+
18+
19+
def test_deploy(juju: jubilant.Juju, charm) -> None:
20+
"""Deploy the charms."""
21+
logging.info("Deploying database charm")
22+
juju.deploy(
23+
charm,
24+
app=DATABASE_APP_NAME,
25+
config={"profile": "testing"},
26+
num_units=1,
27+
)
28+
29+
# Deploy the data integrator if not already deployed.
30+
logging.info("Deploying data integrator charm")
31+
juju.deploy(
32+
DATA_INTEGRATOR_APP_NAME,
33+
app="di1",
34+
channel="latest/edge",
35+
config={"database-name": "postgre1"},
36+
)
37+
juju.deploy(
38+
DATA_INTEGRATOR_APP_NAME,
39+
app="di2",
40+
channel="latest/edge",
41+
config={"database-name": "postgre2"},
42+
)
43+
44+
creds_secret = juju.add_secret("creds-secret", {"tester": "password"})
45+
46+
juju.deploy(
47+
DATA_INTEGRATOR_APP_NAME,
48+
app="di3",
49+
channel="latest/edge",
50+
config={"requested-entities-secret": creds_secret},
51+
)
52+
juju.grant_secret(creds_secret, "di3")
53+
54+
logging.info("Relating data integrator")
55+
juju.integrate(f"{DATABASE_APP_NAME}:database", "di1")
56+
juju.integrate(f"{DATABASE_APP_NAME}:database", "di2")
57+
58+
logging.info("Waiting for the applications to settle")
59+
juju.wait(
60+
ready=wait_for_apps_status(jubilant.all_active, DATABASE_APP_NAME, "di1", "di2"),
61+
timeout=20 * MINUTE_SECS,
62+
)
63+
64+
65+
def test_prefix_too_short(juju: jubilant.Juju) -> None:
66+
logging.info("Setting short prefix")
67+
juju.config("di3", {"database-name": "po*"})
68+
69+
logging.info("Integrating with database")
70+
juju.integrate(f"{DATABASE_APP_NAME}:database", "di3")
71+
72+
logging.info("Waiting for the applications to settle")
73+
db_leader = get_app_leader(juju, DATABASE_APP_NAME)
74+
juju.wait(
75+
ready=lambda status: status.apps[DATABASE_APP_NAME]
76+
.units[db_leader]
77+
.workload_status.message
78+
== "Prefix too short"
79+
and status.apps[DATABASE_APP_NAME].units[db_leader].is_blocked,
80+
timeout=5 * MINUTE_SECS,
81+
)
82+
83+
juju.remove_relation(f"{DATABASE_APP_NAME}", "di3")
84+
juju.wait(jubilant.all_agents_idle, timeout=5 * MINUTE_SECS)
85+
86+
87+
def test_get_prefix(juju: jubilant.Juju) -> None:
88+
db_ip = get_unit_ip(juju, DATABASE_APP_NAME, get_app_leader(juju, DATABASE_APP_NAME))
89+
90+
logging.info("Setting prefix")
91+
juju.config("di3", {"database-name": "postgre*"})
92+
93+
logging.info("Integrating with database")
94+
juju.integrate(f"{DATABASE_APP_NAME}:database", "di3")
95+
96+
logging.info("Waiting for the applications to settle")
97+
juju.wait(
98+
ready=wait_for_apps_status(jubilant.all_active, DATABASE_APP_NAME, "di1", "di2", "di3"),
99+
timeout=5 * MINUTE_SECS,
100+
)
101+
102+
integrator_leader = get_app_leader(juju, "di3")
103+
result = juju.run(unit=integrator_leader, action="get-credentials")
104+
result.raise_on_failure()
105+
106+
# Assert credentials and prefix
107+
pg_data = result.results["postgresql"]
108+
assert "postgres" not in pg_data["prefix-databases"]
109+
assert pg_data["prefix-databases"] == "postgre1,postgre2"
110+
assert pg_data["username"] == "tester"
111+
assert pg_data["password"] == "password"
112+
assert pg_data["uris"] == f"postgresql://tester:password@{db_ip}:5432/postgre1"
113+
114+
# Connect to database
115+
psycopg2.connect(f"postgresql://tester:password@{db_ip}:5432/postgre1")
116+
psycopg2.connect(f"postgresql://tester:password@{db_ip}:5432/postgre2")
117+
118+
119+
def test_remove_db_from_prefix(juju: jubilant.Juju) -> None:
120+
db_ip = get_unit_ip(juju, DATABASE_APP_NAME, get_app_leader(juju, DATABASE_APP_NAME))
121+
122+
juju.remove_relation(f"{DATABASE_APP_NAME}", "di1")
123+
juju.wait(jubilant.all_agents_idle, timeout=5 * MINUTE_SECS)
124+
125+
integrator_leader = get_app_leader(juju, "di3")
126+
result = juju.run(unit=integrator_leader, action="get-credentials")
127+
result.raise_on_failure()
128+
129+
pg_data = result.results["postgresql"]
130+
assert "postgres" not in pg_data["prefix-databases"]
131+
assert pg_data["prefix-databases"] == "postgre2"
132+
assert pg_data["uris"] == f"postgresql://tester:password@{db_ip}:5432/postgre2"
133+
134+
# Connect to database
135+
# TODO this should fail
136+
psycopg2.connect(f"postgresql://tester:password@{db_ip}:5432/postgre1")
137+
psycopg2.connect(f"postgresql://tester:password@{db_ip}:5432/postgre2")
138+
139+
def test_readd_db_from_prefix(juju: jubilant.Juju) -> None:
140+
db_ip = get_unit_ip(juju, DATABASE_APP_NAME, get_app_leader(juju, DATABASE_APP_NAME))
141+
142+
juju.integrate(f"{DATABASE_APP_NAME}", "di1")
143+
juju.wait(jubilant.all_agents_idle, timeout=5 * MINUTE_SECS)
144+
145+
integrator_leader = get_app_leader(juju, "di3")
146+
result = juju.run(unit=integrator_leader, action="get-credentials")
147+
result.raise_on_failure()
148+
149+
pg_data = result.results["postgresql"]
150+
assert "postgres" not in pg_data["prefix-databases"]
151+
assert pg_data["prefix-databases"] == "postgre2"
152+
assert pg_data["uris"] == f"postgresql://tester:password@{db_ip}:5432/postgre2"
153+
154+
# Connect to database
155+
psycopg2.connect(f"postgresql://tester:password@{db_ip}:5432/postgre1")
156+
psycopg2.connect(f"postgresql://tester:password@{db_ip}:5432/postgre2")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
summary: test_username_prefix.py
2+
environment:
3+
TEST_MODULE: test_username_prefix.py
4+
execute: |
5+
tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results"
6+
artifacts:
7+
- allure-results

0 commit comments

Comments
 (0)