Skip to content

Commit 62c086a

Browse files
authored
[DPE-4213] Tests legacy and modern endpoints simultaneously (#434)
* deployment legacy + modern endpoints * deployment legacy + modern endpoints * fix postgresql_provider * fix test group * simplifying test
1 parent a627e0b commit 62c086a

File tree

6 files changed

+206
-4
lines changed

6 files changed

+206
-4
lines changed

src/constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@
3939

4040
SECRET_KEY_OVERRIDES = {"ca": "cauth"}
4141
BACKUP_TYPE_OVERRIDES = {"full": "full", "differential": "diff", "incremental": "incr"}
42+
43+
DATABASE = "database"
44+
LEGACY_DB = "db"
45+
LEGACY_DB_ADMIN = "db-admin"
46+
ALL_LEGACY_RELATIONS = [LEGACY_DB, LEGACY_DB_ADMIN]
47+
48+
ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = (
49+
"Please choose one endpoint to use. No need to relate all of them simultaneously!"
50+
)

src/relations/db.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from ops.model import ActiveStatus, BlockedStatus, Relation, Unit
2323
from pgconnstr import ConnectionString
2424

25-
from constants import DATABASE_PORT
25+
from constants import ALL_LEGACY_RELATIONS, DATABASE_PORT, ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE
2626
from utils import new_password
2727

2828
logger = logging.getLogger(__name__)
@@ -90,10 +90,28 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None:
9090
if not self.charm.unit.is_leader():
9191
return
9292

93+
if self._check_multiple_endpoints():
94+
self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE)
95+
return
96+
9397
logger.warning(f"DEPRECATION WARNING - `{self.relation_name}` is a legacy interface")
9498

9599
self.set_up_relation(event.relation)
96100

101+
def _check_exist_current_relation(self) -> bool:
102+
for r in self.charm.client_relations:
103+
if r in ALL_LEGACY_RELATIONS:
104+
return True
105+
return False
106+
107+
def _check_multiple_endpoints(self) -> bool:
108+
"""Checks if there are relations with other endpoints."""
109+
is_exist = self._check_exist_current_relation()
110+
for relation in self.charm.client_relations:
111+
if relation.name not in ALL_LEGACY_RELATIONS and is_exist:
112+
return True
113+
return False
114+
97115
def _get_extensions(self, relation: Relation) -> Tuple[List, Set]:
98116
"""Returns the list of required and disabled extensions."""
99117
requested_extensions = relation.data.get(relation.app, {}).get("extensions", "").split(",")
@@ -320,6 +338,24 @@ def _update_unit_status(self, relation: Relation) -> None:
320338
if not self._check_for_blocking_relations(relation.id):
321339
self.charm.unit.status = ActiveStatus()
322340

341+
self._update_unit_status_on_blocking_endpoint_simultaneously()
342+
343+
def _update_unit_status_on_blocking_endpoint_simultaneously(self):
344+
"""Clean up Blocked status if this is due related of multiple endpoints."""
345+
if (
346+
self.charm._has_blocked_status
347+
and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE
348+
):
349+
if not self._check_multiple_endpoints():
350+
self.charm.unit.status = ActiveStatus()
351+
352+
def _check_multiple_endpoints(self) -> bool:
353+
"""Checks if there are relations with other endpoints."""
354+
relation_names = {relation.name for relation in self.charm.client_relations}
355+
if "database" in relation_names and len(relation_names) > 1:
356+
return True
357+
return False
358+
323359
def _get_allowed_subnets(self, relation: Relation) -> str:
324360
"""Build the list of allowed subnets as in the legacy charm."""
325361

src/relations/postgresql_provider.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
PostgreSQLDeleteUserError,
1717
PostgreSQLGetPostgreSQLVersionError,
1818
)
19-
from ops.charm import CharmBase, RelationBrokenEvent, RelationDepartedEvent
19+
from ops.charm import CharmBase, RelationBrokenEvent, RelationChangedEvent, RelationDepartedEvent
2020
from ops.framework import Object
2121
from ops.model import ActiveStatus, BlockedStatus, Relation
2222

23-
from constants import DATABASE_PORT
23+
from constants import DATABASE_PORT, ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE
2424
from utils import new_password
2525

2626
logger = logging.getLogger(__name__)
@@ -50,7 +50,10 @@ def __init__(self, charm: CharmBase, relation_name: str = "database") -> None:
5050
self.framework.observe(
5151
charm.on[self.relation_name].relation_broken, self._on_relation_broken
5252
)
53-
53+
self.framework.observe(
54+
charm.on[self.relation_name].relation_changed,
55+
self._on_relation_changed_event,
56+
)
5457
self.charm = charm
5558

5659
# Charm events defined in the database provides charm library.
@@ -193,6 +196,22 @@ def update_read_only_endpoint(self, event: DatabaseRequestedEvent = None) -> Non
193196
endpoints,
194197
)
195198

