diff --git a/config.yaml b/config.yaml index d3a247926..f7c89a8b0 100644 --- a/config.yaml +++ b/config.yaml @@ -44,6 +44,19 @@ 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". + 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: | + 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 experimental-max-connections: type: int diff --git a/lib/charms/mysql/v0/mysql.py b/lib/charms/mysql/v0/mysql.py index 5067f799b..60788408c 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" @@ -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, @@ -1000,8 +1001,7 @@ 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": "LOGINS", + "loose-audit_log_policy": audit_log_policy.upper(), "loose-audit_log_file": f"{snap_common}/var/log/mysql/audit.log", } @@ -2950,8 +2950,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"] @@ -3017,8 +3017,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: @@ -3058,8 +3058,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 = [ @@ -3095,8 +3095,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 = [ @@ -3129,8 +3129,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}") diff --git a/src/charm.py b/src/charm.py index a75128381..d2340e5ee 100755 --- a/src/charm.py +++ b/src/charm.py @@ -27,7 +27,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 +74,6 @@ from constants import ( BACKUPS_PASSWORD_KEY, BACKUPS_USERNAME, - CHARMED_MYSQL_COMMON_DIRECTORY, CHARMED_MYSQL_SNAP_NAME, CHARMED_MYSQLD_SERVICE, CLUSTER_ADMIN_PASSWORD_KEY, @@ -97,6 +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 from mysql_vm_helpers import ( MySQL, MySQLCreateCustomMySQLDConfigError, @@ -189,6 +188,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 +285,12 @@ 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) + # Override log rotation + self.log_rotation_setup.setup() if ( self.mysql_config.keys_requires_restart(changed_config) @@ -312,9 +300,9 @@ def _on_config_changed(self, _) -> None: 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() @@ -325,7 +313,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. @@ -650,7 +640,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 @@ -703,12 +693,12 @@ 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.log_rotation_setup.setup() self._mysql.reset_root_password_and_start_mysqld() 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/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..85fbde5dd --- /dev/null +++ b/src/log_rotation_setup.py @@ -0,0 +1,106 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Handler for log rotation setup in relation to COS.""" + +import logging +import typing +from pathlib import Path + +import yaml +from ops.framework import Object + +from constants import COS_AGENT_RELATION_NAME + +if typing.TYPE_CHECKING: + from charm import MySQLOperatorCharm + +logger = logging.getLogger(__name__) + +_POSITIONS_FILE = "/var/snap/grafana-agent/current/grafana-agent-positions/log_file_scraper.yml" +_LOGS_SYNCED = "logs_synced" + + +class LogRotationSetup(Object): + """Configure logrotation settings in relation to COS integration.""" + + def __init__(self, charm: "MySQLOperatorCharm"): + super().__init__(charm, "log-rotation-setup") + + self.charm = charm + + 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 + ) + self.framework.observe( + self.charm.on[COS_AGENT_RELATION_NAME].relation_broken, self._cos_relation_broken + ) + + @property + def _logs_are_syncing(self): + return self.charm.unit_peer_data.get(_LOGS_SYNCED) == "true" + + 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) + + # compression setting + compress = self._logs_are_syncing or not self.charm.has_cos_relation + + 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 if promtail/gagent start sync. + """ + 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.unit_peer_data[_LOGS_SYNCED] = "true" + self.setup() + + 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.""" + logger.info("Reconfigure log rotation after logs upload stops") + + 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 403dac4a6..74d0dc11f 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: int, + 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,9 @@ 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" + # days * minutes/day = amount of rotated files to keep + logs_rotations = logs_retention_period * 1440 + with open("templates/logrotate.j2", "r") as file: template = jinja2.Template(file.read()) @@ -298,6 +311,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=logs_retention_period, + logs_rotations=logs_rotations, + logs_compression=logs_compression, ) self.write_content_to_file( diff --git a/src/upgrade.py b/src/upgrade.py index 87a00bf18..26ce16eae 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) + self.charm._mysql.install_plugins(["audit_log"]) except VersionError: logger.exception("Failed to upgrade MySQL dependencies") self.set_unit_failed() 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 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) diff --git a/tests/unit/test_log_rotation_setup.py b/tests/unit/test_log_rotation_setup.py new file mode 100644 index 000000000..87618be63 --- /dev/null +++ b/tests/unit/test_log_rotation_setup.py @@ -0,0 +1,58 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +import unittest +from unittest.mock import MagicMock, mock_open, 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("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_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, 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() + 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") + 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 0ce984382..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", @@ -2072,6 +2071,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 +2088,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 +2104,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 +2116,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 +2129,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..427f125c8 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: @@ -328,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", @@ -364,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", 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"}