Skip to content

Commit 64607cc

Browse files
[DPE-6488] Port over mysql charmlib changes from vm operator related to unit initialized (#593)
* Port over mysql charmlib changes from vm operator * Remove redundant check when executing manual rejoin * Update to latest the mysql charm lib * Attempt to make scale detection faster and easier in crash during setup test
1 parent 5006746 commit 64607cc

File tree

14 files changed

+119
-70
lines changed

14 files changed

+119
-70
lines changed

lib/charms/mysql/v0/async_replication.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ def _on_consumer_relation_created(self, event):
730730
"""Handle the async_replica relation being created on the leader unit."""
731731
if not self._charm.unit.is_leader():
732732
return
733-
if not self._charm.unit_initialized and not self.returning_cluster:
733+
if not self._charm.unit_initialized() and not self.returning_cluster:
734734
# avoid running too early for non returning clusters
735735
self._charm.unit.status = BlockedStatus(
736736
"Wait until unit is initialized before running create-replication on offer side"

lib/charms/mysql/v0/mysql.py

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def wait_until_mysql_connection(self) -> None:
133133
# Increment this major API version when introducing breaking changes
134134
LIBAPI = 0
135135

136-
LIBPATCH = 84
136+
LIBPATCH = 85
137137

138138
UNIT_TEARDOWN_LOCKNAME = "unit-teardown"
139139
UNIT_ADD_LOCKNAME = "unit-add"
@@ -418,6 +418,10 @@ class MySQLPluginInstallError(Error):
418418
"""Exception raised when there is an issue installing a MySQL plugin."""
419419

420420

421+
class MySQLClusterMetadataExistsError(Error):
422+
"""Exception raised when there is an issue checking if cluster metadata exists."""
423+
424+
421425
@dataclasses.dataclass
422426
class RouterUser:
423427
"""MySQL Router user."""
@@ -618,8 +622,15 @@ def cluster_initialized(self) -> bool:
618622
return False
619623

620624
for unit in self.app_units:
621-
if self._mysql.cluster_metadata_exists(self.get_unit_address(unit)):
622-
return True
625+
try:
626+
if unit != self.unit and self._mysql.cluster_metadata_exists(
627+
self.get_unit_address(unit)
628+
):
629+
return True
630+
elif self._mysql.cluster_metadata_exists():
631+
return True
632+
except MySQLClusterMetadataExistsError:
633+
pass
623634

624635
return False
625636

@@ -660,11 +671,6 @@ def unit_configured(self) -> bool:
660671
self.get_unit_address(self.unit), self.unit_label
661672
)
662673

663-
@property
664-
def unit_initialized(self) -> bool:
665-
"""Check if the unit is added to the cluster."""
666-
return self._mysql.cluster_metadata_exists(self.get_unit_address(self.unit))
667-
668674
@property
669675
def app_peer_data(self) -> Union[ops.RelationDataContent, dict]:
670676
"""Application peer relation data object."""
@@ -743,6 +749,15 @@ def removing_unit(self) -> bool:
743749
"""Check if the unit is being removed."""
744750
return self.unit_peer_data.get("unit-status") == "removing"
745751

752+
def unit_initialized(self, raise_exceptions: bool = False) -> bool:
753+
"""Check if the unit is added to the cluster."""
754+
try:
755+
return self._mysql.cluster_metadata_exists()
756+
except MySQLClusterMetadataExistsError:
757+
if raise_exceptions:
758+
raise
759+
return False
760+
746761
def peer_relation_data(self, scope: Scopes) -> DataPeerData:
747762
"""Returns the peer relation data per scope."""
748763
if scope == APP_SCOPE:
@@ -1671,35 +1686,62 @@ def is_cluster_in_cluster_set(self, cluster_name: str) -> Optional[bool]:
16711686

16721687
return cluster_name in cs_status["clusters"]
16731688

1674-
def cluster_metadata_exists(self, from_instance: str) -> bool:
1675-
"""Check if this cluster metadata exists on database."""
1676-
check_cluster_metadata_commands = (
1677-
"result = session.run_sql(\"SHOW DATABASES LIKE 'mysql_innodb_cluster_metadata'\")",
1678-
"content = result.fetch_all()",
1679-
"if content:",
1680-
(
1681-
' result = session.run_sql("SELECT cluster_name FROM mysql_innodb_cluster_metadata'
1682-
f".clusters where cluster_name = '{self.cluster_name}';\")"
1683-
),
1684-
" print(bool(result.fetch_one()))",
1685-
"else:",
1686-
" print(False)",
1689+
def cluster_metadata_exists(self, from_instance: Optional[str] = None) -> bool:
1690+
"""Check if this cluster metadata exists on database.
1691+
1692+
Use mysqlsh when querying clusters from remote instances. However, use
1693+
mysqlcli when querying locally since this method can be called before
1694+
the cluster is initialized (before serverconfig and root users are set up
1695+
correctly)
1696+
"""
1697+
get_clusters_query = (
1698+
"SELECT cluster_name "
1699+
"FROM mysql_innodb_cluster_metadata.clusters "
1700+
"WHERE EXISTS ("
1701+
"SELECT * "
1702+
"FROM information_schema.schemata "
1703+
"WHERE schema_name = 'mysql_innodb_cluster_metadata'"
1704+
")"
16871705
)
16881706

1707+
if from_instance:
1708+
check_cluster_metadata_commands = (
1709+
f'cursor = session.run_sql("{get_clusters_query}")',
1710+
"print(cursor.fetch_all())",
1711+
)
1712+
1713+
try:
1714+
output = self._run_mysqlsh_script(
1715+
"\n".join(check_cluster_metadata_commands),
1716+
user=self.server_config_user,
1717+
password=self.server_config_password,
1718+
host=self.instance_def(self.server_config_user, from_instance),
1719+
timeout=60,
1720+
exception_as_warning=True,
1721+
)
1722+
except MySQLClientError:
1723+
logger.warning(f"Failed to check if cluster metadata exists {from_instance=}")
1724+
raise MySQLClusterMetadataExistsError(
1725+
f"Failed to check if cluster metadata exists {from_instance=}"
1726+
)
1727+
1728+
return self.cluster_name in output
1729+
16891730
try:
1690-
output = self._run_mysqlsh_script(
1691-
"\n".join(check_cluster_metadata_commands),
1692-
user=self.server_config_user,
1693-
password=self.server_config_password,
1694-
host=self.instance_def(self.server_config_user, from_instance),
1731+
output = self._run_mysqlcli_script(
1732+
(get_clusters_query,),
1733+
user=ROOT_USERNAME,
1734+
password=self.root_password,
16951735
timeout=60,
16961736
exception_as_warning=True,
1737+
log_errors=False,
16971738
)
16981739
except MySQLClientError:
1699-
logger.warning(f"Failed to check if cluster metadata exists {from_instance=}")
1700-
return False
1740+
logger.warning("Failed to check if local cluster metadata exists")
1741+
raise MySQLClusterMetadataExistsError("Failed to check if cluster metadata exists")
17011742

1702-
return output.strip() == "True"
1743+
cluster_names = [entry[0].strip() for entry in output]
1744+
return self.cluster_name in cluster_names
17031745

17041746
def rejoin_cluster(self, cluster_name) -> None:
17051747
"""Try to rejoin a cluster to the cluster set."""
@@ -1939,8 +1981,11 @@ def rescan_cluster(
19391981

19401982
def is_instance_in_cluster(self, unit_label: str) -> bool:
19411983
"""Confirm if instance is in the cluster."""
1942-
if not self.cluster_metadata_exists(self.instance_address):
1943-
# early return if instance has no cluster metadata
1984+
try:
1985+
if not self.cluster_metadata_exists(self.instance_address):
1986+
# early return if instance has no cluster metadata
1987+
return False
1988+
except MySQLClusterMetadataExistsError:
19441989
return False
19451990

19461991
commands = (
@@ -3244,21 +3289,26 @@ def kill_client_sessions(self) -> None:
32443289
logger.error("Failed to kill external sessions")
32453290
raise MySQLKillSessionError
32463291

3247-
def check_mysqlsh_connection(self) -> bool:
3248-
"""Checks if it is possible to connect to the server with mysqlsh."""
3249-
connect_commands = 'session.run_sql("SELECT 1")'
3292+
def check_mysqlcli_connection(self) -> bool:
3293+
"""Checks if it is possible to connect to the server with mysqlcli."""
3294+
connect_commands = ("SELECT 1",)
32503295

32513296
try:
3252-
self._run_mysqlsh_script(
3297+
self._run_mysqlcli_script(
32533298
connect_commands,
32543299
user=self.server_config_user,
32553300
password=self.server_config_password,
3256-
host=self.instance_def(self.server_config_user),
32573301
)
32583302
return True
32593303
except MySQLClientError:
3260-
logger.error("Failed to connect to MySQL with mysqlsh")
3261-
return False
3304+
logger.warning("Failed to connect to MySQL with mysqlcli with server config user")
3305+
3306+
try:
3307+
self._run_mysqlcli_script(connect_commands)
3308+
return True
3309+
except MySQLClientError:
3310+
logger.error("Failed to connect to MySQL with mysqlcli with default root user")
3311+
return False
32623312

32633313
def get_pid_of_port_3306(self) -> Optional[str]:
32643314
"""Retrieves the PID of the process that is bound to port 3306."""
@@ -3427,6 +3477,7 @@ def _run_mysqlcli_script(
34273477
password: Optional[str] = None,
34283478
timeout: Optional[int] = None,
34293479
exception_as_warning: bool = False,
3480+
log_errors: bool = False,
34303481
) -> list:
34313482
"""Execute a MySQL CLI script.
34323483
@@ -3442,6 +3493,7 @@ def _run_mysqlcli_script(
34423493
password: (optional) password to invoke the mysql cli script with
34433494
timeout: (optional) time before the query should timeout
34443495
exception_as_warning: (optional) whether the exception should be treated as warning
3496+
log_errors: (optional) whether errors in the output should be logged
34453497
"""
34463498
raise NotImplementedError
34473499

lib/charms/mysql/v0/tls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def _on_set_tls_private_key(self, event: ActionEvent) -> None:
9191

9292
def _on_tls_relation_joined(self, event) -> None:
9393
"""Request certificate when TLS relation joined."""
94-
if not self.charm.unit_initialized:
94+
if not self.charm.unit_initialized():
9595
event.defer()
9696
return
9797
self._request_certificate(None)

src/charm.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -302,15 +302,14 @@ def text_logs(self) -> list:
302302

303303
return text_logs
304304

305-
@property
306-
def unit_initialized(self) -> bool:
305+
def unit_initialized(self, raise_exceptions: bool = False) -> bool:
307306
"""Return whether a unit is started.
308307
309308
Override parent class method to include container accessibility check.
310309
"""
311310
container = self.unit.get_container(CONTAINER_NAME)
312311
if container.can_connect():
313-
return super().unit_initialized
312+
return super().unit_initialized()
314313
else:
315314
return False
316315

@@ -367,7 +366,7 @@ def _is_unit_waiting_to_join_cluster(self) -> bool:
367366
and self.unit_peer_data.get("member-state") == "waiting"
368367
and self.unit_configured
369368
and (
370-
not self.unit_initialized
369+
not self.unit_initialized()
371370
or not self._mysql.is_instance_in_cluster(self.unit_label)
372371
)
373372
and self.cluster_initialized
@@ -505,7 +504,7 @@ def _restart(self, _: EventBase) -> None:
505504
if not container.can_connect():
506505
return
507506

508-
if not self.unit_initialized:
507+
if not self.unit_initialized():
509508
logger.debug("Restarting standalone mysqld")
510509
container.restart(MYSQLD_SERVICE)
511510
return
@@ -1013,7 +1012,7 @@ def _on_peer_relation_changed(self, event: RelationChangedEvent) -> None:
10131012
def _on_database_storage_detaching(self, _) -> None:
10141013
"""Handle the database storage detaching event."""
10151014
# Only executes if the unit was initialised
1016-
if not self.unit_initialized:
1015+
if not self.unit_initialized():
10171016
return
10181017

10191018
# No need to remove the instance from the cluster if it is not a member of the cluster

src/log_rotate_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def start_log_rotate_manager(self):
3838
not isinstance(self.charm.unit.status, ActiveStatus)
3939
or self.charm.peers is None
4040
or not container.can_connect()
41-
or not self.charm.unit_initialized
41+
or not self.charm.unit_initialized()
4242
):
4343
return
4444

src/mysql_k8s_helpers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ def wait_until_mysql_connection(self, check_port: bool = True) -> None:
281281
raise MySQLServiceNotRunningError
282282

283283
try:
284-
if check_port and not self.check_mysqlsh_connection():
285-
raise MySQLServiceNotRunningError("Connection with mysqlsh not possible")
284+
if check_port and not self.check_mysqlcli_connection():
285+
raise MySQLServiceNotRunningError("Connection with mysqlcli not possible")
286286
except MySQLClientError:
287287
raise MySQLServiceNotRunningError
288288

@@ -736,6 +736,7 @@ def _run_mysqlcli_script(
736736
password: Optional[str] = None,
737737
timeout: Optional[int] = None,
738738
exception_as_warning: bool = False,
739+
**_,
739740
) -> list:
740741
"""Execute a MySQL CLI script.
741742

src/relations/mysql.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def _update_status(self, _) -> None:
140140
if not container.can_connect():
141141
return
142142

143-
if not self.charm.unit_initialized:
143+
if not self.charm.unit_initialized():
144144
# Skip update status for uninitialized unit
145145
return
146146

@@ -168,7 +168,7 @@ def _on_peer_relation_changed(self, event: RelationChangedEvent) -> None:
168168
if not self.model.get_relation(LEGACY_MYSQL):
169169
return
170170

171-
if not (self.charm._is_peer_data_set and self.charm.unit_initialized):
171+
if not (self.charm._is_peer_data_set and self.charm.unit_initialized()):
172172
# Avoid running too early
173173
logger.info("Unit not ready to set `mysql` relation data. Deferring")
174174
event.defer()
@@ -211,7 +211,7 @@ def _on_mysql_relation_created(self, event: RelationCreatedEvent) -> None: # no
211211
# and for the member to be initialized and online
212212
if (
213213
not self.charm._is_peer_data_set
214-
or not self.charm.unit_initialized
214+
or not self.charm.unit_initialized()
215215
or self.charm.unit_peer_data.get("member-state") != "online"
216216
):
217217
logger.info("Unit not ready to execute `mysql` relation created. Deferring")

src/relations/mysql_provider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def _configure_endpoints(self, _) -> None:
217217
return
218218

219219
relations = self.charm.model.relations.get(DB_RELATION_NAME, [])
220-
if not relations or not self.charm.unit_initialized:
220+
if not relations or not self.charm.unit_initialized():
221221
return
222222

223223
relation_data = self.database.fetch_relation_data()
@@ -248,7 +248,7 @@ def _on_update_status(self, _) -> None:
248248
if (
249249
not container.can_connect()
250250
or not self.charm.cluster_initialized
251-
or not self.charm.unit_initialized
251+
or not self.charm.unit_initialized()
252252
):
253253
return
254254

src/relations/mysql_root.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def _on_mysql_root_relation_created(self, event: RelationCreatedEvent) -> None:
159159

160160
# Wait until on-config-changed event is executed
161161
# (wait for root password to have been set) or wait until the unit is initialized
162-
if not (self.charm._is_peer_data_set and self.charm.unit_initialized):
162+
if not (self.charm._is_peer_data_set and self.charm.unit_initialized()):
163163
event.defer()
164164
return
165165

src/rotate_mysql_logs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def _rotate_mysql_logs(self, _) -> None:
4646
if (
4747
self.charm.peers is None
4848
or not self.charm._mysql.is_mysqld_running()
49-
or not self.charm.unit_initialized
49+
or not self.charm.unit_initialized()
5050
or not self.charm.upgrade.idle
5151
):
5252
# skip when not initialized, during an upgrade, or when mysqld is not running

0 commit comments

Comments
 (0)