Skip to content

Commit c4c6663

Browse files
authored
Includes virtual memory parameters in Sysctl (#874)
TL;DR ----- Updates Sysctl collector and analyzer for virtual memory parameters Details ------- Adds supoort for virtual memory parameters to the Sysctl collector and analyzers. I uncovered this writing a pre-flight for a Helm chart that includes ECK as a subchart. Since ECK requires a specific minimum value for `vm.max_map_count` I wanted to use the Sysctl analyzer to check for the expected value, but wasn't able to because of the limited values it collected. I also learned that Sonarqube expects the same parameter to be increased, so it seemed like a general enough requirement to add it in. The code updates the collector to collect values under `/proc/sys/vm` and adds tests to the analyzer to based on the ECK requirements. Making the tests pass required adding operators to the when expression, since the existing code only allowed for `=`, `==`, and `===`. The when expression now supports `>`, `<`, `>=`, and `<=`. All tests pass.
1 parent f13a959 commit c4c6663

File tree

3 files changed

+218
-1
lines changed

3 files changed

+218
-1
lines changed

pkg/analyze/sysctl.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path/filepath"
88
"regexp"
99
"sort"
10+
"strconv"
1011
"strings"
1112

1213
"github.com/pkg/errors"
@@ -124,7 +125,7 @@ func evalSysctlOutcome(nodeParams map[string]map[string]string, outcome *trouble
124125
}
125126

126127
// Example: net.ipv4.ip_forward = 0
127-
var sysctlWhenRX = regexp.MustCompile(`([^\s]+)\s+(=+)\s+(.+)`)
128+
var sysctlWhenRX = regexp.MustCompile(`([^\s]+)\s+([><=]=*)\s+(.+)`)
128129

129130
// Returns the list of node names the condition is true for. The condition is not considered true
130131
// if the parameter is missing for the node.
@@ -151,6 +152,95 @@ func evalSysctlWhen(nodeParams map[string]map[string]string, when string) ([]str
151152
sort.Strings(nodes)
152153

153154
return nodes, nil
155+
156+
case "<":
157+
var nodes []string
158+
159+
for nodeName, params := range nodeParams {
160+
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
161+
if err != nil {
162+
return nil, fmt.Errorf("Failed to parse when %q", when)
163+
}
164+
165+
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
166+
if err != nil {
167+
return nil, fmt.Errorf("Failed to parse when %q", when)
168+
}
169+
if nodeValue < expectedValue {
170+
nodes = append(nodes, nodeName)
171+
}
172+
}
173+
174+
sort.Strings(nodes)
175+
176+
return nodes, nil
177+
178+
case "<=":
179+
var nodes []string
180+
181+
for nodeName, params := range nodeParams {
182+
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
183+
if err != nil {
184+
return nil, fmt.Errorf("Failed to parse when %q", when)
185+
}
186+
187+
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
188+
if err != nil {
189+
return nil, fmt.Errorf("Failed to parse when %q", when)
190+
}
191+
if nodeValue <= expectedValue {
192+
nodes = append(nodes, nodeName)
193+
}
194+
}
195+
196+
sort.Strings(nodes)
197+
198+
return nodes, nil
199+
200+
case ">":
201+
var nodes []string
202+
203+
for nodeName, params := range nodeParams {
204+
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
205+
if err != nil {
206+
return nil, fmt.Errorf("Failed to parse when %q", when)
207+
}
208+
209+
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
210+
if err != nil {
211+
return nil, fmt.Errorf("Failed to parse when %q", when)
212+
}
213+
if nodeValue > expectedValue {
214+
nodes = append(nodes, nodeName)
215+
}
216+
}
217+
218+
sort.Strings(nodes)
219+
220+
return nodes, nil
221+
222+
case ">=":
223+
var nodes []string
224+
225+
for nodeName, params := range nodeParams {
226+
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
227+
if err != nil {
228+
return nil, fmt.Errorf("Failed to parse when %q", when)
229+
}
230+
231+
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
232+
if err != nil {
233+
return nil, fmt.Errorf("Failed to parse when %q", when)
234+
}
235+
if nodeValue >= expectedValue {
236+
nodes = append(nodes, nodeName)
237+
}
238+
}
239+
240+
sort.Strings(nodes)
241+
242+
return nodes, nil
243+
154244
default:
155245
return nil, fmt.Errorf("Unknown operator %q", matches[2])
156246
}

pkg/analyze/sysctl_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ func TestParseSysctlParameters(t *testing.T) {
1212
/proc/sys/net/ipv4/ip_forward = 1
1313
/proc/sys/net/ipv4/ip_local_port_range = 32768 60999
1414
/proc/sys/net/bridge/bridge-nf-call-iptables = 0
15+
/proc/sys/vm/max_map_count = 65530
1516
`
1617
got := parseSysctlParameters([]byte(parameters))
1718
expect := map[string]string{
1819
"net.ipv4.ip_forward": "1",
1920
"net.ipv4.ip_local_port_range": "32768 60999",
2021
"net.bridge.bridge-nf-call-iptables": "0",
22+
"vm.max_map_count": "65530",
2123
}
2224

2325
assert.Equal(t, expect, got)
@@ -69,6 +71,36 @@ func TestEvalSysctlWhen(t *testing.T) {
6971
expect: []string{"node-b"},
7072
expectErr: false,
7173
},
74+
{
75+
name: "No nodes have max map count > 65530",
76+
when: "vm.max_map_count > 65530",
77+
nodeParams: map[string]map[string]string{
78+
"node-a": {"vm.max_map_count": "65530"},
79+
"node-b": {"vm.max_map_count": "65530"},
80+
},
81+
expect: []string{},
82+
expectErr: false,
83+
},
84+
{
85+
name: "One node has max map count > 65530",
86+
when: "vm.max_map_count > 65530",
87+
nodeParams: map[string]map[string]string{
88+
"node-a": {"vm.max_map_count": "65530"},
89+
"node-b": {"vm.max_map_count": "262144"},
90+
},
91+
expect: []string{"node-b"},
92+
expectErr: false,
93+
},
94+
{
95+
name: "All nodes have max map count > 65530",
96+
when: "vm.max_map_count > 65530",
97+
nodeParams: map[string]map[string]string{
98+
"node-a": {"vm.max_map_count": "262144"},
99+
"node-b": {"vm.max_map_count": "262144"},
100+
},
101+
expect: []string{"node-a", "node-b"},
102+
expectErr: false,
103+
},
72104
}
73105

74106
for _, test := range tests {
@@ -270,6 +302,100 @@ func TestAnalyzeSysctl(t *testing.T) {
270302
},
271303
expect: nil,
272304
},
305+
{
306+
name: "Fail one node too low on max map count",
307+
files: map[string][]byte{
308+
"a": []byte(`
309+
/proc/sys/vm/max_map_count = 65530
310+
`),
311+
"b": []byte(`
312+
/proc/sys/vm/max_map_count = 262144
313+
`),
314+
},
315+
analyzer: &troubleshootv1beta2.SysctlAnalyze{
316+
Outcomes: []*troubleshootv1beta2.Outcome{
317+
{
318+
Fail: &troubleshootv1beta2.SingleOutcome{
319+
When: "vm.max_map_count < 262144 ",
320+
Message: "Max map count too low",
321+
},
322+
},
323+
{
324+
Pass: &troubleshootv1beta2.SingleOutcome{
325+
Message: "Max map count sufficient",
326+
},
327+
},
328+
},
329+
},
330+
expect: &AnalyzeResult{
331+
Title: "Sysctl",
332+
IsFail: true,
333+
Message: "Node a: Max map count too low",
334+
},
335+
},
336+
{
337+
name: "Fail two nodes too low on max map count",
338+
files: map[string][]byte{
339+
"a": []byte(`
340+
/proc/sys/vm/max_map_count = 65530
341+
`),
342+
"b": []byte(`
343+
/proc/sys/vm/max_map_count = 65530
344+
`),
345+
},
346+
analyzer: &troubleshootv1beta2.SysctlAnalyze{
347+
Outcomes: []*troubleshootv1beta2.Outcome{
348+
{
349+
Fail: &troubleshootv1beta2.SingleOutcome{
350+
When: "vm.max_map_count < 262144 ",
351+
Message: "Max map count too low",
352+
},
353+
},
354+
{
355+
Pass: &troubleshootv1beta2.SingleOutcome{
356+
Message: "Max map count sufficient",
357+
},
358+
},
359+
},
360+
},
361+
expect: &AnalyzeResult{
362+
Title: "Sysctl",
363+
IsFail: true,
364+
Message: "Nodes a, b: Max map count too low",
365+
},
366+
},
367+
{
368+
name: "Pass sufficient max map count on both nodes",
369+
files: map[string][]byte{
370+
"a": []byte(`
371+
/proc/sys/vm/max_map_count = 262144
372+
`),
373+
"b": []byte(`
374+
/proc/sys/vm/max_map_count = 262144
375+
`),
376+
},
377+
analyzer: &troubleshootv1beta2.SysctlAnalyze{
378+
Outcomes: []*troubleshootv1beta2.Outcome{
379+
{
380+
Fail: &troubleshootv1beta2.SingleOutcome{
381+
When: "vm.max_map_count < 262144 ",
382+
Message: "Max map count too low",
383+
},
384+
},
385+
{
386+
Pass: &troubleshootv1beta2.SingleOutcome{
387+
When: "vm.max_map_count >= 262144 ",
388+
Message: "Max map count sufficient",
389+
},
390+
},
391+
},
392+
},
393+
expect: &AnalyzeResult{
394+
Title: "Sysctl",
395+
IsPass: true,
396+
Message: "Nodes a, b: Max map count sufficient",
397+
},
398+
},
273399
}
274400

275401
for _, test := range tests {

pkg/collect/sysctl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (c *CollectSysctl) Collect(progressChan chan<- interface{}) (CollectorResul
6767
command := `
6868
find /proc/sys/net/ipv4 -type f | while read f; do v=$(cat $f 2>/dev/null); echo "$f = $v"; done
6969
find /proc/sys/net/bridge -type f | while read f; do v=$(cat $f 2>/dev/null); echo "$f = $v"; done
70+
find /proc/sys/vm -type f | while read f; do v=$(cat $f 2>/dev/null); echo "$f = $v"; done
7071
`
7172
runPodOptions.Command = []string{"sh", "-c", command}
7273

0 commit comments

Comments
 (0)