Skip to content

Commit c9ebb0e

Browse files
committed
fix(inputs.diskio): honor host path environment variables on linux
1 parent 81815e3 commit c9ebb0e

2 files changed

Lines changed: 136 additions & 7 deletions

File tree

plugins/inputs/diskio/diskio_linux.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,34 @@ type diskInfoCache struct {
2020
values map[string]string
2121
}
2222

23+
func getHostPath(envVar, fallback string) string {
24+
if path := os.Getenv(envVar); path != "" {
25+
return path
26+
}
27+
if root := os.Getenv("HOST_ROOT"); root != "" {
28+
return filepath.Join(root, fallback)
29+
}
30+
if prefix := os.Getenv("HOST_MOUNT_PREFIX"); prefix != "" {
31+
return filepath.Join(prefix, fallback)
32+
}
33+
return filepath.Join("/", fallback)
34+
}
35+
36+
func getDevPath() string {
37+
return getHostPath("HOST_DEV", "dev")
38+
}
39+
40+
func getRunPath() string {
41+
return getHostPath("HOST_RUN", "run")
42+
}
43+
44+
func getSysPath() string {
45+
return getHostPath("HOST_SYS", "sys")
46+
}
47+
2348
func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
2449
// Check if the device exists
25-
path := "/dev/" + devName
50+
path := filepath.Join(getDevPath(), devName)
2651
var stat unix.Stat_t
2752
if err := unix.Stat(path, &stat); err != nil {
2853
return nil, fmt.Errorf("error reading %s: %w", path, err)
@@ -43,10 +68,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
4368
} else {
4469
major := unix.Major(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures
4570
minor := unix.Minor(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures
46-
udevDataPath = fmt.Sprintf("/run/udev/data/b%d:%d", major, minor)
71+
udevDataPath = filepath.Join(getRunPath(), "udev", "data", fmt.Sprintf("b%d:%d", major, minor))
4772
if _, err := os.Stat(udevDataPath); err != nil {
4873
// This path failed, try the fallback .udev style (non-systemd)
49-
udevDataPath = "/dev/.udev/db/block:" + devName
74+
udevDataPath = filepath.Join(getDevPath(), ".udev", "db", "block:"+devName)
5075
if _, err := os.Stat(udevDataPath); err != nil {
5176
// Giving up, cannot retrieve disk info
5277
return nil, fmt.Errorf("error reading %s: %w", udevDataPath, err)
@@ -66,7 +91,7 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
6691
// This allows us to also "poison" it during test scenarios
6792
sysBlockPath = ic.sysBlockPath
6893
} else {
69-
sysBlockPath = "/sys/class/block/" + devName
94+
sysBlockPath = filepath.Join(getSysPath(), "class", "block", devName)
7095
}
7196

7297
devInfo, err := readDevData(sysBlockPath)
@@ -81,6 +106,7 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
81106
d.infoCache[devName] = diskInfoCache{
82107
modifiedAt: stat.Mtim.Nano(),
83108
udevDataPath: udevDataPath,
109+
sysBlockPath: sysBlockPath,
84110
values: info,
85111
}
86112

@@ -158,7 +184,11 @@ func readDevData(path string) (map[string]string, error) {
158184
// Find the DEVPATH property
159185
if devlnk, err := filepath.EvalSymlinks(filepath.Join(path, "device")); err == nil {
160186
devlnk = filepath.Join(devlnk, filepath.Base(path))
187+
devlnk = strings.TrimPrefix(devlnk, getSysPath())
161188
devlnk = strings.TrimPrefix(devlnk, "/sys")
189+
if !strings.HasPrefix(devlnk, "/") {
190+
devlnk = "/" + devlnk
191+
}
162192
info["DEVPATH"] = devlnk
163193
}
164194

@@ -173,8 +203,14 @@ func resolveName(name string) string {
173203
if !errors.Is(err, fs.ErrNotExist) {
174204
return name
175205
}
176-
// Try to prepend "/dev"
177-
resolved, err = filepath.EvalSymlinks("/dev/" + name)
206+
// Try to resolve relative to the host device path.
207+
switch {
208+
case strings.HasPrefix(name, "/dev/"):
209+
name = strings.TrimPrefix(name, "/dev/")
210+
case strings.HasPrefix(name, "/"):
211+
name = strings.TrimPrefix(name, "/")
212+
}
213+
resolved, err = filepath.EvalSymlinks(filepath.Join(getDevPath(), name))
178214
if err != nil {
179215
return name
180216
}
@@ -183,7 +219,7 @@ func resolveName(name string) string {
183219
}
184220

185221
func getDeviceWWID(name string) string {
186-
path := fmt.Sprintf("/sys/block/%s/wwid", filepath.Base(name))
222+
path := filepath.Join(getSysPath(), "block", filepath.Base(name), "wwid")
187223
buf, err := os.ReadFile(path)
188224
if err != nil {
189225
return ""

plugins/inputs/diskio/diskio_linux_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ package diskio
44

55
import (
66
"fmt"
7+
"os"
8+
"path/filepath"
79
"testing"
810

911
"github.com/stretchr/testify/require"
12+
"golang.org/x/sys/unix"
1013
)
1114

1215
func TestDiskInfo(t *testing.T) {
@@ -82,3 +85,93 @@ func TestDiskIOStats_diskTags(t *testing.T) {
8285
dt := plugin.diskTags("null")
8386
require.Equal(t, map[string]string{"MY_PARAM_2": "myval2"}, dt)
8487
}
88+
89+
func TestDiskInfoHonorsHostDevAndRun(t *testing.T) {
90+
hostRoot := t.TempDir()
91+
devPath := filepath.Join(hostRoot, "dev", "mockdev")
92+
require.NoError(t, os.MkdirAll(filepath.Dir(devPath), 0o755))
93+
require.NoError(t, os.WriteFile(devPath, []byte(""), 0o600))
94+
95+
// Stat'ing a regular file results in major/minor being 0:0, so diskInfo()
96+
// should look for udev data in b0:0.
97+
udevDataPath := filepath.Join(hostRoot, "run", "udev", "data", "b0:0")
98+
require.NoError(t, os.MkdirAll(filepath.Dir(udevDataPath), 0o755))
99+
require.NoError(t, os.WriteFile(udevDataPath, []byte("E:MY_PARAM_1=myval1\n"), 0o600))
100+
101+
t.Setenv("HOST_DEV", filepath.Join(hostRoot, "dev"))
102+
t.Setenv("HOST_RUN", filepath.Join(hostRoot, "run"))
103+
t.Setenv("HOST_SYS", filepath.Join(hostRoot, "sys"))
104+
t.Setenv("HOST_ROOT", "")
105+
t.Setenv("HOST_MOUNT_PREFIX", "")
106+
107+
plugin := &DiskIO{infoCache: map[string]diskInfoCache{}}
108+
di, err := plugin.diskInfo("mockdev")
109+
require.NoError(t, err)
110+
require.Equal(t, "myval1", di["MY_PARAM_1"])
111+
}
112+
113+
func TestDiskInfoHonorsHostPrefixFallbacksForDev(t *testing.T) {
114+
tests := []struct {
115+
name string
116+
setupEnv func(*testing.T, string)
117+
createDev func(string) string
118+
}{
119+
{
120+
name: "host root fallback",
121+
setupEnv: func(t *testing.T, hostRoot string) {
122+
t.Setenv("HOST_DEV", "")
123+
t.Setenv("HOST_ROOT", hostRoot)
124+
t.Setenv("HOST_MOUNT_PREFIX", filepath.Join(hostRoot, "unused"))
125+
},
126+
createDev: func(hostRoot string) string { return filepath.Join(hostRoot, "dev") },
127+
},
128+
{
129+
name: "host mount prefix fallback",
130+
setupEnv: func(t *testing.T, hostRoot string) {
131+
t.Setenv("HOST_DEV", "")
132+
t.Setenv("HOST_ROOT", "")
133+
t.Setenv("HOST_MOUNT_PREFIX", hostRoot)
134+
},
135+
createDev: func(hostRoot string) string { return filepath.Join(hostRoot, "dev") },
136+
},
137+
}
138+
139+
for _, tc := range tests {
140+
t.Run(tc.name, func(t *testing.T) {
141+
hostRoot := t.TempDir()
142+
devPath := filepath.Join(tc.createDev(hostRoot), "mockdev")
143+
require.NoError(t, os.MkdirAll(filepath.Dir(devPath), 0o755))
144+
require.NoError(t, os.WriteFile(devPath, []byte(""), 0o600))
145+
146+
var stat unix.Stat_t
147+
require.NoError(t, unix.Stat(devPath, &stat))
148+
tc.setupEnv(t, hostRoot)
149+
150+
plugin := &DiskIO{
151+
infoCache: map[string]diskInfoCache{
152+
"mockdev": {
153+
modifiedAt: stat.Mtim.Nano(),
154+
values: map[string]string{"cached": "1"},
155+
},
156+
},
157+
}
158+
159+
di, err := plugin.diskInfo("mockdev")
160+
require.NoError(t, err)
161+
require.Equal(t, "1", di["cached"])
162+
})
163+
}
164+
}
165+
166+
func TestGetDeviceWWIDHonorsHostSys(t *testing.T) {
167+
hostSys := t.TempDir()
168+
wwidPath := filepath.Join(hostSys, "block", "sda", "wwid")
169+
require.NoError(t, os.MkdirAll(filepath.Dir(wwidPath), 0o755))
170+
require.NoError(t, os.WriteFile(wwidPath, []byte("my-wwid\n"), 0o600))
171+
172+
t.Setenv("HOST_SYS", hostSys)
173+
t.Setenv("HOST_ROOT", "")
174+
t.Setenv("HOST_MOUNT_PREFIX", "")
175+
176+
require.Equal(t, "my-wwid", getDeviceWWID("sda"))
177+
}

0 commit comments

Comments
 (0)