Skip to content

Commit bee7876

Browse files
Support predefined roles
1 parent 29437d3 commit bee7876

File tree

16 files changed

+816
-172
lines changed

16 files changed

+816
-172
lines changed

lib/charms/mysql/v0/mysql.py

Lines changed: 220 additions & 90 deletions
Large diffs are not rendered by default.

src/charm.py

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
MySQLAddInstanceToClusterError,
3535
MySQLCharmBase,
3636
MySQLConfigureInstanceError,
37+
MySQLConfigureMySQLRolesError,
3738
MySQLConfigureMySQLUsersError,
3839
MySQLCreateClusterError,
40+
MySQLCreateClusterSetError,
3941
MySQLGetClusterPrimaryAddressError,
4042
MySQLGetMySQLVersionError,
4143
MySQLInitializeJujuOperationsTableError,
@@ -372,6 +374,25 @@ def is_unit_busy(self) -> bool:
372374
"""Returns whether the unit is busy."""
373375
return self._is_cluster_blocked()
374376

377+
def _create_cluster(self) -> None:
378+
juju_version = ops.JujuVersion.from_environ()
379+
380+
try:
381+
# Create the cluster when is the leader unit
382+
logger.info(f"Creating cluster {self.app_peer_data['cluster-name']}")
383+
self.create_cluster()
384+
self.unit.set_ports(3306, 33060) if juju_version.supports_open_port_on_k8s else None
385+
self.unit.status = ops.ActiveStatus(self.active_status_message)
386+
except (
387+
MySQLCreateClusterError,
388+
MySQLCreateClusterSetError,
389+
MySQLInitializeJujuOperationsTableError,
390+
MySQLNoMemberStateError,
391+
MySQLUnableToGetMemberStateError,
392+
):
393+
logger.exception("Failed to initialize primary")
394+
raise
395+
375396
def _get_primary_from_online_peer(self) -> Optional[str]:
376397
"""Get the primary address from an online peer."""
377398
for unit in self.peers.units:
@@ -677,17 +698,6 @@ def _on_leader_elected(self, _) -> None:
677698
"cluster-set-domain-name", self.config.cluster_set_name or f"cluster-set-{common_hash}"
678699
)
679700

680-
def _open_ports(self) -> None:
681-
"""Open ports if supported.
682-
683-
Used if `juju expose` ran on application
684-
"""
685-
if ops.JujuVersion.from_environ().supports_open_port_on_k8s:
686-
try:
687-
self.unit.set_ports(3306, 33060)
688-
except ops.ModelError:
689-
logger.exception("failed to open port")
690-
691701
def _write_mysqld_configuration(self) -> dict:
692702
"""Write the mysqld configuration to the file."""
693703
memory_limit_bytes = (self.config.profile_limit_memory or 0) * BYTES_1MB
@@ -725,7 +735,9 @@ def _configure_instance(self, container) -> None:
725735

726736
logger.info("Configuring initialized mysqld")
727737
# Configure all base users and revoke privileges from the root users
728-
self._mysql.configure_mysql_users()
738+
self._mysql.configure_mysql_router_roles()
739+
self._mysql.configure_mysql_system_roles()
740+
self._mysql.configure_mysql_system_users()
729741

