From ed5b89aa0bee1ad46ec2c914d7d7b4d56fea4462 Mon Sep 17 00:00:00 2001 From: Niraj Yadav Date: Wed, 27 Nov 2024 15:08:29 +0530 Subject: [PATCH] rbd: Use assume_storage_prezeroed when formatting Instead of passing lazy_itable_init=1 and lazy_journal_init=1 to mkfs.ext4, pass assume_storage_prezeroed=1 which is stronger and allows the filesystem to skip inode table zeroing completely instead of simply doing it lazily. The support for this flag is checked by trying to format a fake temporary image with mkfs.ext4 and checking its STDERR. Closes: #4948 Signed-off-by: Niraj Yadav --- internal/rbd/nodeserver.go | 101 +++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/internal/rbd/nodeserver.go b/internal/rbd/nodeserver.go index 905fcf8affe..cbd1764b8d2 100644 --- a/internal/rbd/nodeserver.go +++ b/internal/rbd/nodeserver.go @@ -67,6 +67,10 @@ const ( xfsReflinkNoSupport xfsReflinkSupport + ext4PrezeroedUnset + ext4PrezeroedNoSupport + ext4PrezeroedSupport + staticVol = "staticVolume" volHealerCtx = "volumeHealerContext" tryOtherMounters = "tryOtherMounters" @@ -100,10 +104,9 @@ var ( // checking the support for reflink. xfsHasReflink = xfsReflinkUnset - mkfsDefaultArgs = map[string][]string{ - "ext4": {"-m0", "-Enodiscard,lazy_itable_init=1,lazy_journal_init=1"}, - "xfs": {"-K"}, - } + // ext4HasPrezeroed is set by ext4SupportsPrezeroed(), use the function when + // checking the support for assume_storage_prezeroed. + ext4HasPrezeroedSupport = ext4PrezeroedUnset mountDefaultOpts = map[string][]string{ "xfs": {"nouuid"}, @@ -793,7 +796,7 @@ func (ns *NodeServer) mountVolumeToStagePath( } if existingFormat == "" && !staticVol && !readOnly && !isBlock { - args := mkfsDefaultArgs[fsType] + args := ns.getMkfsArgs(fsType) // if the VolumeContext contains "mkfsOptions", use those as args instead volumeCtx := req.GetVolumeContext() @@ -1314,6 +1317,55 @@ func (ns *NodeServer) xfsSupportsReflink() bool { return false } +// ext4SupportsPrezeroed checks if the ext4 filesystem supports the +// "assume_storage_prezeroed" option. It does this by creating a temporary +// image file, attempting to format it with the ext4 filesystem using the +// "assume_storage_prezeroed" option, and checking the output for errors. +func (ns *NodeServer) ext4SupportsPrezeroed() bool { + if ext4HasPrezeroedSupport != ext4PrezeroedUnset { + return ext4HasPrezeroedSupport == ext4PrezeroedSupport + } + + ctx := context.TODO() + tempImgFile, err := os.CreateTemp(os.TempDir(), "prezeroed.img") + if err != nil { + log.WarningLog(ctx, "failed to create temporary image file: %v", err) + + return false + } + defer os.Remove(tempImgFile.Name()) + + if err = createSparseFile(tempImgFile, 1); err != nil { + log.WarningLog(ctx, "failed to create sparse file: %v", err) + + return false + } + + diskMounter := &mount.SafeFormatAndMount{Interface: ns.Mounter, Exec: utilexec.New()} + + // `-n` Causes mke2fs to not actually create a file system + // but display what it would do if it were to create a file system. + out, err := diskMounter.Exec.Command( + "mkfs.ext4", + "-n", + "-Enodiscard,assume_storage_prezeroed=1", + tempImgFile.Name(), + ).CombinedOutput() + if err != nil { + if strings.Contains(string(out), "Bad option(s) specified") { + ext4HasPrezeroedSupport = ext4PrezeroedNoSupport + } + + // if the error is not due to the missing option, we can't be sure + // if the option is supported or not, return false optimistically + return false + } + + ext4HasPrezeroedSupport = ext4PrezeroedSupport + + return true +} + // NodeGetVolumeStats returns volume stats. func (ns *NodeServer) NodeGetVolumeStats( ctx context.Context, @@ -1397,3 +1449,42 @@ func getDeviceSize(ctx context.Context, devicePath string) (uint64, error) { return size, nil } + +// getMkfsArgs returns the appropriate mkfs arguments for the given filesystem type. +// Supported filesystem types are "ext4" and "xfs". For "ext4", it checks if the +// ext4 filesystem supports pre-zeroed storage and returns the corresponding arguments. +// For "xfs", it returns the arguments to disable discard. If an unknown filesystem +// type is provided, a warning is logged and empty options are returned. +func (ns *NodeServer) getMkfsArgs(fsType string) []string { + switch fsType { + case "ext4": + if ns.ext4SupportsPrezeroed() { + return []string{"-m0", "-Enodiscard,assume_storage_prezeroed=1"} + } + + return []string{"-m0", "-Enodiscard,lazy_itable_init=1,lazy_journal_init=1"} + case "xfs": + return []string{"-K"} + default: + log.WarningLogMsg(fmt.Sprintf("unknown fsType: %q, using default mkfs options", fsType)) + + return []string{} + } +} + +// createSpareFile makes `file` a sparse file of size `sizeMB`. +func createSparseFile(file *os.File, sizeMB int64) error { + sizeBytes := sizeMB * 1024 * 1024 + + // seek to the end of the file. + if _, err := file.Seek(sizeBytes-1, 0); err != nil { + return fmt.Errorf("failed to seek to the end of the file: %w", err) + } + + // write a single byte, effectively making it a sparse file. + if _, err := file.Write([]byte{0}); err != nil { + return fmt.Errorf("failed to write to the end of the file: %w", err) + } + + return nil +}