Skip to content

Commit 907cf89

Browse files
[DPE-2057] Enable Discourse-K8S test (#180)
* Enable Discourse test * Move Discourse test to the right file * Add minor fixes and unit tests for db relation * Change test condition
1 parent 6b6ae85 commit 907cf89

File tree

5 files changed

+539
-80
lines changed

5 files changed

+539
-80
lines changed

src/relations/db.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ def set_up_relation(self, relation: Relation) -> bool:
146146
self.charm.postgresql.create_user(user, password, self.admin)
147147
self.charm.postgresql.create_database(database, user)
148148

149+
# Enable/disable extensions in the new database.
150+
self.charm.enable_disable_extensions(database)
151+
149152
# Build the primary's connection string.
150153
primary = str(
151154
ConnectionString(
@@ -201,6 +204,8 @@ def set_up_relation(self, relation: Relation) -> bool:
201204
)
202205
return False
203206

207+
self._update_unit_status(relation)
208+
204209
return True
205210

206211
def _check_for_blocking_relations(self, relation_id: int) -> bool:
@@ -277,12 +282,15 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
277282
f"Failed to delete user during {self.relation_name} relation broken event"
278283
)
279284

280-
# Clean up Blocked status if caused by the departed relation
285+
self._update_unit_status(event.relation)
286+
287+
def _update_unit_status(self, relation: Relation) -> None:
288+
"""# Clean up Blocked status if it's due to extensions request."""
281289
if (
282290
self.charm._has_blocked_status
283291
and self.charm.unit.status.message == EXTENSIONS_BLOCKING_MESSAGE
284292
):
285-
if not self._check_for_blocking_relations(event.relation.id):
293+
if not self._check_for_blocking_relations(relation.id):
286294
self.charm.unit.status = ActiveStatus()
287295

288296
def _get_allowed_subnets(self, relation: Relation) -> str:

tests/integration/helpers.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,19 @@ async def check_database_users_existence(
132132
assert user not in output
133133

134134

135-
async def check_database_creation(ops_test: OpsTest, database: str) -> None:
135+
async def check_database_creation(
136+
ops_test: OpsTest, database: str, database_app_name: str = DATABASE_APP_NAME
137+
) -> None:
136138
"""Checks that database and tables are successfully created for the application.
137139
138140
Args:
139141
ops_test: The ops test framework
140142
database: Name of the database that should have been created
143+
database_app_name: Application name of the database charm
141144
"""
142-
password = await get_password(ops_test)
145+
password = await get_password(ops_test, database_app_name=database_app_name)
143146

144-
for unit in ops_test.model.applications[DATABASE_APP_NAME].units:
147+
for unit in ops_test.model.applications[database_app_name].units:
145148
unit_address = await get_unit_address(ops_test, unit.name)
146149

147150
# Ensure database exists in PostgreSQL.

tests/integration/test_db.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
check_database_creation,
1313
check_database_users_existence,
1414
deploy_and_relate_application_with_postgresql,
15+
get_primary,
1516
wait_for_relation_removed_between,
1617
)
1718

1819
EXTENSIONS_BLOCKING_MESSAGE = "extensions requested through relation"
1920
FINOS_WALTZ_APP_NAME = "finos-waltz"
2021
ANOTHER_FINOS_WALTZ_APP_NAME = "another-finos-waltz"
22+
DISCOURSE_APP_NAME = "discourse-k8s"
23+
REDIS_APP_NAME = "redis-k8s"
2124
APPLICATION_UNITS = 1
2225
DATABASE_UNITS = 3
2326

@@ -196,7 +199,7 @@ async def test_indico_db_blocked(ops_test: OpsTest) -> None:
196199
)
197200
wait_for_relation_removed_between(ops_test, database_application_name, "indico1")
198201
await ops_test.model.wait_for_idle(
199-
apps=[database_application_name], status="active", idle_period=15, timeout=2000
202+
apps=[database_application_name, "indico1"], status="active", idle_period=15
200203
)
201204

202205
await ops_test.model.relate(f"{database_application_name}:db", "indico1:db")
@@ -217,9 +220,62 @@ async def test_indico_db_blocked(ops_test: OpsTest) -> None:
217220

