mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 10:19:54 +00:00
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
This commit is contained in:
@@ -2655,6 +2655,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -895,6 +895,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -895,6 +895,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -19542,6 +19542,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -5,6 +5,14 @@ metadata:
|
||||
spec:
|
||||
collectors:
|
||||
- sysctl:
|
||||
collectorName: sysctl
|
||||
#TODO add analyzer once implemented
|
||||
analyzers: []
|
||||
collectorName: host sysctl
|
||||
analyzers:
|
||||
- sysctl:
|
||||
collectorName: host sysctl
|
||||
outcomes:
|
||||
- warn:
|
||||
when: 'kern.ostype == Darwin'
|
||||
message: "Running sysctl on a Darwin host"
|
||||
- pass:
|
||||
when: 'net.ipv4.conf.default.arp_ignore > 0'
|
||||
message: "ARP ignore is enabled for the default interfaces interfaces on the host."
|
||||
|
||||
@@ -63,6 +63,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
|
||||
return &AnalyzeHostJsonCompare{analyzer.JsonCompare}, true
|
||||
case analyzer.NetworkNamespaceConnectivity != nil:
|
||||
return &AnalyzeHostNetworkNamespaceConnectivity{analyzer.NetworkNamespaceConnectivity}, true
|
||||
case analyzer.Sysctl != nil:
|
||||
return &AnalyzeHostSysctl{analyzer.Sysctl}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
106
pkg/analyze/host_sysctl.go
Normal file
106
pkg/analyze/host_sysctl.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
// Ensure `AnalyzeHostSysctl` implements `HostAnalyzer` interface at compile time.
|
||||
var _ HostAnalyzer = (*AnalyzeHostSysctl)(nil)
|
||||
|
||||
type AnalyzeHostSysctl struct {
|
||||
hostAnalyzer *troubleshootv1beta2.HostSysctlAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSysctl) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Sysctl")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSysctl) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSysctl) Analyze(
|
||||
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
|
||||
) ([]*AnalyzeResult, error) {
|
||||
result := AnalyzeResult{Title: a.Title()}
|
||||
|
||||
// Use the generic function to collect both local and remote data
|
||||
collectedContents, err := retrieveCollectedContents(
|
||||
getCollectedFileContents,
|
||||
collect.HostSysctlPath, // Local path
|
||||
collect.NodeInfoBaseDir, // Remote base directory
|
||||
collect.HostSysctlFileName, // Remote file name
|
||||
)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, err
|
||||
}
|
||||
|
||||
results, err := analyzeHostCollectorResults(collectedContents, a.hostAnalyzer.Outcomes, a.CheckCondition, a.Title())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to analyze sysctl output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// checkCondition checks the condition of the when clause
|
||||
func (a *AnalyzeHostSysctl) CheckCondition(when string, data []byte) (bool, error) {
|
||||
|
||||
sysctl := map[string]string{}
|
||||
if err := json.Unmarshal(data, &sysctl); err != nil {
|
||||
return false, errors.Wrap(err, "failed to unmarshal data")
|
||||
}
|
||||
|
||||
// <1:key> <2:operator> <3:value>
|
||||
matches := sysctlWhenRX.FindStringSubmatch(when)
|
||||
if len(matches) < 4 {
|
||||
return false, fmt.Errorf("expected 3 parts in when %q", when)
|
||||
}
|
||||
|
||||
param := matches[1]
|
||||
expected := matches[3]
|
||||
opString := matches[2]
|
||||
operator, err := ParseComparisonOperator(opString)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, fmt.Sprintf("failed to parse comparison operator %q", opString))
|
||||
}
|
||||
|
||||
if _, ok := sysctl[param]; !ok {
|
||||
return false, fmt.Errorf("kernel parameter %q does not exist on collected sysctl output", param)
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case Equal:
|
||||
return expected == sysctl[param], nil
|
||||
}
|
||||
|
||||
// operator used is an inequality operator, the only valid inputs should be ints, if not we'll error out
|
||||
value, err := strconv.Atoi(sysctl[param])
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("collected sysctl param %q has value %q, cannot be used with provided operator %q", param, sysctl[param], opString)
|
||||
}
|
||||
expectedInt, err := strconv.Atoi(expected)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("expected value for sysctl param %q has value %q, cannot be used with provided operator %q", param, expected, opString)
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case LessThan:
|
||||
return value < expectedInt, nil
|
||||
case LessThanOrEqual:
|
||||
return value <= expectedInt, nil
|
||||
case GreaterThan:
|
||||
return value > expectedInt, nil
|
||||
case GreaterThanOrEqual:
|
||||
return value >= expectedInt, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported operator %q", opString)
|
||||
}
|
||||
|
||||
}
|
||||
321
pkg/analyze/host_sysctl_test.go
Normal file
321
pkg/analyze/host_sysctl_test.go
Normal file
@@ -0,0 +1,321 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func (a *AnalyzeHostTime) Analyze(
|
||||
getCollectedFileContents,
|
||||
collect.HostTimePath,
|
||||
collect.NodeInfoBaseDir,
|
||||
collect.HostMemoryFileName,
|
||||
collect.HostTimeFileName,
|
||||
)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, err
|
||||
|
||||
@@ -135,6 +135,12 @@ type KernelConfigsAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostSysctlAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostAnalyze struct {
|
||||
CPU *CPUAnalyze `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
TCPLoadBalancer *TCPLoadBalancerAnalyze `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
|
||||
@@ -160,4 +166,5 @@ type HostAnalyze struct {
|
||||
KernelConfigs *KernelConfigsAnalyze `json:"kernelConfigs,omitempty" yaml:"kernelConfigs,omitempty"`
|
||||
JsonCompare *JsonCompare `json:"jsonCompare,omitempty" yaml:"jsonCompare,omitempty"`
|
||||
NetworkNamespaceConnectivity *NetworkNamespaceConnectivityAnalyze `json:"networkNamespaceConnectivity,omitempty" yaml:"networkNamespaceConnectivity,omitempty"`
|
||||
Sysctl *HostSysctlAnalyze `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1925,6 +1925,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(NetworkNamespaceConnectivityAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Sysctl != nil {
|
||||
in, out := &in.Sysctl, &out.Sysctl
|
||||
*out = new(HostSysctlAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
|
||||
@@ -2707,6 +2712,33 @@ func (in *HostSysctl) DeepCopy() *HostSysctl {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostSysctlAnalyze) DeepCopyInto(out *HostSysctlAnalyze) {
|
||||
*out = *in
|
||||
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostSysctlAnalyze.
|
||||
func (in *HostSysctlAnalyze) DeepCopy() *HostSysctlAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostSysctlAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostSystemPackages) DeepCopyInto(out *HostSystemPackages) {
|
||||
*out = *in
|
||||
|
||||
@@ -19,6 +19,7 @@ var _ HostCollector = (*CollectHostSysctl)(nil)
|
||||
var execCommand = exec.Command
|
||||
|
||||
const HostSysctlPath = `host-collectors/system/sysctl.json`
|
||||
const HostSysctlFileName = `sysctl.json`
|
||||
|
||||
type CollectHostSysctl struct {
|
||||
hostCollector *troubleshootv1beta2.HostSysctl
|
||||
|
||||
@@ -4052,6 +4052,82 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sysctl": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"outcomes"
|
||||
],
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"warn": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemPackages": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -18507,6 +18507,82 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sysctl": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"outcomes"
|
||||
],
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"warn": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemPackages": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -25,6 +25,7 @@ func TestHostLocalCollector(t *testing.T) {
|
||||
"hostos_info.json",
|
||||
"ipv4Interfaces.json",
|
||||
"memory.json",
|
||||
"sysctl.json",
|
||||
},
|
||||
notExpectedPaths: []string{
|
||||
"node_list.json",
|
||||
|
||||
@@ -11,6 +11,7 @@ spec:
|
||||
- memory: {}
|
||||
- blockDevices: {}
|
||||
- kernelConfigs: {}
|
||||
- sysctl: {}
|
||||
- copy:
|
||||
collectorName: etc-resolv
|
||||
path: /etc/resolv.conf
|
||||
|
||||
@@ -12,6 +12,7 @@ spec:
|
||||
- memory: {}
|
||||
- blockDevices: {}
|
||||
- kernelConfigs: {}
|
||||
- sysctl: {}
|
||||
- copy:
|
||||
collectorName: etc-resolv
|
||||
path: /etc/resolv.conf
|
||||
|
||||
Reference in New Issue
Block a user