Skip to content

Commit 44c82db

Browse files
Support predefined roles
1 parent 57441ed commit 44c82db

File tree

20 files changed

+1114
-296
lines changed

20 files changed

+1114
-296
lines changed

lib/charms/mysql/v0/mysql.py

Lines changed: 221 additions & 91 deletions
Large diffs are not rendered by default.

src/charm.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
MySQLAddInstanceToClusterError,
3333
MySQLCharmBase,
3434
MySQLConfigureInstanceError,
35+
MySQLConfigureMySQLRolesError,
3536
MySQLConfigureMySQLUsersError,
3637
MySQLCreateClusterError,
3738
MySQLCreateClusterSetError,
@@ -326,6 +327,9 @@ def _on_start(self, event: StartEvent) -> None:
326327

327328
try:
328329
self.workload_initialise()
330+
except MySQLConfigureMySQLRolesError:
331+
self.unit.status = BlockedStatus("Failed to initialize MySQL roles")
332+
return
329333
except MySQLConfigureMySQLUsersError:
330334
self.unit.status = BlockedStatus("Failed to initialize MySQL users")
331335
return
@@ -350,19 +354,7 @@ def _on_start(self, event: StartEvent) -> None:
350354
self.unit_peer_data["member-state"] = "waiting"
351355
return
352356

353-
try:
354-
# Create the cluster and cluster set from the leader unit
355-
logger.info(f"Creating cluster {self.app_peer_data['cluster-name']}")
356-
self.create_cluster()
357-
self._open_ports()
358-
self.unit.status = ActiveStatus(self.active_status_message)
359-
except (
360-
MySQLCreateClusterError,
361-
MySQLCreateClusterSetError,
362-
MySQLInitializeJujuOperationsTableError,
363-
) as e:
364-
logger.exception("Failed to create cluster")
365-
raise e
357+
self._create_cluster()
366358

367359
def _on_peer_relation_changed(self, event: RelationChangedEvent) -> None:
368360
"""Handle the peer relation changed event."""
@@ -770,7 +762,9 @@ def workload_initialise(self) -> None:
770762
self._mysql.write_mysqld_config()
771763
self.log_rotation_setup.setup()
772764
self._mysql.reset_root_password_and_start_mysqld()
773-
self._mysql.configure_mysql_users()
765+
self._mysql.configure_mysql_router_roles()
766+
self._mysql.configure_mysql_system_roles()
767+
self._mysql.configure_mysql_system_users()
774768

775769
if self.config.plugin_audit_enabled:
776770
self._mysql.install_plugins(["audit_log"])
@@ -806,16 +800,6 @@ def update_endpoints(self) -> None:
806800
self.database_relation._update_endpoints_all_relations(None)
807801
self._on_update_status(None)
808802