199+
def _check_multiple_endpoints(self) -> bool:
200+
"""Checks if there are relations with other endpoints."""
201+
relation_names = {relation.name for relation in self.charm.client_relations}
202+
if "database" in relation_names and len(relation_names) > 1:
203+
return True
204+
return False
205+
206+
def _update_unit_status_on_blocking_endpoint_simultaneously(self):
207+
"""Clean up Blocked status if this is due related of multiple endpoints."""
208+
if (
209+
self.charm._has_blocked_status
210+
and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE
211+
):
212+
if not self._check_multiple_endpoints():
213+
self.charm.unit.status = ActiveStatus()
214+
196215
def _update_unit_status(self, relation: Relation) -> None:
197216
"""# Clean up Blocked status if it's due to extensions request."""
198217
if (
@@ -202,6 +221,18 @@ def _update_unit_status(self, relation: Relation) -> None:
202221
if not self.check_for_invalid_extra_user_roles(relation.id):
203222
self.charm.unit.status = ActiveStatus()
204223

224+
self._update_unit_status_on_blocking_endpoint_simultaneously()
225+
226+
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
227+
"""Event emitted when the relation has changed."""
228+
# Leader only
229+
if not self.charm.unit.is_leader():
230+
return
231+
232+
if self._check_multiple_endpoints():
233+
self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE)
234+
return
235+
205236
def check_for_invalid_extra_user_roles(self, relation_id: int) -> bool:
206237
"""Checks if there are relations with invalid extra user roles.
207238
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright 2024 Canonical Ltd.
2+
# See LICENSE file for licensing details.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024 Canonical Ltd.
3+
# See LICENSE file for licensing details.
4+
5+
6+
from ..helpers import METADATA
7+
from ..new_relations.test_new_relations import (
8+
APPLICATION_APP_NAME,
9+
)
10+
11+
APP_NAME = METADATA["name"]
12+
DB_RELATION = "db"
13+
DATABASE_RELATION = "database"
14+
FIRST_DATABASE_RELATION = "first-database"
15+
APP_NAMES = [APP_NAME, APPLICATION_APP_NAME]
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024 Canonical Ltd.
3+
# See LICENSE file for licensing details.
4+
import asyncio
5+
import logging
6+
7+
import pytest
8+
from pytest_operator.plugin import OpsTest
9+
10+
from ..helpers import CHARM_SERIES
11+
from ..new_relations.test_new_relations import (
12+
APPLICATION_APP_NAME,
13+
DATABASE_APP_METADATA,
14+
)
15+
from ..relations.helpers import (
16+
APP_NAME,
17+
DATABASE_RELATION,
18+
DB_RELATION,
19+
FIRST_DATABASE_RELATION,
20+
)
21+
22+
logger = logging.getLogger(__name__)
23+
24+
25+
@pytest.mark.group(1)
26+
@pytest.mark.abort_on_fail
27+
async def test_deploy_charms(ops_test: OpsTest, database_charm):
28+
"""Deploy both charms (application and database) to use in the tests."""
29+
# Deploy both charms (multiple units for each application to test that later they correctly
30+
# set data in the relation application databag using only the leader unit).
31+
async with ops_test.fast_forward():
32+
await asyncio.gather(
33+
ops_test.model.deploy(
34+
APPLICATION_APP_NAME,
35+
application_name=APPLICATION_APP_NAME,
36+
num_units=1,
37+
series=CHARM_SERIES,
38+
channel="edge",
39+
),
40+
ops_test.model.deploy(
41+
database_charm,
42+
resources={
43+
"postgresql-image": DATABASE_APP_METADATA["resources"]["postgresql-image"][
44+
"upstream-source"
45+
]
46+
},
47+
application_name=APP_NAME,
48+
num_units=1,
49+
series=CHARM_SERIES,
50+
config={
51+
"profile": "testing",
52+
"plugin_unaccent_enable": "True",
53+
"plugin_pg_trgm_enable": "True",
54+
},
55+
),
56+
)
57+
58+
await ops_test.model.wait_for_idle(
59+
apps=[APP_NAME, APPLICATION_APP_NAME], status="active", timeout=3000
60+
)
61+
62+
63+
@pytest.mark.group(1)
64+
async def test_legacy_and_modern_endpoints_simultaneously(ops_test: OpsTest):
65+
await ops_test.model.relate(APPLICATION_APP_NAME, f"{APP_NAME}:{DB_RELATION}")
66+
await ops_test.model.wait_for_idle(
67+
status="active",
68+
timeout=1500,
69+
raise_on_error=False,
70+
)
71+
72+
logger.info(" add relation with modern endpoints")
73+
app = ops_test.model.applications[APP_NAME]
74+
async with ops_test.fast_forward():
75+
await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}")
76+
await ops_test.model.block_until(
77+
lambda: "blocked" in {unit.workload_status for unit in app.units},
78+
timeout=1500,
79+
)
80+
81+
logger.info(" remove relation with legacy endpoints")
82+
await ops_test.model.applications[APP_NAME].destroy_relation(
83+
f"{APP_NAME}:{DB_RELATION}", f"{APPLICATION_APP_NAME}:{DB_RELATION}"
84+
)
85+
await ops_test.model.wait_for_idle(status="active", timeout=1500)
86+
87+
logger.info(" add relation with legacy endpoints")
88+
async with ops_test.fast_forward():
89+
await ops_test.model.relate(APPLICATION_APP_NAME, f"{APP_NAME}:{DB_RELATION}")
90+
await ops_test.model.block_until(
91+
lambda: "blocked" in {unit.workload_status for unit in app.units},
92+
timeout=1500,
93+
)
94+
95+
logger.info(" remove relation with modern endpoints")
96+
await ops_test.model.applications[APP_NAME].destroy_relation(
97+
f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}"
98+
)
99+
await ops_test.model.wait_for_idle(status="active", timeout=1500)
100+
101+
logger.info(" remove relation with legacy endpoints")
102+
await ops_test.model.applications[APP_NAME].destroy_relation(
103+
f"{APP_NAME}:{DB_RELATION}", f"{APPLICATION_APP_NAME}:{DB_RELATION}"
104+
)
105+
await ops_test.model.wait_for_idle(status="active", timeout=1500)
106+
107+
logger.info(" add relation with modern endpoints")
108+
await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}")
109+
await ops_test.model.wait_for_idle(status="active", timeout=1500)

0 commit comments

Comments
 (0)