From d828a7f232b57fb1b783d955b149f9c6d029f1eb Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 24 Jan 2025 14:47:12 -0300 Subject: [PATCH 01/11] feat: add new log rotation configuration options --- config.yaml | 12 +++++ lib/charms/mysql/v0/mysql.py | 3 +- scripts/wait_for_log_sync.sh | 21 +++++++++ src/charm.py | 45 +++++++++---------- src/config.py | 21 +++++++++ src/log_rotation_setup.py | 85 ++++++++++++++++++++++++++++++++++++ src/mysql_vm_helpers.py | 28 ++++++++++-- templates/logrotate.j2 | 13 ++++-- 8 files changed, 198 insertions(+), 30 deletions(-) create mode 100755 scripts/wait_for_log_sync.sh create mode 100644 src/log_rotation_setup.py diff --git a/config.yaml b/config.yaml index d3a247926..2003734f5 100644 --- a/config.yaml +++ b/config.yaml @@ -44,6 +44,18 @@ options: description: Number of days for binary logs retention type: int default: 7 + logs_audit_policy: + description: | + Audit log policy. Allowed values are: "all", "logins" (default), "queries". + Reg. at https://docs.percona.com/percona-server/8.0/audit-log-plugin.html#audit_log_policy + type: string + default: logins + logs_retention_period: + description: | + Period for rotated logs retention, in days. When set to "auto" (default) it will default to + to 3 days, or 1 day if related to COS. Accept values equal or greater then 3 otherwise. + type: string + default: auto # Experimental features experimental-max-connections: type: int diff --git a/lib/charms/mysql/v0/mysql.py b/lib/charms/mysql/v0/mysql.py index 45fc231cb..1f14d3061 100644 --- a/lib/charms/mysql/v0/mysql.py +++ b/lib/charms/mysql/v0/mysql.py @@ -930,6 +930,7 @@ def render_mysqld_configuration( # noqa: C901 profile: str, audit_log_enabled: bool, audit_log_strategy: str, + audit_log_policy: str, memory_limit: Optional[int] = None, experimental_max_connections: Optional[int] = None, binlog_retention_days: int, @@ -1001,7 +1002,7 @@ def render_mysqld_configuration( # noqa: C901 "slow_query_log_file": f"{snap_common}/var/log/mysql/slow.log", "binlog_expire_logs_seconds": f"{binlog_retention_seconds}", "loose-audit_log_filter": "OFF", - "loose-audit_log_policy": "LOGINS", + "loose-audit_log_policy": audit_log_policy.upper(), "loose-audit_log_file": f"{snap_common}/var/log/mysql/audit.log", } diff --git a/scripts/wait_for_log_sync.sh b/scripts/wait_for_log_sync.sh new file mode 100755 index 000000000..4d243aab0 --- /dev/null +++ b/scripts/wait_for_log_sync.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Trigger custom juju event when promtail start syncing logs +# + +CHARM_DIR="/var/lib/juju/agents/unit-$(echo "${JUJU_UNIT_NAME}" | tr / -)/charm" +POSITIONS_FILE="/var/snap/grafana-agent/current/grafana-agent-positions/log_file_scraper.yml" + +# Support bin path for 3.x and 2.x +[[ -x "/usr/bin/juju-exec" ]] && JUJU_CMD="/usr/bin/juju-exec" || JUJU_CMD="/usr/bin/juju-run" + +while true; do + if [[ -e "${POSITIONS_FILE}" ]]; then + # Test logic for mysql log files path in position file lines + if grep -q "mysql.*log" "${POSITIONS_FILE}"; then + ${JUJU_CMD} -u "${JUJU_UNIT_NAME}" JUJU_DISPATCH_PATH=hooks/log-syncing "${CHARM_DIR}"/dispatch + exit 0 + fi + fi + sleep 30 +done diff --git a/src/charm.py b/src/charm.py index 91c2ef7a3..7bf9fc8a7 100755 --- a/src/charm.py +++ b/src/charm.py @@ -7,6 +7,8 @@ from charms.mysql.v0.architecture import WrongArchitectureWarningCharm, is_wrong_architecture from ops.main import main +from log_rotation_setup import LogRotationSetup, LogSyncingEvents + if is_wrong_architecture() and __name__ == "__main__": main(WrongArchitectureWarningCharm) @@ -27,7 +29,6 @@ ) from charms.mysql.v0.backups import S3_INTEGRATOR_RELATION_NAME, MySQLBackups from charms.mysql.v0.mysql import ( - BYTES_1MB, Error, MySQLAddInstanceToClusterError, MySQLCharmBase, @@ -75,7 +76,6 @@ from constants import ( BACKUPS_PASSWORD_KEY, BACKUPS_USERNAME, - CHARMED_MYSQL_COMMON_DIRECTORY, CHARMED_MYSQL_SNAP_NAME, CHARMED_MYSQLD_SERVICE, CLUSTER_ADMIN_PASSWORD_KEY, @@ -121,7 +121,9 @@ class MySQLDNotRestartedError(Error): """Exception raised when MySQLD is not restarted after configuring instance.""" -class MySQLCustomCharmEvents(FlushMySQLLogsCharmEvents, IPAddressChangeCharmEvents): +class MySQLCustomCharmEvents( + FlushMySQLLogsCharmEvents, IPAddressChangeCharmEvents, LogSyncingEvents +): """Custom event sources for the charm.""" @@ -189,6 +191,8 @@ def __init__(self, *args): self.framework.observe( self.on[COS_AGENT_RELATION_NAME].relation_broken, self._on_cos_agent_relation_broken ) + + self.log_rotation_setup = LogRotationSetup(self) self.s3_integrator = S3Requirer(self, S3_INTEGRATOR_RELATION_NAME) self.backups = MySQLBackups(self, self.s3_integrator) self.hostname_resolution = MySQLMachineHostnameResolution(self) @@ -284,25 +288,17 @@ def _on_config_changed(self, _) -> None: return # render the new config - memory_limit_bytes = (self.config.profile_limit_memory or 0) * BYTES_1MB - new_config_content, new_config_dict = self._mysql.render_mysqld_configuration( - profile=self.config.profile, - audit_log_enabled=self.config.plugin_audit_enabled, - audit_log_strategy=self.config.plugin_audit_strategy, - snap_common=CHARMED_MYSQL_COMMON_DIRECTORY, - memory_limit=memory_limit_bytes, - experimental_max_connections=self.config.experimental_max_connections, - binlog_retention_days=self.config.binlog_retention_days, - ) + new_config_dict = self._mysql.write_mysqld_config() changed_config = compare_dictionaries(previous_config, new_config_dict) - logger.info("Persisting configuration changes to file") - # always persist config to file - self._mysql.write_content_to_file( - path=MYSQLD_CUSTOM_CONFIG_FILE, content=new_config_content - ) - self._mysql.setup_logrotate_and_cron(self.text_logs) + # Log rotation setup as DA124 + if self.config.logs_retention_period == "auto" and self.model.get_relation("logging"): + retention_period, compress = "1", self.unit_peer_data.get("logs_synced") == "true" + else: + retention_period, compress = "3", True + + self._mysql.setup_logrotate_and_cron(retention_period, self.text_logs, compress) if ( self.mysql_config.keys_requires_restart(changed_config) @@ -325,7 +321,9 @@ def _on_config_changed(self, _) -> None: if config not in new_config_dict: # skip removed configs continue - self._mysql.set_dynamic_variable(config, new_config_dict[config]) + self._mysql.set_dynamic_variable( + config.removeprefix("loose-"), new_config_dict[config] + ) def _on_start(self, event: StartEvent) -> None: """Handle the start event. @@ -618,7 +616,7 @@ def get_unit_hostname(self, unit_name: Optional[str] = None) -> str: """Get the hostname of the unit.""" if unit_name: unit = self.model.get_unit(unit_name) - return self.peers.data[unit]["instance-hostname"].split(":")[0] + return self.peers.data[unit]["instance-hostname"].split(":")[0] # type: ignore return self.unit_peer_data["instance-hostname"].split(":")[0] @property @@ -671,7 +669,10 @@ def workload_initialise(self) -> None: self.hostname_resolution.update_etc_hosts(None) self._mysql.write_mysqld_config() - self._mysql.setup_logrotate_and_cron(self.text_logs) + self._mysql.setup_logrotate_and_cron( + logs_retention_period=self.config.logs_retention_period, + enabled_log_files=self.text_logs, + ) self._mysql.reset_root_password_and_start_mysqld() self._mysql.configure_mysql_users() diff --git a/src/config.py b/src/config.py index 0db902e75..8177419a7 100644 --- a/src/config.py +++ b/src/config.py @@ -70,6 +70,8 @@ class CharmConfig(BaseConfigModel): binlog_retention_days: int plugin_audit_enabled: bool plugin_audit_strategy: str + logs_audit_policy: str + logs_retention_period: str @validator("profile") @classmethod @@ -142,3 +144,22 @@ def plugin_audit_strategy_validator(cls, value: str) -> Optional[str]: raise ValueError("Value not one of 'async' or 'semi-async'") return value + + @validator("logs_audit_policy") + @classmethod + def logs_audit_policy_validator(cls, value: str) -> Optional[str]: + """Check values for audit log policy.""" + valid_values = ["all", "logins", "queries"] + if value not in valid_values: + raise ValueError(f"logs_audit_policy not one of {', '.join(valid_values)}") + + return value + + @validator("logs_retention_period") + @classmethod + def logs_retention_period_validator(cls, value: str) -> str: + """Check logs retention period.""" + if (value.isalpha() and value != "auto") or (value.isdigit() and int(value) < 3): + raise ValueError("logs_retention_period must be >= 3 or `auto`") + + return value diff --git a/src/log_rotation_setup.py b/src/log_rotation_setup.py new file mode 100644 index 000000000..2d225c3c3 --- /dev/null +++ b/src/log_rotation_setup.py @@ -0,0 +1,85 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Handler for log rotation setup on relation to COS.""" + +import logging +import os +import subprocess +import typing + +from ops.charm import CharmEvents +from ops.framework import EventBase, EventSource, Object + +from constants import COS_AGENT_RELATION_NAME + +if typing.TYPE_CHECKING: + from charm import MySQLOperatorCharm + +logger = logging.getLogger(__name__) + + +class LogSyncing(EventBase): + """Custom event for the start of log syncing.""" + + +class LogSyncingEvents(CharmEvents): + """Charm event for log syncing init.""" + + log_syncing = EventSource(LogSyncing) + + +class LogRotationSetup(Object): + """TODO: Proper comment""" + + def __init__(self, charm: "MySQLOperatorCharm"): + super().__init__(charm, "log-rotation-setup") + + self.charm = charm + + self.framework.observe(self.charm.on.log_syncing, self._log_syncing) + self.framework.observe( + self.charm.on[COS_AGENT_RELATION_NAME].relation_created, self._cos_relation_created + ) + self.framework.observe( + self.charm.on[COS_AGENT_RELATION_NAME].relation_broken, self._cos_relation_broken + ) + + def _cos_relation_created(self, _): + script_path = f"{self.charm.charm_dir}/scripts/wait_for_log_sync.sh" + + new_env = os.environ.copy() + if "JUJU_CONTEXT_ID" in new_env: + new_env.pop("JUJU_CONTEXT_ID") + + subprocess.Popen([script_path], env=new_env) + logger.info("Started log sync wait script") + + def _log_syncing(self, _): + """LogSyncing event handler. + + Reconfigure log rotation after promtail start sync. + """ + if self.charm.config.logs_retention_period != "auto": + return + + logger.info("Reconfigure log rotation after logs upload started") + self.charm._mysql.setup_logrotate_and_cron( + logs_retention_period="1", + enabled_log_files=self.charm.text_logs, + logs_compression=True, + ) + + self.charm.unit_peer_data["logs_synced"] = "true" + + def _cos_relation_broken(self, _): + if self.charm.config.logs_retention_period != "auto": + return + logger.info("Reconfigure log rotation after logs upload stops") + self.charm._mysql.setup_logrotate_and_cron( + logs_retention_period="3", + enabled_log_files=self.charm.text_logs, + logs_compression=True, + ) + + del self.charm.unit_peer_data["logs_synced"] diff --git a/src/mysql_vm_helpers.py b/src/mysql_vm_helpers.py index 403dac4a6..d59d35364 100644 --- a/src/mysql_vm_helpers.py +++ b/src/mysql_vm_helpers.py @@ -244,22 +244,23 @@ def get_available_memory(self) -> int: logger.error("Failed to query system memory") raise MySQLGetAvailableMemoryError - def write_mysqld_config(self) -> None: + def write_mysqld_config(self) -> dict: """Create custom mysql config file. Raises: MySQLCreateCustomMySQLDConfigError if there is an error creating the custom mysqld config """ - logger.debug("Writing mysql configuration file") + logger.info("Writing mysql configuration file") memory_limit = None if self.charm.config.profile_limit_memory: # Convert from config value in MB to bytes memory_limit = self.charm.config.profile_limit_memory * BYTES_1MB try: - content_str, _ = self.render_mysqld_configuration( + content_str, content_dict = self.render_mysqld_configuration( profile=self.charm.config.profile, audit_log_enabled=self.charm.config.plugin_audit_enabled, audit_log_strategy=self.charm.config.plugin_audit_strategy, + audit_log_policy=self.charm.config.logs_audit_policy, snap_common=CHARMED_MYSQL_COMMON_DIRECTORY, memory_limit=memory_limit, binlog_retention_days=self.charm.config.binlog_retention_days, @@ -277,11 +278,20 @@ def write_mysqld_config(self) -> None: content=content_str, ) - def setup_logrotate_and_cron(self, enabled_log_files: Iterable) -> None: + return content_dict + + def setup_logrotate_and_cron( + self, + logs_retention_period: str, + enabled_log_files: Iterable, + logs_compression: bool = True, + ) -> None: """Setup log rotation configuration for text files. Args: + logs_retention_period: logs retention period in days enabled_log_files: a iterable of enabled text logs + logs_compression: whether logs should be compressed after rotation """ logger.debug("Creating logrotate config file") config_path = "/etc/logrotate.d/flush_mysql_logs" @@ -289,6 +299,13 @@ def setup_logrotate_and_cron(self, enabled_log_files: Iterable) -> None: cron_path = "/etc/cron.d/flush_mysql_logs" logs_dir = f"{CHARMED_MYSQL_COMMON_DIRECTORY}/var/log/mysql" + if logs_retention_period == "auto": + retention_period = 3 + else: + retention_period = int(logs_retention_period) + # days * minutes/day = amount of rotated files to keep + logs_rotations = retention_period * 1440 + with open("templates/logrotate.j2", "r") as file: template = jinja2.Template(file.read()) @@ -298,6 +315,9 @@ def setup_logrotate_and_cron(self, enabled_log_files: Iterable) -> None: charm_directory=self.charm.charm_dir, unit_name=self.charm.unit.name, enabled_log_files=enabled_log_files, + logs_retention_period=retention_period, + logs_rotations=logs_rotations, + logs_compression=logs_compression, ) self.write_content_to_file( diff --git a/templates/logrotate.j2 b/templates/logrotate.j2 index c0e2c033a..53934806a 100644 --- a/templates/logrotate.j2 +++ b/templates/logrotate.j2 @@ -6,8 +6,16 @@ createolddir 770 {{ system_user }} {{ system_user }} # Frequency of logs rotation hourly -maxage 7 -rotate 10800 +maxage {{ logs_retention_period }} +rotate {{ logs_rotations }} + +# Compression settings +{% if logs_compression %} +compress +delaycompress +{% else %} +nocompress +{% endif %} # Naming of rotated files should be in the format: dateext @@ -16,7 +24,6 @@ dateformat -%Y%m%d_%H%M # Settings to prevent misconfigurations and unwanted behaviours ifempty missingok -nocompress nomail nosharedscripts nocopytruncate From da373c833d56412356c6dfcd0ef185975044d82d Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 24 Jan 2025 16:46:56 -0300 Subject: [PATCH 02/11] lint fixes --- src/charm.py | 4 ++-- src/log_rotation_setup.py | 6 ++++-- tests/unit/test_config.py | 16 ++++++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/charm.py b/src/charm.py index 7bf9fc8a7..968cb626f 100755 --- a/src/charm.py +++ b/src/charm.py @@ -7,7 +7,6 @@ from charms.mysql.v0.architecture import WrongArchitectureWarningCharm, is_wrong_architecture from ops.main import main -from log_rotation_setup import LogRotationSetup, LogSyncingEvents if is_wrong_architecture() and __name__ == "__main__": main(WrongArchitectureWarningCharm) @@ -97,6 +96,7 @@ from flush_mysql_logs import FlushMySQLLogsCharmEvents, MySQLLogs from hostname_resolution import MySQLMachineHostnameResolution from ip_address_observer import IPAddressChangeCharmEvents +from log_rotation_setup import LogRotationSetup, LogSyncingEvents from mysql_vm_helpers import ( MySQL, MySQLCreateCustomMySQLDConfigError, @@ -271,7 +271,7 @@ def _on_leader_settings_changed(self, _) -> None: """Handle the leader settings changed event.""" self.unit_peer_data.update({"leader": "false"}) - def _on_config_changed(self, _) -> None: + def _on_config_changed(self, _) -> None: # noqa: C901 """Handle the config changed event.""" if not self._is_peer_data_set: # skip when not initialized diff --git a/src/log_rotation_setup.py b/src/log_rotation_setup.py index 2d225c3c3..ccad14eb7 100644 --- a/src/log_rotation_setup.py +++ b/src/log_rotation_setup.py @@ -1,7 +1,7 @@ # Copyright 2025 Canonical Ltd. # See LICENSE file for licensing details. -"""Handler for log rotation setup on relation to COS.""" +"""Handler for log rotation setup in relation to COS.""" import logging import os @@ -30,7 +30,7 @@ class LogSyncingEvents(CharmEvents): class LogRotationSetup(Object): - """TODO: Proper comment""" + """Configure logrotation settings in relation to COS integration.""" def __init__(self, charm: "MySQLOperatorCharm"): super().__init__(charm, "log-rotation-setup") @@ -46,6 +46,7 @@ def __init__(self, charm: "MySQLOperatorCharm"): ) def _cos_relation_created(self, _): + """Start monitoring script that tracks start of log syncing.""" script_path = f"{self.charm.charm_dir}/scripts/wait_for_log_sync.sh" new_env = os.environ.copy() @@ -73,6 +74,7 @@ def _log_syncing(self, _): self.charm.unit_peer_data["logs_synced"] = "true" def _cos_relation_broken(self, _): + """Unset auto value for log retention.""" if self.charm.config.logs_retention_period != "auto": return logger.info("Reconfigure log rotation after logs upload stops") diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 036026387..ee41a3bb5 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -35,9 +35,9 @@ def _check_valid_values(_harness, field: str, accepted_values: list, is_long_fie assert _harness.charm.config[field] == value if not is_long_field else int(value) -def _check_invalid_values(_harness, field: str, erroneus_values: list) -> None: +def _check_invalid_values(_harness, field: str, erroneous_values: list) -> None: """Check the incorrectness of the passed values for a field.""" - for value in erroneus_values: + for value in erroneous_values: _harness.update_config({field: value}) with pytest.raises(ValueError): _ = _harness.charm.config[field] @@ -45,8 +45,8 @@ def _check_invalid_values(_harness, field: str, erroneus_values: list) -> None: def test_profile_limit_values(harness) -> None: """Check that integer fields are parsed correctly.""" - erroneus_values = [599, 10**7, -354343] - _check_invalid_values(harness, "profile-limit-memory", erroneus_values) + erroneous_values = [599, 10**7, -354343] + _check_invalid_values(harness, "profile-limit-memory", erroneous_values) valid_values = [600, 1000, 35000] _check_valid_values(harness, "profile-limit-memory", valid_values) @@ -54,8 +54,8 @@ def test_profile_limit_values(harness) -> None: def test_profile_values(harness) -> None: """Test profile values.""" - erroneus_values = ["prod", "Test", "foo", "bar"] - _check_invalid_values(harness, "profile", erroneus_values) + erroneous_values = ["prod", "Test", "foo", "bar"] + _check_invalid_values(harness, "profile", erroneous_values) accepted_values = ["production", "testing"] _check_valid_values(harness, "profile", accepted_values) @@ -63,8 +63,8 @@ def test_profile_values(harness) -> None: def test_cluster_name_values(harness) -> None: """Test cluster name values.""" - erroneus_values = [64 * "a", "1-cluster", "cluster$"] - _check_invalid_values(harness, "cluster-name", erroneus_values) + erroneous_values = [64 * "a", "1-cluster", "cluster$"] + _check_invalid_values(harness, "cluster-name", erroneous_values) accepted_values = ["c1", "cluster_name", "cluster.name", "Cluster-name", 63 * "c"] _check_valid_values(harness, "cluster-name", accepted_values) From 2fbec6f8d58336205974de909c12f7cfd0a6a502 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 24 Jan 2025 17:35:15 -0300 Subject: [PATCH 03/11] fix/add unit tests --- tests/unit/test_log_rotation_setup.py | 49 +++++++++++++++++++++++++++ tests/unit/test_mysql.py | 5 +++ tests/unit/test_mysqlsh_helpers.py | 1 + 3 files changed, 55 insertions(+) create mode 100644 tests/unit/test_log_rotation_setup.py diff --git a/tests/unit/test_log_rotation_setup.py b/tests/unit/test_log_rotation_setup.py new file mode 100644 index 000000000..acd50cd27 --- /dev/null +++ b/tests/unit/test_log_rotation_setup.py @@ -0,0 +1,49 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +import unittest +from unittest.mock import MagicMock, patch + +from ops.testing import Harness + +from charm import MySQLOperatorCharm +from constants import COS_AGENT_RELATION_NAME, PEER + + +class TestLogRotationSetup(unittest.TestCase): + def setUp(self): + self.harness = Harness(MySQLOperatorCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + self.peer_relation_id = self.harness.add_relation(PEER, "mysql") + self.harness.update_relation_data( + self.peer_relation_id, + "mysql", + {"cluster-name": "test_cluster", "cluster-set-domain-name": "test_cluster_set"}, + ) + self.charm = self.harness.charm + + @patch("subprocess.Popen") + def test_cos_relation_created(self, mock_popen): + self.harness.add_relation(COS_AGENT_RELATION_NAME, "grafana-agent") + mock_popen.assert_called_once() + + @patch("mysql_vm_helpers.MySQL.setup_logrotate_and_cron") + def test_log_syncing(self, mock_setup): + self.harness.update_config({"logs_retention_period": "auto"}) + self.harness.add_relation(COS_AGENT_RELATION_NAME, "grafana-agent") + event = MagicMock() + self.charm.log_rotation_setup._log_syncing(event) + self.assertEqual(self.harness.charm.unit_peer_data["logs_synced"], "true") + mock_setup.assert_called_once() + + @patch("mysql_vm_helpers.MySQL.setup_logrotate_and_cron") + def test_cos_relation_broken(self, mock_setup): + self.harness.update_config({"logs_retention_period": "auto"}) + event = MagicMock() + self.charm.log_rotation_setup._cos_relation_broken(event) + self.assertNotIn("logs_synced", self.harness.charm.unit_peer_data) + mock_setup.assert_called_once() + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_mysql.py b/tests/unit/test_mysql.py index fdbced05a..c7e24f928 100644 --- a/tests/unit/test_mysql.py +++ b/tests/unit/test_mysql.py @@ -2072,6 +2072,7 @@ def test_render_mysqld_configuration(self, _get_available_memory): binlog_retention_days=7, audit_log_enabled=True, audit_log_strategy="async", + audit_log_policy="LOGINS", ) self.assertEqual(rendered_config, expected_config) @@ -2088,6 +2089,7 @@ def test_render_mysqld_configuration(self, _get_available_memory): binlog_retention_days=7, audit_log_enabled=True, audit_log_strategy="async", + audit_log_policy="LOGINS", memory_limit=memory_limit, ) self.assertEqual(rendered_config, expected_config) @@ -2103,6 +2105,7 @@ def test_render_mysqld_configuration(self, _get_available_memory): binlog_retention_days=7, audit_log_enabled=True, audit_log_strategy="async", + audit_log_policy="LOGINS", ) self.assertEqual(rendered_config, expected_config) @@ -2114,6 +2117,7 @@ def test_render_mysqld_configuration(self, _get_available_memory): binlog_retention_days=7, audit_log_enabled=True, audit_log_strategy="async", + audit_log_policy="LOGINS", experimental_max_connections=500, memory_limit=memory_limit, ) @@ -2126,6 +2130,7 @@ def test_render_mysqld_configuration(self, _get_available_memory): binlog_retention_days=7, audit_log_enabled=True, audit_log_strategy="async", + audit_log_policy="LOGINS", experimental_max_connections=800, memory_limit=memory_limit, ) diff --git a/tests/unit/test_mysqlsh_helpers.py b/tests/unit/test_mysqlsh_helpers.py index 899e1d860..7a2618c25 100644 --- a/tests/unit/test_mysqlsh_helpers.py +++ b/tests/unit/test_mysqlsh_helpers.py @@ -42,6 +42,7 @@ def __init__(self): self.experimental_max_connections = None self.plugin_audit_strategy = "async" self.binlog_retention_days = 7 + self.logs_audit_policy = "logins" class StubCharm: From 09aa3c414a6ed13295987199174cee76a7abfa3c Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 24 Jan 2025 19:37:28 -0300 Subject: [PATCH 04/11] minor lint fixes --- src/charm.py | 1 - tests/unit/test_log_rotation_setup.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/charm.py b/src/charm.py index 968cb626f..9514a27a5 100755 --- a/src/charm.py +++ b/src/charm.py @@ -7,7 +7,6 @@ from charms.mysql.v0.architecture import WrongArchitectureWarningCharm, is_wrong_architecture from ops.main import main - if is_wrong_architecture() and __name__ == "__main__": main(WrongArchitectureWarningCharm) diff --git a/tests/unit/test_log_rotation_setup.py b/tests/unit/test_log_rotation_setup.py index acd50cd27..e38e00e22 100644 --- a/tests/unit/test_log_rotation_setup.py +++ b/tests/unit/test_log_rotation_setup.py @@ -45,5 +45,6 @@ def test_cos_relation_broken(self, mock_setup): self.assertNotIn("logs_synced", self.harness.charm.unit_peer_data) mock_setup.assert_called_once() + if __name__ == "__main__": unittest.main() From 50f5f4ad24ff062ee60a9680c3d12518ace91f58 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 24 Jan 2025 20:17:44 -0300 Subject: [PATCH 05/11] test fixes --- src/charm.py | 4 +++- src/upgrade.py | 3 +-- tests/unit/test_upgrade.py | 9 +++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/charm.py b/src/charm.py index 9514a27a5..4b3ab0967 100755 --- a/src/charm.py +++ b/src/charm.py @@ -292,7 +292,9 @@ def _on_config_changed(self, _) -> None: # noqa: C901 changed_config = compare_dictionaries(previous_config, new_config_dict) # Log rotation setup as DA124 - if self.config.logs_retention_period == "auto" and self.model.get_relation("logging"): + if self.config.logs_retention_period == "auto" and self.model.get_relation( + COS_AGENT_RELATION_NAME + ): retention_period, compress = "1", self.unit_peer_data.get("logs_synced") == "true" else: retention_period, compress = "3", True diff --git a/src/upgrade.py b/src/upgrade.py index 87a00bf18..56b575082 100644 --- a/src/upgrade.py +++ b/src/upgrade.py @@ -192,14 +192,13 @@ def _on_upgrade_granted(self, event: UpgradeGrantedEvent) -> None: # noqa: C901 self.charm.unit.status = MaintenanceStatus("check if upgrade is possible") self._check_server_upgradeability() # override config, avoid restart - self.charm._mysql.write_mysqld_config() + self.charm._on_config_changed(None) self.charm.unit.status = MaintenanceStatus("starting services...") # stop cron daemon to be able to query `error.log` set_cron_daemon("stop") self.charm._mysql.start_mysqld() if self.charm.config.plugin_audit_enabled: self.charm._mysql.install_plugins(["audit_log", "audit_log_filter"]) - self.charm._mysql.setup_logrotate_and_cron(self.charm.text_logs) except VersionError: logger.exception("Failed to upgrade MySQL dependencies") self.set_unit_failed() diff --git a/tests/unit/test_upgrade.py b/tests/unit/test_upgrade.py index 0948cbc6b..6a42eb92d 100644 --- a/tests/unit/test_upgrade.py +++ b/tests/unit/test_upgrade.py @@ -133,7 +133,6 @@ def test_pre_upgrade_prepare( @patch("mysql_vm_helpers.MySQL.install_plugins") @patch("upgrade.set_cron_daemon") - @patch("mysql_vm_helpers.MySQL.write_mysqld_config") @patch("upgrade.MySQLVMUpgrade._check_server_unsupported_downgrade") @patch("upgrade.MySQLVMUpgrade._reset_on_unsupported_downgrade") @patch("mysql_vm_helpers.MySQL.hold_if_recovering") @@ -146,10 +145,10 @@ def test_pre_upgrade_prepare( @patch("mysql_vm_helpers.MySQL.start_mysqld") @patch("upgrade.MySQLVMUpgrade._check_server_upgradeability") @patch("mysql_vm_helpers.MySQL.is_instance_in_cluster", return_value=True) - @patch("mysql_vm_helpers.MySQL.setup_logrotate_and_cron", return_value=True) + @patch("charm.MySQLOperatorCharm._on_config_changed") def test_upgrade_granted( self, - mock_setup_logrotate_and_cron, + mock_config_change, mock_is_instance_in_cluster, mock_check_server_upgradeability, mock_start_mysqld, @@ -161,7 +160,6 @@ def test_upgrade_granted( mock_hold_if_recovering, mock_reset_on_unsupported_downgrade, mock_check_server_unsupported_downgrade, - mock_write_mysqld_config, mock_set_cron_daemon, mock_install_plugins, ): @@ -181,8 +179,7 @@ def test_upgrade_granted( mock_stop_mysqld.assert_called_once() mock_install_workload.assert_called_once() mock_get_mysql_version.assert_called_once() - mock_setup_logrotate_and_cron.assert_called_once() - mock_write_mysqld_config.assert_called_once() + mock_config_change.assert_called() self.harness.update_relation_data( self.upgrade_relation_id, "mysql/0", {"state": "upgrading"} From 78df2694d9fd9abe68f58fac2b99fdd174c480b4 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Wed, 29 Jan 2025 09:49:07 -0300 Subject: [PATCH 06/11] Add missing types --- lib/charms/mysql/v0/mysql.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/charms/mysql/v0/mysql.py b/lib/charms/mysql/v0/mysql.py index 1f14d3061..a3b006c77 100644 --- a/lib/charms/mysql/v0/mysql.py +++ b/lib/charms/mysql/v0/mysql.py @@ -2926,8 +2926,8 @@ def retrieve_backup_with_xbcloud( temp_restore_directory: str, xbcloud_location: str, xbstream_location: str, - user=None, - group=None, + user: Optional[str] = None, + group: Optional[str] = None, ) -> Tuple[str, str, str]: """Retrieve the specified backup from S3.""" nproc_command = ["nproc"] @@ -2993,8 +2993,8 @@ def prepare_backup_for_restore( backup_location: str, xtrabackup_location: str, xtrabackup_plugin_dir: str, - user=None, - group=None, + user: Optional[str] = None, + group: Optional[str] = None, ) -> Tuple[str, str]: """Prepare the backup in the provided dir for restore.""" try: @@ -3034,8 +3034,8 @@ def prepare_backup_for_restore( def empty_data_files( self, mysql_data_directory: str, - user=None, - group=None, + user: Optional[str] = None, + group: Optional[str] = None, ) -> None: """Empty the mysql data directory in preparation of backup restore.""" empty_data_files_command = [ @@ -3071,8 +3071,8 @@ def restore_backup( defaults_config_file: str, mysql_data_directory: str, xtrabackup_plugin_directory: str, - user=None, - group=None, + user: Optional[str] = None, + group: Optional[str] = None, ) -> Tuple[str, str]: """Restore the provided prepared backup.""" restore_backup_command = [ @@ -3105,8 +3105,8 @@ def restore_backup( def delete_temp_restore_directory( self, temp_restore_directory: str, - user=None, - group=None, + user: Optional[str] = None, + group: Optional[str] = None, ) -> None: """Delete the temp restore directory from the mysql data directory.""" logger.info(f"Deleting temp restore directory in {temp_restore_directory}") From 1cd34deb1335aaf059d7580d7930ad8897796d76 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Wed, 29 Jan 2025 10:00:10 -0300 Subject: [PATCH 07/11] fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sinclert PĂ©rez --- config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index 2003734f5..55c473355 100644 --- a/config.yaml +++ b/config.yaml @@ -52,7 +52,7 @@ options: default: logins logs_retention_period: description: | - Period for rotated logs retention, in days. When set to "auto" (default) it will default to + Period for rotated logs retention, in days. When set to "auto" (default) it will default to 3 days, or 1 day if related to COS. Accept values equal or greater then 3 otherwise. type: string default: auto From c130b7c472ba9833d7cdf9f87947e728758b0bca Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Wed, 29 Jan 2025 18:12:09 -0300 Subject: [PATCH 08/11] ditched custom event to simplify implementation --- lib/charms/mysql/v0/mysql.py | 2 +- scripts/wait_for_log_sync.sh | 21 ------ src/charm.py | 22 ++---- src/log_rotation_setup.py | 99 ++++++++++++++++----------- src/mysql_vm_helpers.py | 10 +-- tests/unit/test_log_rotation_setup.py | 2 +- 6 files changed, 69 insertions(+), 87 deletions(-) delete mode 100755 scripts/wait_for_log_sync.sh diff --git a/lib/charms/mysql/v0/mysql.py b/lib/charms/mysql/v0/mysql.py index d7d3a51bf..1de915293 100644 --- a/lib/charms/mysql/v0/mysql.py +++ b/lib/charms/mysql/v0/mysql.py @@ -133,7 +133,7 @@ def wait_until_mysql_connection(self) -> None: # Increment this major API version when introducing breaking changes LIBAPI = 0 -LIBPATCH = 81 +LIBPATCH = 82 UNIT_TEARDOWN_LOCKNAME = "unit-teardown" UNIT_ADD_LOCKNAME = "unit-add" diff --git a/scripts/wait_for_log_sync.sh b/scripts/wait_for_log_sync.sh deleted file mode 100755 index 4d243aab0..000000000 --- a/scripts/wait_for_log_sync.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# -# Trigger custom juju event when promtail start syncing logs -# - -CHARM_DIR="/var/lib/juju/agents/unit-$(echo "${JUJU_UNIT_NAME}" | tr / -)/charm" -POSITIONS_FILE="/var/snap/grafana-agent/current/grafana-agent-positions/log_file_scraper.yml" - -# Support bin path for 3.x and 2.x -[[ -x "/usr/bin/juju-exec" ]] && JUJU_CMD="/usr/bin/juju-exec" || JUJU_CMD="/usr/bin/juju-run" - -while true; do - if [[ -e "${POSITIONS_FILE}" ]]; then - # Test logic for mysql log files path in position file lines - if grep -q "mysql.*log" "${POSITIONS_FILE}"; then - ${JUJU_CMD} -u "${JUJU_UNIT_NAME}" JUJU_DISPATCH_PATH=hooks/log-syncing "${CHARM_DIR}"/dispatch - exit 0 - fi - fi - sleep 30 -done diff --git a/src/charm.py b/src/charm.py index e095f54b2..e077d68ab 100755 --- a/src/charm.py +++ b/src/charm.py @@ -95,7 +95,7 @@ from flush_mysql_logs import FlushMySQLLogsCharmEvents, MySQLLogs from hostname_resolution import MySQLMachineHostnameResolution from ip_address_observer import IPAddressChangeCharmEvents -from log_rotation_setup import LogRotationSetup, LogSyncingEvents +from log_rotation_setup import LogRotationSetup from mysql_vm_helpers import ( MySQL, MySQLCreateCustomMySQLDConfigError, @@ -120,9 +120,7 @@ class MySQLDNotRestartedError(Error): """Exception raised when MySQLD is not restarted after configuring instance.""" -class MySQLCustomCharmEvents( - FlushMySQLLogsCharmEvents, IPAddressChangeCharmEvents, LogSyncingEvents -): +class MySQLCustomCharmEvents(FlushMySQLLogsCharmEvents, IPAddressChangeCharmEvents): """Custom event sources for the charm.""" @@ -291,15 +289,8 @@ def _on_config_changed(self, _) -> None: # noqa: C901 changed_config = compare_dictionaries(previous_config, new_config_dict) - # Log rotation setup as DA124 - if self.config.logs_retention_period == "auto" and self.model.get_relation( - COS_AGENT_RELATION_NAME - ): - retention_period, compress = "1", self.unit_peer_data.get("logs_synced") == "true" - else: - retention_period, compress = "3", True - - self._mysql.setup_logrotate_and_cron(retention_period, self.text_logs, compress) + # Override log rotation + self.log_rotation_setup.setup() if ( self.mysql_config.keys_requires_restart(changed_config) @@ -702,10 +693,7 @@ def workload_initialise(self) -> None: self.hostname_resolution.update_etc_hosts(None) self._mysql.write_mysqld_config() - self._mysql.setup_logrotate_and_cron( - logs_retention_period=self.config.logs_retention_period, - enabled_log_files=self.text_logs, - ) + self.log_rotation_setup.setup() self._mysql.reset_root_password_and_start_mysqld() self._mysql.configure_mysql_users() diff --git a/src/log_rotation_setup.py b/src/log_rotation_setup.py index ccad14eb7..c2bb5c3b6 100644 --- a/src/log_rotation_setup.py +++ b/src/log_rotation_setup.py @@ -4,12 +4,11 @@ """Handler for log rotation setup in relation to COS.""" import logging -import os -import subprocess import typing +import yaml +from pathlib import Path -from ops.charm import CharmEvents -from ops.framework import EventBase, EventSource, Object +from ops.framework import Object from constants import COS_AGENT_RELATION_NAME @@ -18,15 +17,8 @@ logger = logging.getLogger(__name__) - -class LogSyncing(EventBase): - """Custom event for the start of log syncing.""" - - -class LogSyncingEvents(CharmEvents): - """Charm event for log syncing init.""" - - log_syncing = EventSource(LogSyncing) +_POSITIONS_FILE = "/var/snap/grafana-agent/current/grafana-agent-positions/log_file_scraper.yml" +_LOGS_SYNCED = "logs_synced" class LogRotationSetup(Object): @@ -37,7 +29,7 @@ def __init__(self, charm: "MySQLOperatorCharm"): self.charm = charm - self.framework.observe(self.charm.on.log_syncing, self._log_syncing) + self.framework.observe(self.charm.on.update_status, self._update_logs_rotation) self.framework.observe( self.charm.on[COS_AGENT_RELATION_NAME].relation_created, self._cos_relation_created ) @@ -45,43 +37,70 @@ def __init__(self, charm: "MySQLOperatorCharm"): self.charm.on[COS_AGENT_RELATION_NAME].relation_broken, self._cos_relation_broken ) - def _cos_relation_created(self, _): - """Start monitoring script that tracks start of log syncing.""" - script_path = f"{self.charm.charm_dir}/scripts/wait_for_log_sync.sh" + @property + def _logs_are_syncing(self): + return self.charm.unit_peer_data.get(_LOGS_SYNCED) == "true" - new_env = os.environ.copy() - if "JUJU_CONTEXT_ID" in new_env: - new_env.pop("JUJU_CONTEXT_ID") + def setup(self): + """Setup log rotation.""" + # retention setting + if self.charm.config.logs_retention_period == "auto": + retention_period = 1 if self._logs_are_syncing else 3 + else: + retention_period = int(self.charm.config.logs_retention_period) - subprocess.Popen([script_path], env=new_env) - logger.info("Started log sync wait script") + # compression setting + compress = self._logs_are_syncing or not self.charm.has_cos_relation - def _log_syncing(self, _): - """LogSyncing event handler. + self.charm._mysql.setup_logrotate_and_cron( + retention_period, self.charm.text_logs, compress + ) + + def _update_logs_rotation(self, _): + """Check for log rotation auto configuration handler. - Reconfigure log rotation after promtail start sync. + Reconfigure log rotation if promtail/gagent start sync. """ - if self.charm.config.logs_retention_period != "auto": + if not self.model.get_relation(COS_AGENT_RELATION_NAME): + return + + if self._logs_are_syncing: + # reconfiguration done + return + + positions_file = Path(_POSITIONS_FILE) + + not_started_msg = "Log syncing not yet started." + if not positions_file.exists(): + logger.debug(not_started_msg) + return + + with open(positions_file, "r") as pos_fd: + positions = yaml.safe_load(pos_fd.read()) + + if sync_files := positions.get("positions"): + for log_file, line in sync_files.items(): + if "mysql" in log_file and int(line) > 0: + break + else: + logger.debug(not_started_msg) + return + else: + logger.debug(not_started_msg) return logger.info("Reconfigure log rotation after logs upload started") - self.charm._mysql.setup_logrotate_and_cron( - logs_retention_period="1", - enabled_log_files=self.charm.text_logs, - logs_compression=True, - ) + self.charm.unit_peer_data[_LOGS_SYNCED] = "true" + self.setup() - self.charm.unit_peer_data["logs_synced"] = "true" + def _cos_relation_created(self, _): + """Handle relation created.""" + logger.info("Reconfigure log rotation on cos relation created") + self.setup() def _cos_relation_broken(self, _): """Unset auto value for log retention.""" - if self.charm.config.logs_retention_period != "auto": - return logger.info("Reconfigure log rotation after logs upload stops") - self.charm._mysql.setup_logrotate_and_cron( - logs_retention_period="3", - enabled_log_files=self.charm.text_logs, - logs_compression=True, - ) - del self.charm.unit_peer_data["logs_synced"] + del self.charm.unit_peer_data[_LOGS_SYNCED] + self.setup() diff --git a/src/mysql_vm_helpers.py b/src/mysql_vm_helpers.py index d59d35364..74d0dc11f 100644 --- a/src/mysql_vm_helpers.py +++ b/src/mysql_vm_helpers.py @@ -282,7 +282,7 @@ def write_mysqld_config(self) -> dict: def setup_logrotate_and_cron( self, - logs_retention_period: str, + logs_retention_period: int, enabled_log_files: Iterable, logs_compression: bool = True, ) -> None: @@ -299,12 +299,8 @@ def setup_logrotate_and_cron( cron_path = "/etc/cron.d/flush_mysql_logs" logs_dir = f"{CHARMED_MYSQL_COMMON_DIRECTORY}/var/log/mysql" - if logs_retention_period == "auto": - retention_period = 3 - else: - retention_period = int(logs_retention_period) # days * minutes/day = amount of rotated files to keep - logs_rotations = retention_period * 1440 + logs_rotations = logs_retention_period * 1440 with open("templates/logrotate.j2", "r") as file: template = jinja2.Template(file.read()) @@ -315,7 +311,7 @@ def setup_logrotate_and_cron( charm_directory=self.charm.charm_dir, unit_name=self.charm.unit.name, enabled_log_files=enabled_log_files, - logs_retention_period=retention_period, + logs_retention_period=logs_retention_period, logs_rotations=logs_rotations, logs_compression=logs_compression, ) diff --git a/tests/unit/test_log_rotation_setup.py b/tests/unit/test_log_rotation_setup.py index e38e00e22..a8ee46320 100644 --- a/tests/unit/test_log_rotation_setup.py +++ b/tests/unit/test_log_rotation_setup.py @@ -33,7 +33,7 @@ def test_log_syncing(self, mock_setup): self.harness.update_config({"logs_retention_period": "auto"}) self.harness.add_relation(COS_AGENT_RELATION_NAME, "grafana-agent") event = MagicMock() - self.charm.log_rotation_setup._log_syncing(event) + self.charm.log_rotation_setup._update_logs_rotation(event) self.assertEqual(self.harness.charm.unit_peer_data["logs_synced"], "true") mock_setup.assert_called_once() From 20d2475b3cb43f6eb979712df0a02e4b9735d62d Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Wed, 29 Jan 2025 21:16:43 -0300 Subject: [PATCH 09/11] test fixes --- src/log_rotation_setup.py | 2 +- tests/unit/test_log_rotation_setup.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/log_rotation_setup.py b/src/log_rotation_setup.py index c2bb5c3b6..85fbde5dd 100644 --- a/src/log_rotation_setup.py +++ b/src/log_rotation_setup.py @@ -5,9 +5,9 @@ import logging import typing -import yaml from pathlib import Path +import yaml from ops.framework import Object from constants import COS_AGENT_RELATION_NAME diff --git a/tests/unit/test_log_rotation_setup.py b/tests/unit/test_log_rotation_setup.py index a8ee46320..87618be63 100644 --- a/tests/unit/test_log_rotation_setup.py +++ b/tests/unit/test_log_rotation_setup.py @@ -2,7 +2,7 @@ # See LICENSE file for licensing details. import unittest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, mock_open, patch from ops.testing import Harness @@ -23,18 +23,26 @@ def setUp(self): ) self.charm = self.harness.charm - @patch("subprocess.Popen") - def test_cos_relation_created(self, mock_popen): + @patch("mysql_vm_helpers.MySQL.setup_logrotate_and_cron") + def test_cos_relation_created(self, mock_setup): self.harness.add_relation(COS_AGENT_RELATION_NAME, "grafana-agent") - mock_popen.assert_called_once() + mock_setup.assert_called_once_with(3, self.charm.text_logs, False) + @patch("pathlib.Path.exists", return_value=True) @patch("mysql_vm_helpers.MySQL.setup_logrotate_and_cron") - def test_log_syncing(self, mock_setup): + def test_log_syncing(self, mock_setup, mock_exist): self.harness.update_config({"logs_retention_period": "auto"}) self.harness.add_relation(COS_AGENT_RELATION_NAME, "grafana-agent") + positions = ( + "positions:\n '/var/snap/charmed-mysql/common/var/log/mysql/error.log': '466'\n" + ) event = MagicMock() - self.charm.log_rotation_setup._update_logs_rotation(event) + mock_setup.assert_called_once() + mock_setup.reset_mock() + with patch("builtins.open", mock_open(read_data=positions)): + self.charm.log_rotation_setup._update_logs_rotation(event) self.assertEqual(self.harness.charm.unit_peer_data["logs_synced"], "true") + mock_exist.assert_called_once() mock_setup.assert_called_once() @patch("mysql_vm_helpers.MySQL.setup_logrotate_and_cron") From dfb56311a5c877a4cd0b55a284b481d52efb88dc Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Thu, 30 Jan 2025 09:30:56 -0300 Subject: [PATCH 10/11] better description plus typo fix --- config.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config.yaml b/config.yaml index 55c473355..f7c89a8b0 100644 --- a/config.yaml +++ b/config.yaml @@ -47,13 +47,14 @@ options: logs_audit_policy: description: | Audit log policy. Allowed values are: "all", "logins" (default), "queries". - Reg. at https://docs.percona.com/percona-server/8.0/audit-log-plugin.html#audit_log_policy + Ref. at https://docs.percona.com/percona-server/8.0/audit-log-plugin.html#audit_log_policy type: string default: logins logs_retention_period: description: | - Period for rotated logs retention, in days. When set to "auto" (default) it will default - to 3 days, or 1 day if related to COS. Accept values equal or greater then 3 otherwise. + Specifies the retention period for rotated logs, in days. Accepts an integer value of 3 or + greater, or the special value "auto". When set to "auto" (default), the retention period is + 3 days, except when COS-related, where it is 1 day type: string default: auto # Experimental features From cf4bb0ed826f38f8bdb46062bab3948cabcc3af5 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 31 Jan 2025 12:12:01 -0300 Subject: [PATCH 11/11] audit_log_filter is not in use, disabling it completely --- lib/charms/mysql/v0/mysql.py | 1 - src/charm.py | 8 ++++---- src/upgrade.py | 2 +- tests/unit/test_mysql.py | 1 - tests/unit/test_mysqlsh_helpers.py | 2 -- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/charms/mysql/v0/mysql.py b/lib/charms/mysql/v0/mysql.py index 1de915293..60788408c 100644 --- a/lib/charms/mysql/v0/mysql.py +++ b/lib/charms/mysql/v0/mysql.py @@ -1001,7 +1001,6 @@ def render_mysqld_configuration( # noqa: C901 "general_log_file": f"{snap_common}/var/log/mysql/general.log", "slow_query_log_file": f"{snap_common}/var/log/mysql/slow.log", "binlog_expire_logs_seconds": f"{binlog_retention_seconds}", - "loose-audit_log_filter": "OFF", "loose-audit_log_policy": audit_log_policy.upper(), "loose-audit_log_file": f"{snap_common}/var/log/mysql/audit.log", } diff --git a/src/charm.py b/src/charm.py index e077d68ab..d2340e5ee 100755 --- a/src/charm.py +++ b/src/charm.py @@ -268,7 +268,7 @@ def _on_leader_settings_changed(self, _) -> None: """Handle the leader settings changed event.""" self.unit_peer_data.update({"leader": "false"}) - def _on_config_changed(self, _) -> None: # noqa: C901 + def _on_config_changed(self, _) -> None: """Handle the config changed event.""" if not self._is_peer_data_set: # skip when not initialized @@ -300,9 +300,9 @@ def _on_config_changed(self, _) -> None: # noqa: C901 if "loose-audit_log_format" in changed_config: # plugins are manipulated on running daemon if self.config.plugin_audit_enabled: - self._mysql.install_plugins(["audit_log", "audit_log_filter"]) + self._mysql.install_plugins(["audit_log"]) else: - self._mysql.uninstall_plugins(["audit_log", "audit_log_filter"]) + self._mysql.uninstall_plugins(["audit_log"]) self.on[f"{self.restart.name}"].acquire_lock.emit() @@ -698,7 +698,7 @@ def workload_initialise(self) -> None: self._mysql.configure_mysql_users() if self.config.plugin_audit_enabled: - self._mysql.install_plugins(["audit_log", "audit_log_filter"]) + self._mysql.install_plugins(["audit_log"]) current_mysqld_pid = self._mysql.get_pid_of_port_3306() self._mysql.configure_instance() diff --git a/src/upgrade.py b/src/upgrade.py index 56b575082..26ce16eae 100644 --- a/src/upgrade.py +++ b/src/upgrade.py @@ -198,7 +198,7 @@ def _on_upgrade_granted(self, event: UpgradeGrantedEvent) -> None: # noqa: C901 set_cron_daemon("stop") self.charm._mysql.start_mysqld() if self.charm.config.plugin_audit_enabled: - self.charm._mysql.install_plugins(["audit_log", "audit_log_filter"]) + self.charm._mysql.install_plugins(["audit_log"]) except VersionError: logger.exception("Failed to upgrade MySQL dependencies") self.set_unit_failed() diff --git a/tests/unit/test_mysql.py b/tests/unit/test_mysql.py index be779cefd..8f5b844c8 100644 --- a/tests/unit/test_mysql.py +++ b/tests/unit/test_mysql.py @@ -2058,7 +2058,6 @@ def test_render_mysqld_configuration(self, _get_available_memory): "general_log_file": "/var/log/mysql/general.log", "slow_query_log_file": "/var/log/mysql/slow.log", "binlog_expire_logs_seconds": "604800", - "loose-audit_log_filter": "OFF", "loose-audit_log_format": "JSON", "loose-audit_log_policy": "LOGINS", "loose-audit_log_strategy": "ASYNCHRONOUS", diff --git a/tests/unit/test_mysqlsh_helpers.py b/tests/unit/test_mysqlsh_helpers.py index 7a2618c25..427f125c8 100644 --- a/tests/unit/test_mysqlsh_helpers.py +++ b/tests/unit/test_mysqlsh_helpers.py @@ -329,7 +329,6 @@ def test_write_mysqld_config( "general_log_file = /var/snap/charmed-mysql/common/var/log/mysql/general.log", "slow_query_log_file = /var/snap/charmed-mysql/common/var/log/mysql/slow.log", "binlog_expire_logs_seconds = 604800", - "loose-audit_log_filter = OFF", "loose-audit_log_policy = LOGINS", "loose-audit_log_file = /var/snap/charmed-mysql/common/var/log/mysql/audit.log", "loose-audit_log_format = JSON", @@ -365,7 +364,6 @@ def test_write_mysqld_config( "general_log_file = /var/snap/charmed-mysql/common/var/log/mysql/general.log", "slow_query_log_file = /var/snap/charmed-mysql/common/var/log/mysql/slow.log", "binlog_expire_logs_seconds = 604800", - "loose-audit_log_filter = OFF", "loose-audit_log_policy = LOGINS", "loose-audit_log_file = /var/snap/charmed-mysql/common/var/log/mysql/audit.log", "loose-audit_log_format = JSON",