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

Implement config panel for swap management #1858

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
124 changes: 124 additions & 0 deletions helpers/helpers.v1.d/hardware
tituspijean marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,127 @@ ynh_require_ram() {
return 0
fi
}


# Ask the creation of a swap file for the application.
# The helper might or might not create the swap file, considering the machine's configuration.
#
# [packagingv1]
#
# usage: ynh_add_swap --size=SWAP in Mib
# | arg: -s, --size= - Amount of SWAP to add in Mib.
ynh_add_swap () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need an additional helper, that would "just" call yunohost settings {get,set} 'swap.swapfile.swapfile_size' and make new_swap_size = current_swap_size + requested_app_swap_size.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yunohost settings set got me in an infinite config panel loop last night, we need to be careful with calling hooks from confif panels. ;)

# Declare an array to define the options of this helper.
local -A args_array=( [s]=size= )
local size
# Manage arguments with getopts
ynh_handle_getopts_args "$@"

# Could be moved to an argument
swapfile_dir="$(realpath /)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uuuuuh, why would / be ... something else than / ? x_x

swapfile_path="$(realpath "$swapfile_dir/swap_yunohost")"

# Early warning exit: Can't swap inside LXD
if [[ "$(systemd-detect-virt)" == "lxc" ]]; then
ynh_print_warn --message="You are inside a LXC container, swap will not be added."
ynh_print_warn --message="That can cause troubles for $app. Please make sure you have more than $((size/1024))G of RAM/swap available."
return
fi

# Early warning exit: Preserve SD cards and do not swap on them
if ynh_is_on_sd_card --dir="$swapfile_dir" && [[ "${SD_CARD_CAN_SWAP:-0}" == "0" ]]; then
ynh_print_warn --message="Swap files on an SD card is not recommended, swap will not be added."
ynh_print_warn --message="That can cause troubles for $app. Please make sure you have more than $((size/1024))G of RAM/swap available."
ynh_print_warn --message="If you still want activate the swap, you can relaunch the command preceded by 'SD_CARD_CAN_SWAP=1'"
return
fi

# Early warning exit: swapfile already exists
if [ -f "$swapfile_path" ]; then
ynh_print_warn --message="Swap file $swapfile_path already exists!"
return
fi

local swap_max_size_kb=$(( size * 1024 ))

local free_space_kb
free_space_kb=$(df --block-size=K --output=avail "$swapfile_dir" | sed 1d | sed -e 's/K$//')
# Because we don't want to fill the disk with a swap file, divide by 2 the available space.
local usable_space_kb=$(( free_space_kb / 2 ))

# Compare the available space with the size of the swap.
# And set a acceptable size from the request
size_space_ratio=$(( swap_max_size_kb / usable_space_kb + 1 ))
if (( size_space_ratio > 4)); then
ynh_print_warn --message="Not enough space for a swap file at $swapfile_path."
return
fi
swap_size_kb=$(( swap_max_size_kb / size_space_ratio ))
tituspijean marked this conversation as resolved.
Show resolved Hide resolved

# Configure swappiness
if [ ! -f /etc/sysctl.d/999-yunohost-swap.conf ]; then
echo "vm.swappiness=10" > /etc/sysctl.d/999-ynhswap.conf
sysctl --quiet --system
fi

# Workaround for some copy-on-write filesystems like btrfs.
# Create the file
truncate -s 0 "$swapfile_path"
# Set the No_COW attribute on the swapfile with chattr
chattr +C "$swapfile_path" || true
# Set the final file size
dd if=/dev/zero of="$swapfile_path" bs=1024 count="$swap_size_kb"
chmod 0600 "$swapfile_path"

# Create the swap
mkswap "$swapfile_path"
# And activate it
swapon "$swapfile_path"
# Then add an entry in fstab to load this swap at each boot.
echo -e "$swapfile_path swap swap defaults 0 0 #Swap added by $app" >> /etc/fstab
}

# Delete the swap file created for the app by ynh_add_swap.
#
# [packagingv1]
#
ynh_del_swap () {
# Could be moved to an argument
swapfile_dir="$(realpath /)"
swapfile_path="$(realpath "$swapfile_dir/swap_yunohost")"

if [ ! -f "$swapfile_path" ]; then
return
fi

# Clean the fstab...
sed -i "/#Swap added by $app/d" /etc/fstab
# Desactive the swap file...
swapoff "$swapfile_path"
# And remove it.
rm "$swapfile_path"
}