809-
def _open_ports(self) -> None:
810-
"""Open ports.
811-
812-
Used if `juju expose` ran on application
813-
"""
814-
try:
815-
self.unit.set_ports(3306, 33060)
816-
except ops.ModelError:
817-
logger.exception("failed to open port")
818-
819803
def _can_start(self, event: StartEvent) -> bool:
820804
"""Check if the unit can start.
821805
@@ -865,6 +849,22 @@ def _can_start(self, event: StartEvent) -> bool:
865849

866850
return True
867851

852+
def _create_cluster(self) -> None:
853+
"""Creates the InnoDB cluster and sets up the ports."""
854+
try:
855+
# Create the cluster and cluster set from the leader unit
856+
logger.info(f"Creating cluster {self.app_peer_data['cluster-name']}")
857+
self.create_cluster()
858+
self.unit.set_ports(3306, 33060)
859+
self.unit.status = ActiveStatus(self.active_status_message)
860+
except (
861+
MySQLCreateClusterError,
862+
MySQLCreateClusterSetError,
863+
MySQLInitializeJujuOperationsTableError,
864+
) as e:
865+
logger.exception("Failed to create cluster")
866+
raise e
867+
868868
def _is_unit_waiting_to_join_cluster(self) -> bool:
869869
"""Return if the unit is waiting to join the cluster."""
870870
# alternatively, we could check if the instance is configured

src/mysql_vm_helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
MYSQLD_DEFAULTS_CONFIG_FILE,
5555
MYSQLD_SOCK_FILE,
5656
ROOT_SYSTEM_USER,
57+
ROOT_USERNAME,
5758
XTRABACKUP_PLUGIN_DIR,
5859
)
5960

@@ -819,7 +820,7 @@ def _run_mysqlsh_script(
819820
def _run_mysqlcli_script(
820821
self,
821822
script: tuple[Any, ...] | list[Any],
822-
user: str = "root",
823+
user: str = ROOT_USERNAME,
823824
password: str | None = None,
824825
timeout: int | None = None,
825826
exception_as_warning: bool = False,

src/relations/db_router.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from charms.mysql.v0.mysql import (
1212
MySQLCheckUserExistenceError,
1313
MySQLConfigureRouterUserError,
14-
MySQLCreateApplicationDatabaseAndScopedUserError,
14+
MySQLCreateApplicationDatabaseError,
15+
MySQLCreateApplicationScopedUserError,
1516
MySQLDeleteUsersForUnitError,
1617
MySQLGetClusterPrimaryAddressError,
1718
)
@@ -124,8 +125,8 @@ def _create_requested_users(
124125
Raises:
125126
MySQLCheckUserExistenceError if there is an issue checking a user's existence
126127
MySQLConfigureRouterUserError if there is an issue configuring the mysqlrouter user
127-
MySQLCreateApplicationDatabaseAndScopedUserError if there is an issue creating a
128-
user or said user scoped database
128+
MySQLCreateApplicationDatabaseError if there is an issue creating the database
129+
MySQLCreateApplicationScopedUserError if there is an issue creating the database user
129130
"""
130131
user_passwords = {}
131132
requested_user_applications = set()
@@ -141,7 +142,8 @@ def _create_requested_users(
141142
requested_user.username, password, requested_user.hostname, user_unit_name
142143
)
143144
else:
144-
self.charm._mysql.create_application_database_and_scoped_user(
145+
self.charm._mysql.create_database(requested_user.database)
146+
self.charm._mysql.create_scoped_user(
145147
requested_user.database,
146148
requested_user.username,
147149
password,
@@ -227,7 +229,8 @@ def _on_db_router_relation_changed(self, event: RelationChangedEvent) -> None:
227229
except (
228230
MySQLCheckUserExistenceError,
229231
MySQLConfigureRouterUserError,
230-
MySQLCreateApplicationDatabaseAndScopedUserError,
232+
MySQLCreateApplicationDatabaseError,
233+
MySQLCreateApplicationScopedUserError,
231234
):
232235
self.charm.unit.status = BlockedStatus("Failed to create app user or scoped database")
233236
return

src/relations/mysql.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
from charms.mysql.v0.mysql import (
1212
MySQLCheckUserExistenceError,
13-
MySQLCreateApplicationDatabaseAndScopedUserError,
13+
MySQLCreateApplicationDatabaseError,
14+
MySQLCreateApplicationScopedUserError,
1415
MySQLDeleteUsersForUnitError,
1516
MySQLGetClusterPrimaryAddressError,
1617
)
@@ -195,7 +196,8 @@ def _on_mysql_relation_created(self, event: RelationCreatedEvent) -> None:
195196
password = self._get_or_set_password_in_peer_secrets(username)
196197

197198
try:
198-
self.charm._mysql.create_application_database_and_scoped_user(
199+
self.charm._mysql.create_database(database)
200+
self.charm._mysql.create_scoped_user(
199201
database,
200202
username,
201203
password,
@@ -204,9 +206,9 @@ def _on_mysql_relation_created(self, event: RelationCreatedEvent) -> None:
204206
)
205207

206208
primary_address = self.charm._mysql.get_cluster_primary_address()
207-
208209
except (
209-
MySQLCreateApplicationDatabaseAndScopedUserError,
210+
MySQLCreateApplicationDatabaseError,
211+
MySQLCreateApplicationScopedUserError,
210212
MySQLGetClusterPrimaryAddressError,
211213
):
212214
self.charm.unit.status = BlockedStatus("Failed to initialize `mysql` relation")

src/relations/mysql_provider.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@
88

99
from charms.data_platform_libs.v0.data_interfaces import DatabaseProvides, DatabaseRequestedEvent
1010
from charms.mysql.v0.mysql import (
11+
LEGACY_ROLE_ROUTER,
12+
MODERN_ROLE_ROUTER,
1113
MySQLClientError,
12-
MySQLCreateApplicationDatabaseAndScopedUserError,
14+
MySQLCreateApplicationDatabaseError,
15+
MySQLCreateApplicationScopedUserError,
1316
MySQLDeleteUserError,
1417
MySQLDeleteUsersForRelationError,
1518
MySQLGetClusterEndpointsError,
1619
MySQLGetClusterMembersAddressesError,
1720
MySQLGetMySQLVersionError,
18-
MySQLGrantPrivilegesToUserError,
1921
MySQLRemoveRouterFromMetadataError,
2022
)
2123
from ops.charm import RelationBrokenEvent, RelationDepartedEvent, RelationJoinedEvent
2224
from ops.framework import Object
23-
from ops.model import BlockedStatus
25+
from ops.model import ActiveStatus, BlockedStatus
2426

2527
from constants import DB_RELATION_NAME, PASSWORD_LENGTH, PEER
2628
from utils import generate_random_password
@@ -220,16 +222,17 @@ def _on_database_requested(self, event: DatabaseRequestedEvent):
220222

221223
# get base relation data
222224
relation_id = event.relation.id
225+
app_name = event.app.name
223226
db_name = event.database
227+
224228
extra_user_roles = []
225229
if event.extra_user_roles:
226230
extra_user_roles = event.extra_user_roles.split(",")
231+
227232
# user name is derived from the relation id
228233
db_user = self._get_username(relation_id)
229234
db_pass = self._get_or_set_password(event.relation)
230235

231-
remote_app = event.app.name
232-
233236
# Update endpoint addresses
234237
self.charm.update_endpoint_address(DB_RELATION_NAME)
235238

@@ -242,31 +245,26 @@ def _on_database_requested(self, event: DatabaseRequestedEvent):
242245
self.database.set_version(relation_id, db_version)
243246
self.database.set_read_only_endpoints(relation_id, ro_endpoints)
244247

245-
if "mysqlrouter" in extra_user_roles:
246-
self.charm._mysql.create_application_database_and_scoped_user(
247-
db_name,
248-
db_user,
249-
db_pass,
250-
"%",
251-
# MySQL Router charm does not need a new database
252-
create_database=False,
253-
)
254-
self.charm._mysql.grant_privileges_to_user(
255-
db_user, "%", ["ALL PRIVILEGES"], with_grant_option=True
256-
)
257-
else:
258-
# TODO:
259-
# add setup of tls, tls_ca and status
260-
self.charm._mysql.create_application_database_and_scoped_user(
261-
db_name, db_user, db_pass, "%"
262-
)
263-
264-
logger.info(f"Created user for app {remote_app}")
248+
if not any([
249+
LEGACY_ROLE_ROUTER in extra_user_roles,
250+
MODERN_ROLE_ROUTER in extra_user_roles,
251+
]):
252+
self.charm._mysql.create_database(db_name)
253+
254+
self.charm._mysql.create_scoped_user(
255+
db_name,
256+
db_user,
257+
db_pass,
258+
"%",
259+
extra_roles=extra_user_roles,
260+
)
261+
logger.info(f"Created user for app {app_name}")
262+
self.charm.unit.status = ActiveStatus()
265263
except (
266-
MySQLCreateApplicationDatabaseAndScopedUserError,
264+
MySQLCreateApplicationDatabaseError,
265+
MySQLCreateApplicationScopedUserError,
267266
MySQLGetMySQLVersionError,
268267
MySQLGetClusterMembersAddressesError,
269-
MySQLGrantPrivilegesToUserError,
270268
MySQLClientError,
271269
) as e:
272270
logger.exception("Failed to set up database relation", exc_info=e)

src/relations/shared_db.py

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

99
from charms.mysql.v0.mysql import (
10-
MySQLCreateApplicationDatabaseAndScopedUserError,
10+
MySQLCreateApplicationDatabaseError,
11+
MySQLCreateApplicationScopedUserError,
1112
MySQLGetClusterPrimaryAddressError,
1213
)
1314
from ops.charm import LeaderElectedEvent, RelationChangedEvent, RelationDepartedEvent
@@ -153,8 +154,13 @@ def _on_shared_db_relation_changed(self, event: RelationChangedEvent) -> None:
153154
remote_host = event.relation.data[event.unit].get("private-address")
154155

155156
try:
156-
self._charm._mysql.create_application_database_and_scoped_user(
157-
database_name, database_user, password, remote_host, unit_name=joined_unit
157+
self._charm._mysql.create_database(database_name)
158+
self._charm._mysql.create_scoped_user(
159+
database_name,
160+
database_user,
161+
password,
162+
remote_host,
163+
unit_name=joined_unit,
158164
)
159165

160166
# set the relation data for consumption
@@ -179,7 +185,10 @@ def _on_shared_db_relation_changed(self, event: RelationChangedEvent) -> None:
179185
allowed_units_set
180186
)
181187

182-
except MySQLCreateApplicationDatabaseAndScopedUserError:
188+
except (
189+
MySQLCreateApplicationDatabaseError,
190+
MySQLCreateApplicationScopedUserError,
191+
):
183192
self._charm.unit.status = BlockedStatus("Failed to initialize shared_db relation")
184193
return
185194

tests/integration/roles/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright 2025 Canonical Ltd.
2+
# See LICENSE file for licensing details.

0 commit comments

Comments
 (0)