From 3d104a98a4cd4300aeec909b1f9a3c17acbe071a Mon Sep 17 00:00:00 2001 From: Thomas Jungblut Date: Thu, 7 Mar 2024 10:38:15 +0100 Subject: [PATCH] Add basic XFS powerfailure tests This also introduces mkfs options, in case we need to accomodate for non-default parameters here in the future. Signed-off-by: Thomas Jungblut --- .github/workflows/robustness_template.yaml | 2 +- tests/dmflakey/dmflakey.go | 18 +++-- tests/dmflakey/dmflakey_test.go | 42 ++++++------ tests/robustness/powerfailure_test.go | 76 +++++++++++++++++++--- 4 files changed, 104 insertions(+), 34 deletions(-) diff --git a/.github/workflows/robustness_template.yaml b/.github/workflows/robustness_template.yaml index e9b9a38d7..9300e14eb 100644 --- a/.github/workflows/robustness_template.yaml +++ b/.github/workflows/robustness_template.yaml @@ -30,6 +30,6 @@ jobs: - name: test-robustness run: | set -euo pipefail - sudo apt-get install -y dmsetup + sudo apt-get install -y dmsetup xfsprogs ROBUSTNESS_TESTFLAGS="--count ${{ inputs.count }} --timeout ${{ inputs.testTimeout }} -failfast" make test-robustness diff --git a/tests/dmflakey/dmflakey.go b/tests/dmflakey/dmflakey.go index 25061a4cb..88c3c2d48 100644 --- a/tests/dmflakey/dmflakey.go +++ b/tests/dmflakey/dmflakey.go @@ -90,9 +90,9 @@ const ( // The device-mapper device will be /dev/mapper/$flakeyDevice. And the filesystem // image will be created at $dataStorePath/$flakeyDevice.img. By default, the // device is available for 2 minutes and size is 10 GiB. -func InitFlakey(flakeyDevice, dataStorePath string, fsType FSType) (_ Flakey, retErr error) { +func InitFlakey(flakeyDevice, dataStorePath string, fsType FSType, mkfsOpt string) (_ Flakey, retErr error) { imgPath := filepath.Join(dataStorePath, fmt.Sprintf("%s.img", flakeyDevice)) - if err := createEmptyFSImage(imgPath, fsType); err != nil { + if err := createEmptyFSImage(imgPath, fsType, mkfsOpt); err != nil { return nil, err } defer func() { @@ -276,7 +276,7 @@ func (f *flakey) Teardown() error { // createEmptyFSImage creates empty filesystem on dataStorePath folder with // default size - 10 GiB. -func createEmptyFSImage(imgPath string, fsType FSType) error { +func createEmptyFSImage(imgPath string, fsType FSType, mkfsOpt string) error { if err := validateFSType(fsType); err != nil { return err } @@ -308,10 +308,16 @@ func createEmptyFSImage(imgPath string, fsType FSType) error { imgPath, defaultImgSize, err) } - output, err := exec.Command(mkfs, imgPath).CombinedOutput() + args := []string{imgPath} + if mkfsOpt != "" { + splitArgs := strings.Split(mkfsOpt, " ") + args = append(splitArgs, imgPath) + } + + output, err := exec.Command(mkfs, args...).CombinedOutput() if err != nil { - return fmt.Errorf("failed to mkfs.%s on %s (out: %s): %w", - fsType, imgPath, string(output), err) + return fmt.Errorf("failed to mkfs on %s (%s %v) (out: %s): %w", + imgPath, mkfs, args, string(output), err) } return nil } diff --git a/tests/dmflakey/dmflakey_test.go b/tests/dmflakey/dmflakey_test.go index 41c66db8d..99e2de062 100644 --- a/tests/dmflakey/dmflakey_test.go +++ b/tests/dmflakey/dmflakey_test.go @@ -26,31 +26,35 @@ func TestMain(m *testing.M) { } func TestBasic(t *testing.T) { - tmpDir := t.TempDir() + for _, fsType := range []FSType{FSTypeEXT4, FSTypeXFS} { + t.Run(string(fsType), func(t *testing.T) { + tmpDir := t.TempDir() - flakey, err := InitFlakey("go-dmflakey", tmpDir, FSTypeEXT4) - require.NoError(t, err, "init flakey") - defer func() { - assert.NoError(t, flakey.Teardown()) - }() + flakey, err := InitFlakey("go-dmflakey", tmpDir, fsType, "") + require.NoError(t, err, "init flakey") + defer func() { + assert.NoError(t, flakey.Teardown()) + }() - target := filepath.Join(tmpDir, "root") - require.NoError(t, os.MkdirAll(target, 0600)) + target := filepath.Join(tmpDir, "root") + require.NoError(t, os.MkdirAll(target, 0600)) - require.NoError(t, mount(target, flakey.DevicePath(), "")) - defer func() { - assert.NoError(t, unmount(target)) - }() + require.NoError(t, mount(target, flakey.DevicePath(), "")) + defer func() { + assert.NoError(t, unmount(target)) + }() - file := filepath.Join(target, "test") - assert.NoError(t, writeFile(file, []byte("hello, world"), 0600, true)) + file := filepath.Join(target, "test") + assert.NoError(t, writeFile(file, []byte("hello, world"), 0600, true)) - assert.NoError(t, unmount(target)) + assert.NoError(t, unmount(target)) - assert.NoError(t, flakey.Teardown()) + assert.NoError(t, flakey.Teardown()) + }) + } } -func TestDropWrites(t *testing.T) { +func TestDropWritesExt4(t *testing.T) { flakey, root := initFlakey(t, FSTypeEXT4) // commit=1000 is to delay commit triggered by writeback thread @@ -82,7 +86,7 @@ func TestDropWrites(t *testing.T) { assert.True(t, errors.Is(err, os.ErrNotExist)) } -func TestErrorWrites(t *testing.T) { +func TestErrorWritesExt4(t *testing.T) { flakey, root := initFlakey(t, FSTypeEXT4) // commit=1000 is to delay commit triggered by writeback thread @@ -114,7 +118,7 @@ func initFlakey(t *testing.T, fsType FSType) (_ Flakey, root string) { target := filepath.Join(tmpDir, "root") require.NoError(t, os.MkdirAll(target, 0600)) - flakey, err := InitFlakey("go-dmflakey", tmpDir, FSTypeEXT4) + flakey, err := InitFlakey("go-dmflakey", tmpDir, fsType, "") require.NoError(t, err, "init flakey") t.Cleanup(func() { diff --git a/tests/robustness/powerfailure_test.go b/tests/robustness/powerfailure_test.go index 4b150ccdc..42ba58d66 100644 --- a/tests/robustness/powerfailure_test.go +++ b/tests/robustness/powerfailure_test.go @@ -35,8 +35,8 @@ var panicFailpoints = []string{ "unmapError", } -// TestRestartFromPowerFailure is to test data after unexpected power failure. -func TestRestartFromPowerFailure(t *testing.T) { +// TestRestartFromPowerFailureExt4 is to test data after unexpected power failure on ext4. +func TestRestartFromPowerFailureExt4(t *testing.T) { for _, tc := range []struct { name string du time.Duration @@ -78,13 +78,73 @@ func TestRestartFromPowerFailure(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - doPowerFailure(t, tc.du, tc.fsMountOpt, tc.useFailpoint) + doPowerFailure(t, tc.du, dmflakey.FSTypeEXT4, "", tc.fsMountOpt, tc.useFailpoint) }) } } -func doPowerFailure(t *testing.T, du time.Duration, fsMountOpt string, useFailpoint bool) { - flakey := initFlakeyDevice(t, strings.Replace(t.Name(), "/", "_", -1), dmflakey.FSTypeEXT4, fsMountOpt) +func TestRestartFromPowerFailureXFS(t *testing.T) { + for _, tc := range []struct { + name string + du time.Duration + mkfsOpt string + fsMountOpt string + useFailpoint bool + }{ + { + name: "xfs_no_opts", + du: 5 * time.Second, + mkfsOpt: "", + fsMountOpt: "", + useFailpoint: true, + }, + { + name: "lazy-log", + du: 5 * time.Second, + mkfsOpt: "-l lazy-count=1", + fsMountOpt: "", + useFailpoint: true, + }, + { + name: "odd-allocsize", + du: 5 * time.Second, + mkfsOpt: "", + fsMountOpt: "allocsize=" + fmt.Sprintf("%d", 4096*5), + useFailpoint: true, + }, + { + name: "nolargeio", + du: 5 * time.Second, + mkfsOpt: "", + fsMountOpt: "nolargeio", + useFailpoint: true, + }, + { + name: "odd-alignment", + du: 5 * time.Second, + mkfsOpt: "-d sunit=1024,swidth=1024", + fsMountOpt: "noalign", + useFailpoint: true, + }, + { + name: "openshift-sno-options", + du: 5 * time.Second, + mkfsOpt: "-m bigtime=1,finobt=1,rmapbt=0,reflink=1 -i sparse=1 -l lazy-count=1", + // openshift also supplies seclabel,relatime on RHEL, but that's not supported on our CI + fsMountOpt: "rw,attr2,inode64,logbufs=8,logbsize=32k,prjquota", + useFailpoint: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Logf("mkfs opts: %s", tc.mkfsOpt) + t.Logf("mount opts: %s", tc.fsMountOpt) + doPowerFailure(t, tc.du, dmflakey.FSTypeXFS, tc.mkfsOpt, tc.fsMountOpt, tc.useFailpoint) + }) + } +} + +func doPowerFailure(t *testing.T, du time.Duration, fsType dmflakey.FSType, mkfsOpt string, fsMountOpt string, useFailpoint bool) { + flakey := initFlakeyDevice(t, strings.Replace(t.Name(), "/", "_", -1), fsType, mkfsOpt, fsMountOpt) root := flakey.RootFS() dbPath := filepath.Join(root, "boltdb") @@ -186,10 +246,10 @@ type FlakeyDevice interface { } // initFlakeyDevice returns FlakeyDevice instance with a given filesystem. -func initFlakeyDevice(t *testing.T, name string, fsType dmflakey.FSType, mntOpt string) FlakeyDevice { +func initFlakeyDevice(t *testing.T, name string, fsType dmflakey.FSType, mkfsOpt string, mntOpt string) FlakeyDevice { imgDir := t.TempDir() - flakey, err := dmflakey.InitFlakey(name, imgDir, fsType) + flakey, err := dmflakey.InitFlakey(name, imgDir, fsType, mkfsOpt) require.NoError(t, err, "init flakey %s", name) t.Cleanup(func() { assert.NoError(t, flakey.Teardown()) @@ -240,7 +300,7 @@ func (f *flakeyT) PowerFailure(mntOpt string) error { } if err := unix.Mount(f.DevicePath(), f.rootDir, string(f.Filesystem()), 0, mntOpt); err != nil { - return fmt.Errorf("failed to mount rootfs %s: %w", f.rootDir, err) + return fmt.Errorf("failed to mount rootfs %s (%s): %w", f.rootDir, mntOpt, err) } return nil }