diff --git a/plugins/inputs/diskio/diskio.go b/plugins/inputs/diskio/diskio.go index a5048dd35dfac..b9e5d870598e2 100644 --- a/plugins/inputs/diskio/diskio.go +++ b/plugins/inputs/diskio/diskio.go @@ -4,6 +4,8 @@ package diskio import ( _ "embed" "fmt" + "os" + "path/filepath" "regexp" "strings" "time" @@ -38,6 +40,9 @@ type DiskIO struct { warnDiskTags map[string]bool lastIOCounterStat map[string]disk.IOCountersStat lastCollectTime time.Time + devPath string + runPath string + sysPath string } func (*DiskIO) SampleConfig() string { @@ -60,6 +65,30 @@ func (d *DiskIO) Init() error { d.warnDiskTags = make(map[string]bool) d.lastIOCounterStat = make(map[string]disk.IOCountersStat) + d.devPath = string(os.PathSeparator) + "dev" + d.runPath = string(os.PathSeparator) + "run" + d.sysPath = string(os.PathSeparator) + "sys" + + if prefix := os.Getenv("HOST_MOUNT_PREFIX"); prefix != "" { + d.devPath = filepath.Join(prefix, "dev") + d.runPath = filepath.Join(prefix, "run") + d.sysPath = filepath.Join(prefix, "sys") + } + if root := os.Getenv("HOST_ROOT"); root != "" { + d.devPath = filepath.Join(root, "dev") + d.runPath = filepath.Join(root, "run") + d.sysPath = filepath.Join(root, "sys") + } + if devPath := os.Getenv("HOST_DEV"); devPath != "" { + d.devPath = devPath + } + if runPath := os.Getenv("HOST_RUN"); runPath != "" { + d.runPath = runPath + } + if sysPath := os.Getenv("HOST_SYS"); sysPath != "" { + d.sysPath = sysPath + } + return nil } @@ -67,7 +96,7 @@ func (d *DiskIO) Gather(acc telegraf.Accumulator) error { var devices []string if d.deviceFilter == nil { for _, dev := range d.Devices { - devices = append(devices, resolveName(dev)) + devices = append(devices, d.resolveName(dev)) } } @@ -86,7 +115,7 @@ func (d *DiskIO) Gather(acc telegraf.Accumulator) error { var devLinks []string tags["name"], devLinks = d.diskName(io.Name) - if wwid := getDeviceWWID(io.Name); wwid != "" { + if wwid := d.getDeviceWWID(io.Name); wwid != "" { tags["wwid"] = wwid } diff --git a/plugins/inputs/diskio/diskio_linux.go b/plugins/inputs/diskio/diskio_linux.go index 1c0db61466f83..abe650395e72b 100644 --- a/plugins/inputs/diskio/diskio_linux.go +++ b/plugins/inputs/diskio/diskio_linux.go @@ -22,7 +22,7 @@ type diskInfoCache struct { func (d *DiskIO) diskInfo(devName string) (map[string]string, error) { // Check if the device exists - path := "/dev/" + devName + path := fmt.Sprintf("%s/%s", d.devPath, devName) var stat unix.Stat_t if err := unix.Stat(path, &stat); err != nil { return nil, fmt.Errorf("error reading %s: %w", path, err) @@ -43,10 +43,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) { } else { major := unix.Major(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures minor := unix.Minor(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures - udevDataPath = fmt.Sprintf("/run/udev/data/b%d:%d", major, minor) + udevDataPath = fmt.Sprintf("%s/udev/data/b%d:%d", d.runPath, major, minor) if _, err := os.Stat(udevDataPath); err != nil { // This path failed, try the fallback .udev style (non-systemd) - udevDataPath = "/dev/.udev/db/block:" + devName + udevDataPath = fmt.Sprintf("%s/.udev/db/block:%s", d.devPath, devName) if _, err := os.Stat(udevDataPath); err != nil { // Giving up, cannot retrieve disk info return nil, fmt.Errorf("error reading %s: %w", udevDataPath, err) @@ -66,10 +66,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) { // This allows us to also "poison" it during test scenarios sysBlockPath = ic.sysBlockPath } else { - sysBlockPath = "/sys/class/block/" + devName + sysBlockPath = fmt.Sprintf("%s/class/block/%s", d.sysPath, devName) } - devInfo, err := readDevData(sysBlockPath) + devInfo, err := readDevData(sysBlockPath, d.sysPath) if err == nil { for k, v := range devInfo { info[k] = v @@ -81,6 +81,7 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) { d.infoCache[devName] = diskInfoCache{ modifiedAt: stat.Mtim.Nano(), udevDataPath: udevDataPath, + sysBlockPath: sysBlockPath, values: info, } @@ -128,7 +129,7 @@ func readUdevData(path string) (map[string]string, error) { return info, nil } -func readDevData(path string) (map[string]string, error) { +func readDevData(path, sysPath string) (map[string]string, error) { // Open the file and read line-wise f, err := os.Open(filepath.Join(path, "uevent")) if err != nil { @@ -158,14 +159,14 @@ func readDevData(path string) (map[string]string, error) { // Find the DEVPATH property if devlnk, err := filepath.EvalSymlinks(filepath.Join(path, "device")); err == nil { devlnk = filepath.Join(devlnk, filepath.Base(path)) - devlnk = strings.TrimPrefix(devlnk, "/sys") + devlnk = strings.TrimPrefix(devlnk, sysPath) info["DEVPATH"] = devlnk } return info, nil } -func resolveName(name string) string { +func (d *DiskIO) resolveName(name string) string { resolved, err := filepath.EvalSymlinks(name) if err == nil { return resolved @@ -173,8 +174,8 @@ func resolveName(name string) string { if !errors.Is(err, fs.ErrNotExist) { return name } - // Try to prepend "/dev" - resolved, err = filepath.EvalSymlinks("/dev/" + name) + // Try to resolve relative to the host device path. + resolved, err = filepath.EvalSymlinks(fmt.Sprintf("%s/%s", d.devPath, name)) if err != nil { return name } @@ -182,8 +183,8 @@ func resolveName(name string) string { return resolved } -func getDeviceWWID(name string) string { - path := fmt.Sprintf("/sys/block/%s/wwid", filepath.Base(name)) +func (d *DiskIO) getDeviceWWID(name string) string { + path := fmt.Sprintf("%s/block/%s/wwid", d.sysPath, filepath.Base(name)) buf, err := os.ReadFile(path) if err != nil { return "" diff --git a/plugins/inputs/diskio/diskio_linux_test.go b/plugins/inputs/diskio/diskio_linux_test.go index dd69cd1c2c884..03f4e7dc69c7d 100644 --- a/plugins/inputs/diskio/diskio_linux_test.go +++ b/plugins/inputs/diskio/diskio_linux_test.go @@ -4,20 +4,21 @@ package diskio import ( "fmt" + "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestDiskInfo(t *testing.T) { - plugin := &DiskIO{ - infoCache: map[string]diskInfoCache{ - "null": { - modifiedAt: 0, - udevDataPath: "testdata/udev.txt", - sysBlockPath: "testdata", - values: map[string]string{}, - }, + plugin := &DiskIO{} + require.NoError(t, plugin.Init()) + plugin.infoCache = map[string]diskInfoCache{ + "null": { + modifiedAt: 0, + udevDataPath: "testdata/udev.txt", + sysBlockPath: "testdata", + values: map[string]string{}, }, } @@ -48,15 +49,15 @@ func TestDiskIOStats_diskName(t *testing.T) { for i, tc := range tests { t.Run(fmt.Sprintf("template %d", i), func(t *testing.T) { - plugin := DiskIO{ - NameTemplates: tc.templates, - infoCache: map[string]diskInfoCache{ - "null": { - modifiedAt: 0, - udevDataPath: "testdata/udev.txt", - sysBlockPath: "testdata", - values: map[string]string{}, - }, + plugin := &DiskIO{} + plugin.NameTemplates = tc.templates + require.NoError(t, plugin.Init()) + plugin.infoCache = map[string]diskInfoCache{ + "null": { + modifiedAt: 0, + udevDataPath: "testdata/udev.txt", + sysBlockPath: "testdata", + values: map[string]string{}, }, } name, _ := plugin.diskName("null") @@ -68,17 +69,85 @@ func TestDiskIOStats_diskName(t *testing.T) { // DiskIOStats.diskTags isn't a linux specific function, but dependent // functions are a no-op on non-Linux. func TestDiskIOStats_diskTags(t *testing.T) { - plugin := &DiskIO{ - DeviceTags: []string{"MY_PARAM_2"}, - infoCache: map[string]diskInfoCache{ - "null": { - modifiedAt: 0, - udevDataPath: "testdata/udev.txt", - sysBlockPath: "testdata", - values: map[string]string{}, - }, + plugin := &DiskIO{} + plugin.DeviceTags = []string{"MY_PARAM_2"} + require.NoError(t, plugin.Init()) + plugin.infoCache = map[string]diskInfoCache{ + "null": { + modifiedAt: 0, + udevDataPath: "testdata/udev.txt", + sysBlockPath: "testdata", + values: map[string]string{}, }, } dt := plugin.diskTags("null") require.Equal(t, map[string]string{"MY_PARAM_2": "myval2"}, dt) } + +func TestDiskInfoHonorsHostDev(t *testing.T) { + t.Setenv("HOST_DEV", filepath.Join("testdata", "hostfs", "dev")) + + plugin := &DiskIO{} + require.NoError(t, plugin.Init()) + plugin.infoCache = map[string]diskInfoCache{ + "mockdev": { + modifiedAt: 0, + udevDataPath: "testdata/udev.txt", + values: map[string]string{}, + }, + } + di, err := plugin.diskInfo("mockdev") + require.NoError(t, err) + require.Equal(t, "myval1", di["MY_PARAM_1"]) +} + +func TestDiskInfoHonorsHostPrefixFallbacksForDev(t *testing.T) { + tests := []struct { + name string + env map[string]string + }{ + { + name: "host root fallback", + env: map[string]string{ + "HOST_ROOT": filepath.Join("testdata", "hostfs"), + "HOST_MOUNT_PREFIX": filepath.Join("testdata", "hostfs", "unused"), + }, + }, + { + name: "host mount prefix fallback", + env: map[string]string{ + "HOST_MOUNT_PREFIX": filepath.Join("testdata", "hostfs"), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for key, value := range tc.env { + t.Setenv(key, value) + } + + plugin := &DiskIO{} + require.NoError(t, plugin.Init()) + plugin.infoCache = map[string]diskInfoCache{ + "mockdev": { + modifiedAt: 0, + udevDataPath: "testdata/udev.txt", + values: map[string]string{}, + }, + } + + di, err := plugin.diskInfo("mockdev") + require.NoError(t, err) + require.Equal(t, "myval1", di["MY_PARAM_1"]) + }) + } +} + +func TestGetDeviceWWIDHonorsHostSys(t *testing.T) { + t.Setenv("HOST_SYS", filepath.Join("testdata", "hostfs", "sys")) + + plugin := &DiskIO{} + require.NoError(t, plugin.Init()) + require.Equal(t, "my-wwid", plugin.getDeviceWWID("sda")) +} diff --git a/plugins/inputs/diskio/diskio_other.go b/plugins/inputs/diskio/diskio_other.go index 44507ae3f45da..ab8936bf452ca 100644 --- a/plugins/inputs/diskio/diskio_other.go +++ b/plugins/inputs/diskio/diskio_other.go @@ -8,10 +8,10 @@ func (*DiskIO) diskInfo(_ string) (map[string]string, error) { return nil, nil } -func resolveName(name string) string { +func (*DiskIO) resolveName(name string) string { return name } -func getDeviceWWID(_ string) string { +func (*DiskIO) getDeviceWWID(_ string) string { return "" } diff --git a/plugins/inputs/diskio/testdata/hostfs/dev/mockdev b/plugins/inputs/diskio/testdata/hostfs/dev/mockdev new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/inputs/diskio/testdata/hostfs/sys/block/sda/wwid b/plugins/inputs/diskio/testdata/hostfs/sys/block/sda/wwid new file mode 100644 index 0000000000000..e02e65b091cae --- /dev/null +++ b/plugins/inputs/diskio/testdata/hostfs/sys/block/sda/wwid @@ -0,0 +1 @@ +my-wwid