218221
# Cleanup
219222
await gather(
220-
ops_test.model.remove_application(database_application_name, block_until_done=True),
221223
ops_test.model.remove_application("indico1", block_until_done=True),
222224
ops_test.model.remove_application("indico2", block_until_done=True),
223225
ops_test.model.remove_application("redis-broker", block_until_done=True),
224226
ops_test.model.remove_application("redis-cache", block_until_done=True),
225227
)
228+
229+
230+
async def test_discourse(ops_test: OpsTest):
231+
database_application_name = f"extensions-{DATABASE_APP_NAME}"
232+
233+
# Deploy Discourse and Redis.
234+
await gather(
235+
ops_test.model.deploy(DISCOURSE_APP_NAME, application_name=DISCOURSE_APP_NAME),
236+
ops_test.model.deploy(REDIS_APP_NAME, application_name=REDIS_APP_NAME),
237+
)
238+
239+
# Add both relations to Discourse (PostgreSQL and Redis)
240+
# and wait for it to be ready.
241+
relation = await ops_test.model.add_relation(
242+
f"{database_application_name}:db",
243+
DISCOURSE_APP_NAME,
244+
)
245+
await ops_test.model.add_relation(
246+
REDIS_APP_NAME,
247+
DISCOURSE_APP_NAME,
248+
)
249+
250+
# Discourse requests extensions through relation, so check that the PostgreSQL charm
251+
# becomes blocked.
252+
primary_name = await get_primary(ops_test, database_app_name=database_application_name)
253+
async with ops_test.fast_forward():
254+
await gather(
255+
ops_test.model.block_until(
256+
lambda: ops_test.model.units[primary_name].workload_status == "blocked", timeout=60
257+
),
258+
ops_test.model.wait_for_idle(
259+
apps=[REDIS_APP_NAME], status="active", timeout=2000
260+
), # Redis sometimes takes a longer time to become active.
261+
)
262+
assert (
263+
ops_test.model.units[primary_name].workload_status_message
264+
== EXTENSIONS_BLOCKING_MESSAGE
265+
)
266+
267+
# Enable the plugins/extensions required by Discourse.
268+
config = {"plugin_hstore_enable": "True", "plugin_pg_trgm_enable": "True"}
269+
await ops_test.model.applications[database_application_name].set_config(config)
270+
await ops_test.model.wait_for_idle(
271+
apps=[database_application_name, DISCOURSE_APP_NAME, REDIS_APP_NAME], status="active"
272+
)
273+
274+
# Check for the correct databases and users creation.
275+
await check_database_creation(
276+
ops_test, "discourse", database_app_name=database_application_name
277+
)
278+
discourse_users = [f"relation_id_{relation.id}"]
279+
await check_database_users_existence(
280+
ops_test, discourse_users, [], database_app_name=database_application_name
281+
)

tests/integration/test_db_admin.py

Lines changed: 12 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,99 +6,40 @@
66
import pytest as pytest
77
from pytest_operator.plugin import OpsTest
88

9-
from tests.helpers import METADATA
109
from tests.integration.helpers import (
11-
CHARM_SERIES,
1210
DATABASE_APP_NAME,
11+
build_and_deploy,
1312
check_database_creation,
1413
check_database_users_existence,
15-
get_primary,
1614
get_unit_address,
1715
)
1816

19-
FIRST_DISCOURSE_APP_NAME = "discourse-k8s"
20-
SECOND_DISCOURSE_APP_NAME = "discourse-charmers-discourse-k8s"
17+
DISCOURSE_APP_NAME = "discourse-charmers-discourse-k8s"
2118
REDIS_APP_NAME = "redis-k8s"
2219
APPLICATION_UNITS = 1
2320
DATABASE_UNITS = 3
2421

2522

