Skip to content

Commit 29437d3

Browse files
[DPE-7421] Fix MySQL legacy interface relation (#639)
1 parent dd6cc03 commit 29437d3

File tree

6 files changed

+330
-219
lines changed

6 files changed

+330
-219
lines changed

src/relations/mysql.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def _on_mysql_relation_created(self, event: RelationCreatedEvent) -> None: # no
212212
if (
213213
not self.charm._is_peer_data_set
214214
or not self.charm.unit_initialized()
215-
or self.charm.unit_peer_data.get("member-state") != "online"
215+
or not self.charm.unit_peer_data.get("member-state") == "online"
216216
):
217217
logger.info("Unit not ready to execute `mysql` relation created. Deferring")
218218
event.defer()

src/relations/mysql_root.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
import typing
99

1010
from charms.mysql.v0.mysql import MySQLCheckUserExistenceError, MySQLDeleteUsersForUnitError
11-
from ops.charm import RelationBrokenEvent, RelationCreatedEvent
11+
from ops.charm import (
12+
LeaderElectedEvent,
13+
RelationBrokenEvent,
14+
RelationCreatedEvent,
15+
)
1216
from ops.framework import Object
1317
from ops.model import ActiveStatus, BlockedStatus
1418

@@ -85,14 +89,21 @@ def _get_or_generate_database(self, event_relation_id: int) -> str:
8589
self.charm.config.mysql_root_interface_database or f"database-{event_relation_id}",
8690
)
8791

88-
def _on_leader_elected(self, _) -> None:
92+
def _on_leader_elected(self, event: LeaderElectedEvent) -> None:
8993
"""Handle the leader elected event.
9094
9195
Retrieves relation data from the peer relation databag and copies
9296
the relation data into the new leader unit's databag.
9397
"""
94-
# Skip if the charm is not past the setup phase (config-changed event not executed yet)
95-
if not self.charm._is_peer_data_set:
98+
# Wait until on-config-changed event is executed (for root password to have been set)
99+
# and for the member to be initialized and online
100+
if (
101+
not self.charm._is_peer_data_set
102+
or not self.charm.unit_initialized()
103+
or not self.charm.unit_peer_data.get("member-state") == "online"
104+
):
105+
logger.info("Unit not ready to execute `mysql` leader elected. Deferring")
106+
event.defer()
96107
return
97108

98109
relation_data = json.loads(

tests/integration/relations/test_database.py

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,20 @@
1616
logger = logging.getLogger(__name__)
1717

1818
DB_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
19-
DATABASE_APP_NAME = DB_METADATA["name"]
20-
CLUSTER_NAME = "test_cluster"
2119

20+
DATABASE_APP_NAME = DB_METADATA["name"]
21+
DATABASE_ENDPOINT = "database"
2222
APPLICATION_APP_NAME = "mysql-test-app"
23+
APPLICATION_ENDPOINT = "database"
2324

2425
APPS = [DATABASE_APP_NAME, APPLICATION_APP_NAME]
2526

26-
ENDPOINT = "database"
27-
2827

2928
@pytest.mark.abort_on_fail
3029
@pytest.mark.skip_if_deployed
3130
async def test_build_and_deploy(ops_test: OpsTest, charm):
3231
"""Build the charm and deploy 3 units to ensure a cluster is formed."""
33-
config = {"cluster-name": CLUSTER_NAME, "profile": "testing"}
32+
config = {"cluster-name": "test_cluster", "profile": "testing"}
3433
resources = {"mysql-image": DB_METADATA["resources"]["mysql-image"]["upstream-source"]}
3534

3635
await asyncio.gather(
@@ -52,12 +51,26 @@ async def test_build_and_deploy(ops_test: OpsTest, charm):
5251
),
5352
)
5453

