Skip to content

Commit e832715

Browse files
DPE-1789 Add logrotation via cron (#80)
## Issue We are not rotating the mysqlrouter logs currently ## Solution Rotate the logs using a cron (according to spec DA043) similar to the log rotation in mysql vm charm. - Run a cron every minute that runs `logrotate` with a specified config file for mysqlrouter - Add integration test for logrotation - Fix broken unit tests --------- Co-authored-by: Carl Csaposs <[email protected]>
1 parent f004cf3 commit e832715

17 files changed

+486
-38
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ jobs:
6464
build:
6565
name: Build charms
6666
uses: canonical/data-platform-workflows/.github/workflows/[email protected]
67-
with:
68-
charmcraft-snap-revision: 1349 # version 2.3.0
6967
permissions:
7068
actions: write # Needed to manage GitHub Actions cache
7169

.github/workflows/release.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ jobs:
1818
build:
1919
name: Build charm
2020
uses: canonical/data-platform-workflows/.github/workflows/[email protected]
21-
with:
22-
charmcraft-snap-revision: 1349 # version 2.3.0
2321

2422
release:
2523
name: Release charm

charmcraft.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,3 @@ parts:
2828
exit 1
2929
fi
3030
charm-entrypoint: src/machine_charm.py
31-
charm-binary-python-packages:
32-
- mysql-connector-python==8.0.32

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ authors = []
1212
python = "^3.8.1" # ^3.8.1 required by flake8
1313
ops = "^2.6.0"
1414
tenacity = "^8.2.3"
15+
jinja2 = "^3.1.2"
1516

1617
[tool.poetry.group.charm-libs.dependencies]
1718
# data_platform_libs/v0/data_interfaces.py
@@ -51,6 +52,7 @@ pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workf
5152
pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v5.0.1", subdirectory = "python/pytest_plugins/pytest_operator_groups"}
5253
juju = "^2.9.44.0"
5354
mysql-connector-python = "~8.0.33"
55+
tenacity = "^8.2.2"
5456

5557

5658
[tool.coverage.run]

src/abstract_charm.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import container
1515
import lifecycle
16+
import logrotate
1617
import relations.database_provides
1718
import relations.database_requires
1819
import workload
@@ -59,6 +60,11 @@ def _tls_certificate_saved(self) -> bool:
5960
def _container(self) -> container.Container:
6061
"""Workload container (snap or ROCK)"""
6162

63+
@property
64+
@abc.abstractmethod
65+
def _logrotate(self) -> logrotate.LogRotate:
66+
"""logrotate"""
67+
6268
@property
6369
@abc.abstractmethod
6470
def _read_write_endpoint(self) -> str:
@@ -74,10 +80,11 @@ def get_workload(self, *, event):
7480
if connection_info := self._database_requires.get_connection_info(event=event):
7581
return self._authenticated_workload_type(
7682
container_=self._container,
83+
logrotate_=self._logrotate,
7784
connection_info=connection_info,
7885
charm_=self,
7986
)
80-
return self._workload_type(container_=self._container)
87+
return self._workload_type(container_=self._container, logrotate_=self._logrotate)
8188

8289
@staticmethod
8390
# TODO python3.10 min version: Use `list` instead of `typing.List`

src/logrotate.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2023 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
4+
"""logrotate
5+
6+
https://manpages.ubuntu.com/manpages/jammy/man8/logrotate.8.html
7+
"""
8+
9+
import abc
10+
11+
import container
12+
13+
14+
class LogRotate(abc.ABC):
15+
"""logrotate"""
16+
17+
def __init__(self, *, container_: container.Container):
18+
self._container = container_
19+
20+
@abc.abstractmethod
21+
def enable(self) -> None:
22+
"""Enable logrotate."""
23+
24+
@abc.abstractmethod
25+
def disable(self) -> None:
26+
"""Disable logrotate."""

src/machine_charm.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import ops
1313

1414
import abstract_charm
15+
import machine_logrotate
1516
import relations.database_providers_wrapper
1617
import snap
1718
import socket_workload
@@ -44,6 +45,10 @@ def _subordinate_relation_endpoint_names(self) -> typing.Optional[typing.Iterabl
4445
def _container(self) -> snap.Snap:
4546
return snap.Snap()
4647

48+
@property
49+
def _logrotate(self) -> machine_logrotate.LogRotate:
50+
return machine_logrotate.LogRotate(container_=self._container)
51+
4752
@property
4853
def _read_write_endpoint(self) -> str:
4954
return f'file://{self._container.path("/run/mysqlrouter/mysql.sock")}'

src/machine_logrotate.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2023 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
4+
"""logrotate cron configuration"""
5+
6+
import logging
7+
8+
import jinja2
9+
10+
import container
11+
import logrotate
12+
13+
logger = logging.getLogger(__name__)
14+
15+
CHARMED_MYSQL_COMMON_DIRECTORY = "/var/snap/charmed-mysql/common"
16+
SYSTEM_USER = "snap_daemon"
17+
ROOT_USER = "root"
18+
19+
20+
class LogRotate(logrotate.LogRotate):
21+
"""logrotate cron configuration"""
22+
23+
def __init__(self, *, container_: container.Container):
24+
super().__init__(container_=container_)
25+
self._logrotate_config = self._container.path("/etc/logrotate.d/flush_mysqlrouter_logs")
26+
self._cron_file = self._container.path("/etc/cron.d/flush_mysqlrouter_logs")
27+
28+
def enable(self) -> None:
29+
logger.debug("Creating logrotate config file")
30+
31+
template = jinja2.Template(self._container.path("templates/logrotate.j2").read_text())
32+
33+
log_file_path = self._container.path("/var/log/mysqlrouter/mysqlrouter.log")
34+
rendered = template.render(
35+
log_file_path=str(log_file_path),
36+
system_user=SYSTEM_USER,
37+
)
38+
self._logrotate_config.write_text(rendered)
39+
40+
logger.debug("Created logrotate config file")
41+
logger.debug("Adding cron job for logrotate")
42+
43+
# cron needs the file to be owned by root
44+
self._cron_file.write_text(
45+
"* * * * * snap_daemon logrotate -f -s /tmp/logrotate.status /etc/logrotate.d/flush_mysqlrouter_logs\n\n",
46+
user=ROOT_USER,
47+
group=ROOT_USER,
48+
)
49+
50+
logger.debug("Added cron job for logrotate")
51+
52+
def disable(self) -> None:
53+
logger.debug("Removing cron job for log rotation of mysqlrouter")
54+
self._logrotate_config.unlink()
55+
self._cron_file.unlink()
56+
logger.debug("Removed cron job for log rotation of mysqlrouter")

src/snap.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
_SNAP_NAME = "charmed-mysql"
2121
_REVISION = "69" # v8.0.34
2222
_snap = snap_lib.SnapCache()[_SNAP_NAME]
23+
_UNIX_USERNAME = "snap_daemon"
2324

2425

2526
def install(*, unit: ops.Unit):
@@ -56,8 +57,6 @@ def uninstall():
5657

5758

5859
class _Path(pathlib.PosixPath, container.Path):
59-
_UNIX_USERNAME = "snap_daemon"
60-
6160
def __new__(cls, *args, **kwargs):
6261
path = super().__new__(cls, *args, **kwargs)
6362
if args and isinstance(args[0], cls) and (parent_ := args[0]._container_parent):
@@ -67,7 +66,9 @@ def __new__(cls, *args, **kwargs):
6766
"/var/lib/mysqlrouter"
6867
):
6968
parent = f"/var/snap/{_SNAP_NAME}/current"
70-
elif str(path).startswith("/run"):
69+
elif str(path).startswith("/run/mysqlrouter") or str(path).startswith(
70+
"/var/log/mysqlrouter"
71+
):
7172
parent = f"/var/snap/{_SNAP_NAME}/common"
7273
elif str(path).startswith("/tmp"):
7374
parent = f"/tmp/snap-private-tmp/snap.{_SNAP_NAME}"
@@ -94,14 +95,22 @@ def relative_to_container(self) -> pathlib.PurePosixPath:
9495
def read_text(self, encoding="utf-8", *args, **kwargs) -> str:
9596
return super().read_text(encoding, *args, **kwargs)
9697

97-
def write_text(self, data: str, encoding="utf-8", *args, **kwargs):
98+
def write_text(
99+
self,
100+
data: str,
101+
encoding="utf-8",
102+
*args,
103+
user=_UNIX_USERNAME,
104+
group=_UNIX_USERNAME,
105+
**kwargs,
106+
):
98107
return_value = super().write_text(data, encoding, *args, **kwargs)
99-
shutil.chown(self, user=self._UNIX_USERNAME, group=self._UNIX_USERNAME)
108+
shutil.chown(self, user=user, group=group)
100109
return return_value
101110

102111
def mkdir(self, *args, **kwargs) -> None:
103112
super().mkdir(*args, **kwargs)
104-
shutil.chown(self, user=self._UNIX_USERNAME, group=self._UNIX_USERNAME)
113+
shutil.chown(self, user=_UNIX_USERNAME, group=_UNIX_USERNAME)
105114

106115
def rmtree(self):
107116
shutil.rmtree(self)

0 commit comments

Comments
 (0)