Files
troubleshoot/pkg/analyze/host_block_devices_test.go
Evans Mungai 670a510a2d feat(analyze): optional additionalDeviceTypes parameter for blockDevices (#2002)
feat(analyze): optional additionalDeviceTypes for blockDevices; refactor match config and tests

Allow preflights to count extra lsblk TYPE values (e.g. loop, lvm) by listing them in
blockDevices.additionalDeviceTypes on BlockDevicesAnalyze. Types in this list are
eligible whether or not includeUnmountedPartitions is set; disk and optional
partitions behave as before.

Refactor matching to use blockDevicesMatchConfig and document eligibility on that
type. Add host_block_devices_match_test.go for type-rule tables and preflight-style
integration cases; keep classic scenarios in host_block_devices_test.go with a
shared analyzeHostBlockDevicesOutput helper.
Regenerate CRDs and deepcopy for the new API field.

Signed-off-by: Evans Mungai <evans@replicated.com>
2026-03-31 20:29:51 +01:00

278 lines
6.7 KiB
Go

package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeBlockDevices(t *testing.T) {
tests := []struct {
name string
devices []collect.BlockDeviceInfo
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
result []*AnalyzeResult
expectErr bool
}{
{
name: "sdb == 1, pass when there is an empty /dev/sdb",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sdb == 1",
Message: "Block device available",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Block Devices",
IsPass: true,
Message: "Block device available",
},
},
},
{
name: "sdb == 1, fail when partitioned",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
},
{
Name: "sdb1",
KernelName: "sdb1",
ParentKernelName: "sdb",
Type: "part",
Major: 8,
Minor: 1,
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sdb == 1",
Message: "Block device available",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No block device available",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Block Devices",
IsFail: true,
Message: "No block device available",
},
},
},
{
name: "sdb == 1, fail when it has a filesystem",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
FilesystemType: "ext4",
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sdb == 1",
Message: "Block device available",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No block device available",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Block Devices",
IsFail: true,
Message: "No block device available",
},
},
},
{
name: ".* > 0, fail when only loop devices are found",
devices: []collect.BlockDeviceInfo{
{
Name: "loop0",
KernelName: "loop0",
Type: "loop",
Major: 7,
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ".* > 0",
Message: "Block device available",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No block device available",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Block Devices",
IsFail: true,
Message: "No block device available",
},
},
},
{
name: ".* > 1, pass with unmounted partition",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
},
{
Name: "sdb1",
KernelName: "sdb1",
ParentKernelName: "sdb",
Type: "part",
Major: 8,
Minor: 1,
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
IncludeUnmountedPartitions: true,
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ".* >= 1",
Message: "Block device or partition available",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Block Devices",
IsPass: true,
Message: "Block device or partition available",
},
},
},
{
name: ".* = 2, pass with two unmounted partitions/devices of at least 10gb in size",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
Size: 1024 * 1024 * 1024 * 128,
},
{
Name: "sdb1",
KernelName: "sdb1",
ParentKernelName: "sdb",
Type: "part",
Major: 8,
Minor: 1,
Size: 1024 * 1024 * 1024 * 128,
},
{
Name: "sdc",
KernelName: "sdc",
Type: "disk",
Major: 8,
Serial: "disk2",
Size: 1024 * 1024 * 1024 * 16,
},
{
Name: "sdd",
KernelName: "sdd",
Type: "disk",
Major: 8,
Serial: "disk3",
Size: 1024 * 1024 * 1024 * 8,
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
IncludeUnmountedPartitions: true,
MinimumAcceptableSize: 1024 * 1024 * 1024 * 10,
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ".* = 2",
Message: "Two block devices or partitions of >10gb size available",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Block Devices",
IsPass: true,
Message: "Two block devices or partitions of >10gb size available",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
result, err := analyzeHostBlockDevicesOutput(t, test.devices, test.hostAnalyzer)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
// analyzeHostBlockDevicesOutput runs the host block device analyzer on marshaled fixture data (shared by match tests).
func analyzeHostBlockDevicesOutput(t *testing.T, devices []collect.BlockDeviceInfo, hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze) ([]*AnalyzeResult, error) {
t.Helper()
b, err := json.Marshal(devices)
require.NoError(t, err)
getCollectedFileContents := func(string) ([]byte, error) { return b, nil }
return (&AnalyzeHostBlockDevices{hostAnalyzer}).Analyze(getCollectedFileContents, nil)
}