From 9cf6da5147122bf3db419f5a905cafeccb301a66 Mon Sep 17 00:00:00 2001 From: Leonardo Arena Date: Tue, 17 Mar 2026 17:41:34 +0100 Subject: [PATCH] add LVM support for root partition --- README.md | 1 + TODO.md | 2 +- docs/tiny-cloud.conf.5.scd | 4 ++ lib/tiny-cloud/init | 43 ++++++++++++++++---- lib/tiny-cloud/tiny-cloud.conf | 4 ++ tests/init.test | 71 ++++++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fe033ba..17df451 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ IMDS data. As Tiny Cloud is meant to be tiny, it has few dependencies: * Busybox (`ash`, `wget`, etc.) * `e2fsprogs-extra` (for `resize2fs`) +* `lvm2` (for resizing LVM volumes) * `openssh-server` * `partx` * `sfdisk` diff --git a/TODO.md b/TODO.md index deb83ee..2491408 100644 --- a/TODO.md +++ b/TODO.md @@ -20,7 +20,7 @@ * Support for `#network-config`? -* Support LVM partitioning and non-`ext[234]` filesystems? +* Support non-`ext[234]` filesystems? * Support other cloud providers... * IBM diff --git a/docs/tiny-cloud.conf.5.scd b/docs/tiny-cloud.conf.5.scd index d01cedf..e2dceb5 100644 --- a/docs/tiny-cloud.conf.5.scd +++ b/docs/tiny-cloud.conf.5.scd @@ -51,6 +51,10 @@ Blank lines and shell comments are ignored. *TINY_CLOUD_LOGS*= Log directory used by tiny-cloud. The default is _/var/log_. +*TINY_CLOUD_LV_ALLOCATE*= + How much space to allocate when expanding the "/" LVM logical volume in + percentage. The default is 85 percent of the volume group's space. + *SKIP_INIT_ACTIONS*= ... Whitespace-separated list of init actions to skip during phase execution. diff --git a/lib/tiny-cloud/init b/lib/tiny-cloud/init index 60cd0ec..196441e 100644 --- a/lib/tiny-cloud/init +++ b/lib/tiny-cloud/init @@ -45,14 +45,43 @@ init__expand_root() { *) return;; esac - if [ -n "$partition" ]; then - # it's a partition, resize it - local volume=$(readlink -f "$SYS/class/block/${dev#/dev/}/..") - volume="/dev/${volume##*/}" - echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume" - $MOCK partx -u "$volume" + # check if this is an LVM logical volume + if lvs --noheadings -o lv_name "$dev" >/dev/null 2>&1; then + # LVM volume detected + # find the physical volume backing this LV + local vg_name=$(lvs --noheadings -o vg_name "$dev" 2>/dev/null | tr -d ' ') + local pv_dev=$(pvs --noheadings -o pv_name -S "vg_name=$vg_name" 2>/dev/null | tr -d ' ' | head -n1) + + # get the partition number if PV is on a partition + local pv_partition=$(cat "$SYS/class/block/${pv_dev#/dev/}/partition" 2>/dev/null) + + # only resize partition if PV is on a partition (not whole disk) + if [ -n "$pv_partition" ]; then + # resize the partition containing the PV + local pv_volume=$(readlink -f "$SYS/class/block/${pv_dev#/dev/}/..") + pv_volume="/dev/${pv_volume##*/}" + echo ", +" | $MOCK sfdisk -q --no-reread -N "$pv_partition" "$pv_volume" + $MOCK partx -u "$pv_volume" + fi + + # resize the physical volume (works for both partition and whole disk) + $MOCK pvresize "$pv_dev" + + # extend the logical volume + # default leave 15% for snapshots + $MOCK lvextend -l +${TINY_CLOUD_LV_ALLOCATE:-85}%VG "$dev" + else + # standard partition handling (non-LVM) + if [ -n "$partition" ]; then + # it's a partition, resize it + local volume=$(readlink -f "$SYS/class/block/${dev#/dev/}/..") + volume="/dev/${volume##*/}" + echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume" + $MOCK partx -u "$volume" + fi fi - # resize filesystem + + # resize filesystem (common for both LVM and non-LVM) if [ -e "$dev" ] || [ -n "$MOCK" ]; then $MOCK resize2fs "$dev" fi diff --git a/lib/tiny-cloud/tiny-cloud.conf b/lib/tiny-cloud/tiny-cloud.conf index f2cb82c..006e0e0 100644 --- a/lib/tiny-cloud/tiny-cloud.conf +++ b/lib/tiny-cloud/tiny-cloud.conf @@ -26,6 +26,10 @@ # Location of log directory #TINY_CLOUD_LOGS=/var/log +# How much space to allocate when expanding the "/" LVM logical volume in percentage +# Default: leave 15% unallocated for snapshots +#TINY_CLOUD_LV_ALLOCATE=85 + # Explicitly skip these (whitespace delimited) things during init # examples: expand_root set_hostname set_ssh_keys save_userdata # decompress_userdata run_userdata diff --git a/tests/init.test b/tests/init.test index 158468f..e999c0b 100755 --- a/tests/init.test +++ b/tests/init.test @@ -11,6 +11,8 @@ lib="$srcdir"/lib/tiny-cloud/init init_tests \ expand_root \ expand_root_partition \ + expand_root_lvm_partition \ + expand_root_lvm_whole_disk \ ethernets \ find_first_interface_up \ auto_detect_ethernet_interface \ @@ -69,6 +71,75 @@ expand_root_partition_body() { done } +expand_root_lvm_partition_body() { + # Test LVM on a partition (e.g., /dev/sda2 -> PV -> VG -> LV) + mkdir -p proc sys/class/block \ + sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vda/vda2 \ + sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vda/device \ + dev/mapper + ln -s ../../devices/pci0000:00/0000:00:05.0/virtio2/block/vda sys/class/block/vda + ln -s ../../devices/pci0000:00/0000:00:05.0/virtio2/block/vda/vda2 sys/class/block/vda2 + echo 2 > sys/class/block/vda2/partition + + # Create fake LVM device + mkdir -p sys/class/block/dm-0 + ln -s /dev/mapper/vg0-root dev/mapper/vg0-root + + # Mock LVM commands + fake_bin lvs <<-EOF + #!/bin/sh + echo " vg0" + EOF + fake_bin pvs <<-EOF + #!/bin/sh + echo " /dev/vda2" + EOF + + echo "/dev/mapper/vg0-root / ext4 rw,noatime 0 0" > proc/mounts + for provider in $PROVIDERS; do + CLOUD="$provider" atf_check \ + -o match:"sfdisk .*/dev/vda" \ + -o match:"partx .*/dev/vda" \ + -o match:"pvresize /dev/vda2" \ + -o match:"lvextend -l \\+85%VG /dev/mapper/vg0-root" \ + -o match:"resize2fs /dev/mapper/vg0-root" \ + sh -c ". $lib; init__expand_root" + done +} + +expand_root_lvm_whole_disk_body() { + # Test LVM on whole disk (e.g., /dev/sdb -> PV -> VG -> LV, no partition) + mkdir -p proc sys/class/block \ + sys/devices/pci0000:00/0000:00:05.0/virtio3/block/vdb/device \ + dev/mapper + ln -s ../../devices/pci0000:00/0000:00:05.0/virtio3/block/vdb sys/class/block/vdb + + # Create fake LVM device + mkdir -p sys/class/block/dm-1 + ln -s /dev/mapper/vg1-root dev/mapper/vg1-root + + # Mock LVM commands - PV is on whole disk (no partition) + fake_bin lvs <<-EOF + #!/bin/sh + echo " vg1" + EOF + fake_bin pvs <<-EOF + #!/bin/sh + echo " /dev/vdb" + EOF + + echo "/dev/mapper/vg1-root / ext4 rw,noatime 0 0" > proc/mounts + for provider in $PROVIDERS; do + CLOUD="$provider" atf_check \ + -o match:"pvresize /dev/vdb" \ + -o match:"lvextend -l \\+85%VG /dev/mapper/vg1-root" \ + -o match:"resize2fs /dev/mapper/vg1-root" \ + -o not-match:"sfdisk" \ + -o not-match:"partx" \ + sh -c ". $lib; init__expand_root" + done +} + ethernets_body() { fake_interfaces lo br0 eth0 eth2 eth11