mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-01-31 00:59:30 +00:00
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 <niryadav@redhat.com>
This commit is contained in:
parent
7eb99fc6c9
commit
c308e096da
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
||||||
"github.com/ceph/ceph-csi/internal/util"
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/file"
|
||||||
"github.com/ceph/ceph-csi/internal/util/fscrypt"
|
"github.com/ceph/ceph-csi/internal/util/fscrypt"
|
||||||
"github.com/ceph/ceph-csi/internal/util/log"
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
|
|
||||||
@ -45,6 +46,11 @@ type NodeServer struct {
|
|||||||
// A map storing all volumes with ongoing operations so that additional operations
|
// A map storing all volumes with ongoing operations so that additional operations
|
||||||
// for that same volume (as defined by VolumeID) return an Aborted error
|
// for that same volume (as defined by VolumeID) return an Aborted error
|
||||||
VolumeLocks *util.VolumeLocks
|
VolumeLocks *util.VolumeLocks
|
||||||
|
|
||||||
|
// ext4HasPrezeroedSupport indicates whether the ext4 filesystem has support for pre-zeroed blocks.
|
||||||
|
ext4HasPrezeroedSupport featureFlag
|
||||||
|
// xfsHasReflinkSupport indicates whether the xfs filesystem has support for reflink.
|
||||||
|
xfsHasReflinkSupport featureFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
// stageTransaction struct represents the state a transaction was when it either completed
|
// stageTransaction struct represents the state a transaction was when it either completed
|
||||||
@ -61,11 +67,16 @@ type stageTransaction struct {
|
|||||||
devicePath string
|
devicePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// featureFlag represents a type for defining feature flags within the RBD node server.
|
||||||
|
type featureFlag int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// values for xfsHasReflink.
|
// featureFlagUnset represents the default value for a feature flag.
|
||||||
xfsReflinkUnset int = iota
|
featureFlagUnset featureFlag = iota
|
||||||
xfsReflinkNoSupport
|
// featureFlagNoSupport represents the value for a feature flag when the feature is not supported.
|
||||||
xfsReflinkSupport
|
featureFlagNoSupport
|
||||||
|
// featureFlagSupport represents the value for a feature flag when the feature is supported.
|
||||||
|
featureFlagSupport
|
||||||
|
|
||||||
staticVol = "staticVolume"
|
staticVol = "staticVolume"
|
||||||
volHealerCtx = "volumeHealerContext"
|
volHealerCtx = "volumeHealerContext"
|
||||||
@ -96,15 +107,6 @@ var (
|
|||||||
}, // RHEL 8.2
|
}, // RHEL 8.2
|
||||||
}
|
}
|
||||||
|
|
||||||
// xfsHasReflink is set by xfsSupportsReflink(), use the function when
|
|
||||||
// checking the support for reflink.
|
|
||||||
xfsHasReflink = xfsReflinkUnset
|
|
||||||
|
|
||||||
mkfsDefaultArgs = map[string][]string{
|
|
||||||
"ext4": {"-m0", "-Enodiscard,lazy_itable_init=1,lazy_journal_init=1"},
|
|
||||||
"xfs": {"-K"},
|
|
||||||
}
|
|
||||||
|
|
||||||
mountDefaultOpts = map[string][]string{
|
mountDefaultOpts = map[string][]string{
|
||||||
"xfs": {"nouuid"},
|
"xfs": {"nouuid"},
|
||||||
}
|
}
|
||||||
@ -793,7 +795,7 @@ func (ns *NodeServer) mountVolumeToStagePath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existingFormat == "" && !staticVol && !readOnly && !isBlock {
|
if existingFormat == "" && !staticVol && !readOnly && !isBlock {
|
||||||
args := mkfsDefaultArgs[fsType]
|
args := ns.getMkfsArgs(fsType)
|
||||||
|
|
||||||
// if the VolumeContext contains "mkfsOptions", use those as args instead
|
// if the VolumeContext contains "mkfsOptions", use those as args instead
|
||||||
volumeCtx := req.GetVolumeContext()
|
volumeCtx := req.GetVolumeContext()
|
||||||
@ -1292,8 +1294,8 @@ func (ns *NodeServer) processEncryptedDevice(
|
|||||||
// argument. In case it is supported, return true.
|
// argument. In case it is supported, return true.
|
||||||
func (ns *NodeServer) xfsSupportsReflink() bool {
|
func (ns *NodeServer) xfsSupportsReflink() bool {
|
||||||
// return cached value, if set
|
// return cached value, if set
|
||||||
if xfsHasReflink != xfsReflinkUnset {
|
if ns.xfsHasReflinkSupport != featureFlagUnset {
|
||||||
return xfsHasReflink == xfsReflinkSupport
|
return ns.xfsHasReflinkSupport == featureFlagSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
// run mkfs.xfs in the same namespace as formatting would be done in
|
// run mkfs.xfs in the same namespace as formatting would be done in
|
||||||
@ -1303,17 +1305,65 @@ func (ns *NodeServer) xfsSupportsReflink() bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// mkfs.xfs should fail with an error message (and help text)
|
// mkfs.xfs should fail with an error message (and help text)
|
||||||
if strings.Contains(string(out), "reflink=0|1") {
|
if strings.Contains(string(out), "reflink=0|1") {
|
||||||
xfsHasReflink = xfsReflinkSupport
|
ns.xfsHasReflinkSupport = featureFlagSupport
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xfsHasReflink = xfsReflinkNoSupport
|
ns.xfsHasReflinkSupport = featureFlagNoSupport
|
||||||
|
|
||||||
return false
|
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 ns.ext4HasPrezeroedSupport != featureFlagUnset {
|
||||||
|
return ns.ext4HasPrezeroedSupport == featureFlagSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = file.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",
|
||||||
|
"-Eassume_storage_prezeroed=1",
|
||||||
|
tempImgFile.Name(),
|
||||||
|
).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(string(out), "Bad option(s) specified") {
|
||||||
|
ns.ext4HasPrezeroedSupport = featureFlagNoSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
ns.ext4HasPrezeroedSupport = featureFlagSupport
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// NodeGetVolumeStats returns volume stats.
|
// NodeGetVolumeStats returns volume stats.
|
||||||
func (ns *NodeServer) NodeGetVolumeStats(
|
func (ns *NodeServer) NodeGetVolumeStats(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -1397,3 +1447,25 @@ func getDeviceSize(ctx context.Context, devicePath string) (uint64, error) {
|
|||||||
|
|
||||||
return size, nil
|
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("unknown fsType: %q, using default mkfs options", fsType)
|
||||||
|
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -52,3 +52,20 @@ func CreateTempFile(prefix, contents string) (*os.File, error) {
|
|||||||
|
|
||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
@ -98,3 +98,45 @@ func TestCreateTempFile_WithLargeContent(t *testing.T) {
|
|||||||
t.Fatalf("Content mismatch: got %v, want %v", string(readContent), content)
|
t.Fatalf("Content mismatch: got %v, want %v", string(readContent), content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateSparseFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sizeMB int64
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"WithValidSize", 10, false},
|
||||||
|
{"WithZeroSize", 0, true},
|
||||||
|
{"WithNegativeSize", -1, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
file, err := os.CreateTemp(t.TempDir(), "test-sparse-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CreateSparseFile(file, tt.sizeMB)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
fileInfo, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSize := tt.sizeMB * 1024 * 1024
|
||||||
|
if fileInfo.Size() != expectedSize {
|
||||||
|
t.Fatalf("Size mismatch: got %v, want %v", fileInfo.Size(), expectedSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user