730742
if self.config.plugin_audit_enabled:
731743
# Enable the audit plugin
@@ -737,6 +749,7 @@ def _configure_instance(self, container) -> None:
737749
except (
738750
MySQLInitialiseMySQLDError,
739751
MySQLServiceNotRunningError,
752+
MySQLConfigureMySQLRolesError,
740753
MySQLConfigureMySQLUsersError,
741754
MySQLConfigureInstanceError,
742755
ChangeError,
@@ -758,8 +771,6 @@ def _configure_instance(self, container) -> None:
758771
else:
759772
container.start(MYSQLD_EXPORTER_SERVICE)
760773

761-
self._open_ports()
762-
763774
try:
764775
# Set workload version
765776
if workload_version := self._mysql.get_mysql_version():
@@ -840,23 +851,7 @@ def _on_mysql_pebble_ready(self, event) -> None:
840851
self.join_unit_to_cluster()
841852
return
842853

843-
try:
844-
# Create the cluster when is the leader unit
845-
logger.info(f"Creating cluster {self.app_peer_data['cluster-name']}")
846-
self.unit.status = MaintenanceStatus("Creating cluster")
847-
self.create_cluster()
848-
self.unit.status = ops.ActiveStatus(self.active_status_message)
849-
850-
except (
851-
MySQLCreateClusterError,
852-
MySQLUnableToGetMemberStateError,
853-
MySQLNoMemberStateError,
854-
MySQLInitializeJujuOperationsTableError,
855-
MySQLCreateClusterError,
856-
):
857-
logger.exception("Failed to initialize primary")
858-
raise
859-
854+
self._create_cluster()
860855
self._mysql.reconcile_binlogs_collection(force_restart=True)
861856

862857
def _handle_potential_cluster_crash_scenario(self) -> bool: # noqa: C901

src/mysql_k8s_helpers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ def _wait_until_unit_removed_from_cluster(self, unit_address: str) -> None:
450450
if unit_address in members_in_cluster:
451451
raise MySQLWaitUntilUnitRemovedFromClusterError("Remove member still in cluster")
452452

453-
def create_database(self, database_name: str) -> None:
453+
def create_database_legacy(self, database_name: str) -> None:
454454
"""Creates a database.
455455
456456
Args:
@@ -475,7 +475,9 @@ def create_database(self, database_name: str) -> None:
475475
logger.exception(f"Failed to create database {database_name}", exc_info=e)
476476
raise MySQLCreateDatabaseError(e.message)
477477

478-
def create_user(self, username: str, password: str, label: str, hostname: str = "%") -> None:
478+
def create_user_legacy(
479+
self, username: str, password: str, label: str, hostname: str = "%"
480+
) -> None:
479481
"""Creates a new user.
480482
481483
Args:

src/relations/mysql.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
from charms.mysql.v0.mysql import (
1111
MySQLCheckUserExistenceError,
12-
MySQLCreateApplicationDatabaseAndScopedUserError,
12+
MySQLCreateApplicationDatabaseError,
13+
MySQLCreateApplicationScopedUserError,
1314
MySQLDeleteUsersForUnitError,
1415
)
1516
from ops.charm import RelationBrokenEvent, RelationChangedEvent, RelationCreatedEvent
@@ -245,14 +246,18 @@ def _on_mysql_relation_created(self, event: RelationCreatedEvent) -> None: # no
245246

246247
try:
247248
logger.info("Creating application database and scoped user")
248-
self.charm._mysql.create_application_database_and_scoped_user(
249+
self.charm._mysql.create_database(database)
250+
self.charm._mysql.create_scoped_user(
249251
database,
250252
username,
251253
password,
252254
"%",
253255
unit_name="mysql-legacy-relation",
254256
)
255-
except MySQLCreateApplicationDatabaseAndScopedUserError:
257+
except (
258+
MySQLCreateApplicationDatabaseError,
259+
MySQLCreateApplicationScopedUserError,
260+
):
256261
self.charm.unit.status = BlockedStatus(
257262
"Failed to create application database and scoped user"
258263
)

src/relations/mysql_provider.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
from charms.data_platform_libs.v0.data_interfaces import DatabaseProvides, DatabaseRequestedEvent
1111
from charms.mysql.v0.mysql import (
12-
MySQLCreateApplicationDatabaseAndScopedUserError,
12+
LEGACY_ROLE_ROUTER,
13+
MODERN_ROLE_ROUTER,
14+
MySQLCreateApplicationDatabaseError,
15+
MySQLCreateApplicationScopedUserError,
1316
MySQLDeleteUserError,
1417
MySQLDeleteUsersForRelationError,
1518
MySQLGetMySQLVersionError,
16-
MySQLGrantPrivilegesToUserError,
1719
MySQLRemoveRouterFromMetadataError,
1820
)
1921
from ops.charm import PebbleReadyEvent, RelationBrokenEvent, RelationDepartedEvent
@@ -107,16 +109,17 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
107109

108110
# get base relation data
109111
relation_id = event.relation.id
112+
app_name = event.app.name
110113
db_name = event.database
114+
111115
extra_user_roles = []
112116
if event.extra_user_roles:
113117
extra_user_roles = event.extra_user_roles.split(",")
118+
114119
# user name is derived from the relation id
115120
db_user = self._get_username(relation_id)
116121
db_pass = self._get_or_set_password(event.relation)
117122

118-
remote_app = event.app.name
119-
120123
try:
121124
# make sure pods are labeled before adding service
122125
self.charm._mysql.update_endpoints(DB_RELATION_NAME)
@@ -132,25 +135,19 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
132135
# wait for endpoints to be ready
133136
self.charm.k8s_helpers.wait_service_ready((primary_endpoint, 3306))
134137

135-
if "mysqlrouter" in extra_user_roles:
136-
self.charm._mysql.create_application_database_and_scoped_user(
137-
db_name,
138-
db_user,
139-
db_pass,
140-
"%",
141-
# MySQL Router charm does not need a new database
142-
create_database=False,
143-
)
144-
self.charm._mysql.grant_privileges_to_user(
145-
db_user, "%", ["ALL PRIVILEGES"], with_grant_option=True
146-
)
147-
else:
148-
# TODO:
149-
# add setup of tls, tls_ca and status
150-
# add extra roles parsing from relation data
151-
self.charm._mysql.create_application_database_and_scoped_user(
152-
db_name, db_user, db_pass, "%"
153-
)
138+
if not any([
139+
LEGACY_ROLE_ROUTER in extra_user_roles,
140+
MODERN_ROLE_ROUTER in extra_user_roles,
141+
]):
142+
self.charm._mysql.create_database(db_name)
143+
144+
self.charm._mysql.create_scoped_user(
145+
db_name,
146+
db_user,
147+
db_pass,
148+
"%",
149+
extra_roles=extra_user_roles,
150+
)
154151

155152
# Set relation data
156153
self.database.set_endpoints(relation_id, f"{primary_endpoint}:3306")
@@ -159,11 +156,12 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
159156
self.database.set_version(relation_id, db_version)
160157
self.database.set_database(relation_id, db_name)
161158

162-
logger.info(f"Created user for app {remote_app}")
159+
logger.info(f"Created user for app {app_name}")
160+
self.charm.unit.status = ActiveStatus()
163161
except (
164-
MySQLCreateApplicationDatabaseAndScopedUserError,
162+
MySQLCreateApplicationDatabaseError,
163+
MySQLCreateApplicationScopedUserError,
165164
MySQLGetMySQLVersionError,
166-
MySQLGrantPrivilegesToUserError,
167165
) as e:
168166
logger.exception("Failed to set up database relation", exc_info=e)
169167
self.charm.unit.status = BlockedStatus("Failed to create scoped user")

src/relations/mysql_root.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,12 @@ def _on_mysql_root_relation_created(self, event: RelationCreatedEvent) -> None:
201201
try:
202202
root_password = self.charm.get_secret("app", ROOT_PASSWORD_KEY)
203203
assert root_password, "Root password not set"
204-
self.charm._mysql.create_database(database)
205-
self.charm._mysql.create_user(username, password, "mysql-root-legacy-relation")
204+
self.charm._mysql.create_database_legacy(database)
205+
self.charm._mysql.create_user_legacy(username, password, "mysql-root-legacy-relation")
206206
if not self.charm._mysql.does_mysql_user_exist("root", "%"):
207207
# create `root@%` user if it doesn't exist
208208
# this is needed for the `mysql-root` interface to work
209-
self.charm._mysql.create_user(
209+
self.charm._mysql.create_user_legacy(
210210
"root",
211211
root_password,
212212
"mysql-root-legacy-relation",

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)