Skip to content

Commit 96e26ef

Browse files
inqrphlAhmet Oeztuerk
andauthored
Check drivesize, ignore drive not found errors if empty-state is set (#315)
Co-authored-by: Ahmet Oeztuerk <[email protected]>
1 parent 676319d commit 96e26ef

File tree

5 files changed

+146
-25
lines changed

5 files changed

+146
-25
lines changed

pkg/snclient/check_drivesize.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package snclient
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"maps"
78
"sort"
@@ -151,6 +152,7 @@ func (l *CheckDrivesize) Build() *CheckData {
151152
}
152153
}
153154

155+
//nolint:funlen // no need to split the function, it is simple as is
154156
func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData, _ []Argument) (*CheckResult, error) {
155157
enabled, _, _ := snc.config.Section("/modules").GetBool("CheckDisk")
156158
if !enabled {
@@ -164,14 +166,49 @@ func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData
164166
}
165167
requiredDisks := map[string]map[string]string{}
166168
drives, err := l.getRequiredDisks(l.drives, false)
167-
if err != nil {
168-
return nil, err
169+
var notFoundErr *PartitionNotFoundError
170+
var notMountedErr *PartitionNotMountedError
171+
var partitionDiscoveryErr *PartitionDiscoveryError
172+
173+
// if empty-state is overridden with the filter, emptyStateSet is true
174+
// only handle the errors and return nil if its not overridden
175+
if !check.emptyStateSet {
176+
switch {
177+
case errors.As(err, &notFoundErr):
178+
log.Debugf("check_drivesize, drive is not found : %s, stopping check", notFoundErr.Error())
179+
// do not return (nil,err), finalize the check wihtout entries to check.listData
180+
// we want the empty-state override to work if its specified
181+
case errors.As(err, &notMountedErr):
182+
// no special handling of mount errors
183+
log.Debugf("check_drivesize, mounting error : %s, stopping check", notMountedErr.Error())
184+
185+
return nil, err
186+
case errors.As(err, &partitionDiscoveryErr):
187+
// no special handling of discovery errors
188+
log.Debugf("check_drivesize partition discovery error : %s, stopping check", partitionDiscoveryErr.Error())
189+
190+
return nil, err
191+
case err != nil:
192+
log.Debugf("check_drivesize error of unspecialized type : %s", err.Error())
193+
194+
return nil, err
195+
default:
196+
break
197+
}
198+
} else {
199+
log.Debugf("check_drivesize, ignoring errors relating to drive discovery due to empty-state being set.")
169200
}
170201
maps.Copy(requiredDisks, drives)
171202

203+
// when checking for folders and their mountpoints, set parentFallback to true
172204
folders, err := l.getRequiredDisks(l.folders, true)
173-
if err != nil {
174-
return nil, err
205+
// ignore errors if emptyState set is true, just like the drives
206+
if !check.emptyStateSet {
207+
if err != nil {
208+
return nil, err
209+
}
210+
} else {
211+
log.Debugf("check_drivesize, ignoring errors relating to directory discovery due to empty-state being set.")
175212
}
176213
maps.Copy(requiredDisks, folders)
177214

@@ -417,3 +454,30 @@ func (l *CheckDrivesize) getFlagNames(drive map[string]string) []string {
417454

418455
return flags
419456
}
457+
458+
// have to define the error variables here, this file builds on all platforms
459+
type PartitionNotFoundError struct {
460+
Path string
461+
err error
462+
}
463+
464+
func (e *PartitionNotFoundError) Error() string {
465+
return fmt.Sprintf("partition not found for path: %s , error: %s", e.Path, e.err.Error())
466+
}
467+
468+
type PartitionNotMountedError struct {
469+
Path string
470+
err error
471+
}
472+
473+
func (e *PartitionNotMountedError) Error() string {
474+
return fmt.Sprintf("partition not mounted for path: %s , error: %s", e.Path, e.err.Error())
475+
}
476+
477+
type PartitionDiscoveryError struct {
478+
err error
479+
}
480+
481+
func (e *PartitionDiscoveryError) Error() string {
482+
return fmt.Sprintf("error on disk.partitions call: %s", e.err.Error())
483+
}

pkg/snclient/check_drivesize_other.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Check folder, no matter if its a mountpoint itself or not:
3333
`
3434
}
3535

36+
// if parentFallback is true, try to find a parent folder that is a mountpoint. If its false, only the exact matches are checked for mountpoints.
3637
func (l *CheckDrivesize) getRequiredDisks(drives []string, parentFallback bool) (requiredDisks map[string]map[string]string, err error) {
3738
// create map of required disks/volumes with "drive_or_id" as primary key
3839
requiredDisks = map[string]map[string]string{}
@@ -58,6 +59,7 @@ func (l *CheckDrivesize) getRequiredDisks(drives []string, parentFallback bool)
5859
return requiredDisks, nil
5960
}
6061

62+
// setDisks fills the requiredDisks map with all available disks/partitions
6163
func (l *CheckDrivesize) setDisks(requiredDisks map[string]map[string]string) (err error) {
6264
partitions, err := disk.Partitions(true)
6365
if err != nil {
@@ -80,13 +82,13 @@ func (l *CheckDrivesize) setDisks(requiredDisks map[string]map[string]string) (e
8082
return nil
8183
}
8284

83-
func (l *CheckDrivesize) setCustomPath(drive string, requiredDisks map[string]map[string]string, parentFallback bool) (err error) {
85+
func (l *CheckDrivesize) setCustomPath(path string, requiredDisks map[string]map[string]string, parentFallback bool) (err error) {
8486
// make sure path exists
85-
_, err = os.Stat(drive)
87+
_, err = os.Stat(path)
8688
if err != nil && os.IsNotExist(err) {
87-
log.Debugf("%s: %s", drive, err.Error())
89+
log.Debugf("%s: %s", path, err.Error())
8890

89-
return fmt.Errorf("failed to find disk partition: %s", err.Error())
91+
return &PartitionNotFoundError{Path: path, err: err}
9092
}
9193

9294
// try to find closest matching mount
@@ -99,29 +101,33 @@ func (l *CheckDrivesize) setCustomPath(drive string, requiredDisks map[string]ma
99101
var match *map[string]string
100102
for i := range availMounts {
101103
vol := availMounts[i]
102-
if parentFallback && vol["drive"] != "" && strings.HasPrefix(drive, vol["drive"]) {
104+
if parentFallback && vol["drive"] != "" && strings.HasPrefix(path, vol["drive"]) {
105+
// try to find the longest matching parent path, that is a mountpoint
103106
if match == nil || len((*match)["drive"]) < len(vol["drive"]) {
104107
match = &vol
105108
}
106109
}
107110
// direct match, no need to search further
108-
if drive == vol["drive"] {
111+
if path == vol["drive"] {
109112
match = &vol
110113

111114
break
112115
}
113116
}
117+
114118
if match != nil {
115-
requiredDisks[drive] = utils.CloneStringMap(*match)
116-
requiredDisks[drive]["drive"] = drive
119+
requiredDisks[path] = utils.CloneStringMap(*match)
120+
requiredDisks[path]["drive"] = path
117121

118122
return nil
119123
}
120124

121125
// add anyway to generate an error later with more default values filled in
122-
entry := l.driveEntry(drive)
123-
entry["_error"] = fmt.Sprintf("%s not mounted", drive)
124-
requiredDisks[drive] = entry
126+
entry := l.driveEntry(path)
127+
entry["_error"] = (&PartitionNotMountedError{
128+
Path: path, err: fmt.Errorf("path :%s does exist, but could not match it to a drive. its likely that the partition is not mounted", path),
129+
}).Error()
130+
requiredDisks[path] = entry
125131

126132
return nil
127133
}

pkg/snclient/check_drivesize_test.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ func TestCheckDrivesize(t *testing.T) {
3737
assert.Contains(t, string(res.BuildPluginOutput()), "/ free", "output matches")
3838
assert.Contains(t, string(res.BuildPluginOutput()), ";0;;0;100", "output matches")
3939

40-
res = snc.RunCheck("check_drivesize", []string{"drive=k"})
41-
assert.Equalf(t, CheckExitUnknown, res.State, "state unknown")
42-
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - failed to find disk partition", "output matches")
43-
4440
// must not work, folder is not a mountpoint
4541
tmpFolder := t.TempDir()
4642
res = snc.RunCheck("check_drivesize", []string{"warn=inodes>100%", "crit=inodes>100%", "drive=" + tmpFolder})
@@ -55,3 +51,33 @@ func TestCheckDrivesize(t *testing.T) {
5551

5652
StopTestAgent(t, snc)
5753
}
54+
55+
func TestNonexistingDrive(t *testing.T) {
56+
snc := StartTestAgent(t, "")
57+
58+
res := snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999"})
59+
assert.Equalf(t, CheckExitUnknown, res.State, "state OK")
60+
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - No drives found", "output matches")
61+
62+
res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=ok"})
63+
assert.Equalf(t, CheckExitOK, res.State, "state OK")
64+
assert.Contains(t, string(res.BuildPluginOutput()), "OK - No drives found", "output matches")
65+
66+
res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=warn"})
67+
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
68+
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")
69+
70+
res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=warning"})
71+
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
72+
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")
73+
74+
res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=1"})
75+
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
76+
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")
77+
78+
res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=crit"})
79+
assert.Equalf(t, CheckExitCritical, res.State, "state OK")
80+
assert.Contains(t, string(res.BuildPluginOutput()), "CRITICAL - No drives found", "output matches")
81+
82+
StopTestAgent(t, snc)
83+
}

pkg/snclient/check_drivesize_windows.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ func (l *CheckDrivesize) getRequiredDisks(drives []string, parentFallback bool)
6666
case "all-volumes":
6767
l.setVolumes(requiredDisks)
6868
default:
69+
// drives are like block devices c: -> /dev/sda
70+
// partition is written into the disk in a partition table. it exists independently of volumes -> /dev/sdb3
71+
// volumes are accessible storage areas where a filesystem is made onto a partition. it can be mounted and have files -> / , /tmp/ , /home
6972
// "c" or "c:"" will use the drive c
7073
// "c:\" will use the volume
7174
// "c:\path" will use the best matching volume
@@ -318,7 +321,7 @@ func (l *CheckDrivesize) setDisks(requiredDisks map[string]map[string]string) (e
318321
// "This drive is locked by BitLocker Drive Encryption. You must unlock this drive from Control Panel"
319322
// but there can still be valid elements in partitions,
320323
// so abort here only if partitions is empty.
321-
return fmt.Errorf("disk partitions failed: %s", err.Error())
324+
return &PartitionDiscoveryError{err: err}
322325
}
323326
for _, partition := range partitions {
324327
drive := strings.TrimSuffix(partition.Device, "\\") + "\\"
@@ -443,7 +446,7 @@ func (l *CheckDrivesize) setCustomPath(drive string, requiredDisks map[string]ma
443446
if err != nil && os.IsNotExist(err) {
444447
log.Debugf("%s: %s", drive, err.Error())
445448

446-
return fmt.Errorf("failed to find disk partition: %s", err.Error())
449+
return &PartitionNotFoundError{Path: drive, err: err}
447450
}
448451

449452
// try to find closes matching volume

pkg/snclient/check_drivesize_windows_test.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ func TestCheckDrivesize(t *testing.T) {
2727
assert.Contains(t, string(res.BuildPluginOutput()), "C:\\ free", "output matches")
2828
assert.Contains(t, string(res.BuildPluginOutput()), ";0;;0;100", "output matches")
2929

30-
res = snc.RunCheck("check_drivesize", []string{"drive=k"})
31-
assert.Equalf(t, CheckExitUnknown, res.State, "state unknown")
32-
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - failed to find disk partition", "output matches")
33-
3430
res = snc.RunCheck("check_drivesize", []string{
3531
"warning=used > 99",
3632
"crit=used > 99.5",
@@ -68,3 +64,29 @@ func TestCheckDrivesize(t *testing.T) {
6864

6965
StopTestAgent(t, snc)
7066
}
67+
68+
func TestNonexistingDrive(t *testing.T) {
69+
snc := StartTestAgent(t, "")
70+
71+
res := snc.RunCheck("check_drivesize", []string{"drive=X"})
72+
assert.Equalf(t, CheckExitUnknown, res.State, "state OK")
73+
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - No drives found", "output matches")
74+
75+
res = snc.RunCheck("check_drivesize", []string{"drive=X:", "empty-state=warn"})
76+
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
77+
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")
78+
79+
res = snc.RunCheck("check_drivesize", []string{"drive=X:\\", "empty-state=warn"})
80+
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
81+
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")
82+
83+
res = snc.RunCheck("check_drivesize", []string{"drive=X", "empty-state=warn"})
84+
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
85+
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")
86+
87+
res = snc.RunCheck("check_drivesize", []string{"drive=X", "empty-state=crit"})
88+
assert.Equalf(t, CheckExitCritical, res.State, "state OK")
89+
assert.Contains(t, string(res.BuildPluginOutput()), "CRITICAL - No drives found", "output matches")
90+
91+
StopTestAgent(t, snc)
92+
}

0 commit comments

Comments
 (0)