Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cron service and cleanup script #167

Merged
merged 24 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b1e0109
Add cron service and cleanup script
nrobinaubertin Jan 30, 2024
1ff23fb
Fix rockfile
nrobinaubertin Jan 30, 2024
1392adf
Fix restart_synapse()
nrobinaubertin Jan 30, 2024
2e24638
Remove unused fn
nrobinaubertin Jan 30, 2024
f188f9d
Fix restart_synapse()
nrobinaubertin Jan 30, 2024
42cbed5
Fix linting
nrobinaubertin Jan 30, 2024
2b7d1f8
Fix rockfile
nrobinaubertin Jan 30, 2024
8deeb13
Throttle cleanup operations
nrobinaubertin Feb 1, 2024
0a9ee0e
Merge origin/main
nrobinaubertin Feb 1, 2024
a1fbc49
Add missing headers
nrobinaubertin Feb 1, 2024
d754f9a
Add some licensing exceptions
nrobinaubertin Feb 1, 2024
bd43018
Try to fix headers ignore once again
nrobinaubertin Feb 1, 2024
d2ac528
Merge branch 'main' into cron-cleanup
nrobinaubertin Feb 2, 2024
266118f
Make scripts added to the rock executable
nrobinaubertin Feb 2, 2024
83f9b24
Merge branch 'cron-cleanup' of github.com:canonical/synapse-operator …
nrobinaubertin Feb 2, 2024
09b9165
Merge branch 'main' into cron-cleanup
amandahla Feb 6, 2024
af17124
Merge branch 'main' into cron-cleanup
arturo-seijas Feb 6, 2024
75b4a66
Rerun CI
nrobinaubertin Feb 6, 2024
db10fd9
Merge origin/main
nrobinaubertin Feb 7, 2024
7c1c2c4
Only modify rockcraft.yml where necessary
nrobinaubertin Feb 7, 2024
c6ce4f1
Fix rockcraft scripts permissions
nrobinaubertin Feb 7, 2024
78c4709
Merge branch 'main' into cron-cleanup
nrobinaubertin Feb 7, 2024
6b0b43c
Fix typo
nrobinaubertin Feb 7, 2024
5b7a9e8
Merge branch 'main' into cron-cleanup
arturo-seijas Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ header:
- 'zap_rules.tsv'
- 'lib/**'
- 'templates/**'
- 'synapse_rock/cron/**/*.py'
- 'synapse_rock/scripts/**/*.py'
comment: on-failure
25 changes: 25 additions & 0 deletions src/pebble.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def restart_synapse(self, container: ops.model.Container) -> None:
"""
logger.debug("Restarting the Synapse container")
container.add_layer(synapse.SYNAPSE_SERVICE_NAME, self._pebble_layer, combine=True)
container.add_layer(
synapse.SYNAPSE_CRON_SERVICE_NAME, self._cron_pebble_layer, combine=True
)
container.restart(synapse.SYNAPSE_SERVICE_NAME)

def replan_nginx(self, container: ops.model.Container) -> None:
Expand Down Expand Up @@ -253,3 +256,25 @@ def _mjolnir_pebble_layer(self) -> ops.pebble.LayerDict:
},
}
return typing.cast(ops.pebble.LayerDict, layer)

@property
def _cron_pebble_layer(self) -> ops.pebble.LayerDict:
"""Generate pebble config for the cron service.

