Skip to content

Commit 8b1d2cb

Browse files
authored
refactor for: (#440)
* propagating switchover status change across cluster * use global cluster set primary on standby endpoint * use model_uuid to workaround cluster name clash (instead of generated uuid)
1 parent b3d30d0 commit 8b1d2cb

File tree

2 files changed

+43
-21
lines changed

2 files changed

+43
-21
lines changed

lib/charms/mysql/v0/async_replication.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import enum
77
import logging
88
import typing
9-
import uuid
109
from functools import cached_property
1110
from time import sleep
1211

@@ -55,7 +54,7 @@
5554
# The unique Charmhub library identifier, never change it
5655
LIBID = "4de21f1a022c4e2c87ac8e672ec16f6a"
5756
LIBAPI = 0
58-
LIBPATCH = 3
57+
LIBPATCH = 4
5958

6059
RELATION_OFFER = "replication-offer"
6160
RELATION_CONSUMER = "replication"
@@ -129,10 +128,9 @@ def cluster_set_name(self) -> str:
129128
@property
130129
def relation(self) -> Optional[Relation]:
131130
"""Relation."""
132-
if isinstance(self, MySQLAsyncReplicationOffer):
133-
return self.model.get_relation(RELATION_OFFER)
134-
135-
return self.model.get_relation(RELATION_CONSUMER)
131+
return self.model.get_relation(RELATION_OFFER) or self.model.get_relation(
132+
RELATION_CONSUMER
133+
)
136134

137135
@property
138136
def relation_data(self) -> Optional[RelationDataContent]:
@@ -168,6 +166,10 @@ def _on_promote_to_primary(self, event: ActionEvent) -> None:
168166
logger.info(message)
169167
event.set_results({"message": message})
170168
self._charm._on_update_status(None)
169+
# write counter to propagate status update on the other side
170+
self.relation_data["switchover"] = str(
171+
int(self.relation_data.get("switchover", 0)) + 1
172+
)
171173
except MySQLPromoteClusterToPrimaryError:
172174
logger.exception("Failed to promote cluster to primary")
173175
event.fail("Failed to promote cluster to primary")
@@ -388,6 +390,9 @@ def state(self) -> Optional[States]:
388390
return States.INITIALIZING
389391
else:
390392
return States.RECOVERING
393+
if self.role.relation_side == RELATION_CONSUMER:
394+
# if on the consume and is primary, the cluster is ready
395+
return States.READY
391396

392397
@property
393398
def idle(self) -> bool:
@@ -570,6 +575,11 @@ def _on_offer_relation_changed(self, event):
570575
# Recover replica cluster
571576
self._charm.unit.status = MaintenanceStatus("Replica cluster in recovery")
572577

578+
elif state == States.READY:
579+
# trigger update status on relation update when ready
580+
# speeds up status on switchover
581+
self._charm._on_update_status(None)
582+
573583
def _on_offer_relation_broken(self, event: RelationBrokenEvent):
574584
"""Handle the async_primary relation being broken."""
575585
if self._charm.unit.is_leader():
@@ -642,11 +652,15 @@ def state(self) -> Optional[States]:
642652
# and did not synced credentials
643653
return States.SYNCING
644654

645-
if self.replica_initialized:
646-
# cluster added to cluster-set by primary cluster
647-
if self._charm.cluster_fully_initialized:
648-
return States.READY
649-
return States.RECOVERING
655+
if self.model.get_relation(RELATION_CONSUMER):
656+
if self.replica_initialized:
657+
# cluster added to cluster-set by primary cluster
658+
if self._charm.cluster_fully_initialized:
659+
return States.READY
660+
return States.RECOVERING
661+
else:
662+
return States.READY
663+
650664
return States.INITIALIZING
651665

652666
@property
@@ -672,7 +686,7 @@ def returning_cluster(self) -> bool:
672686

673687
@property
674688
def replica_initialized(self) -> bool:
675-
"""Whether the replica cluster is initialized as such."""
689+
"""Whether the replica cluster was initialized."""
676690
return self.remote_relation_data.get("replica-state") == "initialized"
677691

678692
def _check_version(self) -> bool:
@@ -685,7 +699,8 @@ def _check_version(self) -> bool:
685699

686700
if remote_version != local_version:
687701
logger.error(
688-
f"Primary cluster MySQL version {remote_version} is not compatible with this cluster MySQL version {local_version}"
702+
f"Primary cluster MySQL version {remote_version} is not compatible with this"
703+
f"cluster MySQL version {local_version}"
689704
)
690705
return False
691706

@@ -824,11 +839,12 @@ def _on_consumer_changed(self, event): # noqa: C901
824839

825840
if self.remote_relation_data["cluster-name"] == self.cluster_name: # pyright: ignore
826841
# this cluster need a new cluster name
842+
# we append the model uuid, trimming to a max of 63 characters
827843
logger.warning(
828-
"Cluster name is the same as the primary cluster. Appending generated value"
844+
"Cluster name is the same as the primary cluster. Appending model uuid"
829845
)
830846
self._charm.app_peer_data["cluster-name"] = (
831-
f"{self.cluster_name}{uuid.uuid4().hex[:4]}"
847+
f"{self.cluster_name}{self.model.uuid.replace('-', '')}"[:63]
832848
)
833849

834850
self._charm.unit.status = MaintenanceStatus("Populate endpoint")

lib/charms/mysql/v0/mysql.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def wait_until_mysql_connection(self) -> None:
130130

131131
# Increment this PATCH version before using `charmcraft publish-lib` or reset
132132
# to 0 if you are raising the major API version
133-
LIBPATCH = 60
133+
LIBPATCH = 61
134134

135135
UNIT_TEARDOWN_LOCKNAME = "unit-teardown"
136136
UNIT_ADD_LOCKNAME = "unit-add"
@@ -1963,11 +1963,17 @@ def _get_host_ip(host: str) -> str:
19631963
for v in topology.values()
19641964
if v["mode"] == "r/o" and v["status"] == MySQLMemberState.ONLINE
19651965
}
1966-
rw_endpoints = {
1967-
_get_host_ip(v["address"]) if get_ips else v["address"]
1968-
for v in topology.values()
1969-
if v["mode"] == "r/w" and v["status"] == MySQLMemberState.ONLINE
1970-
}
1966+
1967+
if self.is_cluster_replica():
1968+
# replica return global primary address
1969+
global_primary = self.get_cluster_set_global_primary_address()
1970+
rw_endpoints = {_get_host_ip(global_primary) if get_ips else global_primary}
1971+
else:
1972+
rw_endpoints = {
1973+
_get_host_ip(v["address"]) if get_ips else v["address"]
1974+
for v in topology.values()
1975+
if v["mode"] == "r/w" and v["status"] == MySQLMemberState.ONLINE
1976+
}
19711977
# won't get offline endpoints to IP as they maybe unreachable
19721978
no_endpoints = {
19731979
v["address"] for v in topology.values() if v["status"] != MySQLMemberState.ONLINE

0 commit comments

Comments
 (0)