mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
* 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
322 lines
9.7 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|