Skip to content

Commit 5fe0dd1

Browse files
authored
DPE-4658 global-primary on endpoint (#467)
* refactor for: * 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) * fix state setting
1 parent 2897bab commit 5fe0dd1

File tree

4 files changed

+50
-21
lines changed

4 files changed

+50
-21
lines changed

lib/charms/mysql/v0/async_replication.py

Lines changed: 34 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,12 @@ 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
395+
# the cluster is ready when fully initialized
396+
if self._charm.cluster_fully_initialized:
397+
return States.READY
398+
return States.RECOVERING
391399

392400
@property
393401
def idle(self) -> bool:
@@ -570,6 +578,11 @@ def _on_offer_relation_changed(self, event):
570578
# Recover replica cluster
571579
self._charm.unit.status = MaintenanceStatus("Replica cluster in recovery")
572580

581+
elif state == States.READY:
582+
# trigger update status on relation update when ready
583+
# speeds up status on switchover
584+
self._charm._on_update_status(None)
585+
573586
def _on_offer_relation_broken(self, event: RelationBrokenEvent):
574587
"""Handle the async_primary relation being broken."""
575588
if self._charm.unit.is_leader():
@@ -642,11 +655,15 @@ def state(self) -> Optional[States]:
642655
# and did not synced credentials
643656
return States.SYNCING
644657

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
658+
if self.model.get_relation(RELATION_CONSUMER):
659+
if self.replica_initialized:
660+
# cluster added to cluster-set by primary cluster
661+
if self._charm.cluster_fully_initialized:
662+
return States.READY
663+
return States.RECOVERING
664+
else:
665+
return States.READY
666+
650667
return States.INITIALIZING
651668

652669
@property
@@ -672,7 +689,7 @@ def returning_cluster(self) -> bool:
672689

673690
@property
674691
def replica_initialized(self) -> bool:
675-
"""Whether the replica cluster is initialized as such."""
692+
"""Whether the replica cluster was initialized."""
676693
return self.remote_relation_data.get("replica-state") == "initialized"
677694

678695
def _check_version(self) -> bool:
@@ -685,7 +702,8 @@ def _check_version(self) -> bool:
685702

686703
if remote_version != local_version:
687704
logger.error(
688-
f"Primary cluster MySQL version {remote_version} is not compatible with this cluster MySQL version {local_version}"
705+
f"Primary cluster MySQL version {remote_version} is not compatible with this"
706+
f"cluster MySQL version {local_version}"
689707
)
690708
return False
691709

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

825843
if self.remote_relation_data["cluster-name"] == self.cluster_name: # pyright: ignore
826844
# this cluster need a new cluster name
845+
# we append the model uuid, trimming to a max of 63 characters
827846
logger.warning(
828-
"Cluster name is the same as the primary cluster. Appending generated value"
847+
"Cluster name is the same as the primary cluster. Appending model uuid"
829848
)
830849
self._charm.app_peer_data["cluster-name"] = (
831-
f"{self.cluster_name}{uuid.uuid4().hex[:4]}"
850+
f"{self.cluster_name}{self.model.uuid.replace('-', '')}"[:63]
832851
)
833852

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

lib/charms/mysql/v0/mysql.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

tests/unit/test_async_replication.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ def test_consumer_changed_non_leader(self, _mysql, _):
444444
@patch("charm.MySQLOperatorCharm._mysql")
445445
def test_promote_to_primary(self, _mysql, _):
446446
self.harness.set_leader(True)
447+
self.harness.add_relation(RELATION_CONSUMER, "db1")
447448

448449
_mysql.is_cluster_replica.return_value = True
449450

@@ -455,6 +456,7 @@ def test_promote_to_primary(self, _mysql, _):
455456
_mysql.promote_cluster_to_primary.assert_called_with(
456457
self.charm.app_peer_data["cluster-name"], False
457458
)
459+
self.assertEqual(self.async_replica.relation_data["switchover"], "1")
458460

459461
_mysql.reset_mock()
460462

@@ -469,6 +471,7 @@ def test_promote_to_primary(self, _mysql, _):
469471
_mysql.promote_cluster_to_primary.assert_called_with(
470472
self.charm.app_peer_data["cluster-name"], True
471473
)
474+
self.assertEqual(self.async_replica.relation_data["switchover"], "2")
472475

473476
@patch("charm.MySQLOperatorCharm._mysql")
474477
def test_rejoin_cluster_action(self, _mysql, _):

tests/unit/test_mysql.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,8 +964,9 @@ def test_grant_privileges_to_user(self, _run_mysqlsh_script):
964964

965965
_run_mysqlsh_script.assert_called_with(expected_commands)
966966

967+
@patch("charms.mysql.v0.mysql.MySQLBase.is_cluster_replica", return_value=False)
967968
@patch("charms.mysql.v0.mysql.MySQLBase.get_cluster_status", return_value=SHORT_CLUSTER_STATUS)
968-
def test_get_cluster_endpoints(self, _):
969+
def test_get_cluster_endpoints(self, _, _is_cluster_replica):
969970
"""Test get_cluster_endpoints() method."""
970971
endpoints = self.mysql.get_cluster_endpoints(get_ips=False)
971972

0 commit comments

Comments
 (0)