From c916dba607332f7619614868ea7164a11e18b6d2 Mon Sep 17 00:00:00 2001 From: Hemant Kumar Date: Wed, 30 Apr 2025 14:12:14 -0400 Subject: [PATCH] Disable size check for xfs/ext3/ext4 filesystems before expansion --- .../src/k8s.io/mount-utils/resizefs_linux.go | 85 ++++- .../k8s.io/mount-utils/resizefs_linux_test.go | 314 ++++++++++++++++++ 2 files changed, 394 insertions(+), 5 deletions(-) diff --git a/staging/src/k8s.io/mount-utils/resizefs_linux.go b/staging/src/k8s.io/mount-utils/resizefs_linux.go index 16adc0cb33e..3eaf46a49a1 100644 --- a/staging/src/k8s.io/mount-utils/resizefs_linux.go +++ b/staging/src/k8s.io/mount-utils/resizefs_linux.go @@ -21,9 +21,9 @@ package mount import ( "fmt" + "strconv" "strings" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" utilexec "k8s.io/utils/exec" ) @@ -129,18 +129,93 @@ func (resizefs *ResizeFs) NeedResize(devicePath string, deviceMountPath string) return false, nil } - supportedFormats := sets.New("ext3", "ext4", "xfs", "btrfs") - if !supportedFormats.Has(format) { + switch format { + case "ext3", "ext4", "xfs": + // For ext3/ext4/xfs, recommendation received from linux filesystem folks is to let + // resize2fs/xfs_growfs do the check for us. So we will not do any check here. + return true, nil + case "btrfs": + deviceSize, err := resizefs.getDeviceSize(devicePath) + if err != nil { + return false, err + } + blockSize, fsSize, err := resizefs.getBtrfsSize(devicePath) + klog.V(5).Infof("Btrfs size: filesystem size=%d, block size=%d, err=%v", fsSize, blockSize, err) + if err != nil { + return false, err + } + if deviceSize <= fsSize+blockSize { + return false, nil + } + return true, nil + default: return false, fmt.Errorf("could not parse fs info of given filesystem format: %s. Supported fs types are: xfs, ext3, ext4", format) } - return true, nil +} + +func (resizefs *ResizeFs) getDeviceSize(devicePath string) (uint64, error) { + output, err := resizefs.exec.Command(blockDev, "--getsize64", devicePath).CombinedOutput() + outStr := strings.TrimSpace(string(output)) + if err != nil { + return 0, fmt.Errorf("failed to read size of device %s: %s: %s", devicePath, err, outStr) + } + size, err := strconv.ParseUint(outStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse size of device %s %s: %s", devicePath, outStr, err) + } + return size, nil +} + +func (resizefs *ResizeFs) getBtrfsSize(devicePath string) (uint64, uint64, error) { + output, err := resizefs.exec.Command("btrfs", "inspect-internal", "dump-super", "-f", devicePath).CombinedOutput() + if err != nil { + return 0, 0, fmt.Errorf("failed to read size of filesystem on %s: %s: %s", devicePath, err, string(output)) + } + + blockSize, totalBytes, _ := resizefs.parseBtrfsInfoOutput(string(output), "sectorsize", "total_bytes") + + if blockSize == 0 { + return 0, 0, fmt.Errorf("could not find block size of device %s", devicePath) + } + if totalBytes == 0 { + return 0, 0, fmt.Errorf("could not find total size of device %s", devicePath) + } + return blockSize, totalBytes, nil +} + +func (resizefs *ResizeFs) parseBtrfsInfoOutput(cmdOutput string, blockSizeKey string, totalBytesKey string) (uint64, uint64, error) { + lines := strings.Split(cmdOutput, "\n") + var blockSize, blockCount uint64 + var err error + + for _, line := range lines { + tokens := strings.Fields(line) + if len(tokens) != 2 { + continue + } + key, value := strings.ToLower(strings.TrimSpace(tokens[0])), strings.ToLower(strings.TrimSpace(tokens[1])) + + if key == blockSizeKey { + blockSize, err = strconv.ParseUint(value, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse block size %s: %s", value, err) + } + } + if key == totalBytesKey { + blockCount, err = strconv.ParseUint(value, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse total size %s: %s", value, err) + } + } + } + return blockSize, blockCount, err } func (resizefs *ResizeFs) getDeviceRO(devicePath string) (bool, error) { output, err := resizefs.exec.Command(blockDev, "--getro", devicePath).CombinedOutput() outStr := strings.TrimSpace(string(output)) if err != nil { - return false, fmt.Errorf("failed to get readonly bit from device %s: %s: %s", devicePath, err, outStr) + return false, fmt.Errorf("failed to get readonly bit from device %s: %w: %s", devicePath, err, outStr) } switch outStr { case "0": diff --git a/staging/src/k8s.io/mount-utils/resizefs_linux_test.go b/staging/src/k8s.io/mount-utils/resizefs_linux_test.go index 270027bbab0..9a39a574bd6 100644 --- a/staging/src/k8s.io/mount-utils/resizefs_linux_test.go +++ b/staging/src/k8s.io/mount-utils/resizefs_linux_test.go @@ -20,12 +20,290 @@ limitations under the License. package mount import ( + "fmt" "testing" "k8s.io/utils/exec" fakeexec "k8s.io/utils/exec/testing" ) +func TestGetFileSystemSize(t *testing.T) { + cmdOutputSuccessBtrfs := `superblock: bytenr=65536, device=/dev/loop0 + --------------------------------------------------------- + csum_type 0 (crc32c) + csum_size 4 + csum 0x31693b11 [match] + bytenr 65536 + flags 0x1 + ( WRITTEN ) + magic _BHRfS_M [match] + fsid 3f53c8f7-3c57-4185-bf1d-b305b42cce97 + metadata_uuid 3f53c8f7-3c57-4185-bf1d-b305b42cce97 + label + generation 7 + root 30441472 + sys_array_size 129 + chunk_root_generation 6 + root_level 0 + chunk_root 22036480 + chunk_root_level 0 + log_root 0 + log_root_transid 0 + log_root_level 0 + total_bytes 1048576000 + bytes_used 147456 + sectorsize 4096 + nodesize 16384 + leafsize (deprecated) 16384 + stripesize 4096 + root_dir 6 + num_devices 1 + compat_flags 0x0 + compat_ro_flags 0x3 + ( FREE_SPACE_TREE | + FREE_SPACE_TREE_VALID ) + incompat_flags 0x341 + ( MIXED_BACKREF | + EXTENDED_IREF | + SKINNY_METADATA | + NO_HOLES ) + cache_generation 0 + uuid_tree_generation 7 + dev_item.uuid 987c8423-fba3-4168-9892-560a116feb81 + dev_item.fsid 3f53c8f7-3c57-4185-bf1d-b305b42cce97 [match] + dev_item.type 0 + dev_item.total_bytes 1048576000 + dev_item.bytes_used 130023424 + dev_item.io_align 4096 + dev_item.io_width 4096 + dev_item.sector_size 4096 + dev_item.devid 1 + dev_item.dev_group 0 + dev_item.seek_speed 0 + dev_item.bandwidth 0 + dev_item.generation 0 + sys_chunk_array[2048]: + item 0 key (FIRST_CHUNK_TREE CHUNK_ITEM 22020096) + length 8388608 owner 2 stripe_len 65536 type SYSTEM|DUP + io_align 65536 io_width 65536 sector_size 4096 + num_stripes 2 sub_stripes 1 + stripe 0 devid 1 offset 22020096 + dev_uuid 987c8423-fba3-4168-9892-560a116feb81 + stripe 1 devid 1 offset 30408704 + dev_uuid 987c8423-fba3-4168-9892-560a116feb81 + backup_roots[4]: + backup 0: + backup_tree_root: 30441472 gen: 5 level: 0 + backup_chunk_root: 22020096 gen: 5 level: 0 + backup_extent_root: 30474240 gen: 5 level: 0 + backup_fs_root: 30425088 gen: 5 level: 0 + backup_dev_root: 30457856 gen: 5 level: 0 + backup_csum_root: 30490624 gen: 5 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 147456 + backup_num_devices: 1 + + backup 1: + backup_tree_root: 30588928 gen: 6 level: 0 + backup_chunk_root: 22036480 gen: 6 level: 0 + backup_extent_root: 30408704 gen: 6 level: 0 + backup_fs_root: 30425088 gen: 5 level: 0 + backup_dev_root: 30556160 gen: 6 level: 0 + backup_csum_root: 30490624 gen: 5 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 147456 + backup_num_devices: 1 + + backup 2: + backup_tree_root: 30441472 gen: 7 level: 0 + backup_chunk_root: 22036480 gen: 6 level: 0 + backup_extent_root: 30474240 gen: 7 level: 0 + backup_fs_root: 30425088 gen: 5 level: 0 + backup_dev_root: 30457856 gen: 7 level: 0 + backup_csum_root: 30490624 gen: 5 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 147456 + backup_num_devices: 1 + + backup 3: + backup_tree_root: 30408704 gen: 4 level: 0 + backup_chunk_root: 1064960 gen: 4 level: 0 + backup_extent_root: 5341184 gen: 4 level: 0 + backup_fs_root: 5324800 gen: 3 level: 0 + backup_dev_root: 5242880 gen: 4 level: 0 + backup_csum_root: 1130496 gen: 1 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 114688 + backup_num_devices: 1 + + ` + cmdOutputNoDataBtrfs := `superblock: bytenr=65536, device=/dev/loop0 + --------------------------------------------------------- + csum_type 0 (crc32c) + csum_size 4 + csum 0x31693b11 [match] + bytenr 65536 + flags 0x1 + ( WRITTEN ) + magic _BHRfS_M [match] + fsid 3f53c8f7-3c57-4185-bf1d-b305b42cce97 + metadata_uuid 3f53c8f7-3c57-4185-bf1d-b305b42cce97 + label + generation 7 + root 30441472 + sys_array_size 129 + chunk_root_generation 6 + root_level 0 + chunk_root 22036480 + chunk_root_level 0 + log_root 0 + log_root_transid 0 + log_root_level 0 + bytes_used 147456 + nodesize 16384 + leafsize (deprecated) 16384 + stripesize 4096 + root_dir 6 + num_devices 1 + compat_flags 0x0 + compat_ro_flags 0x3 + ( FREE_SPACE_TREE | + FREE_SPACE_TREE_VALID ) + incompat_flags 0x341 + ( MIXED_BACKREF | + EXTENDED_IREF | + SKINNY_METADATA | + NO_HOLES ) + cache_generation 0 + uuid_tree_generation 7 + dev_item.uuid 987c8423-fba3-4168-9892-560a116feb81 + dev_item.fsid 3f53c8f7-3c57-4185-bf1d-b305b42cce97 [match] + dev_item.type 0 + dev_item.total_bytes 1048576000 + dev_item.bytes_used 130023424 + dev_item.io_align 4096 + dev_item.io_width 4096 + dev_item.sector_size 4096 + dev_item.devid 1 + dev_item.dev_group 0 + dev_item.seek_speed 0 + dev_item.bandwidth 0 + dev_item.generation 0 + sys_chunk_array[2048]: + item 0 key (FIRST_CHUNK_TREE CHUNK_ITEM 22020096) + length 8388608 owner 2 stripe_len 65536 type SYSTEM|DUP + io_align 65536 io_width 65536 sector_size 4096 + num_stripes 2 sub_stripes 1 + stripe 0 devid 1 offset 22020096 + dev_uuid 987c8423-fba3-4168-9892-560a116feb81 + stripe 1 devid 1 offset 30408704 + dev_uuid 987c8423-fba3-4168-9892-560a116feb81 + backup_roots[4]: + backup 0: + backup_tree_root: 30441472 gen: 5 level: 0 + backup_chunk_root: 22020096 gen: 5 level: 0 + backup_extent_root: 30474240 gen: 5 level: 0 + backup_fs_root: 30425088 gen: 5 level: 0 + backup_dev_root: 30457856 gen: 5 level: 0 + backup_csum_root: 30490624 gen: 5 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 147456 + backup_num_devices: 1 + + backup 1: + backup_tree_root: 30588928 gen: 6 level: 0 + backup_chunk_root: 22036480 gen: 6 level: 0 + backup_extent_root: 30408704 gen: 6 level: 0 + backup_fs_root: 30425088 gen: 5 level: 0 + backup_dev_root: 30556160 gen: 6 level: 0 + backup_csum_root: 30490624 gen: 5 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 147456 + backup_num_devices: 1 + + backup 2: + backup_tree_root: 30441472 gen: 7 level: 0 + backup_chunk_root: 22036480 gen: 6 level: 0 + backup_extent_root: 30474240 gen: 7 level: 0 + backup_fs_root: 30425088 gen: 5 level: 0 + backup_dev_root: 30457856 gen: 7 level: 0 + backup_csum_root: 30490624 gen: 5 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 147456 + backup_num_devices: 1 + + backup 3: + backup_tree_root: 30408704 gen: 4 level: 0 + backup_chunk_root: 1064960 gen: 4 level: 0 + backup_extent_root: 5341184 gen: 4 level: 0 + backup_fs_root: 5324800 gen: 3 level: 0 + backup_dev_root: 5242880 gen: 4 level: 0 + backup_csum_root: 1130496 gen: 1 level: 0 + backup_total_bytes: 1048576000 + backup_bytes_used: 114688 + backup_num_devices: 1 + + ` + + testcases := []struct { + name string + devicePath string + blocksize uint64 + blockCount uint64 + cmdOutput string + expectError bool + fsType string + }{ + { + name: "success parse btrfs info", + devicePath: "/dev/test1", + blocksize: 4096, + blockCount: 256000, + cmdOutput: cmdOutputSuccessBtrfs, + expectError: false, + fsType: "btrfs", + }, + { + name: "block size not present - btrfs", + devicePath: "/dev/test1", + blocksize: 0, + blockCount: 0, + cmdOutput: cmdOutputNoDataBtrfs, + expectError: true, + fsType: "btrfs", + }, + } + + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeAction{ + func() ([]byte, []byte, error) { return []byte(test.cmdOutput), nil, nil }, + }, + } + fexec := &fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fcmd, cmd, args...) + }, + }, + } + resizefs := ResizeFs{exec: fexec} + + blockSize, fsSize, err := resizefs.getBtrfsSize(test.devicePath) + + if blockSize != test.blocksize { + t.Fatalf("Parse wrong block size value, expect %d, but got %d", test.blocksize, blockSize) + } + if fsSize != test.blocksize*test.blockCount { + t.Fatalf("Parse wrong fs size value, expect %d, but got %d", test.blocksize*test.blockCount, fsSize) + } + if !test.expectError && err != nil { + t.Fatalf("Expect no error but got %v", err) + } + }) + } +} func TestNeedResize(t *testing.T) { testcases := []struct { name string @@ -60,6 +338,28 @@ func TestNeedResize(t *testing.T) { expectError: false, expectResult: false, }, + { + name: "False - Not needed by size for btrfs", + devicePath: "/dev/test1", + deviceMountPath: "/mnt/test1", + readonly: "0", + deviceSize: "20", + cmdOutputFsType: "TYPE=btrfs", + extSize: "2048", + expectError: false, + expectResult: false, + }, + { + name: "True - needed by size for btrfs", + devicePath: "/dev/test1", + deviceMountPath: "/mnt/test1", + readonly: "0", + deviceSize: "2048", + cmdOutputFsType: "TYPE=btrfs", + extSize: "20", + expectError: false, + expectResult: true, + }, { name: "False - Unsupported fs type", devicePath: "/dev/test1", @@ -87,6 +387,20 @@ func TestNeedResize(t *testing.T) { func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, }, } + if test.cmdOutputFsType == "TYPE=btrfs" { + t.Logf("Adding btrfs size command") + fcmd.CombinedOutputScript = append(fcmd.CombinedOutputScript, func() ([]byte, []byte, error) { return []byte(test.deviceSize), nil, nil }) + fcmd.CombinedOutputScript = append(fcmd.CombinedOutputScript, func() ([]byte, []byte, error) { + return []byte(fmt.Sprintf("sectorsize %s\ntotal_bytes 1\n", test.extSize)), nil, nil + }) + + fexec.CommandScript = append(fexec.CommandScript, func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fcmd, cmd, args...) + }) + fexec.CommandScript = append(fexec.CommandScript, func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fcmd, cmd, args...) + }) + } resizefs := ResizeFs{exec: fexec} needResize, err := resizefs.NeedResize(test.devicePath, test.deviceMountPath)