@@ -133,7 +133,7 @@ def wait_until_mysql_connection(self) -> None:
133
133
# Increment this major API version when introducing breaking changes
134
134
LIBAPI = 0
135
135
136
- LIBPATCH = 84
136
+ LIBPATCH = 85
137
137
138
138
UNIT_TEARDOWN_LOCKNAME = "unit-teardown"
139
139
UNIT_ADD_LOCKNAME = "unit-add"
@@ -418,6 +418,10 @@ class MySQLPluginInstallError(Error):
418
418
"""Exception raised when there is an issue installing a MySQL plugin."""
419
419
420
420
421
+ class MySQLClusterMetadataExistsError (Error ):
422
+ """Exception raised when there is an issue checking if cluster metadata exists."""
423
+
424
+
421
425
@dataclasses .dataclass
422
426
class RouterUser :
423
427
"""MySQL Router user."""
@@ -618,8 +622,15 @@ def cluster_initialized(self) -> bool:
618
622
return False
619
623
620
624
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
623
634
624
635
return False
625
636
@@ -660,11 +671,6 @@ def unit_configured(self) -> bool:
660
671
self .get_unit_address (self .unit ), self .unit_label
661
672
)
662
673
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
-
668
674
@property
669
675
def app_peer_data (self ) -> Union [ops .RelationDataContent , dict ]:
670
676
"""Application peer relation data object."""
@@ -743,6 +749,15 @@ def removing_unit(self) -> bool:
743
749
"""Check if the unit is being removed."""
744
750
return self .unit_peer_data .get ("unit-status" ) == "removing"
745
751
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
+
746
761
def peer_relation_data (self , scope : Scopes ) -> DataPeerData :
747
762
"""Returns the peer relation data per scope."""
748
763
if scope == APP_SCOPE :
@@ -1671,35 +1686,62 @@ def is_cluster_in_cluster_set(self, cluster_name: str) -> Optional[bool]:
1671
1686
1672
1687
return cluster_name in cs_status ["clusters" ]
1673
1688
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
+ ")"
1687
1705
)
1688
1706
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
+
1689
1730
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 ,
1695
1735
timeout = 60 ,
1696
1736
exception_as_warning = True ,
1737
+ log_errors = False ,
1697
1738
)
1698
1739
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" )
1701
1742
1702
- return output .strip () == "True"
1743
+ cluster_names = [entry [0 ].strip () for entry in output ]
1744
+ return self .cluster_name in cluster_names
1703
1745
1704
1746
def rejoin_cluster (self , cluster_name ) -> None :
1705
1747
"""Try to rejoin a cluster to the cluster set."""
@@ -1939,8 +1981,11 @@ def rescan_cluster(
1939
1981
1940
1982
def is_instance_in_cluster (self , unit_label : str ) -> bool :
1941
1983
"""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 :
1944
1989
return False
1945
1990
1946
1991
commands = (
@@ -3244,21 +3289,26 @@ def kill_client_sessions(self) -> None:
3244
3289
logger .error ("Failed to kill external sessions" )
3245
3290
raise MySQLKillSessionError
3246
3291
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" ,)
3250
3295
3251
3296
try :
3252
- self ._run_mysqlsh_script (
3297
+ self ._run_mysqlcli_script (
3253
3298
connect_commands ,
3254
3299
user = self .server_config_user ,
3255
3300
password = self .server_config_password ,
3256
- host = self .instance_def (self .server_config_user ),
3257
3301
)
3258
3302
return True
3259
3303
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
3262
3312
3263
3313
def get_pid_of_port_3306 (self ) -> Optional [str ]:
3264
3314
"""Retrieves the PID of the process that is bound to port 3306."""
@@ -3427,6 +3477,7 @@ def _run_mysqlcli_script(
3427
3477
password : Optional [str ] = None ,
3428
3478
timeout : Optional [int ] = None ,
3429
3479
exception_as_warning : bool = False ,
3480
+ log_errors : bool = False ,
3430
3481
) -> list :
3431
3482
"""Execute a MySQL CLI script.
3432
3483
@@ -3442,6 +3493,7 @@ def _run_mysqlcli_script(
3442
3493
password: (optional) password to invoke the mysql cli script with
3443
3494
timeout: (optional) time before the query should timeout
3444
3495
exception_as_warning: (optional) whether the exception should be treated as warning
3496
+ log_errors: (optional) whether errors in the output should be logged
3445
3497
"""
3446
3498
raise NotImplementedError
3447
3499
0 commit comments