Skip to content

Commit

Permalink
Port over charm lib changes from k8s charm relating to pod reschdule …
Browse files Browse the repository at this point in the history
…during cluster setup (#518)

* Port over charm lib changes from k8s charm relating to pod reschdule during cluster setup

* Update outdated cos_agent charmlib

* Add unit test

* Update outdated tracing charm lib

* Fix typo
  • Loading branch information
shayancanonical authored Sep 13, 2024
1 parent 1b4d923 commit 14224d9
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 21 deletions.
7 changes: 4 additions & 3 deletions lib/charms/mysql/v0/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ def is_unit_blocked(self) -> bool:
MySQLDeleteTempRestoreDirectoryError,
MySQLEmptyDataDirectoryError,
MySQLExecuteBackupCommandsError,
MySQLGetMemberStateError,
MySQLInitializeJujuOperationsTableError,
MySQLKillSessionError,
MySQLNoMemberStateError,
MySQLOfflineModeAndHiddenInstanceExistsError,
MySQLPrepareBackupForRestoreError,
MySQLRescanClusterError,
Expand All @@ -73,6 +73,7 @@ def is_unit_blocked(self) -> bool:
MySQLSetInstanceOptionError,
MySQLStartMySQLDError,
MySQLStopMySQLDError,
MySQLUnableToGetMemberStateError,
)
from charms.mysql.v0.s3_helpers import (
fetch_and_check_existence_of_s3_path,
Expand All @@ -99,7 +100,7 @@ def is_unit_blocked(self) -> bool:

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


if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -339,7 +340,7 @@ def _can_unit_perform_backup(self) -> Tuple[bool, Optional[str]]:

try:
state, role = self.charm._mysql.get_member_state()
except MySQLGetMemberStateError:
except (MySQLNoMemberStateError, MySQLUnableToGetMemberStateError):
return False, "Error obtaining member state"

if role == "primary" and self.charm.app.planned_units() > 1:
Expand Down
50 changes: 43 additions & 7 deletions lib/charms/mysql/v0/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def wait_until_mysql_connection(self) -> None:
# Increment this major API version when introducing breaking changes
LIBAPI = 0

LIBPATCH = 72
LIBPATCH = 73

UNIT_TEARDOWN_LOCKNAME = "unit-teardown"
UNIT_ADD_LOCKNAME = "unit-add"
Expand Down Expand Up @@ -276,8 +276,12 @@ class MySQLGrantPrivilegesToUserError(Error):
"""Exception raised when there is an issue granting privileges to user."""


class MySQLGetMemberStateError(Error):
"""Exception raised when there is an issue getting member state."""
class MySQLNoMemberStateError(Error):
"""Exception raised when there is no member state."""


class MySQLUnableToGetMemberStateError(Error):
"""Exception raised when unable to get member state."""


class MySQLGetClusterEndpointsError(Error):
Expand Down Expand Up @@ -620,6 +624,26 @@ def cluster_initialized(self) -> bool:

return False

@property
def only_one_cluster_node_thats_uninitialized(self) -> Optional[bool]:
"""Check if only a single cluster node exists across all units."""
if not self.app_peer_data.get("cluster-name"):
return None

total_cluster_nodes = 0
for unit in self.app_units:
total_cluster_nodes += self._mysql.get_cluster_node_count(
from_instance=self.get_unit_address(unit)
)

total_online_cluster_nodes = 0
for unit in self.app_units:
total_online_cluster_nodes += self._mysql.get_cluster_node_count(
from_instance=self.get_unit_address(unit), node_status=MySQLMemberState["ONLINE"]
)

return total_cluster_nodes == 1 and total_online_cluster_nodes == 0

@property
def cluster_fully_initialized(self) -> bool:
"""Returns True if the cluster is fully initialized.
Expand Down Expand Up @@ -1728,6 +1752,18 @@ def is_instance_configured_for_innodb(
)
return False

def drop_group_replication_metadata_schema(self) -> None:
"""Drop the group replication metadata schema from current unit."""
commands = (
f"shell.connect('{self.instance_def(self.server_config_user)}')",
"dba.drop_metadata_schema()",
)

try:
self._run_mysqlsh_script("\n".join(commands))
except MySQLClientError:
logger.exception("Failed to drop group replication metadata schema")

def are_locks_acquired(self, from_instance: Optional[str] = None) -> bool:
"""Report if any topology change is being executed."""
commands = (
Expand Down Expand Up @@ -2356,13 +2392,13 @@ def get_member_state(self) -> Tuple[str, str]:
logger.error(
"Failed to get member state: mysqld daemon is down",
)
raise MySQLGetMemberStateError(e.message)
raise MySQLUnableToGetMemberStateError(e.message)

# output is like:
# 'MEMBER_STATE\tMEMBER_ROLE\tMEMBER_ID\t@@server_uuid\nONLINE\tPRIMARY\t<uuid>\t<uuid>\n'
lines = output.strip().lower().split("\n")
if len(lines) < 2:
raise MySQLGetMemberStateError("No member state retrieved")
raise MySQLNoMemberStateError("No member state retrieved")

if len(lines) == 2:
# Instance just know it own state
Expand All @@ -2378,7 +2414,7 @@ def get_member_state(self) -> Tuple[str, str]:
# filter server uuid
return results[0], results[1] or "unknown"

raise MySQLGetMemberStateError("No member state retrieved")
raise MySQLNoMemberStateError("No member state retrieved")

def is_cluster_replica(self, from_instance: Optional[str] = None) -> Optional[bool]:
"""Check if this cluster is a replica in a cluster set."""
Expand Down Expand Up @@ -2435,7 +2471,7 @@ def hold_if_recovering(self) -> None:
while True:
try:
member_state, _ = self.get_member_state()
except MySQLGetMemberStateError:
except (MySQLNoMemberStateError, MySQLUnableToGetMemberStateError):
break
if member_state == MySQLMemberState.RECOVERING:
logger.debug("Unit is recovering")
Expand Down
8 changes: 4 additions & 4 deletions lib/charms/tempo_k8s/v2/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(self, *args):
)
from ops.framework import EventSource, Object
from ops.model import ModelError, Relation
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, Field

# The unique Charmhub library identifier, never change it
LIBID = "12977e9aa0b34367903d8afeb8c3d85d"
Expand All @@ -107,7 +107,7 @@ def __init__(self, *args):

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

PYDEPS = ["pydantic"]

Expand Down Expand Up @@ -338,7 +338,7 @@ class Config:
class ProtocolType(BaseModel):
"""Protocol Type."""

model_config = ConfigDict(
model_config = ConfigDict( # type: ignore
# Allow serializing enum values.
use_enum_values=True
)
Expand Down Expand Up @@ -925,7 +925,7 @@ def get_endpoint(
def charm_tracing_config(
endpoint_requirer: TracingEndpointRequirer, cert_path: Optional[Union[Path, str]]
) -> Tuple[Optional[str], Optional[str]]:
"""Utility function to determine the charm_tracing config you will likely want.
"""Return the charm_tracing config you likely want.
If no endpoint is provided:
disable charm tracing.
Expand Down
1 change: 0 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@
MySQLCreateClusterError,
MySQLCreateClusterSetError,
MySQLGetClusterPrimaryAddressError,
MySQLGetMemberStateError,
MySQLGetMySQLVersionError,
MySQLInitializeJujuOperationsTableError,
MySQLLockAcquisitionError,
MySQLNoMemberStateError,
MySQLPluginInstallError,
MySQLRebootFromCompleteOutageError,
MySQLSetClusterPrimaryError,
MySQLUnableToGetMemberStateError,
)
from charms.mysql.v0.tls import MySQLTLS
from charms.rolling_ops.v0.rollingops import RollingOpsManager
Expand Down Expand Up @@ -511,7 +512,7 @@ def _on_update_status(self, _) -> None: # noqa: C901
state, role = self._mysql.get_member_state()
self.unit_peer_data["member-role"] = role
self.unit_peer_data["member-state"] = state
except MySQLGetMemberStateError:
except (MySQLNoMemberStateError, MySQLUnableToGetMemberStateError):
role = self.unit_peer_data["member-role"] = "unknown"
state = self.unit_peer_data["member-state"] = "unreachable"
logger.info(f"Unit workload member-state is {state} with member-role {role}")
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
MySQLDeleteTempRestoreDirectoryError,
MySQLEmptyDataDirectoryError,
MySQLExecuteBackupCommandsError,
MySQLGetMemberStateError,
MySQLInitializeJujuOperationsTableError,
MySQLNoMemberStateError,
MySQLOfflineModeAndHiddenInstanceExistsError,
MySQLPrepareBackupForRestoreError,
MySQLRescanClusterError,
Expand Down Expand Up @@ -356,7 +356,7 @@ def test_can_unit_perform_backup_failure(
self.harness.remove_relation_unit(self.peer_relation_id, "mysql/1")

# test error getting member state
_get_member_state.side_effect = MySQLGetMemberStateError
_get_member_state.side_effect = MySQLNoMemberStateError

success, error_message = self.mysql_backups._can_unit_perform_backup()
self.assertFalse(success)
Expand Down
19 changes: 17 additions & 2 deletions tests/unit/test_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
MySQLExecuteBackupCommandsError,
MySQLGetAutoTuningParametersError,
MySQLGetClusterPrimaryAddressError,
MySQLGetMemberStateError,
MySQLGetMySQLVersionError,
MySQLGetRouterUsersError,
MySQLGetVariableError,
MySQLInitializeJujuOperationsTableError,
MySQLMemberState,
MySQLNoMemberStateError,
MySQLOfflineModeAndHiddenInstanceExistsError,
MySQLPluginInstallError,
MySQLPrepareBackupForRestoreError,
Expand Down Expand Up @@ -463,6 +463,21 @@ def test_is_instance_configured_for_innodb(self, _run_mysqlsh_script):
)
self.assertFalse(is_instance_configured)

@patch("charms.mysql.v0.mysql.MySQLBase._run_mysqlsh_script")
def test_drop_group_replication_metadata_schema(self, _run_mysqlsh_script):
"""Test with no exceptions while calling the drop_group_replication_metadata_schema method."""
# test successfully configured instance
drop_group_replication_metadata_schema_commands = (
"shell.connect('serverconfig:serverconfigpassword@127.0.0.1:33062')",
"dba.drop_metadata_schema()",
)

self.mysql.drop_group_replication_metadata_schema()

_run_mysqlsh_script.assert_called_once_with(
"\n".join(drop_group_replication_metadata_schema_commands)
)

@patch("charms.mysql.v0.mysql.MySQLBase._run_mysqlsh_script")
def test_is_instance_configured_for_innodb_exceptions(self, _run_mysqlsh_script):
"""Test an exception while calling the is_instance_configured_for_innodb method."""
Expand Down Expand Up @@ -835,7 +850,7 @@ def test_get_member_state(self, _run_mysqlcli_script):

_run_mysqlcli_script.return_value = "MEMBER_STATE\tMEMBER_ROLE\tMEMBER_ID\t@@server_uuid\n"

with self.assertRaises(MySQLGetMemberStateError):
with self.assertRaises(MySQLNoMemberStateError):
self.mysql.get_member_state()

@patch("charms.mysql.v0.mysql.MySQLBase._run_mysqlsh_script")
Expand Down

0 comments on commit 14224d9

Please sign in to comment.