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 3 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
106 changes: 106 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,109 @@ ynh_require_ram() {
return 0
fi
}

# Add swap
#
# 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.
declare -Ar args_array=( [s]=size= )
local size
# Manage arguments with getopts
ynh_handle_getopts_args "$@"

local swap_max_size=$(( $size * 1024 ))

local free_space=$(df --output=avail / | sed 1d)
# Because we don't want to fill the disk with a swap file, divide by 2 the available space.
local usable_space=$(( $free_space / 2 ))

SD_CARD_CAN_SWAP=${SD_CARD_CAN_SWAP:-0}

# Swap on SD card only if it's is specified
if ynh_is_main_device_a_sd_card && [ "$SD_CARD_CAN_SWAP" == "0" ]
then
ynh_print_warn --message="The main mountpoint of your system '/' is on an SD card, swap will not be added to prevent some damage to it. If you still want activate the swap, you can relaunch the command preceded by 'SD_CARD_CAN_SWAP=1'"
return
fi

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

# Compare the available space with the size of the swap.
# And set a acceptable size from the request
if [ $usable_space -ge $swap_max_size ]
then
swap_size=$swap_max_size
elif [ $usable_space -ge $(( $swap_max_size / 2 )) ]
then
swap_size=$(( $swap_max_size / 2 ))
elif [ $usable_space -ge $(( $swap_max_size / 3 )) ]
then
swap_size=$(( $swap_max_size / 3 ))
elif [ $usable_space -ge $(( $swap_max_size / 4 )) ]
then
swap_size=$(( $swap_max_size / 4 ))
else
echo "Not enough space left for a swap file" >&2
swap_size=0
fi

# If there's enough space for a swap, and no existing swap here
if [ $swap_size -ne 0 ] && [ ! -e /swap_file ]
then
# Create file
truncate -s 0 /swap_file

# set the No_COW attribute on the swapfile with chattr
# let's not fail here, as some filesystems like ext4 do not support COW
chattr +C /swap_file || true

# Preallocate space for the swap file, fallocate may sometime not be used, use dd instead in this case
if ! fallocate -l ${swap_size}K /swap_file
then
dd if=/dev/zero of=/swap_file bs=1024 count=${swap_size}
fi
chmod 0600 /swap_file
# Create the swap
mkswap /swap_file
# And activate it
swapon /swap_file
# Then add an entry in fstab to load this swap at each boot.
echo -e "/swap_file swap swap defaults 0 0 #Swap added by YunoHost config panel" >> /etc/fstab
fi
}

ynh_del_swap () {
# If there a swap at this place
if [ -e /swap_file ]
then
# Clean the fstab
sed -i "/#Swap added by YunoHost config panel/d" /etc/fstab
# Desactive the swap file
swapoff /swap_file
# And remove it
rm /swap_file
fi
}

# Check if the device of the main mountpoint "/" is an SD card
#
# [internal]
#
# return 0 if it's an SD card, else 1
ynh_is_main_device_a_sd_card () {
local main_device=$(lsblk --output PKNAME --noheadings $(findmnt / --nofsroot --uniq --output source --noheadings --first-only))

if echo $main_device | grep --quiet "mmc" && [ $(tail -n1 /sys/block/$main_device/queue/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