Returns:
The pebble configuration for the cron service.
"""
layer = {
"summary": "Synapse cron layer",
"description": "Synapse cron layer",
"services": {
synapse.SYNAPSE_CRON_SERVICE_NAME: {
"override": "replace",
"summary": "Cron service",
"command": "/usr/local/bin/run_cron.py",
"environment": synapse.get_environment(self._charm_state),
"startup": "enabled",
},
},
}
return typing.cast(ops.pebble.LayerDict, layer)
1 change: 1 addition & 0 deletions src/synapse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
SYNAPSE_CONFIG_DIR,
SYNAPSE_CONFIG_PATH,
SYNAPSE_CONTAINER_NAME,
SYNAPSE_CRON_SERVICE_NAME,
SYNAPSE_DATA_DIR,
SYNAPSE_GROUP,
SYNAPSE_NGINX_CONTAINER_NAME,
Expand Down
1 change: 1 addition & 0 deletions src/synapse/workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
SYNAPSE_COMMAND_PATH = "/start.py"
SYNAPSE_CONFIG_PATH = f"{SYNAPSE_CONFIG_DIR}/homeserver.yaml"
SYNAPSE_CONTAINER_NAME = "synapse"
SYNAPSE_CRON_SERVICE_NAME = "synapse-cron"
SYNAPSE_DATA_DIR = "/data"
SYNAPSE_GROUP = "synapse"
SYNAPSE_NGINX_CONTAINER_NAME = "synapse-nginx"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/python3
nrobinaubertin marked this conversation as resolved.
Show resolved Hide resolved
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

"""
Synapse does not purge empty directories from its media content storage locations.
Those can accumulate and eat up inodes and space.
Related: https://github.com/matrix-org/synapse/issues/16229
Related: https://github.com/matrix-org/synapse/issues/7690
"""

import os
import json
import time

# We assume that pyyaml is present thanks to synapse
import yaml

"""
To make sure that we don't steal IOPS from the running synapse instance,
we need to throttle the walk/rmdir iterations.
Given the MAX_IOPS, the formula is:
ITERATIONS_BEFORE_SLEEP = (TARGET_IOPS * SLEEP_TIME * MAX_IOPS)/(MAX_IOPS - TARGET_IOPS)
The values choosen here are for a TARGET_IOPS of 100 on a disk of 1600 MAX_IOPS.
Which is very conservative if using a SSD. Check the table at https://en.wikipedia.org/wiki/IOPS#Solid-state_devices
"""
ITERATIONS_BEFORE_SLEEP = 100
SLEEP_TIME = 1


# This function is meant to fail if the media_store_path can't be found
def load_media_store_path(file_path: str) -> str:
with open(file_path, "r", encoding="utf-8") as file:
data = yaml.safe_load(file)
return data["media_store_path"]


def delete_empty_dirs(path: str) -> None:
merkata marked this conversation as resolved.
Show resolved Hide resolved
i = 0
for root, dirs, _ in os.walk(path, topdown=False):
i += 1
if i > ITERATIONS_BEFORE_SLEEP:
i = 0
time.sleep(SLEEP_TIME)
for dir in dirs:
try:
os.rmdir(os.path.join(root, dir))
except OSError:
continue


if __name__ == "__main__":
# load the environment from the file produced by /usr/local/bin/run_cron.py
if os.path.isfile("/var/local/cron/environment.json"):
with open("/var/local/cron/environment.json", "r") as env_fd:
env = json.load(env_fd)
if os.path.isfile(env["SYNAPSE_CONFIG_PATH"]):
path = load_media_store_path(env["SYNAPSE_CONFIG_PATH"])
if path:
delete_empty_dirs(path)
23 changes: 23 additions & 0 deletions synapse_rock/rockcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ license: Apache-2.0
platforms:
amd64:
parts:
scripts:
plugin: dump
source: scripts
organize:
"*": usr/local/bin/
override-prime: |
craftctl default
chmod -R +x usr/local/bin/*
cron:
after:
- scripts
stage-packages:
- cron
plugin: dump
source: cron
organize:
"cron.hourly/*": /etc/cron.hourly
"cron.daily/*": /etc/cron.daily
"cron.weekly/*": /etc/cron.weekly
"cron.monthly/*": /etc/cron.monthly
override-prime: |
craftctl default
chmod -R +x etc/cron.*
add-user:
plugin: nil
overlay-script: |
Expand Down
24 changes: 24 additions & 0 deletions synapse_rock/scripts/run_cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
nrobinaubertin marked this conversation as resolved.
Show resolved Hide resolved
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

"""
The goal of this script is to serialize the environment passed by pebble to a file
and then to start the cron service. We're doing that because the environment of the cron service
is not passed to the commands started by it.
An alternative would have been to write to /etc/profile and start the called cron scripts with a login shell.
But we wanted to use python scripts and not pollute the environment of the users
(Creating a specific user for the cron service would be an added complexity).
"""

import json
import os

if __name__ == "__main__":
file_path = "/var/local/cron/environment.json"
os.makedirs(os.path.dirname(file_path), exist_ok=True)

with open(file_path, "w") as file:
json.dump(dict(os.environ), file)

os.execv("/usr/sbin/cron", ["/usr/sbin/cron", "-f", "-P"])
Loading