mirror of
https://github.com/henrygd/beszel.git
synced 2026-10-03 06:47:46 +02:00
fix(agent): resolve device symlinks so LVM volumes map to disk I/O (#2486)
This commit is contained in:
@@ -97,21 +97,41 @@ func isDockerSpecialMountpoint(mountpoint string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// evalSymlinks resolves device symlinks; it is a seam so tests can fake the
|
||||||
|
// /dev topology (e.g. /dev/vg/lv -> /dev/dm-N created by udev for LVM).
|
||||||
|
var evalSymlinks = filepath.EvalSymlinks
|
||||||
|
|
||||||
// registerFilesystemStats resolves the tracked key and stats payload for a
|
// registerFilesystemStats resolves the tracked key and stats payload for a
|
||||||
// filesystem before it is inserted into fsStats.
|
// filesystem before it is inserted into fsStats.
|
||||||
func registerFilesystemStats(existing map[string]*system.FsStats, device, mountpoint string, root bool, customName string, ctx fsRegistrationContext) (string, *system.FsStats, bool) {
|
func registerFilesystemStats(existing map[string]*system.FsStats, device, mountpoint string, root bool, customName string, ctx fsRegistrationContext) (string, *system.FsStats, bool) {
|
||||||
key := device
|
key := device
|
||||||
|
resolvedKey := ""
|
||||||
if !ctx.isWindows {
|
if !ctx.isWindows {
|
||||||
key = filepath.Base(device)
|
key = filepath.Base(device)
|
||||||
|
// Device-mapper mounts appear as symlinked paths like /dev/vg/lv whose
|
||||||
|
// base name matches neither the diskstats name (dm-N) nor the dm label
|
||||||
|
// (vg-lv); the resolved target's base is one of those existing names.
|
||||||
|
// Bare names (folder devices, ZFS datasets) are skipped because they
|
||||||
|
// would resolve relative to the agent's working directory.
|
||||||
|
if filepath.IsAbs(device) {
|
||||||
|
if resolved, err := evalSymlinks(device); err == nil {
|
||||||
|
if base := filepath.Base(resolved); base != key {
|
||||||
|
resolvedKey = base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if root {
|
if root {
|
||||||
// Try to map root device to a diskIoCounters entry. First checks for an
|
// Try to map root device to a diskIoCounters entry. First checks for an
|
||||||
// exact key match, then uses findIoDevice for normalized / prefix-based
|
// exact key match, then uses findIoDevice for normalized / prefix-based
|
||||||
// matching (e.g. nda0p2 -> nda0), and finally falls back to FILESYSTEM.
|
// matching (e.g. nda0p2 -> nda0) and the symlink-resolved device name,
|
||||||
|
// and finally falls back to FILESYSTEM.
|
||||||
if _, ioMatch := ctx.diskIoCounters[key]; !ioMatch {
|
if _, ioMatch := ctx.diskIoCounters[key]; !ioMatch {
|
||||||
if matchedKey, match := findIoDevice(key, ctx.diskIoCounters); match {
|
if matchedKey, match := findIoDevice(key, ctx.diskIoCounters); match {
|
||||||
key = matchedKey
|
key = matchedKey
|
||||||
|
} else if matchedKey, match := findIoDevice(resolvedKey, ctx.diskIoCounters); match {
|
||||||
|
key = matchedKey
|
||||||
} else if ctx.filesystem != "" {
|
} else if ctx.filesystem != "" {
|
||||||
if matchedKey, match := findIoDevice(ctx.filesystem, ctx.diskIoCounters); match {
|
if matchedKey, match := findIoDevice(ctx.filesystem, ctx.diskIoCounters); match {
|
||||||
key = matchedKey
|
key = matchedKey
|
||||||
@@ -137,6 +157,8 @@ func registerFilesystemStats(existing map[string]*system.FsStats, device, mountp
|
|||||||
if _, ioMatch = ctx.diskIoCounters[key]; !ioMatch {
|
if _, ioMatch = ctx.diskIoCounters[key]; !ioMatch {
|
||||||
if matchedKey, match := findIoDevice(key, ctx.diskIoCounters); match {
|
if matchedKey, match := findIoDevice(key, ctx.diskIoCounters); match {
|
||||||
key = matchedKey
|
key = matchedKey
|
||||||
|
} else if matchedKey, match := findIoDevice(resolvedKey, ctx.diskIoCounters); match {
|
||||||
|
key = matchedKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,6 +273,175 @@ func TestBuildFsStatRegistration(t *testing.T) {
|
|||||||
assert.Empty(t, key)
|
assert.Empty(t, key)
|
||||||
assert.Nil(t, stats)
|
assert.Nil(t, stats)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("maps lvm symlinked device to io device through resolved name", func(t *testing.T) {
|
||||||
|
setEvalSymlinks(t, func(path string) (string, error) {
|
||||||
|
if path == "/dev/vg1/volume_1" {
|
||||||
|
return "/dev/dm-1", nil
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
key, stats, ok := registerFilesystemStats(
|
||||||
|
map[string]*system.FsStats{},
|
||||||
|
"/dev/vg1/volume_1",
|
||||||
|
"/volume1",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
fsRegistrationContext{
|
||||||
|
isWindows: false,
|
||||||
|
efPath: "/extra-filesystems",
|
||||||
|
diskIoCounters: map[string]disk.IOCountersStat{
|
||||||
|
"dm-0": {Name: "dm-0", Label: "vg1-syno_vg_reserved_area"},
|
||||||
|
"dm-1": {Name: "dm-1", Label: "vg1-volume_1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "dm-1", key)
|
||||||
|
assert.Equal(t, "/volume1", stats.Mountpoint)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("maps lvm device through resolved mapper label", func(t *testing.T) {
|
||||||
|
setEvalSymlinks(t, func(path string) (string, error) {
|
||||||
|
if path == "/dev/vg1/volume_1" {
|
||||||
|
return "/dev/mapper/vg1-volume_1", nil
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
key, _, ok := registerFilesystemStats(
|
||||||
|
map[string]*system.FsStats{},
|
||||||
|
"/dev/vg1/volume_1",
|
||||||
|
"/volume1",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
fsRegistrationContext{
|
||||||
|
isWindows: false,
|
||||||
|
efPath: "/extra-filesystems",
|
||||||
|
diskIoCounters: map[string]disk.IOCountersStat{
|
||||||
|
"dm-1": {Name: "dm-1", Label: "vg1-volume_1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "dm-1", key)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("maps lvm root device through resolved name", func(t *testing.T) {
|
||||||
|
setEvalSymlinks(t, func(path string) (string, error) {
|
||||||
|
if path == "/dev/vg1/volume_1" {
|
||||||
|
return "/dev/dm-1", nil
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
key, stats, ok := registerFilesystemStats(
|
||||||
|
map[string]*system.FsStats{},
|
||||||
|
"/dev/vg1/volume_1",
|
||||||
|
"/",
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
fsRegistrationContext{
|
||||||
|
isWindows: false,
|
||||||
|
efPath: "/extra-filesystems",
|
||||||
|
diskIoCounters: map[string]disk.IOCountersStat{
|
||||||
|
"dm-1": {Name: "dm-1", Label: "vg1-volume_1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "dm-1", key)
|
||||||
|
assert.True(t, stats.Root)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("resolved device wins over filesystem fallback", func(t *testing.T) {
|
||||||
|
setEvalSymlinks(t, func(path string) (string, error) {
|
||||||
|
if path == "/dev/vg1/volume_1" {
|
||||||
|
return "/dev/dm-1", nil
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
key, _, ok := registerFilesystemStats(
|
||||||
|
map[string]*system.FsStats{},
|
||||||
|
"/dev/vg1/volume_1",
|
||||||
|
"/",
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
fsRegistrationContext{
|
||||||
|
filesystem: "sda",
|
||||||
|
isWindows: false,
|
||||||
|
efPath: "/extra-filesystems",
|
||||||
|
diskIoCounters: map[string]disk.IOCountersStat{
|
||||||
|
"dm-1": {Name: "dm-1", Label: "vg1-volume_1"},
|
||||||
|
"sda": {Name: "sda"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "dm-1", key)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("keeps base name when symlink resolution fails", func(t *testing.T) {
|
||||||
|
setEvalSymlinks(t, func(path string) (string, error) {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
})
|
||||||
|
|
||||||
|
key, _, ok := registerFilesystemStats(
|
||||||
|
map[string]*system.FsStats{},
|
||||||
|
"/dev/vg1/volume_1",
|
||||||
|
"/volume1",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
fsRegistrationContext{
|
||||||
|
isWindows: false,
|
||||||
|
efPath: "/extra-filesystems",
|
||||||
|
diskIoCounters: map[string]disk.IOCountersStat{
|
||||||
|
"dm-1": {Name: "dm-1", Label: "vg1-volume_1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "volume_1", key)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("does not resolve symlinks for relative device names", func(t *testing.T) {
|
||||||
|
setEvalSymlinks(t, func(path string) (string, error) {
|
||||||
|
return "/dev/sdb1", nil
|
||||||
|
})
|
||||||
|
|
||||||
|
key, _, ok := registerFilesystemStats(
|
||||||
|
map[string]*system.FsStats{},
|
||||||
|
"data",
|
||||||
|
"/mnt/data",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
fsRegistrationContext{
|
||||||
|
isWindows: false,
|
||||||
|
efPath: "/extra-filesystems",
|
||||||
|
diskIoCounters: map[string]disk.IOCountersStat{
|
||||||
|
"sdb1": {Name: "sdb1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "data", key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// setEvalSymlinks swaps the device-symlink resolver for the duration of a test.
|
||||||
|
func setEvalSymlinks(t *testing.T, fn func(string) (string, error)) {
|
||||||
|
t.Helper()
|
||||||
|
old := evalSymlinks
|
||||||
|
evalSymlinks = fn
|
||||||
|
t.Cleanup(func() { evalSymlinks = old })
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddConfiguredRootFs(t *testing.T) {
|
func TestAddConfiguredRootFs(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user