2623
@pytest.mark.abort_on_fail
27-
async def test_build_and_deploy(ops_test: OpsTest):
28-
"""Build the charm-under-test and deploy it.
29-
30-
Assert on the unit status before any relations/configurations take place.
31-
"""
24+
async def test_discourse_from_discourse_charmers(ops_test: OpsTest):
25+
# Build and deploy charm from local source folder (and also redis from Charmhub).
26+
# Both are needed by Discourse.
3227
async with ops_test.fast_forward():
33-
# Build and deploy charm from local source folder (and also redis from Charmhub).
34-
# Both are needed by Discourse charms.
35-
charm = await ops_test.build_charm(".")
36-
resources = {
37-
"postgresql-image": METADATA["resources"]["postgresql-image"]["upstream-source"],
38-
}
3928
await asyncio.gather(
40-
ops_test.model.deploy(
41-
charm,
42-
resources=resources,
43-
application_name=DATABASE_APP_NAME,
44-
trust=True,
45-
num_units=DATABASE_UNITS,
46-
series=CHARM_SERIES,
47-
),
48-
ops_test.model.deploy(
49-
FIRST_DISCOURSE_APP_NAME, application_name=FIRST_DISCOURSE_APP_NAME
50-
),
29+
build_and_deploy(ops_test, DATABASE_UNITS),
5130
ops_test.model.deploy(REDIS_APP_NAME, application_name=REDIS_APP_NAME),
5231
)
5332
await ops_test.model.wait_for_idle(
54-
apps=[DATABASE_APP_NAME, REDIS_APP_NAME], status="active", timeout=1000
33+
apps=[DATABASE_APP_NAME, REDIS_APP_NAME], status="active", timeout=1500
5534
)
56-
# Discourse waits for relations.
57-
await ops_test.model.wait_for_idle(
58-
apps=[FIRST_DISCOURSE_APP_NAME], status="waiting", timeout=1000
59-
)
60-
61-
62-
async def test_discourse(ops_test: OpsTest):
63-
# Test the first Discourse charm.
64-
# Add both relations to Discourse (PostgreSQL and Redis)
65-
# and wait for it to be ready.
66-
await ops_test.model.add_relation(
67-
f"{DATABASE_APP_NAME}:db-admin",
68-
FIRST_DISCOURSE_APP_NAME,
69-
)
70-
await ops_test.model.add_relation(
71-
REDIS_APP_NAME,
72-
FIRST_DISCOURSE_APP_NAME,
73-
)
74-
75-
# Discourse requests extensions through relation, so check that the PostgreSQL charm
76-
# becomes blocked.
77-
primary_name = await get_primary(ops_test)
78-
await ops_test.model.block_until(
79-
lambda: ops_test.model.units[primary_name].workload_status == "blocked", timeout=60
80-
)
81-
assert (
82-
ops_test.model.units[primary_name].workload_status_message
83-
== "extensions requested through relation"
84-
)
85-
86-
# Destroy the relation to remove the blocked status.
87-
await ops_test.model.applications[DATABASE_APP_NAME].destroy_relation(
88-
f"{DATABASE_APP_NAME}:db-admin", FIRST_DISCOURSE_APP_NAME
89-
)
90-
91-
92-
async def test_discourse_from_discourse_charmers(ops_test: OpsTest):
93-
# Test the second Discourse charm.
9435

9536
# Get the Redis instance IP address.
9637
redis_host = await get_unit_address(ops_test, f"{REDIS_APP_NAME}/0")
9738

9839
# Deploy Discourse and wait for it to be blocked waiting for database relation.
9940
await ops_test.model.deploy(
100-
SECOND_DISCOURSE_APP_NAME,
101-
application_name=SECOND_DISCOURSE_APP_NAME,
41+
DISCOURSE_APP_NAME,
42+
application_name=DISCOURSE_APP_NAME,
10243
config={
10344
"redis_host": redis_host,
10445
"developer_emails": "user@foo.internal",
@@ -108,17 +49,15 @@ async def test_discourse_from_discourse_charmers(ops_test: OpsTest):
10849
},
10950
)
11051
# Discourse becomes blocked waiting for PostgreSQL relation.
111-
await ops_test.model.wait_for_idle(
112-
apps=[SECOND_DISCOURSE_APP_NAME], status="blocked", timeout=1000
113-
)
52+
await ops_test.model.wait_for_idle(apps=[DISCOURSE_APP_NAME], status="blocked", timeout=1000)
11453

11554
# Relate PostgreSQL and Discourse, waiting for Discourse to be ready.
11655
relation = await ops_test.model.add_relation(
11756
f"{DATABASE_APP_NAME}:db-admin",
118-
SECOND_DISCOURSE_APP_NAME,
57+
DISCOURSE_APP_NAME,
11958
)
12059
await ops_test.model.wait_for_idle(
121-
apps=[DATABASE_APP_NAME, SECOND_DISCOURSE_APP_NAME, REDIS_APP_NAME],
60+
apps=[DATABASE_APP_NAME, DISCOURSE_APP_NAME, REDIS_APP_NAME],
12261
status="active",
12362
timeout=2000, # Discourse takes a longer time to become active (a lot of setup).
12463
)

0 commit comments

Comments
 (0)