Skip to content

Commit 1c9704f

Browse files
authored
[DPE-2844] Split indico and extension blocking tests (#163)
* Split indico and extension blocking tests * Bump libs
1 parent 767790e commit 1c9704f

File tree

4 files changed

+112
-62
lines changed

4 files changed

+112
-62
lines changed

lib/charms/data_platform_libs/v0/data_interfaces.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):
320320

321321
# Increment this PATCH version before using `charmcraft publish-lib` or reset
322322
# to 0 if you are raising the major API version
323-
LIBPATCH = 22
323+
LIBPATCH = 24
324324

325325
PYDEPS = ["ops>=2.0.0"]
326326

@@ -526,7 +526,16 @@ def get_content(self) -> Dict[str, str]:
526526
"""Getting cached secret content."""
527527
if not self._secret_content:
528528
if self.meta:
529-
self._secret_content = self.meta.get_content()
529+
try:
530+
self._secret_content = self.meta.get_content(refresh=True)
531+
except (ValueError, ModelError) as err:
532+
# https://bugs.launchpad.net/juju/+bug/2042596
533+
# Only triggered when 'refresh' is set
534+
msg = "ERROR either URI or label should be used for getting an owned secret but not both"
535+
if isinstance(err, ModelError) and msg not in str(err):
536+
raise
537+
# Due to: ValueError: Secret owner cannot use refresh=True
538+
self._secret_content = self.meta.get_content()
530539
return self._secret_content
531540

