Skip to content

Commit

Permalink
refactor for: (#440)
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
paulomach authored Jul 2, 2024
1 parent b3d30d0 commit 8b1d2cb
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 21 deletions.
46 changes: 31 additions & 15 deletions lib/charms/mysql/v0/async_replication.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import enum
import logging
import typing
import uuid
from functools import cached_property
from time import sleep

Expand Down Expand Up @@ -55,7 +54,7 @@
# The unique Charmhub library identifier, never change it
LIBID = "4de21f1a022c4e2c87ac8e672ec16f6a"
LIBAPI = 0
LIBPATCH = 3
LIBPATCH = 4

RELATION_OFFER = "replication-offer"
RELATION_CONSUMER = "replication"
Expand Down Expand Up @@ -129,10 +128,9 @@ def cluster_set_name(self) -> str:
@property
def relation(self) -> Optional[Relation]:
"""Relation."""
if isinstance(self, MySQLAsyncReplicationOffer):
return self.model.get_relation(RELATION_OFFER)

return self.model.get_relation(RELATION_CONSUMER)
return self.model.get_relation(RELATION_OFFER) or self.model.get_relation(
RELATION_CONSUMER
)

@property
def relation_data(self) -> Optional[RelationDataContent]:
Expand Down Expand Up @@ -168,6 +166,10 @@ def _on_promote_to_primary(self, event: ActionEvent) -> None:
logger.info(message)
event.set_results({"message": message})
self._charm._on_update_status(None)
# write counter to propagate status update on the other side
self.relation_data["switchover"] = str(
int(self.relation_data.get("switchover", 0)) + 1
)
except MySQLPromoteClusterToPrimaryError:
logger.exception("Failed to promote cluster to primary")
event.fail("Failed to promote cluster to primary")
Expand Down Expand Up @@ -388,6 +390,9 @@ def state(self) -> Optional[States]:
return States.INITIALIZING
else:
return States.RECOVERING
if self.role.relation_side == RELATION_CONSUMER:
# if on the consume and is primary, the cluster is ready
return States.READY

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

elif state == States.READY:
# trigger update status on relation update when ready
# speeds up status on switchover
self._charm._on_update_status(None)

def _on_offer_relation_broken(self, event: RelationBrokenEvent):
"""Handle the async_primary relation being broken."""
if self._charm.unit.is_leader():
Expand Down Expand Up @@ -642,11 +652,15 @@ def state(self) -> Optional[States]:
# and did not synced credentials
return States.SYNCING

if self.replica_initialized:
# cluster added to cluster-set by primary cluster
if self._charm.cluster_fully_initialized:
return States.READY
return States.RECOVERING
if self.model.get_relation(RELATION_CONSUMER):
if self.replica_initialized:
# cluster added to cluster-set by primary cluster
if self._charm.cluster_fully_initialized:
return States.READY
return States.RECOVERING
else:
return States.READY

return States.INITIALIZING

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

@property
def replica_initialized(self) -> bool:
"""Whether the replica cluster is initialized as such."""
"""Whether the replica cluster was initialized."""
return self.remote_relation_data.get("replica-state") == "initialized"

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

if remote_version != local_version:
logger.error(
f"Primary cluster MySQL version {remote_version} is not compatible with this cluster MySQL version {local_version}"
f"Primary cluster MySQL version {remote_version} is not compatible with this"
f"cluster MySQL version {local_version}"
)
return False

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

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

self._charm.unit.status = MaintenanceStatus("Populate endpoint")
Expand Down
18 changes: 12 additions & 6 deletions lib/charms/mysql/v0/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def wait_until_mysql_connection(self) -> None:

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

UNIT_TEARDOWN_LOCKNAME = "unit-teardown"
UNIT_ADD_LOCKNAME = "unit-add"
Expand Down Expand Up @@ -1963,11 +1963,17 @@ def _get_host_ip(host: str) -> str:
for v in topology.values()
if v["mode"] == "r/o" and v["status"] == MySQLMemberState.ONLINE
}
rw_endpoints = {
_get_host_ip(v["address"]) if get_ips else v["address"]
for v in topology.values()
if v["mode"] == "r/w" and v["status"] == MySQLMemberState.ONLINE
}

if self.is_cluster_replica():
# replica return global primary address
global_primary = self.get_cluster_set_global_primary_address()
rw_endpoints = {_get_host_ip(global_primary) if get_ips else global_primary}
else:
rw_endpoints = {
_get_host_ip(v["address"]) if get_ips else v["address"]
for v in topology.values()
if v["mode"] == "r/w" and v["status"] == MySQLMemberState.ONLINE
}
# won't get offline endpoints to IP as they maybe unreachable
no_endpoints = {
v["address"] for v in topology.values() if v["status"] != MySQLMemberState.ONLINE
Expand Down

0 comments on commit 8b1d2cb

Please sign in to comment.