cleanup
This commit is contained in:
@ -1,42 +0,0 @@
|
||||
# bigboot
|
||||
|
||||
The `bigboot` role is used to increase boot partition.
|
||||
|
||||
The role is designed to support the automation of RHEL in-place upgrades, but can also be used for other purposes.
|
||||
|
||||
## Contents
|
||||
|
||||
The role contains the shell scripts to increase the size of the boot partition, as well as the script wrapping it to run as part of the pre-mount step during the boot process.
|
||||
Finally, there is a copy of the [`sfdisk`](https://man7.org/linux/man-pages/man8/sfdisk.8.html) binary with version `2.38.1` to ensure the extend script will work regardless of the `util-linux` package installed in the target host.
|
||||
|
||||
## Role Variables
|
||||
|
||||
### `bigboot_size`
|
||||
|
||||
The variable `bigboot_size` sets the target size of the boot partition after the role has completed.
|
||||
The value can be either in bytes or with optional single letter suffix (1024 bases).
|
||||
See `Unit options` type `iec` of [`numfmt`](https://man7.org/linux/man-pages/man1/numfmt.1.html)
|
||||
|
||||
|
||||
## Example of a playbook to run the role
|
||||
The following yaml is an example of a playbook that runs the role against a group of hosts named `rhel` and increasing the size of its boot partition by 1G.
|
||||
The boot partition is automatically retrieved by the role by identifying the existing mounted partition to `/boot` and passing the information to the script using the `kernel_opts`.
|
||||
|
||||
```yaml
|
||||
- name: Extend boot partition playbook
|
||||
hosts: all
|
||||
vars:
|
||||
bigboot_size: 1G
|
||||
roles:
|
||||
- bigboot
|
||||
```
|
||||
|
||||
# Validate execution
|
||||
The script will add an entry to the kernel messages (`/dev/kmsg`) with success or failure and the time it took to process.
|
||||
In case of failure, it may also include an error message retrieved from the execution of the script.
|
||||
|
||||
A successful execution will look similar to this:
|
||||
```bash
|
||||
[root@localhost ~]# dmesg |grep pre-mount
|
||||
[ 357.163522] [dracut-pre-mount] Boot partition /dev/vda1 successfully increased by 1G (356 seconds)
|
||||
```
|
@ -1 +0,0 @@
|
||||
bigboot_size:
|
@ -1,547 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Script to increase the ext4/xfs boot partition in a BIOS system by shifting
|
||||
# the adjacent partition to the boot partition by the parametrized size. It
|
||||
# expects the device to have enough free space to shift to the right of the
|
||||
# adjacent partition, that is towards the end of the device. It only works
|
||||
# with ext4 and xfs file systems and supports adjacent partitions as primary
|
||||
# or logical partitions and LVM in the partition.
|
||||
#
|
||||
# The parametrized size supports M for MiB and G for GiB. If no units is given,
|
||||
# it is interpreted as bytes
|
||||
#
|
||||
# Usage: bigboot.sh -d=<device_name> -s=<increase_size_with_units> -b=<boot_partition_number> -p=<partition_prefix>
|
||||
#
|
||||
# Example
|
||||
# Given this device partition:
|
||||
# Number Start End Size Type File system Flags
|
||||
# 32.3kB 1049kB 1016kB Free Space
|
||||
# 1 1049kB 11.1GB 11.1GB primary ext4 boot
|
||||
# 2 11.1GB 32.2GB 21.1GB extended
|
||||
# 5 11.1GB 32.2GB 21.1GB logical ext4
|
||||
#
|
||||
# Running the command:
|
||||
# $>bigboot.sh -d=/dev/sda -s=1G -b=1
|
||||
#
|
||||
# Will increase the boot partition in /dev/vdb by 1G and shift the adjacent
|
||||
# partition in the device by the equal amount.
|
||||
#
|
||||
# Number Start End Size Type File system Flags
|
||||
# 32.3kB 1049kB 1016kB Free Space
|
||||
# 1 1049kB 12.2GB 12.2GB primary ext4 boot
|
||||
# 2 12.2GB 32.2GB 20.0GB extended
|
||||
# 5 12.2GB 32.2GB 20.0GB logical ext4
|
||||
#
|
||||
|
||||
# Command parameters
|
||||
INCREMENT_BOOT_PARTITION_SIZE=
|
||||
DEVICE_NAME=
|
||||
BOOT_PARTITION_NUMBER=
|
||||
PARTITION_PREFIX=
|
||||
|
||||
# Script parameters
|
||||
ADJACENT_PARTITION_NUMBER=
|
||||
BOOT_FS_TYPE=
|
||||
EXTENDED_PARTITION_TYPE=extended
|
||||
LOGICAL_VOLUME_DEVICE_NAME=
|
||||
INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES=
|
||||
SHRINK_SIZE_IN_BYTES=
|
||||
|
||||
print_help(){
|
||||
echo "Usage: $(basename "$0") -d=<device_name> -s=<increase_size_with_units> -b=<boot_partition_number> -p=<partition_prefix>"
|
||||
}
|
||||
|
||||
get_device_type(){
|
||||
local device=$1
|
||||
val=$(/usr/bin/lsblk "$device" -o type --noheadings 2>&1)
|
||||
local status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to retrieve device type for $device: $val"
|
||||
exit 1
|
||||
fi
|
||||
type=$(tail -n1 <<<"$val")
|
||||
if [[ -z $type ]]; then
|
||||
echo "Unknown device type for $device"
|
||||
exit 1
|
||||
fi
|
||||
echo "$type"
|
||||
}
|
||||
|
||||
ensure_device_not_mounted() {
|
||||
local device=$1
|
||||
local devices_to_check
|
||||
device_type=$(get_device_type "$device")
|
||||
if [[ $device_type == "lvm" ]]; then
|
||||
# It's an LVM block device
|
||||
# Capture the LV device names. Since we'll have to shift the partition, we need to make sure all LVs are not mounted in the adjacent partition.
|
||||
devices_to_check=$(/usr/sbin/lvm pvdisplay "$device" -m |/usr/bin/grep "Logical volume" |/usr/bin/awk '{print $3}')
|
||||
else
|
||||
# Use the device and partition number instead
|
||||
devices_to_check=$device
|
||||
fi
|
||||
for device_name in $devices_to_check; do
|
||||
/usr/bin/findmnt --source "$device_name" 1>&2>/dev/null
|
||||
status=$?
|
||||
if [[ status -eq 0 ]]; then
|
||||
echo "Device $device_name is mounted"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
validate_device() {
|
||||
local device=$1
|
||||
if [[ -z "${device}" ]]; then
|
||||
echo "Missing device name"
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -e "${device}" ]]; then
|
||||
echo "Device ${device} not found"
|
||||
exit 1
|
||||
fi
|
||||
ret=$(/usr/sbin/fdisk -l "${device}" 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to open device ${device}: $ret"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_increment_partition_size() {
|
||||
if [[ -z "$INCREMENT_BOOT_PARTITION_SIZE" ]]; then
|
||||
echo "Missing incremental size for boot partition"
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
ret=$(/usr/bin/numfmt --from=iec "$INCREMENT_BOOT_PARTITION_SIZE" 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Invalid size value for '$INCREMENT_BOOT_PARTITION_SIZE': $ret"
|
||||
exit $status
|
||||
fi
|
||||
INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES=$ret
|
||||
}
|
||||
|
||||
# Capture all parameters:
|
||||
# Mandatory: Device, Size and Boot Partition Number
|
||||
# Optional: Partition Prefix (e.g. "p" for nvme based volumes)
|
||||
parse_flags() {
|
||||
for i in "$@"
|
||||
do
|
||||
case $i in
|
||||
-d=*|--device=*)
|
||||
DEVICE_NAME="${i#*=}"
|
||||
;;
|
||||
-s=*|--size=*)
|
||||
INCREMENT_BOOT_PARTITION_SIZE="${i#*=}"
|
||||
;;
|
||||
-b=*|--boot=*)
|
||||
BOOT_PARTITION_NUMBER="${i#*=}"
|
||||
;;
|
||||
-p=*|--prefix=*)
|
||||
PARTITION_PREFIX="${i#*=}"
|
||||
;;
|
||||
-h)
|
||||
print_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# unknown option
|
||||
echo "Unknown flag $i"
|
||||
print_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
validate_parameters() {
|
||||
validate_device "${DEVICE_NAME}"
|
||||
validate_increment_partition_size
|
||||
|
||||
# Make sure BOOT_PARTITION_NUMBER is set to avoid passing only DEVICE_NAME
|
||||
if [[ -z "$BOOT_PARTITION_NUMBER" ]]; then
|
||||
echo "Boot partition number was not set"
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
validate_device "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}"
|
||||
|
||||
ensure_device_not_mounted "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}"
|
||||
ensure_extendable_fs_type "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}"
|
||||
}
|
||||
|
||||
get_fs_type(){
|
||||
local device=$1
|
||||
ret=$(/usr/sbin/blkid "$device" -o udev | sed -n -e 's/ID_FS_TYPE=//p' 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
exit $status
|
||||
fi
|
||||
echo "$ret"
|
||||
}
|
||||
|
||||
ensure_extendable_fs_type(){
|
||||
local device=$1
|
||||
ret=$(get_fs_type "$device")
|
||||
if [[ "$ret" != "ext4" ]] && [[ "$ret" != "xfs" ]]; then
|
||||
echo "Boot file system type $ret is not extendable"
|
||||
exit 1
|
||||
fi
|
||||
BOOT_FS_TYPE=$ret
|
||||
}
|
||||
|
||||
get_successive_partition_number() {
|
||||
boot_line_number=$(/usr/sbin/parted -m "$DEVICE_NAME" print |/usr/bin/sed -n '/^'"$BOOT_PARTITION_NUMBER"':/ {=}')
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Unable to identify boot partition number for '$DEVICE_NAME'"
|
||||
exit $status
|
||||
fi
|
||||
if [[ -z "$boot_line_number" ]]; then
|
||||
echo "No boot partition found"
|
||||
exit 1
|
||||
fi
|
||||
# get the extended partition number in case there is one, we will need to shrink it as well
|
||||
EXTENDED_PARTITION_NUMBER=$(/usr/sbin/parted "$DEVICE_NAME" print | /usr/bin/sed -n '/'"$EXTENDED_PARTITION_TYPE"'/p'|awk '{print $1}')
|
||||
if [[ -n "$EXTENDED_PARTITION_NUMBER" ]]; then
|
||||
# if there's an extended partition, use the last one as the target partition to shrink
|
||||
ADJACENT_PARTITION_NUMBER=$(/usr/sbin/parted "$DEVICE_NAME" print |grep -v "^$" |awk 'END{print$1}')
|
||||
else
|
||||
# get the partition number from the next line after the boot partition
|
||||
ADJACENT_PARTITION_NUMBER=$(/usr/sbin/parted -m "$DEVICE_NAME" print | /usr/bin/awk -F ':' '/'"^$BOOT_PARTITION_NUMBER:"'/{getline;print $1}')
|
||||
fi
|
||||
if ! [[ $ADJACENT_PARTITION_NUMBER == +([[:digit:]]) ]]; then
|
||||
echo "Invalid successive partition number '$ADJACENT_PARTITION_NUMBER'"
|
||||
exit 1
|
||||
fi
|
||||
ensure_device_not_mounted "${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
}
|
||||
|
||||
init_variables(){
|
||||
parse_flags "$@"
|
||||
validate_parameters
|
||||
get_successive_partition_number
|
||||
}
|
||||
|
||||
check_filesystem(){
|
||||
local device=$1
|
||||
fstype=$(get_fs_type "$device")
|
||||
if [[ "$fstype" == "swap" ]]; then
|
||||
echo "Warning: cannot run fsck to a swap partition for $device"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$BOOT_FS_TYPE" == "ext4" ]]; then
|
||||
# Retrieve the estimated minimum size in bytes that the device can be shrank
|
||||
ret=$(/usr/sbin/e2fsck -fy "$device" 2>&1)
|
||||
local status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Warning: File system check failed for $device: $ret"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
convert_size_to_fs_blocks(){
|
||||
local device=$1
|
||||
local size=$2
|
||||
block_size_in_bytes=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block size:/{print $3}')
|
||||
echo $(( size / block_size_in_bytes ))
|
||||
}
|
||||
|
||||
calculate_expected_resized_file_system_size_in_blocks(){
|
||||
local device=$1
|
||||
increment_boot_partition_in_blocks=$(convert_size_to_fs_blocks "$device" "$INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES")
|
||||
total_block_count=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block count:/{print $3}')
|
||||
new_fs_size_in_blocks=$(( total_block_count - increment_boot_partition_in_blocks ))
|
||||
echo $new_fs_size_in_blocks
|
||||
}
|
||||
|
||||
get_free_device_size() {
|
||||
free_space=$(/usr/sbin/parted -m "$DEVICE_NAME" unit b print free | /usr/bin/awk -F':' '/'"^$ADJACENT_PARTITION_NUMBER:"'/{getline;print $0}'|awk -F':' '/free/{print $4}'|sed -e 's/B//g')
|
||||
echo "$free_space"
|
||||
}
|
||||
|
||||
get_volume_group_name(){
|
||||
local volume_group_name
|
||||
ret=$(/usr/sbin/lvm pvs "${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" -o vg_name --noheadings|/usr/bin/sed 's/^[[:space:]]*//g')
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to retrieve volume group name for logical volume $LOGICAL_VOLUME_DEVICE_NAME: $ret"
|
||||
exit $status
|
||||
fi
|
||||
echo "$ret"
|
||||
}
|
||||
|
||||
deactivate_volume_group(){
|
||||
local volume_group_name
|
||||
volume_group_name=$(get_volume_group_name)
|
||||
ret=$(/usr/sbin/lvm vgchange -an "$volume_group_name" 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to deactivate volume group $volume_group_name: $ret"
|
||||
exit $status
|
||||
fi
|
||||
# avoid potential deadlocks with udev rules before continuing
|
||||
sleep 1
|
||||
}
|
||||
|
||||
check_available_free_space(){
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
free_device_space_in_bytes=$(get_free_device_size)
|
||||
# if there is enough free space after the adjacent partition, there is no need to shrink it.
|
||||
if [[ $free_device_space_in_bytes -gt $INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES ]]; then
|
||||
SHRINK_SIZE_IN_BYTES=0
|
||||
return
|
||||
fi
|
||||
SHRINK_SIZE_IN_BYTES=$((INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES-free_device_space_in_bytes))
|
||||
device_type=$(get_device_type "${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}")
|
||||
if [[ "$device_type" == "lvm" ]]; then
|
||||
# there is not enough free space after the adjacent partition, calculate how much extra space is needed
|
||||
# to be fred from the PV
|
||||
local volume_group_name
|
||||
volume_group_name=$(get_volume_group_name)
|
||||
pe_size_in_bytes=$(/usr/sbin/lvm pvdisplay "$device" --units b| /usr/bin/awk 'index($0,"PE Size") {print $3}')
|
||||
unusable_space_in_pv_in_bytes=$(/usr/sbin/lvm pvdisplay --units B "$device" | /usr/bin/awk 'index($0,"not usable") {print $(NF-1)}'|/usr/bin/numfmt --from=iec)
|
||||
total_pe_count_in_vg=$(/usr/sbin/lvm vgs "$volume_group_name" -o pv_pe_count --noheadings)
|
||||
allocated_pe_count_in_vg=$(usr/sbin/lvm vgs "$volume_group_name" -o pv_pe_alloc_count --noheadings)
|
||||
free_pe_count=$((total_pe_count_in_vg - allocated_pe_count_in_vg))
|
||||
# factor in the unusable space to match the required number of free PEs
|
||||
required_pe_count=$(((SHRINK_SIZE_IN_BYTES+unusable_space_in_pv_in_bytes)/pe_size_in_bytes))
|
||||
if [[ $required_pe_count -gt $free_pe_count ]]; then
|
||||
echo "Not enough available free PE in VG $volume_group_name: Required $required_pe_count but found $free_pe_count"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_device_name(){
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
device_type=$(get_device_type "$device")
|
||||
if [[ $device_type == "lvm" ]]; then
|
||||
# It's an LVM block device
|
||||
# Determine which is the last LV in the PV
|
||||
# shellcheck disable=SC2016
|
||||
device=$(/usr/sbin/lvm pvdisplay "$device" -m | /usr/bin/sed -n '/Logical volume/h; ${x;p;}' | /usr/bin/awk '{print $3}')
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to identify the last LV in $device"
|
||||
exit $status
|
||||
fi
|
||||
# Capture the LV device name
|
||||
LOGICAL_VOLUME_DEVICE_NAME=$device
|
||||
fi
|
||||
}
|
||||
|
||||
check_device(){
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
resolve_device_name
|
||||
ensure_device_not_mounted "$device"
|
||||
check_available_free_space
|
||||
}
|
||||
|
||||
evict_end_PV() {
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
local shrinking_start_PE=$1
|
||||
ret=$(/usr/sbin/lvm pvmove --alloc anywhere "$device":"$shrinking_start_PE"- 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to move PEs in PV $LOGICAL_VOLUME_DEVICE_NAME: $ret"
|
||||
exit $status
|
||||
fi
|
||||
check_filesystem "$LOGICAL_VOLUME_DEVICE_NAME"
|
||||
}
|
||||
|
||||
shrink_physical_volume() {
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
pe_size_in_bytes=$(/usr/sbin/lvm pvdisplay "$device" --units b| /usr/bin/awk 'index($0,"PE Size") {print $3}')
|
||||
unusable_space_in_pv_in_bytes=$(/usr/sbin/lvm pvdisplay --units B "$device" | /usr/bin/awk 'index($0,"not usable") {print $(NF-1)}'|/usr/bin/numfmt --from=iec)
|
||||
|
||||
total_pe_count=$(/usr/sbin/lvm pvs "$device" -o pv_pe_count --noheadings | /usr/bin/sed 's/^[[:space:]]*//g')
|
||||
evict_size_in_PE=$((SHRINK_SIZE_IN_BYTES/pe_size_in_bytes))
|
||||
shrink_start_PE=$((total_pe_count - evict_size_in_PE))
|
||||
pv_new_size_in_bytes=$(( (shrink_start_PE*pe_size_in_bytes) + unusable_space_in_pv_in_bytes ))
|
||||
|
||||
ret=$(/usr/sbin/lvm pvresize --setphysicalvolumesize "$pv_new_size_in_bytes"B -t "$device" -y 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
if [[ $status -eq 5 ]]; then
|
||||
# ERRNO 5 is equivalent to command failed: https://github.com/lvmteam/lvm2/blob/2eb34edeba8ffc9e22b6533e9cb20e0b5e93606b/tools/errors.h#L23
|
||||
# Try to recover by evicting the ending PEs elsewhere in the PV, in case it's a failure due to ending PE's being inside the shrinking area.
|
||||
evict_end_PV $shrink_start_PE
|
||||
else
|
||||
echo "Failed to resize PV $device: $ret"
|
||||
exit $status
|
||||
fi
|
||||
fi
|
||||
echo "Shrinking PV $device to $pv_new_size_in_bytes bytes" >&2
|
||||
ret=$(/usr/sbin/lvm pvresize --setphysicalvolumesize "$pv_new_size_in_bytes"B "$device" -y 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to resize PV $device during retry: $ret"
|
||||
exit $status
|
||||
fi
|
||||
check_filesystem "$LOGICAL_VOLUME_DEVICE_NAME"
|
||||
}
|
||||
|
||||
calculate_new_end_partition_size_in_bytes(){
|
||||
local partition_number=$1
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${partition_number}"
|
||||
current_partition_size_in_bytes=$(/usr/sbin/parted -m "$DEVICE_NAME" unit b print| /usr/bin/awk '/^'"$partition_number"':/ {split($0,value,":"); print value[3]}'| /usr/bin/sed -e's/B//g')
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to convert new device size to megabytes $device: $ret"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
new_partition_size_in_bytes=$(( current_partition_size_in_bytes - SHRINK_SIZE_IN_BYTES))
|
||||
echo "$new_partition_size_in_bytes"
|
||||
}
|
||||
|
||||
shrink_partition() {
|
||||
local partition_number=$1
|
||||
new_end_partition_size_in_bytes=$(calculate_new_end_partition_size_in_bytes "$partition_number")
|
||||
echo "Shrinking partition $partition_number in $DEVICE_NAME" >&2
|
||||
ret=$(echo Yes | /usr/sbin/parted "$DEVICE_NAME" ---pretend-input-tty unit B resizepart "$partition_number" "$new_end_partition_size_in_bytes" 2>&1 )
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to resize device $DEVICE_NAME$partition_number to size: $ret"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
shrink_adjacent_partition(){
|
||||
if [[ $SHRINK_SIZE_IN_BYTES -eq 0 ]]; then
|
||||
# no need to shrink the PV or the partition as there is already enough free available space after the partition holding the PV
|
||||
return 0
|
||||
fi
|
||||
local device_type
|
||||
device_type=$(get_device_type "${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}")
|
||||
if [[ "$device_type" == "lvm" ]]; then
|
||||
shrink_physical_volume
|
||||
fi
|
||||
shrink_partition "$ADJACENT_PARTITION_NUMBER"
|
||||
if [[ -n "$EXTENDED_PARTITION_NUMBER" ]]; then
|
||||
# resize the extended partition
|
||||
shrink_partition "$EXTENDED_PARTITION_NUMBER"
|
||||
fi
|
||||
}
|
||||
|
||||
shift_adjacent_partition() {
|
||||
# If boot partition is not the last one, shift the successive partition to the right to take advantage of the newly fred space. Use 'echo '<amount_to_shift>,' | sfdisk --move-data <device name> -N <partition number>
|
||||
# to shift the partition to the right.
|
||||
# The astute eye will notice that we're moving the partition, not the last logical volume in the partition.
|
||||
local target_partition=$ADJACENT_PARTITION_NUMBER
|
||||
if [[ -n "$EXTENDED_PARTITION_NUMBER" ]]; then
|
||||
target_partition=$EXTENDED_PARTITION_NUMBER
|
||||
fi
|
||||
( sleep 4
|
||||
while t="$(ps -C sfdisk -o cputime=)"; do
|
||||
echo "Bigboot partition move is progressing, please wait! ($t)" >&2
|
||||
sleep 120
|
||||
done ) &
|
||||
echo "Moving up partition $target_partition in $DEVICE_NAME by $INCREMENT_BOOT_PARTITION_SIZE" >&2
|
||||
ret=$(echo "+$INCREMENT_BOOT_PARTITION_SIZE,"| /usr/sbin/sfdisk --move-data "$DEVICE_NAME" -N "$target_partition" --force 2>&1)
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to shift partition '$DEVICE_NAME$target_partition': $ret"
|
||||
exit $status
|
||||
fi
|
||||
}
|
||||
|
||||
update_kernel_partition_tables(){
|
||||
# Ensure no size inconsistencies between PV and partition
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
device_type=$(get_device_type "$device")
|
||||
if [[ $device_type == "lvm" ]]; then
|
||||
ret=$(/usr/sbin/lvm pvresize "$device" -y 2>&1)
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to align PV and partition sizes '$device': $ret"
|
||||
exit $status
|
||||
fi
|
||||
# ensure that the VG is not active so that the changes to the kernel PT are reflected by the partprobe command
|
||||
deactivate_volume_group
|
||||
fi
|
||||
/usr/sbin/partprobe "$DEVICE_NAME" 2>&1
|
||||
if [[ $device_type == "lvm" ]]; then
|
||||
# reactivate volume group
|
||||
activate_volume_group
|
||||
fi
|
||||
}
|
||||
|
||||
increase_boot_partition() {
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}"
|
||||
local new_fs_size_in_blocks=
|
||||
echo "Increasing boot partition $BOOT_PARTITION_NUMBER in $DEVICE_NAME by $INCREMENT_BOOT_PARTITION_SIZE" >&2
|
||||
ret=$(echo "- +"| /usr/sbin/sfdisk "$DEVICE_NAME" -N "$BOOT_PARTITION_NUMBER" --no-reread --force 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to shift boot partition '$device': $ret"
|
||||
return
|
||||
fi
|
||||
update_kernel_partition_tables
|
||||
# Extend the boot file system with `resize2fs <boot_partition>`
|
||||
if [[ "$BOOT_FS_TYPE" == "ext4" ]]; then
|
||||
check_filesystem "$device"
|
||||
increment_boot_partition_in_blocks=$(convert_size_to_fs_blocks "$device" "$INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES")
|
||||
total_block_count=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block count:/{print $3}')
|
||||
new_fs_size_in_blocks=$(( total_block_count + increment_boot_partition_in_blocks ))
|
||||
ret=$(/usr/sbin/resize2fs "$device" $new_fs_size_in_blocks 2>&1)
|
||||
elif [[ "$BOOT_FS_TYPE" == "xfs" ]]; then
|
||||
block_size_in_bytes=$(/usr/sbin/xfs_db "$device" -c "sb" -c "print blocksize" |/usr/bin/awk '{print $3}')
|
||||
current_blocks_in_data=$(/usr/sbin/xfs_db "$device" -c "sb" -c "print dblocks" |/usr/bin/awk '{print $3}')
|
||||
increment_boot_partition_in_blocks=$((INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES/block_size_in_bytes))
|
||||
new_fs_size_in_blocks=$((current_blocks_in_data + increment_boot_partition_in_blocks))
|
||||
# xfs_growfs requires the file system to be mounted in order to change its size
|
||||
# Create a temporal directory
|
||||
tmp_dir=$(/usr/bin/mktemp -d)
|
||||
# Mount the boot file system in the temporal directory
|
||||
/usr/bin/mount "$device" "$tmp_dir"
|
||||
ret=$(/usr/sbin/xfs_growfs "$device" -D "$new_fs_size_in_blocks" 2>&1)
|
||||
# Capture the status
|
||||
status=$?
|
||||
# Unmount the file system
|
||||
/usr/bin/umount "$device"
|
||||
else
|
||||
echo "Device $device does not contain an ext4 or xfs file system: $BOOT_FS_TYPE"
|
||||
return
|
||||
fi
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to resize boot partition '$device': $ret"
|
||||
return
|
||||
fi
|
||||
echo "Boot file system increased to $new_fs_size_in_blocks blocks" >&2
|
||||
}
|
||||
|
||||
activate_volume_group(){
|
||||
local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}"
|
||||
local volume_group_name
|
||||
volume_group_name=$(get_volume_group_name)
|
||||
ret=$(/usr/sbin/lvm vgchange -ay "$volume_group_name" 2>&1)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to activate volume group $volume_group_name: $ret"
|
||||
exit $status
|
||||
fi
|
||||
# avoid potential deadlocks with udev rules before continuing
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# last steps are to run the fsck on boot partition and activate the volume group if necessary
|
||||
cleanup(){
|
||||
# run a file system check to the boot file system
|
||||
check_filesystem "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}"
|
||||
}
|
||||
|
||||
main() {
|
||||
init_variables "$@"
|
||||
check_device
|
||||
shrink_adjacent_partition
|
||||
shift_adjacent_partition
|
||||
increase_boot_partition
|
||||
cleanup
|
||||
}
|
||||
|
||||
main "$@"
|
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
|
||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
|
||||
|
||||
check(){
|
||||
return 0
|
||||
}
|
||||
|
||||
install() {
|
||||
inst_multiple -o /usr/bin/mount /usr/bin/umount /usr/sbin/parted /usr/bin/mktemp /usr/bin/wc /usr/bin/date /usr/bin/sed /usr/bin/awk /usr/bin/basename /usr/sbin/resize2fs /usr/sbin/tune2fs /usr/sbin/partprobe /usr/bin/numfmt /usr/sbin/lvm /usr/bin/lsblk /usr/sbin/e2fsck /usr/sbin/fdisk /usr/bin/findmnt /usr/bin/tail /usr/ /usr/sbin/xfs_growfs /usr/sbin/xfs_db
|
||||
# shellcheck disable=SC2154
|
||||
inst_hook pre-mount 99 "$moddir/increase-boot-partition.sh"
|
||||
inst_binary "$moddir/sfdisk.static" "/usr/sbin/sfdisk"
|
||||
inst_simple "$moddir/bigboot.sh" "/usr/bin/bigboot.sh"
|
||||
}
|
Binary file not shown.
@ -1,20 +0,0 @@
|
||||
- name: Find the boot mount entry
|
||||
ansible.builtin.set_fact:
|
||||
bigboot_boot_mount_entry: "{{ ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first }}"
|
||||
|
||||
- name: Calculate the partition to look for
|
||||
ansible.builtin.set_fact:
|
||||
bigboot_boot_partition_name: "{{ (bigboot_boot_mount_entry.device | split('/'))[-1] }}"
|
||||
|
||||
- name: Find the boot device parent
|
||||
ansible.builtin.set_fact:
|
||||
bigboot_boot_disk: "{{ item.key }}"
|
||||
with_dict: "{{ ansible_facts.devices }}"
|
||||
when: bigboot_boot_partition_name in item.value.partitions
|
||||
|
||||
- name: Capture boot device details
|
||||
ansible.builtin.set_fact:
|
||||
bigboot_boot_device_partition_prefix: "{{ bigboot_boot_partition_name[(bigboot_boot_disk | length) : -1] }}"
|
||||
bigboot_boot_partition_number: "{{ bigboot_boot_partition_name[-1] }}"
|
||||
bigboot_boot_device_name: "/dev/{{ bigboot_boot_disk }}"
|
||||
bigboot_boot_device_original_size: "{{ bigboot_boot_mount_entry.size_total | int }}"
|
@ -1,66 +0,0 @@
|
||||
---
|
||||
- name: Make sure the required related facts are available
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
- mounts
|
||||
- devices
|
||||
|
||||
- name: Validate bigboot_size is not empty
|
||||
ansible.builtin.assert:
|
||||
that: bigboot_size | length >0
|
||||
fail_msg: "bigboot_size is empty"
|
||||
|
||||
- name: Validate initramfs preflight
|
||||
ansible.builtin.include_role:
|
||||
name: initramfs
|
||||
tasks_from: preflight
|
||||
|
||||
- name: Get boot device info
|
||||
ansible.builtin.include_tasks:
|
||||
file: get_boot_device_info.yml
|
||||
|
||||
- name: Copy extend boot dracut module
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item }}"
|
||||
dest: /usr/lib/dracut/modules.d/99extend_boot/
|
||||
mode: "0554"
|
||||
loop:
|
||||
- bigboot.sh
|
||||
- module-setup.sh
|
||||
- sfdisk.static
|
||||
|
||||
- name: Resolve and copy the shrink-start script
|
||||
ansible.builtin.template:
|
||||
src: increase-boot-partition.sh.j2
|
||||
dest: /usr/lib/dracut/modules.d/99extend_boot/increase-boot-partition.sh
|
||||
mode: '0554'
|
||||
|
||||
- name: Create the initramfs and reboot to run the module
|
||||
vars:
|
||||
initramfs_add_modules: "extend_boot"
|
||||
ansible.builtin.include_role:
|
||||
name: initramfs
|
||||
|
||||
- name: Remove dracut extend boot module
|
||||
ansible.builtin.file:
|
||||
path: /usr/lib/dracut/modules.d/99extend_boot
|
||||
state: absent
|
||||
|
||||
- name: Retrieve mount points
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
- mounts
|
||||
|
||||
- name: Capture boot device new size
|
||||
ansible.builtin.set_fact:
|
||||
bigboot_boot_device_new_size: "{{ (ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first).size_total | int }}"
|
||||
|
||||
- name: Validate boot partition new size
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- bigboot_boot_device_new_size != bigboot_boot_device_original_size
|
||||
fail_msg: "Boot partition size '{{ bigboot_boot_device_new_size }}' did not change"
|
@ -1,31 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
disable_lvm_lock(){
|
||||
tmpfile=$(/usr/bin/mktemp)
|
||||
sed -e 's/\(^[[:space:]]*\)locking_type[[:space:]]*=[[:space:]]*[[:digit:]]/\1locking_type = 1/' /etc/lvm/lvm.conf >"$tmpfile"
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to disable lvm lock: $status" >/dev/kmsg
|
||||
exit 1
|
||||
fi
|
||||
# replace lvm.conf. There is no need to keep a backup since it's an ephemeral file, we are not replacing the original in the initramfs image file
|
||||
mv "$tmpfile" /etc/lvm/lvm.conf
|
||||
}
|
||||
|
||||
main() {
|
||||
name=$(basename "$0")
|
||||
start=$(/usr/bin/date +%s)
|
||||
disable_lvm_lock
|
||||
# run bigboot.sh to increase boot partition and file system size
|
||||
ret=$(sh /usr/bin/bigboot.sh -d="{{ bigboot_boot_device_name }}" -s="{{ bigboot_size }}" -b="{{ bigboot_boot_partition_number }}" -p="{{ bigboot_boot_device_partition_prefix }}" 2>/dev/kmsg)
|
||||
status=$?
|
||||
end=$(/usr/bin/date +%s)
|
||||
# write the log file
|
||||
if [[ $status -eq 0 ]]; then
|
||||
echo "[$name] Boot partition {{ bigboot_boot_device_name }} successfully increased by {{ bigboot_size }} ("$((end-start))" seconds) " >/dev/kmsg
|
||||
else
|
||||
echo "[$name] Failed to extend boot partition: $ret ("$((end-start))" seconds)" >/dev/kmsg
|
||||
fi
|
||||
}
|
||||
|
||||
main "$0"
|
@ -1,59 +0,0 @@
|
||||
# initramfs
|
||||
|
||||
The `initramfs` role is included by the `shrink_lv` and `bigboot` roles to run an atomic flow of building and using a temporary initramfs in a reboot and restoring the original one.
|
||||
|
||||
The role is designed to be internal for this collection and support the automation of RHEL in-place upgrades, but can also be used for other purposes.
|
||||
|
||||
## Contents
|
||||
|
||||
To allow fast fail, the role provides a [`preflight.yml`](./tasks/preflight.yml) tasks file to be used at the start of the playbook.
|
||||
Please note that the [`main`](./tasks/main.yml) task file will not run the preflight checks
|
||||
|
||||
## Role Variables
|
||||
|
||||
All variables are optional
|
||||
|
||||
### `initramfs_add_modules`
|
||||
|
||||
`initramfs_add_modules` is a a space-separated list of dracut modules to be added to the default set of modules.
|
||||
See [`dracut`](https://man7.org/linux/man-pages/man8/dracut.8.html) `-a` parameter for details.
|
||||
|
||||
### `initramfs_backup_extension`
|
||||
|
||||
`initramfs_backup_extension` is the file extension for the backup initramfs file.
|
||||
|
||||
Defaults to `old`
|
||||
|
||||
### `initramfs_post_reboot_delay`
|
||||
|
||||
`initramfs_post_reboot_delay` sets the amount of Seconds to wait after the reboot command was successful before attempting to validate the system rebooted successfully.
|
||||
The value is used for [`post_reboot_delay`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/reboot_module.html#parameter-post_reboot_delay) parameter
|
||||
|
||||
Defaults to `30`
|
||||
|
||||
### `initramfs_reboot_timeout`
|
||||
|
||||
`initramfs_reboot_timeout` sets the maximum seconds to wait for machine to reboot and respond to a test command.
|
||||
The value is used for [`reboot_timeout`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/reboot_module.html#parameter-reboot_timeout) parameter
|
||||
|
||||
Defaults to `7200`
|
||||
|
||||
|
||||
## Example of a playbook to run the role
|
||||
The following yaml is an example of a playbook that runs the role against a group of hosts named `rhel` and increasing the size of its boot partition by 1G.
|
||||
The boot partition is automatically retrieved by the role by identifying the existing mounted partition to `/boot` and passing the information to the script using the `kernel_opts`.
|
||||
|
||||
```yaml
|
||||
- name: Extend boot partition playbook
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Validate initramfs preflight
|
||||
ansible.builtin.include_role:
|
||||
name: initramfs
|
||||
tasks_from: preflight
|
||||
- name: Create the initramfs and reboot to run the module
|
||||
vars:
|
||||
initramfs_add_modules: "my_extra_module"
|
||||
ansible.builtin.include_role:
|
||||
name: initramfs
|
||||
```
|
@ -1,4 +0,0 @@
|
||||
initramfs_backup_extension: old
|
||||
initramfs_add_modules: ""
|
||||
initramfs_post_reboot_delay: 30
|
||||
initramfs_reboot_timeout: 7200
|
@ -1,40 +0,0 @@
|
||||
---
|
||||
- name: Make sure the required related facts are available
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
- kernel
|
||||
|
||||
- name: Get kernel version
|
||||
ansible.builtin.set_fact:
|
||||
initramfs_kernel_version: "{{ ansible_facts.kernel }}"
|
||||
|
||||
- name: Create a backup of the current initramfs
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: /boot/initramfs-{{ initramfs_kernel_version }}.img
|
||||
dest: /boot/initramfs-{{ initramfs_kernel_version }}.img.{{ initramfs_backup_extension }}
|
||||
mode: "0600"
|
||||
|
||||
- name: Create a new initramfs with the optional additional modules
|
||||
# yamllint disable-line rule:line-length
|
||||
ansible.builtin.command: '/usr/bin/dracut {{ ((initramfs_add_modules | length) > 0) | ternary("-a", "") }} "{{ initramfs_add_modules }}" --kver {{ initramfs_kernel_version }} --force'
|
||||
changed_when: true
|
||||
|
||||
- name: Reboot the server
|
||||
ansible.builtin.reboot:
|
||||
post_reboot_delay: "{{ initramfs_post_reboot_delay }}"
|
||||
reboot_timeout: "{{ initramfs_reboot_timeout }}"
|
||||
|
||||
- name: Restore previous initramfs
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: /boot/initramfs-{{ initramfs_kernel_version }}.img.{{ initramfs_backup_extension }}
|
||||
dest: /boot/initramfs-{{ initramfs_kernel_version }}.img
|
||||
mode: "0600"
|
||||
|
||||
- name: Remove initramfs backup file
|
||||
ansible.builtin.file:
|
||||
path: /boot/initramfs-{{ initramfs_kernel_version }}.img.{{ initramfs_backup_extension }}
|
||||
state: absent
|
@ -1,27 +0,0 @@
|
||||
---
|
||||
- name: Make sure the required related facts are available
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
- kernel
|
||||
|
||||
- name: Get kernel version
|
||||
ansible.builtin.set_fact:
|
||||
initramfs_kernel_version: "{{ ansible_facts.kernel }}"
|
||||
|
||||
- name: Get default kernel
|
||||
ansible.builtin.command:
|
||||
cmd: /sbin/grubby --default-kernel
|
||||
register: initramfs_grubby_rc
|
||||
changed_when: false
|
||||
|
||||
- name: Parse default kernel version
|
||||
ansible.builtin.set_fact:
|
||||
initramfs_default_kernel: "{{ ((((initramfs_grubby_rc.stdout_lines[0] | split('/'))[2] | split('-'))[1:]) | join('-')) | trim }}"
|
||||
|
||||
- name: Check the values
|
||||
ansible.builtin.assert:
|
||||
that: initramfs_default_kernel == initramfs_kernel_version
|
||||
fail_msg: "Current kernel version '{{ initramfs_kernel_version }}' is not the default version '{{ initramfs_default_kernel }}'"
|
||||
success_msg: "Current kernel version {{ initramfs_kernel_version }} and default version {{ initramfs_default_kernel }} match"
|
@ -1,55 +0,0 @@
|
||||
# shrink_lv
|
||||
|
||||
The `shrink_lv` role is used to decrease the size of logical volumes and the file system within them.
|
||||
|
||||
The role is designed to support the automation of RHEL in-place upgrades, but can also be used for other purposes.
|
||||
|
||||
## Contents
|
||||
|
||||
The role contains the shell scripts to shrink the logical volume and file system, as well as the script wrapping it to run as part of the pre-mount step during the boot process.
|
||||
|
||||
## Role Variables
|
||||
|
||||
### `shrink_lv_devices`
|
||||
|
||||
The variable `shrink_lv_devices` is the list of logical volumes to shrink and the target size for those volumes.
|
||||
|
||||
#### `device`
|
||||
|
||||
The device that is mounted as listed under `/proc/mount`.
|
||||
If the same device has multiple paths, e.g. `/dev/vg/lv` and `/dev/mapper/vg/lv` pass the path that is mounted
|
||||
|
||||
#### `size`
|
||||
|
||||
The target size of the logical volume and filesystem after the role has completed.
|
||||
The value can be either in bytes or with optional single letter suffix (1024 bases).
|
||||
See `Unit options` type `iec` of [`numfmt`](https://man7.org/linux/man-pages/man1/numfmt.1.html)
|
||||
|
||||
## Example of a playbook to run the role
|
||||
|
||||
The following yaml is an example of a playbook that runs the role against all hosts to shrink the logical volume `lv` in volume group `vg` to 4G.
|
||||
|
||||
```yaml
|
||||
- name: Shrink Logical Volumes playbook
|
||||
hosts: all
|
||||
vars:
|
||||
shrink_lv_devices:
|
||||
- device: /dev/vg/lv
|
||||
size: 4G
|
||||
roles:
|
||||
- shrink_lv
|
||||
```
|
||||
|
||||
# Validate execution
|
||||
The script will add an entry to the kernel messages (`/dev/kmsg` or `/var/log/messages`) with success or failure.
|
||||
In case of failure, it may also include an error message retrieved from the execution of the script.
|
||||
|
||||
A successful execution will look similar to this:
|
||||
```bash
|
||||
[root@localhost ~]# cat /var/log/messages |grep Resizing -A 2 -B 2
|
||||
Oct 16 17:55:00 localhost /dev/mapper/rhel-root: 29715/2686976 files (0.2% non-contiguous), 534773/10743808 blocks
|
||||
Oct 16 17:55:00 localhost dracut-pre-mount: resize2fs 1.42.9 (28-Dec-2013)
|
||||
Oct 16 17:55:00 localhost journal: Resizing the filesystem on /dev/mapper/rhel-root to 9699328 (4k) blocks.#012The filesystem on /dev/mapper/rhel-root is now 9699328 blocks long.
|
||||
Oct 16 17:55:00 localhost journal: Size of logical volume rhel/root changed from 40.98 GiB (10492 extents) to 37.00 GiB (9472 extents).
|
||||
Oct 16 17:55:00 localhost journal: Logical volume rhel/root successfully resized.
|
||||
```
|
@ -1 +0,0 @@
|
||||
shrink_lv_backup_extension: old
|
@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
|
||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
|
||||
|
||||
check(){
|
||||
return 0
|
||||
}
|
||||
|
||||
install() {
|
||||
inst_multiple -o /usr/bin/numfmt /usr/bin/findmnt /usr/bin/lsblk /usr/sbin/lvm /usr/bin/awk /usr/bin/sed /usr/bin/mktemp /usr/bin/date /usr/bin/head /usr/sbin/blockdev /usr/sbin/tune2fs /usr/sbin/resize2fs /usr/bin/cut /usr/sbin/fsadm /usr/sbin/fsck.ext4
|
||||
# shellcheck disable=SC2154
|
||||
inst_hook pre-mount 99 "$moddir/shrink-start.sh"
|
||||
inst_simple "$moddir/shrink.sh" "/usr/bin/shrink.sh"
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
#!/bin/bash
|
||||
VOLUME_SIZE_ALIGNMENT=4096
|
||||
|
||||
function get_device_name() {
|
||||
if [[ "$1" == "UUID="* ]]; then
|
||||
dev_name=$( parse_uuid "$1" )
|
||||
else
|
||||
dev_name=$(/usr/bin/cut -d " " -f 1 <<< "$1")
|
||||
fi
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
return $status
|
||||
fi
|
||||
echo "$dev_name"
|
||||
return $status
|
||||
}
|
||||
|
||||
function ensure_size_in_bytes() {
|
||||
local expected_size
|
||||
expected_size=$(/usr/bin/numfmt --from iec "$1")
|
||||
(( expected_size=(expected_size+VOLUME_SIZE_ALIGNMENT)/VOLUME_SIZE_ALIGNMENT*VOLUME_SIZE_ALIGNMENT ))
|
||||
echo $expected_size
|
||||
}
|
||||
|
||||
function is_device_mounted() {
|
||||
/usr/bin/findmnt --source "$1" 1>&2>/dev/null
|
||||
status=$?
|
||||
if [[ status -eq 0 ]]; then
|
||||
echo "Device $1 is mounted" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function get_current_volume_size() {
|
||||
val=$(/usr/bin/lsblk -b "$1" -o SIZE --noheadings)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
return $status
|
||||
fi
|
||||
echo "$val"
|
||||
return 0
|
||||
}
|
||||
|
||||
function is_lvm(){
|
||||
val=$( /usr/bin/lsblk "$1" --noheadings -o TYPE 2>&1)
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to list block device properties for $2: $val" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$val" != "lvm" ]]; then
|
||||
echo "Device $device_name is not of lvm type" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function parse_uuid() {
|
||||
uuid=$(/usr/bin/awk '{print $1}'<<< "$1"|/usr/bin/awk -F'UUID=' '{print $2}')
|
||||
val=$(/usr/bin/lsblk /dev/disk/by-uuid/"$uuid" -o NAME --noheadings 2>/dev/null)
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
echo "Failed to retrieve device name for UUID=$uuid" >&2
|
||||
return $status
|
||||
fi
|
||||
echo "/dev/mapper/$val"
|
||||
return 0
|
||||
}
|
||||
|
||||
function shrink_volume() {
|
||||
/usr/sbin/lvm lvreduce --resizefs -L "$2b" "$1"
|
||||
return $?
|
||||
}
|
||||
|
||||
function check_volume_size() {
|
||||
current_size=$(get_current_volume_size "$1")
|
||||
if [[ $current_size -lt $2 ]];then
|
||||
echo "Current volume size for device $1 ($current_size bytes) is lower to expected $2 bytes" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ $current_size -eq $2 ]]; then
|
||||
echo "Current volume size for device $1 already equals $2 bytes" >&2
|
||||
return 1
|
||||
fi
|
||||
return $?
|
||||
}
|
||||
|
||||
function convert_size_to_fs_blocks(){
|
||||
local device=$1
|
||||
local size=$2
|
||||
block_size_in_bytes=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block size:/{print $3}')
|
||||
echo $(( size / block_size_in_bytes ))
|
||||
}
|
||||
|
||||
function calculate_expected_resized_file_system_size_in_blocks(){
|
||||
local device=$1
|
||||
increment_boot_partition_in_blocks=$(convert_size_to_fs_blocks "$device" "$INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES")
|
||||
total_block_count=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block count:/{print $3}')
|
||||
new_fs_size_in_blocks=$(( total_block_count - increment_boot_partition_in_blocks ))
|
||||
echo $new_fs_size_in_blocks
|
||||
}
|
||||
|
||||
function check_filesystem_size() {
|
||||
local device=$1
|
||||
local new_fs_size_in_blocks=$2
|
||||
new_fs_size_in_blocks=$(calculate_expected_resized_file_system_size_in_blocks "$device")
|
||||
# it is possible that running this command after resizing it might give an even smaller number.
|
||||
minimum_blocks_required=$(/usr/sbin/resize2fs -P "$device" 2> /dev/null | /usr/bin/awk '{print $NF}')
|
||||
|
||||
if [[ "$new_fs_size_in_blocks" -le "0" ]]; then
|
||||
echo "Unable to shrink volume: New size is 0 blocks"
|
||||
return 1
|
||||
fi
|
||||
if [[ $minimum_blocks_required -gt $new_fs_size_in_blocks ]]; then
|
||||
echo "Unable to shrink volume: Estimated minimum size of the file system $1 ($minimum_blocks_required blocks) is greater than the new size $new_fs_size_in_blocks blocks" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function process_entry() {
|
||||
is_lvm "$1" "$3"
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
return "$status"
|
||||
fi
|
||||
expected_size_in_bytes=$(ensure_size_in_bytes "$2")
|
||||
check_filesystem_size "$1" "$expected_size_in_bytes"
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
return "$status"
|
||||
fi
|
||||
check_volume_size "$1" "$expected_size_in_bytes"
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
return "$status"
|
||||
fi
|
||||
is_device_mounted "$1"
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
return "$status"
|
||||
fi
|
||||
shrink_volume "$1" "$expected_size_in_bytes"
|
||||
return $?
|
||||
}
|
||||
|
||||
function display_help() {
|
||||
echo "Program to shrink ext4 file systems hosted in Logical Volumes.
|
||||
|
||||
Usage: '$(basename "$0")' [-h] [-d=|--device=]
|
||||
|
||||
Example:
|
||||
|
||||
where:
|
||||
-h show this help text
|
||||
-d|--device= name or UUID of the device that holds an ext4 and the new size separated by a ':'
|
||||
for example /dev/my_group/my_vol:2G
|
||||
Sizes will be rounded to be 4K size aligned"
|
||||
}
|
||||
|
||||
function parse_flags() {
|
||||
for i in "$@"
|
||||
do
|
||||
case $i in
|
||||
-d=*|--device=*)
|
||||
entries+=("${i#*=}")
|
||||
;;
|
||||
-h)
|
||||
display_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# unknown option
|
||||
echo "Unknown flag $i"
|
||||
display_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [[ ${#entries[@]} == 0 ]]; then
|
||||
display_help
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
function parse_entry() {
|
||||
IFS=':'
|
||||
read -ra strarr <<< "$1"
|
||||
|
||||
if [[ ${#strarr[@]} != 2 ]]; then
|
||||
echo "Invalid device entry $1"
|
||||
display_help
|
||||
return 1
|
||||
fi
|
||||
|
||||
device="${strarr[0]}"
|
||||
expected_size="${strarr[1]}"
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
local -a entries=()
|
||||
local run_status=0
|
||||
|
||||
parse_flags "$@"
|
||||
|
||||
for entry in "${entries[@]}"
|
||||
do
|
||||
local device
|
||||
local expected_size
|
||||
parse_entry "$entry"
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
run_status=$status
|
||||
continue
|
||||
fi
|
||||
device_name=$( get_device_name "$device" )
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
run_status=$status
|
||||
continue
|
||||
fi
|
||||
|
||||
process_entry "$device_name" "$expected_size" "$device"
|
||||
|
||||
status=$?
|
||||
if [[ $status -ne 0 ]]; then
|
||||
run_status=$status
|
||||
fi
|
||||
done
|
||||
|
||||
exit $run_status
|
||||
}
|
||||
|
||||
main "$@"
|
@ -1,20 +0,0 @@
|
||||
- name: Get the mount point info
|
||||
ansible.builtin.set_fact:
|
||||
shrink_lv_mount_info: "{{ ansible_facts.mounts | selectattr('device', 'equalto', item.device) }}"
|
||||
|
||||
- name: Assert that the mount point exists
|
||||
ansible.builtin.assert:
|
||||
that: (shrink_lv_mount_info | length) == 1
|
||||
fail_msg: "Mount point {{ item.device }} does not exist"
|
||||
|
||||
- name: Assert that the filesystem is supported
|
||||
ansible.builtin.assert:
|
||||
that: shrink_lv_mount_info[0].fstype in ['ext4']
|
||||
fail_msg: "Unsupported filesystem '{{ shrink_lv_mount_info[0].fstype }}' on '{{ item.device }}'"
|
||||
|
||||
- name: Assert that the filesystem has enough free space
|
||||
ansible.builtin.assert:
|
||||
that: shrink_lv_mount_info[0].block_size * shrink_lv_mount_info[0].block_used < (item.size | ansible.builtin.human_to_bytes)
|
||||
fail_msg: >
|
||||
Requested size {{ item.size }} is smaller than currently used
|
||||
{{ (shrink_lv_mount_info[0].block_size * shrink_lv_mount_info[0].block_used) | ansible.builtin.human_readable }}
|
@ -1,54 +0,0 @@
|
||||
---
|
||||
- name: Make sure the required facts are available
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
- kernel
|
||||
- mounts
|
||||
|
||||
- name: Run preflight checks
|
||||
ansible.builtin.include_tasks: preflight.yaml
|
||||
|
||||
- name: Copy shrink LV dracut module
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item }}"
|
||||
dest: /usr/lib/dracut/modules.d/99shrink_lv/
|
||||
mode: "0554"
|
||||
loop:
|
||||
- module-setup.sh
|
||||
- shrink.sh
|
||||
|
||||
- name: Resolve and copy the shrink-start script
|
||||
ansible.builtin.template:
|
||||
src: shrink-start.sh.j2
|
||||
dest: /usr/lib/dracut/modules.d/99shrink_lv/shrink-start.sh
|
||||
mode: '0554'
|
||||
|
||||
- name: Create the initramfs and reboot to run the module
|
||||
vars:
|
||||
initramfs_add_modules: "shrink_lv lvm"
|
||||
ansible.builtin.include_role:
|
||||
name: initramfs
|
||||
|
||||
- name: Remove dracut extend boot module
|
||||
ansible.builtin.file:
|
||||
path: /usr/lib/dracut/modules.d/99shrink_lv
|
||||
state: absent
|
||||
|
||||
- name: Retrieve mount points
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
- mounts
|
||||
|
||||
- name: Assert that the filesystem has shrunk
|
||||
ansible.builtin.assert:
|
||||
# yamllint disable-line rule:line-length
|
||||
that: (ansible_facts.mounts | selectattr('device', 'equalto', item.device) | map(attribute='size_total') | join | int) <= (item.size | ansible.builtin.human_to_bytes)
|
||||
fail_msg: >
|
||||
Logical Volume {{ item.device }} was not shrunk to {{ item.size }} as requested
|
||||
success_msg: >
|
||||
Logical Volume {{ item.device }} has been shrunk to {{ item.size }} as requested.
|
||||
loop: "{{ shrink_lv_devices }}"
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
- name: Assert shrink_lv_devices
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- shrink_lv_devices is defined
|
||||
- shrink_lv_devices | type_debug == "list"
|
||||
- shrink_lv_devices | length > 0
|
||||
fail_msg: shrink_lv_devices must be a list and include at least one element
|
||||
|
||||
- name: Validate initramfs preflight
|
||||
ansible.builtin.include_role:
|
||||
name: initramfs
|
||||
tasks_from: preflight
|
||||
|
||||
- name: Check all devices
|
||||
ansible.builtin.include_tasks: check_device.yaml
|
||||
loop: "{{ shrink_lv_devices }}"
|
@ -1,27 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
disable_lvm_lock(){
|
||||
tmpfile=$(/usr/bin/mktemp)
|
||||
sed -e 's/\(^[[:space:]]*\)locking_type[[:space:]]*=[[:space:]]*[[:digit:]]/\1locking_type = 1/' /etc/lvm/lvm.conf >"$tmpfile"
|
||||
status=$?
|
||||
if [[ status -ne 0 ]]; then
|
||||
echo "Failed to disable lvm lock: $status" >/dev/kmsg
|
||||
exit 1
|
||||
fi
|
||||
# replace lvm.conf. There is no need to keep a backup since it's an ephemeral file, we are not replacing the original in the initramfs image file
|
||||
mv "$tmpfile" /etc/lvm/lvm.conf
|
||||
}
|
||||
|
||||
activate_volume_groups(){
|
||||
for vg in `/usr/sbin/lvm vgs -o name --noheading 2>/dev/null`; do
|
||||
/usr/sbin/lvm vgchange -ay $vg
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
activate_volume_groups
|
||||
disable_lvm_lock
|
||||
/usr/bin/shrink.sh {% for device in shrink_lv_devices %}--device={{ device.device }}:{{ device.size }} {% endfor %} 1>&2 >/dev/kmsg
|
||||
}
|
||||
|
||||
main "$0"
|
@ -1,84 +0,0 @@
|
||||
# snapshot_create role
|
||||
|
||||
|
||||
The `snapshot_create` role is used to control the creation for a defined set of LVM snapshot volumes.
|
||||
In addition, it can optionally save the Grub configuration and image files under /boot and configure settings to enable the LVM snapshot autoextend capability.
|
||||
The role will verify free space and should fail if there is not enough or if any snapshots already exist for the given `snapshot_create_set_name`.
|
||||
|
||||
The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities.
|
||||
|
||||
## Role Variables
|
||||
|
||||
### `snapshot_create_check_only`
|
||||
|
||||
When set to `true` the role will only verify there is enough free space for the specified snapshots and not create them.
|
||||
Default `false`
|
||||
|
||||
### `snapshot_create_set_name`
|
||||
|
||||
The variable `snapshot_create_set_name` is used to identify the list of volumes to be operated upon.
|
||||
The role will use the following naming convention when creating the snapshots:
|
||||
|
||||
`<Origin LV name>_<snapshot_create_set_name>`
|
||||
|
||||
### `snapshot_create_boot_backup`
|
||||
|
||||
Boolean to specify that the role should preserve the Grub configuration and image files under /boot required for booting the default kernel.
|
||||
The files are preserved in a compressed tar archive at `/root/boot-backup-<snapshot_create_set_name>.tgz`. Default is `false`.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> When automating RHEL in-place upgrades, do not perform a Grub to Grub2 migration as part of your upgrade playbook. It will invalidate your boot backup and cause a subsequent `revert` action to fail. For example, if you are using the [`upgrade`](https://github.com/redhat-cop/infra.leapp/tree/main/roles/upgrade#readme) role from the [`infra.leapp`](https://github.com/redhat-cop/infra.leapp) collection, do not set `update_grub_to_grub_2` to `true`. Grub to Grub2 migration should only be performed after the `remove` action has been performed to delete the snapshots and boot backup.
|
||||
|
||||
### `snapshot_create_snapshot_autoextend_threshold`
|
||||
|
||||
Configure the given `snapshot_create_autoextend_threshold` setting in lvm.conf before creating snapshots.
|
||||
|
||||
### `snapshot_create_snapshot_autoextend_percent`
|
||||
|
||||
Configure the given `snapshot_create_snapshot_autoextend_percent` setting in lvm.conf before creating snapshots.
|
||||
|
||||
### `snapshot_create_volumes`
|
||||
|
||||
This is the list of logical volumes for which snapshots are to be created and the size requirements for those snapshots. The volumes list is only required when the role is run with the check or create action.
|
||||
|
||||
### `vg`
|
||||
|
||||
The volume group of the origin logical volume for which a snapshot will be created.
|
||||
|
||||
### `lv`
|
||||
|
||||
The origin logical volume for which a snapshot will be created.
|
||||
|
||||
### `size`
|
||||
|
||||
The size of the logical volume according to the definition of the
|
||||
[size](https://docs.ansible.com/ansible/latest/collections/community/general/lvol_module.html#parameter-size)
|
||||
parameter of the `community.general.lvol` module.
|
||||
|
||||
To create thin provisioned snapshot of a thin provisioned volume, omit the `size` parameter or set it to `0`
|
||||
|
||||
## Example Playbooks
|
||||
|
||||
Perform space check and fail of there will not be enough space for all the snapshots in the set.
|
||||
If there is sufficient space, proceed to create snapshots for the listed logical volumes.
|
||||
Each snapshot will be sized to 20% of the origin volume size.
|
||||
Snapshot autoextend settings are configured to enable free space in the volume group to be allocated to any snapshot that may exceed 70% usage in the future.
|
||||
Files under /boot will be preserved.
|
||||
|
||||
```yaml
|
||||
- hosts: all
|
||||
roles:
|
||||
- name: snapshot_create
|
||||
snapshot_create_set_name: ripu
|
||||
snapshot_create_snapshot_autoextend_threshold: 70
|
||||
snapshot_create_snapshot_autoextend_percent: 20
|
||||
snapshot_create_boot_backup: true
|
||||
snapshot_create_volumes:
|
||||
- vg: rootvg
|
||||
lv: root
|
||||
size: 2G
|
||||
- vg: rootvg
|
||||
lv: var
|
||||
size: 2G
|
||||
```
|
@ -1,2 +0,0 @@
|
||||
snapshot_create_volumes: []
|
||||
snapshot_create_boot_backup: false
|
@ -1,238 +0,0 @@
|
||||
'''
|
||||
Check is there is enough space to created all the requested snapshots
|
||||
The input should be a json string array.
|
||||
Each element should have the following keys:
|
||||
- vg: Name of the volume group
|
||||
- lv: Name of the Logical Volume
|
||||
- size: The size of the requested snapshot.
|
||||
Follow (https://docs.ansible.com/ansible/latest/collections/community/general/lvol_module.html#parameter-size)
|
||||
without support for sign
|
||||
'''
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
_VGS_COMMAND = '/usr/sbin/vgs'
|
||||
_LVS_COMMAND = '/usr/sbin/lvs'
|
||||
|
||||
_EXIT_CODE_SUCCESS = 0
|
||||
_EXIT_CODE_VOLUME_GROUP_SPACE = 1
|
||||
_EXIT_CODE_FILE_SYSTEM_TYPE = 2
|
||||
_EXIT_CODE_VOLUME_SPACE = 3
|
||||
|
||||
_supported_filesystems = [
|
||||
'',
|
||||
'ext2',
|
||||
'ext3',
|
||||
'ext4'
|
||||
]
|
||||
|
||||
|
||||
class CheckException(Exception):
|
||||
""" Exception wrapper """
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('that', help='What should the script check', type=str, choices=['snapshots', 'resize'])
|
||||
parser.add_argument('volumes', help='Volumes JSON array in a string', type=str)
|
||||
|
||||
|
||||
def _main():
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
volumes = json.loads(args.volumes)
|
||||
except json.decoder.JSONDecodeError:
|
||||
print("Provided volume list '{volumes}' it not a valid json string".format(volumes=sys.argv[1]))
|
||||
sys.exit(1)
|
||||
|
||||
groups_names = set(vol['vg'] for vol in volumes)
|
||||
groups_info = {
|
||||
group: _get_group_info(group) for group in groups_names
|
||||
}
|
||||
|
||||
for vol in volumes:
|
||||
vol['normalized_size'] = _calc_requested_size(groups_info[vol["vg"]], vol)
|
||||
groups_info[vol["vg"]]['requested_size'] += vol['normalized_size']
|
||||
|
||||
if args.that == 'snapshots':
|
||||
exit_code = _check_free_size_for_snapshots(groups_info)
|
||||
if exit_code == _EXIT_CODE_SUCCESS:
|
||||
norm_vols = [
|
||||
{
|
||||
'vg': vol['vg'],
|
||||
'lv': vol['lv'],
|
||||
'size': "{size}B".format(size=vol['normalized_size'])
|
||||
} for vol in volumes
|
||||
]
|
||||
print(json.dumps(norm_vols))
|
||||
if args.that == 'resize':
|
||||
exit_code = _check_free_size_for_resize(volumes, groups_info)
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
def _check_free_size_for_snapshots(groups_info):
|
||||
return _check_requested_size(groups_info, 'free')
|
||||
|
||||
|
||||
def _check_free_size_for_resize(volumes, groups_info):
|
||||
exit_code = _check_requested_size(groups_info, 'size')
|
||||
if exit_code != _EXIT_CODE_SUCCESS:
|
||||
return exit_code
|
||||
|
||||
mtab = _parse_mtab()
|
||||
|
||||
for volume in volumes:
|
||||
mtab_entry = mtab.get("/dev/mapper/{vg}-{lv}".format(vg=volume['vg'], lv=volume['lv']))
|
||||
volume['fs_type'] = mtab_entry['type'] if mtab_entry else ''
|
||||
volume['fs_size'] = _calc_filesystem_size(mtab_entry) if mtab_entry else 0
|
||||
|
||||
filesystems_supported = all(volume['fs_type'] in _supported_filesystems for volume in volumes)
|
||||
if not filesystems_supported:
|
||||
exit_code = _EXIT_CODE_FILE_SYSTEM_TYPE
|
||||
|
||||
enough_space = all(vol['normalized_size'] > vol['fs_size'] for vol in volumes)
|
||||
if not enough_space:
|
||||
exit_code = _EXIT_CODE_VOLUME_SPACE
|
||||
|
||||
if exit_code != _EXIT_CODE_SUCCESS:
|
||||
print(json.dumps(_to_printable_volumes(volumes)))
|
||||
|
||||
return exit_code
|
||||
|
||||
|
||||
def _check_requested_size(groups_info, group_field):
|
||||
enough_space = all(group['requested_size'] <= group[group_field] for _, group in groups_info.items())
|
||||
if not enough_space:
|
||||
print(json.dumps(groups_info))
|
||||
return _EXIT_CODE_VOLUME_GROUP_SPACE
|
||||
return _EXIT_CODE_SUCCESS
|
||||
|
||||
|
||||
def _get_group_info(group):
|
||||
group_info_str = subprocess.check_output([_VGS_COMMAND, group, '-v', '--units', 'b', '--reportformat', 'json'])
|
||||
group_info_json = json.loads(group_info_str)
|
||||
group_info = group_info_json['report'][0]['vg'][0]
|
||||
return {
|
||||
'name': group,
|
||||
'size': _get_size_from_report(group_info['vg_size']),
|
||||
'free': _get_size_from_report(group_info['vg_free']),
|
||||
'extent_size': _get_size_from_report(group_info['vg_extent_size']),
|
||||
'requested_size': 0
|
||||
}
|
||||
|
||||
|
||||
def _calc_requested_size(group_info, volume):
|
||||
unit = 'm'
|
||||
requested_size = volume.get('size', 0)
|
||||
if requested_size == 0:
|
||||
# handle thin provisioning
|
||||
pass
|
||||
if isinstance(requested_size, int) or isinstance(requested_size, float):
|
||||
size = requested_size
|
||||
else:
|
||||
parts = requested_size.split('%')
|
||||
if len(parts) == 2:
|
||||
unit = 'b'
|
||||
percent = float(parts[0])
|
||||
percent_of = parts[1]
|
||||
if percent_of == 'VG':
|
||||
size = group_info['size'] * percent / 100
|
||||
elif percent_of == 'FREE':
|
||||
size = group_info['free'] * percent / 100
|
||||
elif percent_of == 'ORIGIN':
|
||||
origin_size = _get_volume_size(volume)
|
||||
size = origin_size * percent / 100
|
||||
else:
|
||||
raise CheckException("Unsupported base type {base_type}".format(base_type=percent_of))
|
||||
else:
|
||||
try:
|
||||
size = float(requested_size[:-1])
|
||||
unit = requested_size[-1].lower()
|
||||
except ValueError:
|
||||
raise CheckException('Failed to read requested size {size}'.format(size=requested_size))
|
||||
return _align_to_extent(_convert_to_bytes(size, unit), group_info['extent_size'])
|
||||
|
||||
|
||||
def _get_volume_size(vol):
|
||||
volume_info_str = subprocess.check_output(
|
||||
[_LVS_COMMAND, "{vg}/{lv}".format(vg=vol['vg'], lv=vol['lv']), '-v', '--units', 'b', '--reportformat', 'json']
|
||||
)
|
||||
volume_info_json = json.loads(volume_info_str)
|
||||
volume_info = volume_info_json['report'][0]['lv'][0]
|
||||
return _get_size_from_report(volume_info['lv_size'])
|
||||
|
||||
|
||||
def _get_size_from_report(reported_size):
|
||||
try:
|
||||
size = float(reported_size)
|
||||
unit = 'm'
|
||||
except ValueError:
|
||||
if reported_size[0] == '<':
|
||||
reported_size = reported_size[1:]
|
||||
size = float(reported_size[:-1])
|
||||
unit = reported_size[-1].lower()
|
||||
return _convert_to_bytes(size, unit)
|
||||
|
||||
|
||||
def _align_to_extent(size, extent_size):
|
||||
return math.ceil(size / extent_size) * extent_size
|
||||
|
||||
|
||||
def _calc_filesystem_size(mtab_entry):
|
||||
fs_stat = os.statvfs(mtab_entry['mount_point'])
|
||||
return (fs_stat.f_blocks - fs_stat.f_bfree) * fs_stat.f_bsize
|
||||
|
||||
|
||||
def _parse_mtab():
|
||||
mtab = {}
|
||||
with open('/etc/mtab') as f:
|
||||
for m in f:
|
||||
fs_spec, fs_file, fs_vfstype, _fs_mntops, _fs_freq, _fs_passno = m.split()
|
||||
mtab[fs_spec] = {
|
||||
'mount_point': fs_file,
|
||||
'type': fs_vfstype
|
||||
}
|
||||
return mtab
|
||||
|
||||
|
||||
def _convert_to_bytes(size, unit):
|
||||
convertion_table = {
|
||||
'b': 1024 ** 0,
|
||||
'k': 1024 ** 1,
|
||||
'm': 1024 ** 2,
|
||||
'g': 1024 ** 3,
|
||||
't': 1024 ** 4,
|
||||
'p': 1024 ** 5,
|
||||
'e': 1024 ** 6,
|
||||
}
|
||||
return size * convertion_table[unit]
|
||||
|
||||
|
||||
def _convert_to_unit_size(bytes):
|
||||
units = ['b', 'k', 'm', 'g', 't', 'p', 'e']
|
||||
i = 0
|
||||
while bytes >= 1024:
|
||||
i += 1
|
||||
bytes /= 1024
|
||||
# Round down bytes to two digits
|
||||
bytes = math.floor(bytes * 100) / 100
|
||||
return "{size}{unit}".format(size=bytes, unit=units[i])
|
||||
|
||||
|
||||
def _to_printable_volumes(volumes):
|
||||
return {
|
||||
volume['vg'] + "_" + volume['lv']: {
|
||||
'file_system_type': volume['fs_type'],
|
||||
'used': _convert_to_unit_size(volume['fs_size']),
|
||||
'requested_size': _convert_to_unit_size(volume['normalized_size'])
|
||||
} for volume in volumes
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
@ -1,26 +0,0 @@
|
||||
- name: Verify that all volumes exist
|
||||
ansible.builtin.include_tasks: verify_volume_exists.yml
|
||||
loop: "{{ snapshot_create_volumes }}"
|
||||
|
||||
- name: Verify that there are no existing snapshots
|
||||
ansible.builtin.include_tasks: verify_no_existing_snapshot.yml
|
||||
loop: "{{ snapshot_create_volumes }}"
|
||||
|
||||
- name: Verify that there is enough storage space
|
||||
ansible.builtin.script: check.py snapshots '{{ snapshot_create_volumes | to_json }}'
|
||||
args:
|
||||
executable: "{{ ansible_python.executable }}"
|
||||
register: snapshot_create_check_status
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Store check return in case of failure
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_create_check_failure_json: "{{ snapshot_create_check_status.stdout | from_json }}"
|
||||
when: snapshot_create_check_status.rc != 0
|
||||
|
||||
- name: Assert results
|
||||
ansible.builtin.assert:
|
||||
that: snapshot_create_check_status.rc == 0
|
||||
fail_msg: Not enough space in the Volume Groups to create the requested snapshots
|
||||
success_msg: The Volume Groups have enough space to create the requested snapshots
|
@ -1,68 +0,0 @@
|
||||
- name: Update lvm configuration
|
||||
block:
|
||||
- name: Stringify snapshot_autoextend_percent setting
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_create_snapshot_autoextend_percent_config: "activation/snapshot_autoextend_percent={{ snapshot_create_snapshot_autoextend_percent }}"
|
||||
when: snapshot_create_snapshot_autoextend_percent is defined
|
||||
- name: Stringify snapshot_autoextend_threshold setting
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_create_snapshot_autoextend_threshold_config: "activation/snapshot_autoextend_threshold={{ snapshot_create_snapshot_autoextend_threshold }}"
|
||||
when: snapshot_create_snapshot_autoextend_threshold is defined
|
||||
- name: Stringify the new config
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_create_new_lvm_config: >
|
||||
{{ snapshot_create_snapshot_autoextend_percent_config | default('') }}
|
||||
{{ snapshot_create_snapshot_autoextend_threshold_config | default('') }}
|
||||
- name: Set LVM configuration
|
||||
ansible.builtin.command: 'lvmconfig --mergedconfig --config "{{ snapshot_create_new_lvm_config }}" --file /etc/lvm/lvm.conf'
|
||||
changed_when: true
|
||||
when: ((snapshot_create_new_lvm_config | trim) | length) > 0
|
||||
|
||||
- name: Check for grubenv saved_entry
|
||||
ansible.builtin.lineinfile:
|
||||
name: /boot/grub2/grubenv
|
||||
regexp: ^saved_entry=
|
||||
state: absent
|
||||
check_mode: true
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
register: snapshot_create_grubenv
|
||||
|
||||
- name: Add grubenv saved_entry
|
||||
ansible.builtin.shell: 'grubby --set-default-index=$(grubby --default-index)'
|
||||
changed_when: true
|
||||
when: snapshot_create_grubenv.found is defined and snapshot_create_grubenv.found == 0
|
||||
|
||||
- name: Create snapshots
|
||||
community.general.lvol:
|
||||
vg: "{{ item.vg }}"
|
||||
lv: "{{ item.lv }}"
|
||||
snapshot: "{{ item.lv }}_{{ snapshot_create_set_name }}"
|
||||
size: "{{ item.size | default(omit) }}"
|
||||
loop: "{{ snapshot_create_volumes }}"
|
||||
|
||||
- name: Required packages are present
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- gzip
|
||||
- tar
|
||||
state: present
|
||||
|
||||
- name: Create boot backup
|
||||
community.general.archive:
|
||||
format: gz
|
||||
mode: '0644'
|
||||
dest: "/root/boot-backup-{{ snapshot_create_set_name }}.tgz"
|
||||
path:
|
||||
- "/boot/initramfs-{{ ansible_kernel }}.img"
|
||||
- "/boot/vmlinuz-{{ ansible_kernel }}"
|
||||
- "/boot/System.map-{{ ansible_kernel }}"
|
||||
- "/boot/symvers-{{ ansible_kernel }}.gz"
|
||||
- "/boot/config-{{ ansible_kernel }}"
|
||||
- "/boot/.vmlinuz-{{ ansible_kernel }}.hmac"
|
||||
- /boot/grub/grub.conf
|
||||
- /boot/grub2/grub.cfg
|
||||
- /boot/grub2/grubenv
|
||||
- /boot/loader/entries
|
||||
- /boot/efi/EFI/redhat/grub.cfg
|
||||
when: snapshot_create_boot_backup
|
@ -1,8 +0,0 @@
|
||||
- name: Check available disk space
|
||||
ansible.builtin.include_tasks: check.yml
|
||||
|
||||
- name: Create Snapshot
|
||||
vars:
|
||||
snapshot_create_volumes: "{{ snapshot_create_check_status.stdout | from_json }}"
|
||||
ansible.builtin.include_tasks: create.yml
|
||||
when: not (snapshot_create_check_only | default(false))
|
@ -1,21 +0,0 @@
|
||||
- name: Run lvs
|
||||
ansible.builtin.command: >
|
||||
lvs
|
||||
--select 'vg_name = {{ item.vg }}
|
||||
&& origin = {{ item.lv }}
|
||||
&& lv_name = {{ item.lv }}_{{ snapshot_create_set_name }}'
|
||||
--reportformat json
|
||||
register: snapshot_create_lvs_response
|
||||
changed_when: false
|
||||
|
||||
- name: Parse report
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_create_lv_snapshot_report_array: "{{ (snapshot_create_lvs_response.stdout | from_json).report[0].lv }}"
|
||||
|
||||
- name: Verify that the no snapshot exists for the volume
|
||||
ansible.builtin.assert:
|
||||
that: (snapshot_create_lv_snapshot_report_array | length) == 0
|
||||
fail_msg: >
|
||||
The volume '{{ item.lv }}' in volume group '{{ item.vg }}'
|
||||
already has at least one snapshot
|
||||
'{{ snapshot_create_lv_snapshot_report_array[0].lv_name | default('none') }}'
|
@ -1,9 +0,0 @@
|
||||
- name: Run lvs
|
||||
ansible.builtin.command: "lvs --select 'vg_name = {{ item.vg }} && lv_name = {{ item.lv }}' --reportformat json"
|
||||
register: snapshot_create_lvs_response
|
||||
changed_when: false
|
||||
|
||||
- name: Verify that the volume was found
|
||||
ansible.builtin.assert:
|
||||
that: (((snapshot_create_lvs_response.stdout | from_json).report[0].lv) | length) > 0
|
||||
fail_msg: "Could not find volume '{{ item.lv }}' in volume group '{{ item.vg }}'"
|
@ -1,32 +0,0 @@
|
||||
# snapshot_remove role
|
||||
|
||||
The `snapshot_remove` role is used to remove snapshots.
|
||||
In addition, it removes the Grub configuration and image files under /boot if it was previously backed up
|
||||
It is intended to be used along with the `snapshot_create` role.
|
||||
|
||||
The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities.
|
||||
|
||||
## Role Variables
|
||||
|
||||
### `snapshot_remove_set_name`
|
||||
|
||||
The variable `snapshot_remove_set_name` is used to identify the list of volumes to be operated upon.
|
||||
The role will use the following naming convention when reverting the snapshots:
|
||||
|
||||
`<Origin LV name>_<snapshot_remove_set_name>`
|
||||
|
||||
This naming convention will be used to identify the snapshots to be removed.
|
||||
|
||||
## Example Playbooks
|
||||
|
||||
### Commit
|
||||
|
||||
A commit playbook is used when users are comfortable the snapshots are not needed any longer.
|
||||
Each snapshot in the snapshot set is removed and the backed up image files from /boot are deleted.
|
||||
|
||||
```yaml
|
||||
- hosts: all
|
||||
roles:
|
||||
- name: snapshot_remove
|
||||
snapshot_remove_set_name: ripu
|
||||
```
|
@ -1,23 +0,0 @@
|
||||
- name: Calculate the list of snapshots
|
||||
block:
|
||||
- name: Get list of volumes
|
||||
ansible.builtin.command: "lvs --select 'lv_name =~ {{ snapshot_remove_set_name }}$ && origin != \"\"' --reportformat json "
|
||||
register: snapshot_remove_lvs_response
|
||||
changed_when: false
|
||||
- name: Get LV dict List
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_remove_snapshots: "{{ (snapshot_remove_lvs_response.stdout | from_json).report[0].lv }}"
|
||||
|
||||
- name: Remove snapshots
|
||||
community.general.lvol:
|
||||
state: absent
|
||||
vg: "{{ item.vg_name }}"
|
||||
lv: "{{ item.origin }}"
|
||||
snapshot: "{{ item.lv_name }}"
|
||||
force: true
|
||||
loop: "{{ snapshot_remove_snapshots }}"
|
||||
|
||||
- name: Remove boot backup
|
||||
ansible.builtin.file:
|
||||
path: "/root/boot-backup-{{ snapshot_remove_set_name }}.tgz"
|
||||
state: absent
|
@ -1,36 +0,0 @@
|
||||
# snapshot_revert role
|
||||
|
||||
|
||||
The `snapshot_revert` role is used to merge snapshots to origin and reboot (i.e., rollback).
|
||||
The role will verify that all snapshots in the set are still in active state before doing any merges.
|
||||
This is to prevent rolling back if any snapshots have become invalidated in which case the role should fail.
|
||||
In addition, it restores the Grub configuration and image files under /boot is it was previously backed up
|
||||
It is intended to be used along with the `snapshot_create` role.
|
||||
|
||||
The role is designed to support the automation of RHEL in-place upgrades, but can also be used to reduce the risk of more mundane system maintenance activities.
|
||||
|
||||
## Role Variables
|
||||
|
||||
### `snapshot_revert_set_name`
|
||||
|
||||
The variable `snapshot_revert_set_name` is used to identify the list of volumes to be operated upon.
|
||||
The role will use the following naming convention when reverting the snapshots:
|
||||
|
||||
`<Origin LV name>_<snapshot_revert_set_name>`
|
||||
|
||||
This naming convention will be used to identify the snapshots to be merged.
|
||||
|
||||
The `revert` action will verify that all snapshots in the set are still active state before doing any merges. This is to prevent rolling back if any snapshots have become invalidated in which case the `revert` action should fail.
|
||||
|
||||
## Example Playbooks
|
||||
|
||||
This playbook rolls back the host using the snapshots created using the `snapshot_create` role.
|
||||
After verifying that all snapshots are still valid, each logical volume in the snapshot set is merged.
|
||||
The image files under /boot will be restored and then the host will be rebooted.
|
||||
|
||||
```yaml
|
||||
- hosts: all
|
||||
roles:
|
||||
- name: snapshot_revert
|
||||
snapshot_revert_set_name: ripu
|
||||
```
|
@ -1,73 +0,0 @@
|
||||
- name: Calculate the list of snapshots
|
||||
block:
|
||||
- name: Get list of volumes
|
||||
ansible.builtin.command: "lvs --select 'lv_name =~ {{ snapshot_revert_set_name }}$ && origin != \"\"' --reportformat json "
|
||||
register: snapshot_revert_lvs_response
|
||||
changed_when: false
|
||||
- name: Get LV dict List
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_revert_snapshots: "{{ (snapshot_revert_lvs_response.stdout | from_json).report[0].lv }}"
|
||||
|
||||
- name: Verify that all snapshots are active
|
||||
ansible.builtin.include_tasks: verify_snapshot_active.yml
|
||||
loop: "{{ snapshot_revert_snapshots }}"
|
||||
|
||||
- name: Required packages are present
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- gzip
|
||||
- tar
|
||||
state: present
|
||||
|
||||
- name: Check if Boot backup exists
|
||||
ansible.builtin.stat:
|
||||
path: "/root/boot-backup-{{ snapshot_revert_set_name }}.tgz"
|
||||
register: snapshot_revert_boot_archive_stat
|
||||
|
||||
- name: Restore boot backup
|
||||
ansible.builtin.unarchive:
|
||||
remote_src: true
|
||||
src: "{{ snapshot_revert_boot_archive_stat.stat.path }}"
|
||||
dest: /boot
|
||||
when: snapshot_revert_boot_archive_stat.stat.exists
|
||||
|
||||
- name: Revert to snapshots
|
||||
ansible.builtin.command: "lvconvert --merge /dev/{{ item.vg_name }}/{{ item.lv_name }}"
|
||||
loop: "{{ snapshot_revert_snapshots }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Reboot
|
||||
ansible.builtin.reboot:
|
||||
|
||||
- name: Check if /boot is on LVM
|
||||
ansible.builtin.command: "grub2-probe --target=abstraction /boot"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
register: snapshot_revert_boot_abstraction
|
||||
|
||||
- name: Reinstall Grub to boot device
|
||||
when: snapshot_revert_boot_abstraction.stdout == 'lvm'
|
||||
block:
|
||||
- name: Get boot device
|
||||
ansible.builtin.shell: "lsblk -spnlo name $(grub2-probe --target=device /boot)"
|
||||
changed_when: false
|
||||
register: snapshot_revert_boot_dev_deps
|
||||
|
||||
- name: Run grub2-install
|
||||
ansible.builtin.command: "grub2-install {{ snapshot_revert_boot_dev_deps.stdout_lines | last }}"
|
||||
changed_when: true
|
||||
|
||||
- name: Remove boot backup
|
||||
ansible.builtin.file:
|
||||
path: "{{ snapshot_revert_boot_archive_stat.stat.path }}"
|
||||
state: absent
|
||||
when: snapshot_revert_boot_archive_stat.stat.exists
|
||||
|
||||
- name: Wait for the snapshot to drain
|
||||
ansible.builtin.command: "lvs --select 'vg_name = {{ item.vg_name }} && lv_name = {{ item.origin }}' --reportformat json"
|
||||
register: snapshot_revert_lv_drain_check
|
||||
until: (snapshot_revert_lv_drain_check.stdout | from_json).report[0].lv[0].data_percent == ""
|
||||
retries: 20
|
||||
delay: 30
|
||||
loop: "{{ snapshot_revert_snapshots }}"
|
||||
changed_when: false
|
@ -1,14 +0,0 @@
|
||||
- name: Run lvs
|
||||
ansible.builtin.command: "lvs --select 'lv_name = {{ item.lv_name }}' --reportformat json"
|
||||
register: snapshot_revert_lvs_response
|
||||
changed_when: false
|
||||
|
||||
- name: Parse report
|
||||
ansible.builtin.set_fact:
|
||||
snapshot_revert_lv_attr: "{{ (snapshot_revert_lvs_response.stdout | from_json).report[0].lv[0].lv_attr }}"
|
||||
|
||||
- name: Verify that the snapshot is active
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- snapshot_revert_lv_attr[0] == 's'
|
||||
- snapshot_revert_lv_attr[4] == 'a'
|
Reference in New Issue
Block a user