Skip to content

Commit 3c65601

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

6 files changed

Lines changed: 183 additions & 42 deletions

File tree

plugins/inputs/diskio/diskio.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package diskio
44
import (
55
_ "embed"
66
"fmt"
7+
"os"
8+
"path/filepath"
79
"regexp"
810
"strings"
911
"time"
@@ -38,6 +40,9 @@ type DiskIO struct {
3840
warnDiskTags map[string]bool
3941
lastIOCounterStat map[string]disk.IOCountersStat
4042
lastCollectTime time.Time
43+
devPath string
44+
runPath string
45+
sysPath string
4146
}
4247

4348
func (*DiskIO) SampleConfig() string {
@@ -60,14 +65,38 @@ func (d *DiskIO) Init() error {
6065
d.warnDiskTags = make(map[string]bool)
6166
d.lastIOCounterStat = make(map[string]disk.IOCountersStat)
6267

68+
d.devPath = string(os.PathSeparator) + "dev"
69+
d.runPath = string(os.PathSeparator) + "run"
70+
d.sysPath = string(os.PathSeparator) + "sys"
71+
72+
if prefix := os.Getenv("HOST_MOUNT_PREFIX"); prefix != "" {
73+
d.devPath = filepath.Join(prefix, "dev")
74+
d.runPath = filepath.Join(prefix, "run")
75+
d.sysPath = filepath.Join(prefix, "sys")
76+
}
77+
if root := os.Getenv("HOST_ROOT"); root != "" {
78+
d.devPath = filepath.Join(root, "dev")
79+
d.runPath = filepath.Join(root, "run")
80+
d.sysPath = filepath.Join(root, "sys")
81+
}
82+
if devPath := os.Getenv("HOST_DEV"); devPath != "" {
83+
d.devPath = devPath
84+
}
85+
if runPath := os.Getenv("HOST_RUN"); runPath != "" {
86+
d.runPath = runPath
87+
}
88+
if sysPath := os.Getenv("HOST_SYS"); sysPath != "" {
89+
d.sysPath = sysPath
90+
}
91+
6392
return nil
6493
}
6594

6695
func (d *DiskIO) Gather(acc telegraf.Accumulator) error {
6796
var devices []string
6897
if d.deviceFilter == nil {
6998
for _, dev := range d.Devices {
70-
devices = append(devices, resolveName(dev))
99+
devices = append(devices, d.resolveName(dev))
71100
}
72101
}
73102

@@ -86,7 +115,7 @@ func (d *DiskIO) Gather(acc telegraf.Accumulator) error {
86115
var devLinks []string
87116
tags["name"], devLinks = d.diskName(io.Name)
88117

89-
if wwid := getDeviceWWID(io.Name); wwid != "" {
118+
if wwid := d.getDeviceWWID(io.Name); wwid != "" {
90119
tags["wwid"] = wwid
91120
}
92121

plugins/inputs/diskio/diskio_linux.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type diskInfoCache struct {
2222

2323
func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
2424
// Check if the device exists
25-
path := "/dev/" + devName
25+
path := fmt.Sprintf("%s/%s", d.devPath, devName)
2626
var stat unix.Stat_t
2727
if err := unix.Stat(path, &stat); err != nil {
2828
return nil, fmt.Errorf("error reading %s: %w", path, err)
@@ -43,10 +43,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
4343
} else {
4444
major := unix.Major(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures
4545
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)
46+
udevDataPath = fmt.Sprintf("%s/udev/data/b%d:%d", d.runPath, major, minor)
4747
if _, err := os.Stat(udevDataPath); err != nil {
4848
// This path failed, try the fallback .udev style (non-systemd)
49-
udevDataPath = "/dev/.udev/db/block:" + devName
49+
udevDataPath = fmt.Sprintf("%s/.udev/db/block:%s", d.devPath, devName)
5050
if _, err := os.Stat(udevDataPath); err != nil {
5151
// Giving up, cannot retrieve disk info
5252
return nil, fmt.Errorf("error reading %s: %w", udevDataPath, err)
@@ -66,10 +66,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
6666
// This allows us to also "poison" it during test scenarios
6767
sysBlockPath = ic.sysBlockPath
6868
} else {
69-
sysBlockPath = "/sys/class/block/" + devName
69+
sysBlockPath = fmt.Sprintf("%s/class/block/%s", d.sysPath, devName)
7070
}
7171

72-
devInfo, err := readDevData(sysBlockPath)
72+
devInfo, err := readDevData(sysBlockPath, d.sysPath)
7373
if err == nil {
7474
for k, v := range devInfo {
7575
info[k] = v
@@ -81,6 +81,7 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
8181
d.infoCache[devName] = diskInfoCache{
8282
modifiedAt: stat.Mtim.Nano(),
8383
udevDataPath: udevDataPath,
84+
sysBlockPath: sysBlockPath,
8485
values: info,
8586
}
8687

@@ -128,7 +129,7 @@ func readUdevData(path string) (map[string]string, error) {
128129
return info, nil
129130
}
130131

131-
func readDevData(path string) (map[string]string, error) {
132+
func readDevData(path, sysPath string) (map[string]string, error) {
132133
// Open the file and read line-wise
133134
f, err := os.Open(filepath.Join(path, "uevent"))
134135
if err != nil {
@@ -158,32 +159,32 @@ func readDevData(path string) (map[string]string, error) {
158159
// Find the DEVPATH property
159160
if devlnk, err := filepath.EvalSymlinks(filepath.Join(path, "device")); err == nil {
160161
devlnk = filepath.Join(devlnk, filepath.Base(path))
161-
devlnk = strings.TrimPrefix(devlnk, "/sys")
162+
devlnk = strings.TrimPrefix(devlnk, sysPath)
162163
info["DEVPATH"] = devlnk
163164
}
164165

165166
return info, nil
166167
}
167168

168-
func resolveName(name string) string {
169+
func (d *DiskIO) resolveName(name string) string {
169170
resolved, err := filepath.EvalSymlinks(name)
170171
if err == nil {
171172
return resolved
172173
}
173174
if !errors.Is(err, fs.ErrNotExist) {
174175
return name
175176
}
176-
// Try to prepend "/dev"
177-
resolved, err = filepath.EvalSymlinks("/dev/" + name)
177+
// Try to resolve relative to the host device path.
178+
resolved, err = filepath.EvalSymlinks(fmt.Sprintf("%s/%s", d.devPath, name))
178179
if err != nil {
179180
return name
180181
}
181182

182183
return resolved
183184
}
184185

185-
func getDeviceWWID(name string) string {
186-
path := fmt.Sprintf("/sys/block/%s/wwid", filepath.Base(name))
186+
func (d *DiskIO) getDeviceWWID(name string) string {
187+
path := fmt.Sprintf("%s/block/%s/wwid", d.sysPath, filepath.Base(name))
187188
buf, err := os.ReadFile(path)
188189
if err != nil {
189190
return ""

plugins/inputs/diskio/diskio_linux_test.go

Lines changed: 136 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,41 @@ package diskio
44

55
import (
66
"fmt"
7+
"path/filepath"
78
"testing"
89

910
"github.com/stretchr/testify/require"
11+
"golang.org/x/sys/unix"
1012
)
1113

14+
func clearHostEnv(t *testing.T) {
15+
t.Helper()
16+
17+
t.Setenv("HOST_DEV", "")
18+
t.Setenv("HOST_RUN", "")
19+
t.Setenv("HOST_SYS", "")
20+
t.Setenv("HOST_ROOT", "")
21+
t.Setenv("HOST_MOUNT_PREFIX", "")
22+
}
23+
24+
func newPlugin(t *testing.T) *DiskIO {
25+
t.Helper()
26+
27+
plugin := &DiskIO{}
28+
require.NoError(t, plugin.Init())
29+
return plugin
30+
}
31+
1232
func TestDiskInfo(t *testing.T) {
13-
plugin := &DiskIO{
14-
infoCache: map[string]diskInfoCache{
15-
"null": {
16-
modifiedAt: 0,
17-
udevDataPath: "testdata/udev.txt",
18-
sysBlockPath: "testdata",
19-
values: map[string]string{},
20-
},
33+
clearHostEnv(t)
34+
35+
plugin := newPlugin(t)
36+
plugin.infoCache = map[string]diskInfoCache{
37+
"null": {
38+
modifiedAt: 0,
39+
udevDataPath: "testdata/udev.txt",
40+
sysBlockPath: "testdata",
41+
values: map[string]string{},
2142
},
2243
}
2344

@@ -48,15 +69,16 @@ func TestDiskIOStats_diskName(t *testing.T) {
4869

4970
for i, tc := range tests {
5071
t.Run(fmt.Sprintf("template %d", i), func(t *testing.T) {
51-
plugin := DiskIO{
52-
NameTemplates: tc.templates,
53-
infoCache: map[string]diskInfoCache{
54-
"null": {
55-
modifiedAt: 0,
56-
udevDataPath: "testdata/udev.txt",
57-
sysBlockPath: "testdata",
58-
values: map[string]string{},
59-
},
72+
clearHostEnv(t)
73+
74+
plugin := newPlugin(t)
75+
plugin.NameTemplates = tc.templates
76+
plugin.infoCache = map[string]diskInfoCache{
77+
"null": {
78+
modifiedAt: 0,
79+
udevDataPath: "testdata/udev.txt",
80+
sysBlockPath: "testdata",
81+
values: map[string]string{},
6082
},
6183
}
6284
name, _ := plugin.diskName("null")
@@ -68,17 +90,105 @@ func TestDiskIOStats_diskName(t *testing.T) {
6890
// DiskIOStats.diskTags isn't a linux specific function, but dependent
6991
// functions are a no-op on non-Linux.
7092
func TestDiskIOStats_diskTags(t *testing.T) {
71-
plugin := &DiskIO{
72-
DeviceTags: []string{"MY_PARAM_2"},
73-
infoCache: map[string]diskInfoCache{
74-
"null": {
75-
modifiedAt: 0,
76-
udevDataPath: "testdata/udev.txt",
77-
sysBlockPath: "testdata",
78-
values: map[string]string{},
79-
},
93+
clearHostEnv(t)
94+
95+
plugin := newPlugin(t)
96+
plugin.DeviceTags = []string{"MY_PARAM_2"}
97+
plugin.infoCache = map[string]diskInfoCache{
98+
"null": {
99+
modifiedAt: 0,
100+
udevDataPath: "testdata/udev.txt",
101+
sysBlockPath: "testdata",
102+
values: map[string]string{},
80103
},
81104
}
82105
dt := plugin.diskTags("null")
83106
require.Equal(t, map[string]string{"MY_PARAM_2": "myval2"}, dt)
84107
}
108+
109+
func TestDiskInfoHonorsHostDev(t *testing.T) {
110+
hostRoot := filepath.Join("testdata", "hostfs")
111+
t.Setenv("HOST_DEV", filepath.Join(hostRoot, "dev"))
112+
t.Setenv("HOST_RUN", "")
113+
t.Setenv("HOST_SYS", "")
114+
t.Setenv("HOST_ROOT", "")
115+
t.Setenv("HOST_MOUNT_PREFIX", "")
116+
117+
plugin := newPlugin(t)
118+
plugin.infoCache = map[string]diskInfoCache{
119+
"mockdev": {
120+
modifiedAt: 0,
121+
udevDataPath: "testdata/udev.txt",
122+
values: map[string]string{},
123+
},
124+
}
125+
di, err := plugin.diskInfo("mockdev")
126+
require.NoError(t, err)
127+
require.Equal(t, "myval1", di["MY_PARAM_1"])
128+
}
129+
130+
func TestDiskInfoHonorsHostPrefixFallbacksForDev(t *testing.T) {
131+
tests := []struct {
132+
name string
133+
setupEnv func(*testing.T, string)
134+
createDev func(string) string
135+
}{
136+
{
137+
name: "host root fallback",
138+
setupEnv: func(t *testing.T, hostRoot string) {
139+
t.Setenv("HOST_DEV", "")
140+
t.Setenv("HOST_RUN", "")
141+
t.Setenv("HOST_SYS", "")
142+
t.Setenv("HOST_ROOT", hostRoot)
143+
t.Setenv("HOST_MOUNT_PREFIX", filepath.Join(hostRoot, "unused"))
144+
},
145+
createDev: func(hostRoot string) string { return filepath.Join(hostRoot, "dev") },
146+
},
147+
{
148+
name: "host mount prefix fallback",
149+
setupEnv: func(t *testing.T, hostRoot string) {
150+
t.Setenv("HOST_DEV", "")
151+
t.Setenv("HOST_RUN", "")
152+
t.Setenv("HOST_SYS", "")
153+
t.Setenv("HOST_ROOT", "")
154+
t.Setenv("HOST_MOUNT_PREFIX", hostRoot)
155+
},
156+
createDev: func(hostRoot string) string { return filepath.Join(hostRoot, "dev") },
157+
},
158+
}
159+
160+
for _, tc := range tests {
161+
t.Run(tc.name, func(t *testing.T) {
162+
hostRoot := filepath.Join("testdata", "hostfs")
163+
devPath := filepath.Join(tc.createDev(hostRoot), "mockdev")
164+
165+
var stat unix.Stat_t
166+
require.NoError(t, unix.Stat(devPath, &stat))
167+
tc.setupEnv(t, hostRoot)
168+
169+
plugin := newPlugin(t)
170+
plugin.infoCache = map[string]diskInfoCache{
171+
"mockdev": {
172+
modifiedAt: stat.Mtim.Nano(),
173+
values: map[string]string{"cached": "1"},
174+
},
175+
}
176+
177+
di, err := plugin.diskInfo("mockdev")
178+
require.NoError(t, err)
179+
require.Equal(t, "1", di["cached"])
180+
})
181+
}
182+
}
183+
184+
func TestGetDeviceWWIDHonorsHostSys(t *testing.T) {
185+
hostSys := filepath.Join("testdata", "hostfs", "sys")
186+
t.Setenv("HOST_DEV", "")
187+
t.Setenv("HOST_RUN", "")
188+
t.Setenv("HOST_SYS", hostSys)
189+
t.Setenv("HOST_ROOT", "")
190+
t.Setenv("HOST_MOUNT_PREFIX", "")
191+
192+
plugin := newPlugin(t)
193+
require.Equal(t, "my-wwid", plugin.getDeviceWWID("sda"))
194+
}

plugins/inputs/diskio/diskio_other.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ func (*DiskIO) diskInfo(_ string) (map[string]string, error) {
88
return nil, nil
99
}
1010

11-
func resolveName(name string) string {
11+
func (*DiskIO) resolveName(name string) string {
1212
return name
1313
}
1414

15-
func getDeviceWWID(_ string) string {
15+
func (*DiskIO) getDeviceWWID(_ string) string {
1616
return ""
1717
}

plugins/inputs/diskio/testdata/hostfs/dev/mockdev

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
my-wwid

0 commit comments

Comments
 (0)