Skip to content

Commit 4d52760

Browse files
author
Andrew Reed
authored
Collector and analyzer for sysctl parameters (#441)
Collector and analyzer for sysctl parameters
1 parent 6e34aa6 commit 4d52760

File tree

11 files changed

+817
-0
lines changed

11 files changed

+817
-0
lines changed

examples/troubleshoot/sysctl.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: troubleshoot.sh/v1beta2
2+
kind: SupportBundle
3+
metadata:
4+
name: sysctl
5+
spec:
6+
collectors:
7+
- sysctl:
8+
image: debian:buster-slim
9+
analyzers:
10+
- sysctl:
11+
checkName: IP forwarding enabled
12+
outcomes:
13+
- fail:
14+
when: "net.ipv4.ip_forward = 0"
15+
message: "IP forwarding is not enabled"

pkg/analyze/analyzer.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,5 +358,23 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
358358
return results, nil
359359
}
360360

361+
if analyzer.Sysctl != nil {
362+
isExcluded, err := isExcluded(analyzer.Sysctl.Exclude)
363+
if err != nil {
364+
return nil, err
365+
}
366+
if isExcluded {
367+
return nil, nil
368+
}
369+
result, err := analyzeSysctl(analyzer.Sysctl, findFiles)
370+
if err != nil {
371+
return nil, err
372+
}
373+
if result == nil {
374+
return []*AnalyzeResult{}, nil
375+
}
376+
return []*AnalyzeResult{result}, nil
377+
}
378+
361379
return nil, errors.New("invalid analyzer")
362380
}

pkg/analyze/sysctl.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package analyzer
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"path/filepath"
8+
"regexp"
9+
"sort"
10+
"strings"
11+
12+
"github.com/pkg/errors"
13+
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
14+
)
15+
16+
// The when condition for outcomes in this analyzer is interpreted as "for some node".
17+
// For example, "when: net.ipv4.ip_forward = 0" is true if at least one node has IP forwarding
18+
// disabled.
19+
func analyzeSysctl(analyzer *troubleshootv1beta2.SysctlAnalyze, findFiles func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
20+
files, err := findFiles("sysctl/*")
21+
if err != nil {
22+
return nil, errors.Wrap(err, "failed to find collected sysctl parameters")
23+
}
24+
if len(files) == 0 {
25+
return nil, errors.Wrap(err, "no sysctl parameters collected")
26+
}
27+
28+
nodeParams := map[string]map[string]string{}
29+
30+
for filename, parameters := range files {
31+
nodeName := filepath.Base(filename)
32+
nodeParams[nodeName] = parseSysctlParameters(parameters)
33+
}
34+
35+
for _, outcome := range analyzer.Outcomes {
36+
result, err := evalSysctlOutcome(nodeParams, outcome)
37+
if err != nil {
38+
return nil, err
39+
}
40+
if result != nil {
41+
result.Title = analyzer.CheckName
42+
if result.Title == "" {
43+
result.Title = "Sysctl"
44+
}
45+
return result, nil
46+
}
47+
}
48+
49+
return nil, nil
50+
}
51+
52+
// Example: /proc/sys/net/ipv4/ip_forward = 1
53+
var sysctlParamRX = regexp.MustCompile(`(^[^\s]+)\s=\s(.+)`)
54+
55+
func parseSysctlParameters(parameters []byte) map[string]string {
56+
buffer := bytes.NewBuffer(parameters)
57+
scanner := bufio.NewScanner(buffer)
58+
59+
parsed := map[string]string{}
60+
61+
for scanner.Scan() {
62+
matches := sysctlParamRX.FindStringSubmatch(scanner.Text())
63+
if len(matches) != 3 {
64+
continue
65+
}
66+
key := matches[1]
67+
value := matches[2]
68+
69+
// "/proc/sys/net/ipv4/ip_forward" => "net.ipv4.ip_forward"
70+
key = strings.TrimPrefix(key, "/proc/sys/")
71+
parts := strings.Split(key, "/")
72+
key = strings.Join(parts, ".")
73+
74+
parsed[key] = value
75+
}
76+
77+
return parsed
78+
}
79+
80+
func evalSysctlOutcome(nodeParams map[string]map[string]string, outcome *troubleshootv1beta2.Outcome) (*AnalyzeResult, error) {
81+
result := &AnalyzeResult{}
82+
83+
var singleOutcome *troubleshootv1beta2.SingleOutcome
84+
85+
if outcome.Pass != nil {
86+
singleOutcome = outcome.Pass
87+
result.IsPass = true
88+
}
89+
if outcome.Warn != nil {
90+
singleOutcome = outcome.Warn
91+
result.IsWarn = true
92+
}
93+
if outcome.Fail != nil {
94+
singleOutcome = outcome.Fail
95+
result.IsFail = true
96+
}
97+
98+
if singleOutcome.When == "" {
99+
result.Message = singleOutcome.Message
100+
return result, nil
101+
}
102+
103+
nodes, err := evalSysctlWhen(nodeParams, singleOutcome.When)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
// The when for this outcome is not true for any
109+
if len(nodes) == 0 {
110+
return nil, nil
111+
}
112+
113+
// The when condition is true for at least one node
114+
if len(nodes) == 1 {
115+
result.Message = fmt.Sprintf("Node %s: %s", nodes[0], singleOutcome.Message)
116+
} else {
117+
result.Message = fmt.Sprintf("Nodes %s: %s", strings.Join(nodes, ", "), singleOutcome.Message)
118+
}
119+
120+
result.URI = singleOutcome.URI
121+
122+
return result, nil
123+
}
124+
125+
// Example: net.ipv4.ip_forward = 0
126+
var sysctlWhenRX = regexp.MustCompile(`([^\s]+)\s+(=+)\s+(.+)`)
127+
128+
// Returns the list of node names the condition is true for. The condition is not considered true
129+
// if the parameter is missing for the node.
130+
func evalSysctlWhen(nodeParams map[string]map[string]string, when string) ([]string, error) {
131+
matches := sysctlWhenRX.FindStringSubmatch(when)
132+
if len(matches) != 4 {
133+
return nil, fmt.Errorf("Failed to parse when %q", when)
134+
}
135+
136+
switch matches[2] {
137+
case "=", "==", "===":
138+
var nodes []string
139+
140+
for nodeName, params := range nodeParams {
141+
nodeValue, ok := params[matches[1]]
142+
if !ok {
143+
continue
144+
}
145+
if nodeValue == strings.TrimSpace(matches[3]) {
146+
nodes = append(nodes, nodeName)
147+
}
148+
}
149+
150+
sort.Strings(nodes)
151+
152+
return nodes, nil
153+
default:
154+
return nil, fmt.Errorf("Unknown operator %q", matches[2])
155+
}
156+
}

0 commit comments

Comments
 (0)