532541
def set_content(self, content: Dict[str, str]) -> None:
@@ -807,6 +816,9 @@ def _fetch_relation_data_without_secrets(
807816
This is used typically when the Provides side wants to read the Requires side's data,
808817
or when the Requires side may want to read its own data.
809818
"""
819+
if app not in relation.data or not relation.data[app]:
820+
return {}
821+
810822
if fields:
811823
return {k: relation.data[app][k] for k in fields if k in relation.data[app]}
812824
else:
@@ -830,6 +842,9 @@ def _fetch_relation_data_with_secrets(
830842
normal_fields = []
831843

832844
if not fields:
845+
if app not in relation.data or not relation.data[app]:
846+
return {}
847+
833848
all_fields = list(relation.data[app].keys())
834849
normal_fields = [field for field in all_fields if not self._is_secret_field(field)]
835850

@@ -853,8 +868,11 @@ def _fetch_relation_data_with_secrets(
853868

854869
def _update_relation_data_without_secrets(
855870
self, app: Application, relation: Relation, data: Dict[str, str]
856-
):
871+
) -> None:
857872
"""Updating databag contents when no secrets are involved."""
873+
if app not in relation.data or relation.data[app] is None:
874+
return
875+
858876
if any(self._is_secret_field(key) for key in data.keys()):
859877
raise SecretsIllegalUpdateError("Can't update secret {key}.")
860878

@@ -865,8 +883,19 @@ def _delete_relation_data_without_secrets(
865883
self, app: Application, relation: Relation, fields: List[str]
866884
) -> None:
867885
"""Remove databag fields 'fields' from Relation."""
886+
if app not in relation.data or not relation.data[app]:
887+
return
888+
868889
for field in fields:
869-
relation.data[app].pop(field)
890+
try:
891+
relation.data[app].pop(field)
892+
except KeyError:
893+
logger.debug(
894+
"Non-existing field was attempted to be removed from the databag %s, %s",
895+
str(relation.id),
896+
str(field),
897+
)
898+
pass
870899

871900
# Public interface methods
872901
# Handling Relation Fields seamlessly, regardless if in databag or a Juju Secret
@@ -880,9 +909,6 @@ def get_relation(self, relation_name, relation_id) -> Relation:
880909
"Relation %s %s couldn't be retrieved", relation_name, relation_id
881910
)
882911

883-
if not relation.app:
884-
raise DataInterfacesError("Relation's application missing")
885-
886912
return relation
887913

888914
def fetch_relation_data(
@@ -1068,7 +1094,7 @@ def _delete_relation_secret(
10681094
secret = self._get_relation_secret(relation.id, group)
10691095

10701096
if not secret:
1071-
logging.error("Can't update secret for relation %s", str(relation.id))
1097+
logging.error("Can't delete secret for relation %s", str(relation.id))
10721098
return False
10731099

10741100
old_content = secret.get_content()
@@ -1089,7 +1115,10 @@ def _delete_relation_secret(
10891115
# Remove secret from the relation if it's fully gone
10901116
if not new_content:
10911117
field = self._generate_secret_field_name(group)
1092-
relation.data[self.local_app].pop(field)
1118+
try:
1119+
relation.data[self.local_app].pop(field)
1120+
except KeyError:
1121+
pass
10931122

10941123
# Return the content that was removed
10951124
return True
@@ -1807,7 +1836,8 @@ def _assign_relation_alias(self, relation_id: int) -> None:
18071836

18081837
# We need to set relation alias also on the application level so,
18091838
# it will be accessible in show-unit juju command, executed for a consumer application unit
1810-
self.update_relation_data(relation_id, {"alias": available_aliases[0]})
1839+
if self.local_unit.is_leader():
1840+
self.update_relation_data(relation_id, {"alias": available_aliases[0]})
18111841

18121842
def _emit_aliased_event(self, event: RelationChangedEvent, event_name: str) -> None:
18131843
"""Emit an aliased event to a particular relation if it has an alias.
@@ -1894,6 +1924,9 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:
18941924

18951925
# Sets both database and extra user roles in the relation
18961926
# if the roles are provided. Otherwise, sets only the database.
1927+
if not self.local_unit.is_leader():
1928+
return
1929+
18971930
if self.extra_user_roles:
18981931
self.update_relation_data(
18991932
event.relation.id,
@@ -2153,6 +2186,9 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:
21532186
"""Event emitted when the Kafka relation is created."""
21542187
super()._on_relation_created_event(event)
21552188

2189+
if not self.local_unit.is_leader():
2190+
return
2191+
21562192
# Sets topic, extra user roles, and "consumer-group-prefix" in the relation
21572193
relation_data = {
21582194
f: getattr(self, f.replace("-", "_"), "")
@@ -2325,6 +2361,9 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:
23252361
"""Event emitted when the OpenSearch relation is created."""
23262362
super()._on_relation_created_event(event)
23272363

2364+
if not self.local_unit.is_leader():
2365+
return
2366+
23282367
# Sets both index and extra user roles in the relation if the roles are provided.
23292368
# Otherwise, sets only the index.
23302369
data = {"index": self.index}

lib/charms/postgresql_k8s/v0/postgresql.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
# Increment this PATCH version before using `charmcraft publish-lib` or reset
3434
# to 0 if you are raising the major API version
35-
LIBPATCH = 18
35+
LIBPATCH = 20
3636

3737
INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles"
3838

@@ -172,8 +172,7 @@ def create_database(self, database: str, user: str, plugins: List[str] = []) ->
172172
raise PostgreSQLCreateDatabaseError()
173173

174174
# Enable preset extensions
175-
for plugin in plugins:
176-
self.enable_disable_extension(plugin, True, database)
175+
self.enable_disable_extensions({plugin: True for plugin in plugins}, database)
177176

178177
def create_user(
179178
self, user: str, password: str = None, admin: bool = False, extra_user_roles: str = None
@@ -270,22 +269,16 @@ def delete_user(self, user: str) -> None:
270269
logger.error(f"Failed to delete user: {e}")
271270
raise PostgreSQLDeleteUserError()
272271

273-
def enable_disable_extension(self, extension: str, enable: bool, database: str = None) -> None:
272+
def enable_disable_extensions(self, extensions: Dict[str, bool], database: str = None) -> None:
274273
"""Enables or disables a PostgreSQL extension.
275274
276275
Args:
277-
extension: the name of the extensions.
278-
enable: whether the extension should be enabled or disabled.
276+
extensions: the name of the extensions.
279277
database: optional database where to enable/disable the extension.
280278
281279
Raises:
282280
PostgreSQLEnableDisableExtensionError if the operation fails.
283281
"""
284-
statement = (
285-
f"CREATE EXTENSION IF NOT EXISTS {extension};"
286-
if enable
287-
else f"DROP EXTENSION IF EXISTS {extension};"
288-
)
289282
connection = None
290283
try:
291284
if database is not None:
@@ -301,7 +294,12 @@ def enable_disable_extension(self, extension: str, enable: bool, database: str =
301294
with self._connect_to_database(
302295
database=database
303296
) as connection, connection.cursor() as cursor:
304-
cursor.execute(statement)
297+
for extension, enable in extensions.items():
298+
cursor.execute(
299+
f"CREATE EXTENSION IF NOT EXISTS {extension};"
300+
if enable
301+
else f"DROP EXTENSION IF EXISTS {extension};"
302+
)
305303
except psycopg2.errors.UniqueViolation:
306304
pass
307305
except psycopg2.Error:
@@ -514,7 +512,7 @@ def build_postgresql_parameters(
514512
)
515513
if profile == "production":
516514
# Use 25% of the available memory for shared_buffers.
517-
# and the remaind as cache memory.
515+
# and the remaining as cache memory.
518516
shared_buffers = int(available_memory * 0.25)
519517
effective_cache_size = int(available_memory - shared_buffers)
520518
parameters.setdefault("shared_buffers", f"{int(shared_buffers/10**6)}MB")

tests/integration/relations/pgbouncer_provider/test_pgbouncer_provider.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
import asyncio
55
import json
66
import logging
7-
from pathlib import Path
87

98
import psycopg2
109
import pytest
11-
import yaml
1210
from pytest_operator.plugin import OpsTest
1311

1412
from constants import BACKEND_RELATION_NAME
1513

1614
from ...helpers.helpers import (
1715
CHARM_SERIES,
16+
PG,
17+
PGB,
18+
PGB_METADATA,
1819
get_app_relation_databag,
1920
get_backend_relation,
2021
get_backend_user_pass,
@@ -40,12 +41,9 @@
4041
CLIENT_APP_NAME = "postgresql-test-app"
4142
SECONDARY_CLIENT_APP_NAME = "secondary-application"
4243
DATA_INTEGRATOR_APP_NAME = "data-integrator"
43-
PG = "postgresql-k8s"
44-
PGB_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
4544
PGB_RESOURCES = {
4645
"pgbouncer-image": PGB_METADATA["resources"]["pgbouncer-image"]["upstream-source"]
4746
}
48-
PGB = "pgbouncer-k8s"
4947
APP_NAMES = [CLIENT_APP_NAME, PG, PGB]
5048
FIRST_DATABASE_RELATION_NAME = "first-database"
5149
SECOND_DATABASE_RELATION_NAME = "second-database"
@@ -468,6 +466,42 @@ async def test_relation_with_data_integrator(ops_test: OpsTest):
468466
await ops_test.model.wait_for_idle(status="active")
469467

470468

469+
async def test_indico_datatabase(ops_test: OpsTest) -> None:
470+
"""Tests deploying and relating to the Indico charm."""
471+
async with ops_test.fast_forward(fast_interval="30s"):
472+
await ops_test.model.deploy(
473+
"indico",
474+
channel="stable",
475+
application_name="indico",
476+
num_units=1,
477+
)
478+
await ops_test.model.deploy("redis-k8s", channel="stable", application_name="redis-broker")
479+
await ops_test.model.deploy("redis-k8s", channel="stable", application_name="redis-cache")
480+
await asyncio.gather(
481+
ops_test.model.relate("redis-broker", "indico:redis-broker"),
482+
ops_test.model.relate("redis-cache", "indico:redis-cache"),
483+
)
484+
485+
# Wait for model to stabilise
486+
await ops_test.model.wait_for_idle(
487+
apps=["indico"],
488+
status="waiting",
489+
timeout=1000,
490+
)
491+
492+
# Verify that the charm doesn't block when the extensions are enabled.
493+
logger.info("Verifying that the charm doesn't block when the extensions are enabled")
494+
config = {"plugin_pg_trgm_enable": "True", "plugin_unaccent_enable": "True"}
495+
await ops_test.model.applications[PG].set_config(config)
496+
await ops_test.model.wait_for_idle(apps=[PG], status="active")
497+
await ops_test.model.relate(PGB, "indico")
498+
await ops_test.model.wait_for_idle(
499+
apps=[PG, PGB, "indico"],
500+
status="active",
501+
timeout=2000,
502+
)
503+
504+
471505
async def test_connection_is_possible_after_pod_deletion(ops_test: OpsTest) -> None:
472506
"""Tests that the connection is possible after the pod is deleted."""
473507
# Delete the pod.

tests/integration/relations/test_db.py

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33

44
import asyncio
55
import logging
6-
from pathlib import Path
76

87
import pytest
9-
import yaml
108
from pytest_operator.plugin import OpsTest
119

1210
from constants import EXTENSIONS_BLOCKING_MESSAGE
1311

1412
from ..helpers.helpers import (
1513
CHARM_SERIES,
14+
CLIENT_APP_NAME,
15+
PG,
16+
PGB,
17+
PGB_METADATA,
1618
get_app_relation_databag,
1719
get_backend_user_pass,
1820
get_cfg,
@@ -25,9 +27,6 @@
2527
check_database_users_existence,
2628
)
2729

28-
METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
29-
PGB = METADATA["name"]
30-
PG = "postgresql-k8s"
3130
FINOS_WALTZ = "finos-waltz"
3231
ANOTHER_FINOS_WALTZ = "another-finos-waltz"
3332
OPENLDAP = "openldap"
@@ -39,7 +38,7 @@ async def test_create_db_legacy_relation(ops_test: OpsTest, pgb_charm):
3938
"""Test that the pgbouncer and postgres charms can relate to one another."""
4039
# Build, deploy, and relate charms.
4140
resources = {
42-
"pgbouncer-image": METADATA["resources"]["pgbouncer-image"]["upstream-source"],
41+
"pgbouncer-image": PGB_METADATA["resources"]["pgbouncer-image"]["upstream-source"],
4342
}
4443

4544
async with ops_test.fast_forward():
@@ -170,48 +169,28 @@ async def test_create_db_legacy_relation(ops_test: OpsTest, pgb_charm):
170169
assert "waltz_standby" not in cfg["databases"].keys()
171170

172171

173-
@pytest.mark.skip(reason="Should be ported and moved to the new relation tests")
174-
async def test_relation_with_indico(ops_test: OpsTest):
175-
"""Test the relation with Indico charm."""
176-
logger.info("Deploying indico")
177-
await ops_test.model.deploy("indico", channel="stable")
178-
await ops_test.model.deploy("redis-k8s", channel="edge", application_name="redis-broker")
179-
await ops_test.model.deploy("redis-k8s", channel="edge", application_name="redis-cache")
180-
await asyncio.gather(
181-
ops_test.model.relate("redis-broker", "indico"),
182-
ops_test.model.relate("redis-cache", "indico"),
183-
)
184-
await ops_test.model.add_relation(f"{PGB}:db", "indico:db")
185-
186-
# Wait for model to stabilise
187-
await ops_test.model.wait_for_idle(
188-
apps=["indico"],
189-
status="waiting",
190-
raise_on_blocked=False,
191-
timeout=1000,
192-
)
193-
unit = ops_test.model.units.get("indico/0")
194-
await ops_test.model.block_until(
195-
lambda: unit.workload_status_message == "Waiting for database availability",
196-
timeout=1000,
197-
)
172+
async def test_extensions_blocking(ops_test: OpsTest) -> None:
173+
"""Test the relation blocks with extensions."""
174+
logger.info("Deploying test app")
175+
await ops_test.model.deploy(CLIENT_APP_NAME)
176+
await ops_test.model.add_relation(f"{PGB}:db", f"{CLIENT_APP_NAME}:db")
198177

199178
logger.info("Wait for PGB to block due to extensions")
200179
await ops_test.model.wait_for_idle(apps=[PGB], status="blocked", timeout=1000)
201180
assert (
202181
ops_test.model.applications[PGB].units[0].workload_status_message
203182
== EXTENSIONS_BLOCKING_MESSAGE
204183
)
205-
await ops_test.model.applications[PGB].destroy_relation(f"{PGB}:db", "indico:db")
184+
await ops_test.model.applications[PGB].destroy_relation(f"{PGB}:db", f"{CLIENT_APP_NAME}:db")
206185
await ops_test.model.wait_for_idle(apps=[PGB], status="active", idle_period=15)
207186

208187
logger.info("Rerelate with extensions enabled")
209188
config = {"plugin_pg_trgm_enable": "True", "plugin_unaccent_enable": "True"}
210189
await ops_test.model.applications[PG].set_config(config)
211190
await ops_test.model.wait_for_idle(apps=[PG], status="active", idle_period=15)
212-
await ops_test.model.relate(f"{PGB}:db", "indico:db")
191+
await ops_test.model.relate(f"{PGB}:db", f"{CLIENT_APP_NAME}:db")
213192
await ops_test.model.wait_for_idle(
214-
apps=[PG, PGB, "indico"],
193+
apps=[PG, PGB, CLIENT_APP_NAME],
215194
status="active",
216195
raise_on_blocked=False,
217196
timeout=3000,

0 commit comments

Comments
 (0)