# Check if the device of the provided directory is an SD card
#
# [internal]
#
# usage: ynh_is_on_sd_card --dir=/
# | arg: -d, --dir= - Directory to check
ynh_is_on_sd_card () {
# Declare an array to define the options of this helper.
local -A args_array=( [d]=dir= )
local dir
# Manage arguments with getopts
ynh_handle_getopts_args "$@"

device_dev=$(findmnt --nofsroot --uniq --output source --noheadings --first-only --target "$dir")
device_pkname=$(lsblk --output PKNAME --noheadings "$device_dev")
device_rotational=$(lsblk --output ROTA --noheadings "$device_dev")

if [[ "$device_pkname" == *"mmc"* ]] && [[ "$device_rotational" == "0" ]]; then
return 0
else
return 1
fi
}
31 changes: 31 additions & 0 deletions hooks/conf_regen/53-swap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash

source /usr/share/yunohost/helpers

do_pre_regen() {
:
}

do_post_regen() {
swapfile_enabled="$(yunohost settings get 'swap.swapfile.swapfile_enabled')"
swapfile_size="$(yunohost settings get 'swap.swapfile.swapfile_size')"

if [ ${swapfile_enabled} -eq 1 ]; then
# If a swapfile is requested
if [ -f /swap_file ] && [ $(stat -c%s /swap_file) -ne $swapfile_size ]; then
# Let's delete it first if it is not of the requested size
ynh_print_info --message="Deleting swap first..."
ynh_del_swap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really dangerous. Let's imagine this situation:

  • RAM is 1GB, usage 900MB, Swapfile is 1GB, usage 500MB
  • you want a 2GB swap file now, so we fall into this code
  • ynh_del_swap deletes the original swap file, making the server (or services) most probably crash from OOM (1.4GB required on a 1GB available ram)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two better solutions would be :

  • just create a second swap file of size = difference (in my example, 2GB(total) - 1GB(existing) = 1GB new swap file)
  • create a second swap file of the total requested swap size, then afterwards delete the old file. The danger being the disk space but eh, i think we are okay, ram/swap is way smaller than disks usually…

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good, thank you for giving some thoughts into this. :)
I had added a very lazy "Beware, if you reduce its current size, you may crash your server." in the config panel, but indeed there are other ways to crash everything.
I think your proposal is good!

fi
# create the swapfile
ynh_print_info --message="Attempting to create $swapfile_size MB swap..."
ynh_add_swap --size=$swapfile_size
ynh_print_info --message="A $((swap_size / 1024)) MB swap has been created."
else
# If not, make sure it is deleted
ynh_print_info --message="Deleting swap..."
ynh_del_swap
fi
}

do_$1_regen ${@:2}
22 changes: 22 additions & 0 deletions share/config_global.toml
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,25 @@ name = "Other"
choices.ipv4 = "IPv4 Only"
choices.ipv6 = "IPv6 Only"
default = "both"
[swap]
name = "Swap"
[swap.swapfile]
name = "Swap file configuration"
[swap.swapfile.swapfile_warning]
ask = "Absolutely do not create a swap file if your root partition is on a SD card!"
help = "The intensive writing on the SD card will degrade it fast."
type = "alert"
style = "warning"

[swap.swapfile.swapfile_enabled]
ask = "Do you need to create a swap file?"
help = "A swap file will extend the available RAM by using some of your storage space."
type = "boolean"
default = false

[swap.swapfile.swapfile_size]
ask = "How many Mebibytes should the swap file be?"
help = "Only integers are accepted ( 1 MiB = 1024 B). Beware, if you reduce its current size, you may crash your server."
type = "number"
default = 1024
visible = "swapfile_enabled"
6 changes: 6 additions & 0 deletions src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,9 @@ def reconfigure_dovecot(setting_name, old_value, new_value):
regen_conf(names=["dovecot"])
command = ["apt-get", "-y", "remove", dovecot_package]
subprocess.call(command, env=environment)

@post_change_hook("swapfile_enabled")
@post_change_hook("swapfile_size")
def reconfigure_swapfile(setting_name, old_value, new_value):
if old_value != new_value:
regen_conf(names=["swap"])
Loading