54+
55+
@pytest.mark.abort_on_fail
56+
async def test_relation_creation_eager(ops_test: OpsTest):
57+
"""Relate charms before they have time to properly start.
58+
59+
It simulates a Terraform-like deployment strategy
60+
"""
61+
await ops_test.model.relate(
62+
f"{APPLICATION_APP_NAME}:{APPLICATION_ENDPOINT}",
63+
f"{DATABASE_APP_NAME}:{DATABASE_ENDPOINT}",
64+
)
65+
await ops_test.model.block_until(
66+
lambda: is_relation_joined(ops_test, APPLICATION_ENDPOINT, DATABASE_ENDPOINT) == True # noqa: E712
67+
)
68+
5569
# Reduce the update_status frequency until the cluster is deployed
5670
async with ops_test.fast_forward("60s"):
5771
await ops_test.model.block_until(
5872
lambda: len(ops_test.model.applications[DATABASE_APP_NAME].units) == 3
5973
)
60-
6174
await ops_test.model.block_until(
6275
lambda: len(ops_test.model.applications[APPLICATION_APP_NAME].units) == 2
6376
)
@@ -78,26 +91,14 @@ async def test_build_and_deploy(ops_test: OpsTest, charm):
7891
),
7992
)
8093

81-
assert len(ops_test.model.applications[DATABASE_APP_NAME].units) == 3
82-
83-
for unit in ops_test.model.applications[DATABASE_APP_NAME].units:
84-
assert unit.workload_status == "active"
85-
86-
assert len(ops_test.model.applications[APPLICATION_APP_NAME].units) == 2
87-
8894

8995
@pytest.mark.abort_on_fail
9096
@markers.only_without_juju_secrets
9197
async def test_relation_creation_databag(ops_test: OpsTest):
9298
"""Relate charms and wait for the expected changes in status."""
93-
await ops_test.model.relate(APPLICATION_APP_NAME, f"{DATABASE_APP_NAME}:{ENDPOINT}")
94-
9599
async with ops_test.fast_forward("60s"):
96-
await ops_test.model.block_until(
97-
lambda: is_relation_joined(ops_test, ENDPOINT, ENDPOINT) == True # noqa: E712
98-
)
99-
100100
await ops_test.model.wait_for_idle(apps=APPS, status="active")
101+
101102
relation_data = await get_relation_data(ops_test, APPLICATION_APP_NAME, "database")
102103
assert {"password", "username"} <= set(relation_data[0]["application-data"])
103104

@@ -106,14 +107,9 @@ async def test_relation_creation_databag(ops_test: OpsTest):
106107
@markers.only_with_juju_secrets
107108
async def test_relation_creation(ops_test: OpsTest):
108109
"""Relate charms and wait for the expected changes in status."""
109-
await ops_test.model.relate(APPLICATION_APP_NAME, f"{DATABASE_APP_NAME}:{ENDPOINT}")
110-
111110
async with ops_test.fast_forward("60s"):
112-
await ops_test.model.block_until(
113-
lambda: is_relation_joined(ops_test, ENDPOINT, ENDPOINT) == True # noqa: E712
114-
)
115-
116111
await ops_test.model.wait_for_idle(apps=APPS, status="active")
112+
117113
relation_data = await get_relation_data(ops_test, APPLICATION_APP_NAME, "database")
118114
assert not {"password", "username"} <= set(relation_data[0]["application-data"])
119115
assert "secret-user" in relation_data[0]["application-data"]
@@ -123,11 +119,12 @@ async def test_relation_creation(ops_test: OpsTest):
123119
async def test_relation_broken(ops_test: OpsTest):
124120
"""Remove relation and wait for the expected changes in status."""
125121
await ops_test.model.applications[DATABASE_APP_NAME].remove_relation(
126-
f"{APPLICATION_APP_NAME}:{ENDPOINT}", f"{DATABASE_APP_NAME}:{ENDPOINT}"
122+
f"{APPLICATION_APP_NAME}:{APPLICATION_ENDPOINT}",
123+
f"{DATABASE_APP_NAME}:{DATABASE_ENDPOINT}",
127124
)
128125

129126
await ops_test.model.block_until(
130-
lambda: is_relation_broken(ops_test, ENDPOINT, ENDPOINT) == True # noqa: E712
127+
lambda: is_relation_broken(ops_test, APPLICATION_ENDPOINT, DATABASE_ENDPOINT) == True # noqa: E712
131128
)
132129

133130
async with ops_test.fast_forward("60s"):

0 commit comments

Comments
 (0)