Files
troubleshoot/pkg/analyze/host_sysctl_test.go
João Antunes 197f6de425 feat(host_analyzer): add host sysctl analyzer (#1681)
* feat(host_analyzer): add host sysctl analyzer

* chore: add e2e tests to support bundle collection

* chore: missing spec e2e test update

* chore: cleanup remote collector and use parse operator

* chore: update schemas
2024-11-08 18:55:24 +00:00

322 lines
9.7 KiB
Go

package analyzer
import (
"encoding/json"
"fmt"
"testing"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeHostSysctlCheckCondition(t *testing.T) {
tests := []struct {
name string
conditional string
collected string
expected bool
expectErr string
}{
{
name: "errors out if we can't unmarshal data",
conditional: "net.ipv4.conf.all.arp_filter = 0",
collected: `{not JSON}`,
expected: false,
expectErr: "failed to unmarshal data",
},
{
name: "errors out if the matched conditional is missing elements",
conditional: "net.ipv4.conf.all.arp_filter =",
collected: `{}`,
expected: false,
expectErr: `expected 3 parts in when "net.ipv4.conf.all.arp_filter ="`,
},
{
name: "errors out if the parameter in the condition was not collected",
conditional: "net.ipv4.conf.all.arp_filter = 0",
collected: `{"net.ipv4.conf.all.arp_ignore": "0"}`,
expected: false,
expectErr: `"net.ipv4.conf.all.arp_filter" does not exist on collected sysctl`,
},
{
name: "errors out if the collected parameter does not support inequalities",
conditional: "net.ipv4.tcp_available_congestion_control > 0",
collected: `{"net.ipv4.tcp_available_congestion_control": "reno cubic"}`,
expected: false,
expectErr: `has value "reno cubic", cannot be used with provided operator ">"`,
},
{
name: "errors out if the provided value for the conditional does not support inequalities",
conditional: "net.ipv4.conf.all.arp_filter > broken",
collected: `{"net.ipv4.conf.all.arp_filter": "0"}`,
expected: false,
expectErr: `has value "broken", cannot be used with provided operator ">"`,
},
{
name: "errors out if the provided operator is unsupported",
conditional: "net.ipv4.conf.all.arp_filter <== 0",
collected: `{"net.ipv4.conf.all.arp_filter": "0"}`,
expected: false,
expectErr: `failed to parse comparison operator "<=="`,
},
{
name: "equals with ints",
conditional: "net.ipv4.conf.all.arp_filter = 0",
collected: `{"net.ipv4.conf.all.arp_filter": "0"}`,
expected: true,
},
{
name: "equals with different data types",
conditional: "net.ipv4.conf.all.arp_filter = will be false",
collected: `{"net.ipv4.conf.all.arp_filter": "0"}`,
expected: false,
},
{
name: "equals with strings",
conditional: "net.ipv4.tcp_available_congestion_control = reno cubic",
collected: `{"net.ipv4.tcp_available_congestion_control": "reno cubic"}`,
expected: true,
},
{
name: "triple equals works",
conditional: "net.ipv4.tcp_available_congestion_control === reno cubic",
collected: `{"net.ipv4.tcp_available_congestion_control": "reno cubic"}`,
expected: true,
},
{
name: "double equals works",
conditional: "net.ipv4.tcp_available_congestion_control == reno cubic",
collected: `{"net.ipv4.tcp_available_congestion_control": "reno cubic"}`,
expected: true,
},
{
name: "lower than succeeds",
conditional: "net.ipv4.conf.default.arp_ignore < 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "0"}`,
expected: true,
},
{
name: "lower than fails",
conditional: "net.ipv4.conf.default.arp_ignore < 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "1"}`,
expected: false,
},
{
name: "lower than or equals succeeds",
conditional: "net.ipv4.conf.default.arp_ignore <= 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "1"}`,
expected: true,
},
{
name: "lower than or equals fails",
conditional: "net.ipv4.conf.default.arp_ignore <= 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "2"}`,
expected: false,
},
{
name: "higher than succeeds",
conditional: "net.ipv4.conf.default.arp_ignore > 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "2"}`,
expected: true,
},
{
name: "higher than fails",
conditional: "net.ipv4.conf.default.arp_ignore > 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "1"}`,
expected: false,
},
{
name: "higher than or equals succeeds",
conditional: "net.ipv4.conf.default.arp_ignore >= 1",
collected: `{"net.ipv4.conf.default.arp_ignore": "1"}`,
expected: true,
},
{
name: "higher than or equals fails",
conditional: "net.ipv4.conf.default.arp_ignore >= 2",
collected: `{"net.ipv4.conf.default.arp_ignore": "1"}`,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
analyzeHostSysctl := AnalyzeHostSysctl{}
// JSON encoded sysctl collected output
data := []byte(test.collected)
// Call the CheckCondition method
result, err := analyzeHostSysctl.CheckCondition(test.conditional, data)
if test.expectErr != "" {
req.ErrorContains(err, test.expectErr)
} else {
req.NoError(err)
}
assert.Equal(t, test.expected, result)
})
}
}
func TestAnalyzeHostSysctl(t *testing.T) {
tests := []struct {
name string
hostAnalyzer *troubleshootv1beta2.HostSysctlAnalyze
getCollectedFileContents func(string) ([]byte, error)
expectedResults []*AnalyzeResult
expectedError string
}{
{
name: "Pass on successful condition (local)",
hostAnalyzer: &troubleshootv1beta2.HostSysctlAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "net.ipv4.conf.default.arp_ignore >= 1",
Message: "ARP ignore is enabled",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
// Simulate local sysctl content retrieval
if path == collect.HostSysctlPath {
data := map[string]string{
"net.ipv4.conf.default.arp_ignore": "2",
}
return json.Marshal(data)
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Sysctl",
IsPass: true,
Message: "ARP ignore is enabled",
},
},
expectedError: "",
},
{
name: "Fail on condition (remote node)",
hostAnalyzer: &troubleshootv1beta2.HostSysctlAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "net.ipv4.conf.default.arp_filter = 0",
Message: "ARP filter is disabled, please enable it via `sysctl net.ipv4.conf.default.arp_filter=1`",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
// Simulate remote node list and sysctl content retrieval
if path == constants.NODE_LIST_FILE {
nodeNames := nodeNames{Nodes: []string{"node1"}}
return json.Marshal(nodeNames)
}
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostSysctlFileName) {
data := map[string]string{
"net.ipv4.conf.default.arp_filter": "0",
}
return json.Marshal(data)
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Sysctl - Node node1",
IsFail: true,
Message: "ARP filter is disabled, please enable it via `sysctl net.ipv4.conf.default.arp_filter=1`",
},
},
expectedError: "",
},
{
name: "Warn on condition(remote node)",
hostAnalyzer: &troubleshootv1beta2.HostSysctlAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "net.ipv4.tcp_available_congestion_control = reno cubic",
Message: "Unexpected TCP congestion control algorithm available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
// Simulate remote node list and sysctl content retrieval
if path == constants.NODE_LIST_FILE {
nodeNames := nodeNames{Nodes: []string{"node1"}}
return json.Marshal(nodeNames)
}
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostSysctlFileName) {
data := map[string]string{
"net.ipv4.tcp_available_congestion_control": "reno cubic",
}
return json.Marshal(data)
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Sysctl - Node node1",
IsWarn: true,
Message: "Unexpected TCP congestion control algorithm available",
},
},
expectedError: "",
},
{
name: "Return error if collection fails",
hostAnalyzer: &troubleshootv1beta2.HostSysctlAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "net.ipv4.tcp_available_congestion_control = reno cubic",
Message: "Unexpected TCP congestion control algorithm available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Sysctl",
},
},
expectedError: "file not found",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
analyzeHostSysctl := AnalyzeHostSysctl{
hostAnalyzer: test.hostAnalyzer,
}
results, err := analyzeHostSysctl.Analyze(test.getCollectedFileContents, nil)
if test.expectedError != "" {
req.ErrorContains(err, test.expectedError)
} else {
req.NoError(err)
}
req.Equal(test.expectedResults, results)
})
}
}