From c100b023cc3101c7d59c0c3b3dd933196b5b8b34 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Tue, 14 May 2024 13:03:24 +1000 Subject: [PATCH 01/11] new struct and update schemas --- config/crds/troubleshoot.sh_hostcollectors.yaml | 7 +++++++ config/crds/troubleshoot.sh_hostpreflights.yaml | 7 +++++++ config/crds/troubleshoot.sh_supportbundles.yaml | 7 +++++++ pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go | 5 +++++ schemas/supportbundle-troubleshoot-v1beta2.json | 11 +++++++++++ 5 files changed, 37 insertions(+) diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 2573c4daf..ab18d1766 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1338,6 +1338,13 @@ spec: exclude: type: BoolString type: object + kernelConfigs: + properties: + collectorName: + type: string + exclude: + type: BoolString + type: object kernelModules: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index f96d6d1fe..b89acf34d 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1338,6 +1338,13 @@ spec: exclude: type: BoolString type: object + kernelConfigs: + properties: + collectorName: + type: string + exclude: + type: BoolString + type: object kernelModules: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 5a328cf3f..5b1a10874 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -19888,6 +19888,13 @@ spec: exclude: type: BoolString type: object + kernelConfigs: + properties: + collectorName: + type: string + exclude: + type: BoolString + type: object kernelModules: properties: collectorName: diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index 7eb693f98..5b8d32b24 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -190,6 +190,10 @@ type HostRun struct { Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` } +type HostKernelConfigs struct { + HostCollectorMeta `json:",inline" yaml:",inline"` +} + type HostCollect struct { CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"` Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"` @@ -214,6 +218,7 @@ type HostCollect struct { HostOS *HostOS `json:"hostOS,omitempty" yaml:"hostOS,omitempty"` HostRun *HostRun `json:"run,omitempty" yaml:"run,omitempty"` HostCopy *HostCopy `json:"copy,omitempty" yaml:"copy,omitempty"` + HostKernelConfigs *HostKernelConfigs `json:"kernelConfigs,omitempty" yaml:"kernelConfigs,omitempty"` } func (c *HostCollect) GetName() string { diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index e548b5f71..b6fcc78d8 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -18833,6 +18833,17 @@ } } }, + "kernelConfigs": { + "type": "object", + "properties": { + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "kernelModules": { "type": "object", "properties": { From e430ba8dbe016430717cfca482d9d944d6c113ce Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 15 May 2024 11:42:51 +1000 Subject: [PATCH 02/11] implement Collect function --- pkg/collect/host_kernel_configs.go | 144 ++++++++++++++++++++++++ pkg/collect/host_kernel_configs_test.go | 71 ++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 pkg/collect/host_kernel_configs.go create mode 100644 pkg/collect/host_kernel_configs_test.go diff --git a/pkg/collect/host_kernel_configs.go b/pkg/collect/host_kernel_configs.go new file mode 100644 index 000000000..86884ecf7 --- /dev/null +++ b/pkg/collect/host_kernel_configs.go @@ -0,0 +1,144 @@ +package collect + +import ( + "bufio" + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/pkg/errors" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" +) + +type CollectHostKernelConfigs struct { + hostCollector *troubleshootv1beta2.HostKernelConfigs + BundlePath string +} + +type kConfigs map[string]kConfigOption +type kConfigOption string + +const HostKernelConfigsPath = `host-collectors/system/kernel-configs.json` + +const ( + kConfigUnknown kConfigOption = "" + kConfigBuiltIn kConfigOption = "y" + kConfigAsModule kConfigOption = "m" + kConfigLeftOut kConfigOption = "n" +) + +func (c *CollectHostKernelConfigs) Title() string { + return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "kernel-configs") +} + +func (c *CollectHostKernelConfigs) IsExcluded() (bool, error) { + return isExcluded(c.hostCollector.Exclude) +} + +func (c *CollectHostKernelConfigs) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { + + kernelRelease, err := getKernelRelease() + if err != nil { + return nil, errors.Wrap(err, "failed to get kernel release") + } + + var kConfigs kConfigs + kConfigs, err = loadKConfigs(kernelRelease) + if err != nil { + return nil, errors.Wrap(err, "failed to load kernel configs") + } + + b, err := json.Marshal(kConfigs) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal kernel configs") + } + + output := NewResult() + output.SaveResult(c.BundlePath, HostKernelConfigsPath, bytes.NewBuffer(b)) + + return output, nil +} + +func getKernelRelease() (string, error) { + out, err := exec.Command("uname", "-r").Output() + if err != nil { + return "", errors.Wrap(err, "failed to determine kernel release") + } + release := strings.TrimSpace(string(out)) + return release, nil +} + +// https://github.com/k0sproject/k0s/blob/ddee3f980443e19620e678a6e1dc136ff053bff9/internal/pkg/sysinfo/probes/linux/kernel.go#L282 +// loadKConfigs checks a list of well-known file system paths for kernel +// configuration files and tries to parse them. +func loadKConfigs(kernelRelease string) (kConfigs, error) { + // At least some references to those paths may be fond here: + // https://github.com/torvalds/linux/blob/v4.3/init/Kconfig#L794 + // https://github.com/torvalds/linux/blob/v4.3/init/Kconfig#L9 + possiblePaths := []string{ + "/proc/config.gz", + "/boot/config-" + kernelRelease, + "/usr/src/linux-" + kernelRelease + "/.config", + "/usr/src/linux/.config", + "/usr/lib/modules/" + kernelRelease + "/config", + "/usr/lib/ostree-boot/config-" + kernelRelease, + "/usr/lib/kernel/config-" + kernelRelease, + "/usr/src/linux-headers-" + kernelRelease + "/.config", + "/lib/modules/" + kernelRelease + "/build/.config", + } + + for _, path := range possiblePaths { + // open file for reading + f, err := os.Open(path) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + defer f.Close() + + r := io.Reader(bufio.NewReader(f)) + + // This is a gzip file (config.gz), unzip it. + if filepath.Ext(path) == ".gz" { + gr, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + defer gr.Close() + r = gr + } + + return parseKConfigs(r) + } + + return nil, errors.Errorf("no kernel config files found for kernel release %q", kernelRelease) +} + +// parseKConfigs parses `r` line by line, extracting all kernel config options. +func parseKConfigs(r io.Reader) (kConfigs, error) { + configs := kConfigs{} + kConfigLineRegex := regexp.MustCompile(fmt.Sprintf( + "^(CONFIG_[A-Z0-9_]+)=([%s%s%s])$", + string(kConfigBuiltIn), string(kConfigLeftOut), string(kConfigAsModule), + )) + s := bufio.NewScanner(r) + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + if matches := kConfigLineRegex.FindStringSubmatch(s.Text()); matches != nil { + configs[matches[1]] = kConfigOption(matches[2]) + } + } + return configs, nil +} diff --git a/pkg/collect/host_kernel_configs_test.go b/pkg/collect/host_kernel_configs_test.go new file mode 100644 index 000000000..490bbeb9c --- /dev/null +++ b/pkg/collect/host_kernel_configs_test.go @@ -0,0 +1,71 @@ +package collect + +import ( + "reflect" + "strings" + "testing" +) + +func TestLoadKConfigsNoFiles(t *testing.T) { + _, err := loadKConfigs("nonexistent-kernel-release") + if err == nil { + t.Errorf("loadKConfigs() error = %v, wantErr", err) + } +} + +func TestParseKConfigs(t *testing.T) { + tests := []struct { + name string + input string + expected kConfigs + }{ + { + name: "Valid input", + input: ` +# +# Automatically generated file; DO NOT EDIT. +# Linux/x86 6.5.0-1018-gcp Kernel Configuration +# +CONFIG_CC_VERSION_TEXT="x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0" +CONFIG_CC_IS_GCC=y +CONFIG_GCC_VERSION=120300 +CONFIG_CLANG_VERSION=0 +CONFIG_AS_IS_GNU=y +CONFIG_AS_VERSION=23800 +CONFIG_IKHEADERS=m`, + expected: kConfigs{ + "CONFIG_CC_IS_GCC": kConfigBuiltIn, + "CONFIG_AS_IS_GNU": kConfigBuiltIn, + "CONFIG_IKHEADERS": kConfigAsModule, + }, + }, + { + name: "Empty input", + input: "", + expected: kConfigs{}, + }, + { + name: "Invalid input", + input: ` +foobar + CONFIG_AS_IS_GNU=y +CONFIG_IKHEADERS = m + `, + expected: kConfigs{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := strings.NewReader(tt.input) + configs, err := parseKConfigs(r) + if err != nil { + t.Fatalf("parseKConfigs() error = %v", err) + return + } + if !reflect.DeepEqual(configs, tt.expected) { + t.Errorf("parseKConfigs() = %v, want %v", configs, tt.expected) + } + }) + } +} From e4f929d6ebdabc2d8fcf2c004eaa4b183f94b851 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 15 May 2024 12:58:03 +1000 Subject: [PATCH 03/11] add kernel config to collector struct --- pkg/collect/host_collector.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/collect/host_collector.go b/pkg/collect/host_collector.go index f365a0e46..ba7d00dd5 100644 --- a/pkg/collect/host_collector.go +++ b/pkg/collect/host_collector.go @@ -61,6 +61,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str return &CollectHostRun{collector.HostRun, bundlePath}, true case collector.HostCopy != nil: return &CollectHostCopy{collector.HostCopy, bundlePath}, true + case collector.HostKernelConfigs != nil: + return &CollectHostKernelConfigs{collector.HostKernelConfigs, bundlePath}, true default: return nil, false } From 14b01824808b911736113a77b6b93fc88d33bc17 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 15 May 2024 13:08:52 +1000 Subject: [PATCH 04/11] generate kernel config analyzer schema --- config/crds/troubleshoot.sh_analyzers.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_hostcollectors.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_hostpreflights.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_supportbundles.yaml | 49 ++++++++++++ .../v1beta2/hostanalyzer_shared.go | 7 ++ schemas/analyzer-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ .../supportbundle-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ 7 files changed, 355 insertions(+) diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index 4a35936fe..3fff661e0 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -2299,6 +2299,55 @@ spec: required: - outcomes type: object + kernelConfigs: + 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 kernelModules: properties: annotations: diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index ab18d1766..7dd214114 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -588,6 +588,55 @@ spec: required: - outcomes type: object + kernelConfigs: + 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 kernelModules: properties: annotations: diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index b89acf34d..d3db0715e 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -588,6 +588,55 @@ spec: required: - outcomes type: object + kernelConfigs: + 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 kernelModules: properties: annotations: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 5b1a10874..c5920803b 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -19138,6 +19138,55 @@ spec: required: - outcomes type: object + kernelConfigs: + 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 kernelModules: properties: annotations: diff --git a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go index 73ca6070c..f9d1ed0d0 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go @@ -122,6 +122,12 @@ type HostOSAnalyze struct { Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` } +type KernelConfigsAnalyze 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"` @@ -144,4 +150,5 @@ type HostAnalyze struct { HostServices *HostServicesAnalyze `json:"hostServices,omitempty" yaml:"hostServices,omitempty"` HostOS *HostOSAnalyze `json:"hostOS,omitempty" yaml:"hostOS,omitempty"` TextAnalyze *TextAnalyze `json:"textAnalyze,omitempty" yaml:"textAnalyze,omitempty"` + KernelConfigs *KernelConfigsAnalyze `json:"kernelConfigs,omitempty" yaml:"kernelConfigs,omitempty"` } diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index b1ba8ad58..fc4ea7655 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -3501,6 +3501,82 @@ } } }, + "kernelConfigs": { + "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"}] + } + } + }, "kernelModules": { "type": "object", "required": [ diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index b6fcc78d8..0b945a566 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -17731,6 +17731,82 @@ } } }, + "kernelConfigs": { + "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"}] + } + } + }, "kernelModules": { "type": "object", "required": [ From 68a555b9ac7858f45be7cf8f7ad5e8f2631d8ccb Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 15 May 2024 17:02:05 +1000 Subject: [PATCH 05/11] implement kernel config analyzer --- pkg/analyze/host_analyzer.go | 2 + pkg/analyze/host_kernel_configs.go | 112 +++++++++++++++ pkg/analyze/host_kernel_configs_test.go | 174 ++++++++++++++++++++++++ pkg/collect/host_kernel_configs.go | 20 ++- pkg/collect/host_kernel_configs_test.go | 8 +- pkg/constants/constants.go | 5 + 6 files changed, 306 insertions(+), 15 deletions(-) create mode 100644 pkg/analyze/host_kernel_configs.go create mode 100644 pkg/analyze/host_kernel_configs_test.go diff --git a/pkg/analyze/host_analyzer.go b/pkg/analyze/host_analyzer.go index 38c1bbed8..e8ac119ab 100644 --- a/pkg/analyze/host_analyzer.go +++ b/pkg/analyze/host_analyzer.go @@ -52,6 +52,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b return &AnalyzeHostOS{analyzer.HostOS}, true case analyzer.TextAnalyze != nil: return &AnalyzeHostTextAnalyze{analyzer.TextAnalyze}, true + case analyzer.KernelConfigs != nil: + return &AnalyzeHostKernelConfigs{analyzer.KernelConfigs}, true default: return nil, false } diff --git a/pkg/analyze/host_kernel_configs.go b/pkg/analyze/host_kernel_configs.go new file mode 100644 index 000000000..078a560f0 --- /dev/null +++ b/pkg/analyze/host_kernel_configs.go @@ -0,0 +1,112 @@ +package analyzer + +import ( + "encoding/json" + "strings" + + "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" +) + +type AnalyzeHostKernelConfigs struct { + hostAnalyzer *troubleshootv1beta2.KernelConfigsAnalyze +} + +func (a *AnalyzeHostKernelConfigs) Title() string { + return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Kernel Configs") +} + +func (a *AnalyzeHostKernelConfigs) IsExcluded() (bool, error) { + return isExcluded(a.hostAnalyzer.Exclude) +} + +func (a *AnalyzeHostKernelConfigs) Analyze( + getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents, +) ([]*AnalyzeResult, error) { + hostAnalyzer := a.hostAnalyzer + + contents, err := getCollectedFileContents(collect.HostKernelConfigsPath) + if err != nil { + return nil, errors.Wrap(err, "failed to get collected file") + } + + kConfigs := collect.KConfigs{} + if err := json.Unmarshal(contents, &kConfigs); err != nil { + return nil, errors.Wrap(err, "failed to read kernel configs") + } + + var results []*AnalyzeResult + for _, outcome := range hostAnalyzer.Outcomes { + result := &AnalyzeResult{ + Title: a.Title(), + Strict: hostAnalyzer.Strict.BoolOrDefaultFalse(), + } + + if err := analyzeSingleOutcome(kConfigs, result, outcome.Pass, constants.OUTCOME_PASS); err != nil { + return nil, errors.Wrap(err, "failed to analyze pass outcome") + } + + if err := analyzeSingleOutcome(kConfigs, result, outcome.Fail, constants.OUTCOME_FAIL); err != nil { + return nil, errors.Wrap(err, "failed to analyze fail outcome") + } + + if err := analyzeSingleOutcome(kConfigs, result, outcome.Warn, constants.OUTCOME_WARN); err != nil { + return nil, errors.Wrap(err, "failed to analyze warn outcome") + } + + results = append(results, result) + } + + return results, nil +} + +func analyzeSingleOutcome(kConfigs collect.KConfigs, result *AnalyzeResult, outcome *troubleshootv1beta2.SingleOutcome, outcomeType string) error { + if outcome == nil { + return nil + } + + if outcome.When == "" { + return errors.New("when attribute is required") + } + + isMatch, err := match(kConfigs, outcome.When) + if err != nil { + return errors.Wrap(err, "failed to match") + } + + result.Message = outcome.Message + result.URI = outcome.URI + + if !isMatch { + return nil + } + + switch outcomeType { + case constants.OUTCOME_PASS: + result.IsPass = true + case constants.OUTCOME_FAIL: + result.IsFail = true + case constants.OUTCOME_WARN: + result.IsWarn = true + } + + return nil +} + +func match(kConfigs collect.KConfigs, when string) (bool, error) { + parts := strings.SplitN(when, "=", 2) + if len(parts) != 2 { + return false, errors.New("invalid when attribute") + } + key, value := parts[0], parts[1] + + // check if the key exists + if kConfig, ok := kConfigs[key]; ok { + return kConfig == strings.TrimSpace(value), nil + } + + // kernel config not found + return false, nil +} diff --git a/pkg/analyze/host_kernel_configs_test.go b/pkg/analyze/host_kernel_configs_test.go new file mode 100644 index 000000000..95820c187 --- /dev/null +++ b/pkg/analyze/host_kernel_configs_test.go @@ -0,0 +1,174 @@ +package analyzer + +import ( + "testing" + + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/replicatedhq/troubleshoot/pkg/collect" + "github.com/stretchr/testify/assert" +) + +func TestAnalyzeKernelConfigs(t *testing.T) { + kConfigs := collect.KConfigs{ + "CONFIG_CGROUP_FREEZER": "y", + "CONFIG_NETFILTER_XTABLES": "m", + } + + tests := []struct { + name string + kConfigs collect.KConfigs + outcomes []*troubleshootv1beta2.Outcome + results []*AnalyzeResult + expectErr bool + }{ + { + name: "all pass", + kConfigs: kConfigs, + outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "CONFIG_CGROUP_FREEZER=y", + Message: "Freezer cgroup subsystem built-in", + }, + }, + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "CONFIG_NETFILTER_XTABLES=m", + Message: "Netfilter Xtables support module", + }, + }, + }, + results: []*AnalyzeResult{ + { + Title: "Kernel Configs", + IsPass: true, + Message: "Freezer cgroup subsystem built-in", + }, { + Title: "Kernel Configs", + IsPass: true, + Message: "Netfilter Xtables support module", + }, + }, + expectErr: false, + }, + { + name: "has fail", + kConfigs: kConfigs, + outcomes: []*troubleshootv1beta2.Outcome{ + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "CONFIG_NETFILTER_XTABLES=m", + Message: "Netfilter Xtables support module", + }, + }, + }, + results: []*AnalyzeResult{ + { + Title: "Kernel Configs", + IsFail: true, + Message: "Netfilter Xtables support module", + }, + }, + expectErr: false, + }, + { + name: "has warn", + kConfigs: kConfigs, + outcomes: []*troubleshootv1beta2.Outcome{ + { + Warn: &troubleshootv1beta2.SingleOutcome{ + When: "CONFIG_NETFILTER_XTABLES=m", + Message: "Netfilter Xtables support module", + }, + }, + }, + results: []*AnalyzeResult{ + { + Title: "Kernel Configs", + IsWarn: true, + Message: "Netfilter Xtables support module", + }, + }, + expectErr: false, + }, + { + name: "missing kernel config", + kConfigs: kConfigs, + outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "CONFIG_NF_NAT_IPV4=y", + Message: "IPv4 NAT option", + }, + }, + }, + results: []*AnalyzeResult{ + { + Title: "Kernel Configs", + IsPass: false, + Message: "IPv4 NAT option", + }, + }, + expectErr: false, + }, + { + name: "kernel config disabled", + kConfigs: kConfigs, + outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "CONFIG_CGROUP_FREEZER=n", + Message: "CONFIG_CGROUP_FREEZER is disabled", + }, + }, + }, + results: []*AnalyzeResult{ + { + Title: "Kernel Configs", + IsPass: false, + Message: "CONFIG_CGROUP_FREEZER is disabled", + }, + }, + expectErr: false, + }, + { + name: "missing when attribute", + outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "CONFIG_foo is enabled", + When: "", + }, + }, + }, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + fn := func(_ string) ([]byte, error) { + return []byte(`{"CONFIG_CGROUP_FREEZER": "y", "CONFIG_NETFILTER_XTABLES": "m"}`), nil + } + + analyzer := AnalyzeHostKernelConfigs{ + hostAnalyzer: &troubleshootv1beta2.KernelConfigsAnalyze{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + CheckName: "Kernel Configs", + }, + Outcomes: tt.outcomes, + }, + } + + results, err := analyzer.Analyze(fn, nil) + + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.results, results) + } + }) + } +} diff --git a/pkg/collect/host_kernel_configs.go b/pkg/collect/host_kernel_configs.go index 86884ecf7..0fcb0e55f 100644 --- a/pkg/collect/host_kernel_configs.go +++ b/pkg/collect/host_kernel_configs.go @@ -22,16 +22,14 @@ type CollectHostKernelConfigs struct { BundlePath string } -type kConfigs map[string]kConfigOption -type kConfigOption string +type KConfigs map[string]string const HostKernelConfigsPath = `host-collectors/system/kernel-configs.json` const ( - kConfigUnknown kConfigOption = "" - kConfigBuiltIn kConfigOption = "y" - kConfigAsModule kConfigOption = "m" - kConfigLeftOut kConfigOption = "n" + kConfigBuiltIn string = "y" + kConfigAsModule string = "m" + kConfigLeftOut string = "n" ) func (c *CollectHostKernelConfigs) Title() string { @@ -49,7 +47,7 @@ func (c *CollectHostKernelConfigs) Collect(progressChan chan<- interface{}) (map return nil, errors.Wrap(err, "failed to get kernel release") } - var kConfigs kConfigs + var kConfigs KConfigs kConfigs, err = loadKConfigs(kernelRelease) if err != nil { return nil, errors.Wrap(err, "failed to load kernel configs") @@ -78,7 +76,7 @@ func getKernelRelease() (string, error) { // https://github.com/k0sproject/k0s/blob/ddee3f980443e19620e678a6e1dc136ff053bff9/internal/pkg/sysinfo/probes/linux/kernel.go#L282 // loadKConfigs checks a list of well-known file system paths for kernel // configuration files and tries to parse them. -func loadKConfigs(kernelRelease string) (kConfigs, error) { +func loadKConfigs(kernelRelease string) (KConfigs, error) { // At least some references to those paths may be fond here: // https://github.com/torvalds/linux/blob/v4.3/init/Kconfig#L794 // https://github.com/torvalds/linux/blob/v4.3/init/Kconfig#L9 @@ -124,8 +122,8 @@ func loadKConfigs(kernelRelease string) (kConfigs, error) { } // parseKConfigs parses `r` line by line, extracting all kernel config options. -func parseKConfigs(r io.Reader) (kConfigs, error) { - configs := kConfigs{} +func parseKConfigs(r io.Reader) (KConfigs, error) { + configs := KConfigs{} kConfigLineRegex := regexp.MustCompile(fmt.Sprintf( "^(CONFIG_[A-Z0-9_]+)=([%s%s%s])$", string(kConfigBuiltIn), string(kConfigLeftOut), string(kConfigAsModule), @@ -137,7 +135,7 @@ func parseKConfigs(r io.Reader) (kConfigs, error) { } if matches := kConfigLineRegex.FindStringSubmatch(s.Text()); matches != nil { - configs[matches[1]] = kConfigOption(matches[2]) + configs[matches[1]] = matches[2] } } return configs, nil diff --git a/pkg/collect/host_kernel_configs_test.go b/pkg/collect/host_kernel_configs_test.go index 490bbeb9c..2f9c86019 100644 --- a/pkg/collect/host_kernel_configs_test.go +++ b/pkg/collect/host_kernel_configs_test.go @@ -17,7 +17,7 @@ func TestParseKConfigs(t *testing.T) { tests := []struct { name string input string - expected kConfigs + expected KConfigs }{ { name: "Valid input", @@ -33,7 +33,7 @@ CONFIG_CLANG_VERSION=0 CONFIG_AS_IS_GNU=y CONFIG_AS_VERSION=23800 CONFIG_IKHEADERS=m`, - expected: kConfigs{ + expected: KConfigs{ "CONFIG_CC_IS_GCC": kConfigBuiltIn, "CONFIG_AS_IS_GNU": kConfigBuiltIn, "CONFIG_IKHEADERS": kConfigAsModule, @@ -42,7 +42,7 @@ CONFIG_IKHEADERS=m`, { name: "Empty input", input: "", - expected: kConfigs{}, + expected: KConfigs{}, }, { name: "Invalid input", @@ -51,7 +51,7 @@ foobar CONFIG_AS_IS_GNU=y CONFIG_IKHEADERS = m `, - expected: kConfigs{}, + expected: KConfigs{}, }, } diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index d312598e9..9a6e7f29d 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -103,4 +103,9 @@ const ( // are not always the same GP_DEFAULT_IMAGE = "alpine:3" GP_DEFAULT_NAMESPACE = "default" + + // Analyzer Outcome types + OUTCOME_PASS = "pass" + OUTCOME_WARN = "warn" + OUTCOME_FAIL = "fail" ) From e7e0f69d1e0bb349f34b0a9203ab7823aae74bd9 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 15 May 2024 17:29:30 +1000 Subject: [PATCH 06/11] fail on no match in pass outcome --- pkg/analyze/host_kernel_configs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/analyze/host_kernel_configs.go b/pkg/analyze/host_kernel_configs.go index 078a560f0..a946c3406 100644 --- a/pkg/analyze/host_kernel_configs.go +++ b/pkg/analyze/host_kernel_configs.go @@ -79,7 +79,11 @@ func analyzeSingleOutcome(kConfigs collect.KConfigs, result *AnalyzeResult, outc result.Message = outcome.Message result.URI = outcome.URI + // if no match, set pass outcome to fail if !isMatch { + if outcomeType == constants.OUTCOME_PASS { + result.IsFail = true + } return nil } From b2bcdfe62c2f3e9d9f32fc87e1bac1bc2adbf5a1 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Thu, 16 May 2024 09:31:00 +1000 Subject: [PATCH 07/11] run make check-schemas --- .../v1beta2/zz_generated.deepcopy.go | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 648af77a7..c3d84e9cb 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -1863,6 +1863,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) { *out = new(TextAnalyze) (*in).DeepCopyInto(*out) } + if in.KernelConfigs != nil { + in, out := &in.KernelConfigs, &out.KernelConfigs + *out = new(KernelConfigsAnalyze) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze. @@ -2057,6 +2062,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) { *out = new(HostCopy) (*in).DeepCopyInto(*out) } + if in.HostKernelConfigs != nil { + in, out := &in.HostKernelConfigs, &out.HostKernelConfigs + *out = new(HostKernelConfigs) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect. @@ -2247,6 +2257,22 @@ func (in *HostHTTP) DeepCopy() *HostHTTP { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostKernelConfigs) DeepCopyInto(out *HostKernelConfigs) { + *out = *in + in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostKernelConfigs. +func (in *HostKernelConfigs) DeepCopy() *HostKernelConfigs { + if in == nil { + return nil + } + out := new(HostKernelConfigs) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostKernelModules) DeepCopyInto(out *HostKernelModules) { *out = *in @@ -2824,6 +2850,33 @@ func (in *JsonCompare) DeepCopy() *JsonCompare { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KernelConfigsAnalyze) DeepCopyInto(out *KernelConfigsAnalyze) { + *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 KernelConfigsAnalyze. +func (in *KernelConfigsAnalyze) DeepCopy() *KernelConfigsAnalyze { + if in == nil { + return nil + } + out := new(KernelConfigsAnalyze) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KernelModulesAnalyze) DeepCopyInto(out *KernelModulesAnalyze) { *out = *in From 6f400597ca20a8c340a309232298b3e37f0a2c0f Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Thu, 16 May 2024 10:15:57 +1000 Subject: [PATCH 08/11] fix failed unit test --- pkg/analyze/host_kernel_configs_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/analyze/host_kernel_configs_test.go b/pkg/analyze/host_kernel_configs_test.go index 95820c187..29502408b 100644 --- a/pkg/analyze/host_kernel_configs_test.go +++ b/pkg/analyze/host_kernel_configs_test.go @@ -106,6 +106,7 @@ func TestAnalyzeKernelConfigs(t *testing.T) { { Title: "Kernel Configs", IsPass: false, + IsFail: true, Message: "IPv4 NAT option", }, }, @@ -126,6 +127,7 @@ func TestAnalyzeKernelConfigs(t *testing.T) { { Title: "Kernel Configs", IsPass: false, + IsFail: true, Message: "CONFIG_CGROUP_FREEZER is disabled", }, }, From add21c3d6f87d0667ce95857ab7c5b1c216f4807 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Mon, 20 May 2024 21:55:44 +1000 Subject: [PATCH 09/11] update from code review --- pkg/collect/host_kernel_configs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/collect/host_kernel_configs.go b/pkg/collect/host_kernel_configs.go index 0fcb0e55f..8ab0dcd86 100644 --- a/pkg/collect/host_kernel_configs.go +++ b/pkg/collect/host_kernel_configs.go @@ -67,7 +67,7 @@ func (c *CollectHostKernelConfigs) Collect(progressChan chan<- interface{}) (map func getKernelRelease() (string, error) { out, err := exec.Command("uname", "-r").Output() if err != nil { - return "", errors.Wrap(err, "failed to determine kernel release") + return "", errors.Wrap(err, "failed to determine kernel release using uname -r") } release := strings.TrimSpace(string(out)) return release, nil From 2c34fda480d37de9fe5a5a20c0c9487dc90652a8 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Fri, 24 May 2024 13:26:51 +1000 Subject: [PATCH 10/11] add selectedConfigs field --- config/crds/troubleshoot.sh_analyzers.yaml | 5 + .../crds/troubleshoot.sh_hostcollectors.yaml | 5 + .../crds/troubleshoot.sh_hostpreflights.yaml | 5 + .../crds/troubleshoot.sh_supportbundles.yaml | 5 + pkg/analyze/host_kernel_configs.go | 101 +++++++--------- pkg/analyze/host_kernel_configs_test.go | 112 +++++------------- .../v1beta2/hostanalyzer_shared.go | 7 +- schemas/analyzer-troubleshoot-v1beta2.json | 9 +- .../supportbundle-troubleshoot-v1beta2.json | 9 +- 9 files changed, 109 insertions(+), 149 deletions(-) diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index 3fff661e0..4b49cf09f 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -2343,10 +2343,15 @@ spec: type: object type: object type: array + selectedConfigs: + items: + type: string + type: array strict: type: BoolString required: - outcomes + - selectedConfigs type: object kernelModules: properties: diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 7dd214114..f37f5ba04 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -632,10 +632,15 @@ spec: type: object type: object type: array + selectedConfigs: + items: + type: string + type: array strict: type: BoolString required: - outcomes + - selectedConfigs type: object kernelModules: properties: diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index d3db0715e..1ee5b40a0 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -632,10 +632,15 @@ spec: type: object type: object type: array + selectedConfigs: + items: + type: string + type: array strict: type: BoolString required: - outcomes + - selectedConfigs type: object kernelModules: properties: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index c5920803b..a32ab7c15 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -19182,10 +19182,15 @@ spec: type: object type: object type: array + selectedConfigs: + items: + type: string + type: array strict: type: BoolString required: - outcomes + - selectedConfigs type: object kernelModules: properties: diff --git a/pkg/analyze/host_kernel_configs.go b/pkg/analyze/host_kernel_configs.go index a946c3406..90154663c 100644 --- a/pkg/analyze/host_kernel_configs.go +++ b/pkg/analyze/host_kernel_configs.go @@ -2,12 +2,14 @@ package analyzer import ( "encoding/json" + "regexp" + "strings" "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" + "k8s.io/klog/v2" ) type AnalyzeHostKernelConfigs struct { @@ -37,80 +39,59 @@ func (a *AnalyzeHostKernelConfigs) Analyze( return nil, errors.Wrap(err, "failed to read kernel configs") } - var results []*AnalyzeResult - for _, outcome := range hostAnalyzer.Outcomes { - result := &AnalyzeResult{ - Title: a.Title(), - Strict: hostAnalyzer.Strict.BoolOrDefaultFalse(), + var configsNotFound []string + kConfigRegex := regexp.MustCompile("^(CONFIG_[A-Z0-9_]+)=([ymn])$") + for _, config := range hostAnalyzer.SelectedConfigs { + matches := kConfigRegex.FindStringSubmatch(config) + // zero tolerance for invalid kernel config + if matches == nil || len(matches) < 3 { + return nil, errors.Errorf("invalid kernel config: %s", config) } - if err := analyzeSingleOutcome(kConfigs, result, outcome.Pass, constants.OUTCOME_PASS); err != nil { - return nil, errors.Wrap(err, "failed to analyze pass outcome") - } + key := matches[1] + value := matches[2] - if err := analyzeSingleOutcome(kConfigs, result, outcome.Fail, constants.OUTCOME_FAIL); err != nil { - return nil, errors.Wrap(err, "failed to analyze fail outcome") + // check if the kernel config exists + if _, ok := kConfigs[key]; !ok { + configsNotFound = append(configsNotFound, config) + continue } - - if err := analyzeSingleOutcome(kConfigs, result, outcome.Warn, constants.OUTCOME_WARN); err != nil { - return nil, errors.Wrap(err, "failed to analyze warn outcome") + // check if the kernel config value matches + if kConfigs[key] != value { + klog.V(2).Infof("collected kernel config %s=%s does not match expected value %s", key, kConfigs[key], value) + configsNotFound = append(configsNotFound, config) } - - results = append(results, result) - } - - return results, nil -} - -func analyzeSingleOutcome(kConfigs collect.KConfigs, result *AnalyzeResult, outcome *troubleshootv1beta2.SingleOutcome, outcomeType string) error { - if outcome == nil { - return nil - } - - if outcome.When == "" { - return errors.New("when attribute is required") } - isMatch, err := match(kConfigs, outcome.When) - if err != nil { - return errors.Wrap(err, "failed to match") - } + var results []*AnalyzeResult + for _, outcome := range hostAnalyzer.Outcomes { + result := &AnalyzeResult{ + Title: a.Title(), + Strict: hostAnalyzer.Strict.BoolOrDefaultFalse(), + } - result.Message = outcome.Message - result.URI = outcome.URI + if outcome.Pass != nil && len(configsNotFound) == 0 { + result.IsPass = true + result.Message = outcome.Pass.Message + results = append(results, result) + break + } - // if no match, set pass outcome to fail - if !isMatch { - if outcomeType == constants.OUTCOME_PASS { + if outcome.Fail != nil && len(configsNotFound) > 0 { result.IsFail = true + result.Message = addMissingKernelConfigs(outcome.Fail.Message, configsNotFound) + results = append(results, result) + break } - return nil - } - switch outcomeType { - case constants.OUTCOME_PASS: - result.IsPass = true - case constants.OUTCOME_FAIL: - result.IsFail = true - case constants.OUTCOME_WARN: - result.IsWarn = true } - return nil + return results, nil } -func match(kConfigs collect.KConfigs, when string) (bool, error) { - parts := strings.SplitN(when, "=", 2) - if len(parts) != 2 { - return false, errors.New("invalid when attribute") +func addMissingKernelConfigs(message string, missingConfigs []string) string { + if message == "" && len(missingConfigs) == 0 { + return message } - key, value := parts[0], parts[1] - - // check if the key exists - if kConfig, ok := kConfigs[key]; ok { - return kConfig == strings.TrimSpace(value), nil - } - - // kernel config not found - return false, nil + return strings.ReplaceAll(message, "{{ .ConfigsNotFound }}", strings.Join(missingConfigs, ", ")) } diff --git a/pkg/analyze/host_kernel_configs_test.go b/pkg/analyze/host_kernel_configs_test.go index 29502408b..b7d737cae 100644 --- a/pkg/analyze/host_kernel_configs_test.go +++ b/pkg/analyze/host_kernel_configs_test.go @@ -15,26 +15,21 @@ func TestAnalyzeKernelConfigs(t *testing.T) { } tests := []struct { - name string - kConfigs collect.KConfigs - outcomes []*troubleshootv1beta2.Outcome - results []*AnalyzeResult - expectErr bool + name string + kConfigs collect.KConfigs + selectedConfigs []string + outcomes []*troubleshootv1beta2.Outcome + results []*AnalyzeResult + expectErr bool }{ { - name: "all pass", - kConfigs: kConfigs, + name: "all pass", + kConfigs: kConfigs, + selectedConfigs: []string{"CONFIG_CGROUP_FREEZER=y", "CONFIG_NETFILTER_XTABLES=m"}, outcomes: []*troubleshootv1beta2.Outcome{ { Pass: &troubleshootv1beta2.SingleOutcome{ - When: "CONFIG_CGROUP_FREEZER=y", - Message: "Freezer cgroup subsystem built-in", - }, - }, - { - Pass: &troubleshootv1beta2.SingleOutcome{ - When: "CONFIG_NETFILTER_XTABLES=m", - Message: "Netfilter Xtables support module", + Message: "required kernel configs are available", }, }, }, @@ -42,108 +37,56 @@ func TestAnalyzeKernelConfigs(t *testing.T) { { Title: "Kernel Configs", IsPass: true, - Message: "Freezer cgroup subsystem built-in", - }, { - Title: "Kernel Configs", - IsPass: true, - Message: "Netfilter Xtables support module", + Message: "required kernel configs are available", }, }, expectErr: false, }, { - name: "has fail", - kConfigs: kConfigs, + name: "has fail", + kConfigs: kConfigs, + selectedConfigs: []string{"CONFIG_UTS_NS=y"}, outcomes: []*troubleshootv1beta2.Outcome{ { Fail: &troubleshootv1beta2.SingleOutcome{ - When: "CONFIG_NETFILTER_XTABLES=m", - Message: "Netfilter Xtables support module", - }, - }, - }, - results: []*AnalyzeResult{ - { - Title: "Kernel Configs", - IsFail: true, - Message: "Netfilter Xtables support module", - }, - }, - expectErr: false, - }, - { - name: "has warn", - kConfigs: kConfigs, - outcomes: []*troubleshootv1beta2.Outcome{ - { - Warn: &troubleshootv1beta2.SingleOutcome{ - When: "CONFIG_NETFILTER_XTABLES=m", - Message: "Netfilter Xtables support module", - }, - }, - }, - results: []*AnalyzeResult{ - { - Title: "Kernel Configs", - IsWarn: true, - Message: "Netfilter Xtables support module", - }, - }, - expectErr: false, - }, - { - name: "missing kernel config", - kConfigs: kConfigs, - outcomes: []*troubleshootv1beta2.Outcome{ - { - Pass: &troubleshootv1beta2.SingleOutcome{ - When: "CONFIG_NF_NAT_IPV4=y", - Message: "IPv4 NAT option", + Message: "missing kernel config(s): {{ .ConfigsNotFound }}", }, }, }, results: []*AnalyzeResult{ { Title: "Kernel Configs", - IsPass: false, IsFail: true, - Message: "IPv4 NAT option", + Message: "missing kernel config(s): CONFIG_UTS_NS=y", }, }, expectErr: false, }, { - name: "kernel config disabled", - kConfigs: kConfigs, + name: "kernel config disabled", + kConfigs: kConfigs, + selectedConfigs: []string{"CONFIG_CGROUP_FREEZER=n"}, outcomes: []*troubleshootv1beta2.Outcome{ { - Pass: &troubleshootv1beta2.SingleOutcome{ - When: "CONFIG_CGROUP_FREEZER=n", - Message: "CONFIG_CGROUP_FREEZER is disabled", + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "missing kernel config(s): {{ .ConfigsNotFound }}", }, }, }, results: []*AnalyzeResult{ { Title: "Kernel Configs", - IsPass: false, IsFail: true, - Message: "CONFIG_CGROUP_FREEZER is disabled", + Message: "missing kernel config(s): CONFIG_CGROUP_FREEZER=n", }, }, expectErr: false, }, { - name: "missing when attribute", - outcomes: []*troubleshootv1beta2.Outcome{ - { - Pass: &troubleshootv1beta2.SingleOutcome{ - Message: "CONFIG_foo is enabled", - When: "", - }, - }, - }, - expectErr: true, + name: "invalid kernel config", + kConfigs: kConfigs, + selectedConfigs: []string{"foobar=n"}, + expectErr: true, }, } @@ -159,7 +102,8 @@ func TestAnalyzeKernelConfigs(t *testing.T) { AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ CheckName: "Kernel Configs", }, - Outcomes: tt.outcomes, + SelectedConfigs: tt.selectedConfigs, + Outcomes: tt.outcomes, }, } diff --git a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go index f9d1ed0d0..4e38bcb6c 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go @@ -123,9 +123,10 @@ type HostOSAnalyze struct { } type KernelConfigsAnalyze struct { - AnalyzeMeta `json:",inline" yaml:",inline"` - CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"` - Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` + AnalyzeMeta `json:",inline" yaml:",inline"` + CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"` + SelectedConfigs []string `json:"selectedConfigs" yaml:"selectedConfigs"` + Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` } type HostAnalyze struct { diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index fc4ea7655..50ddc5026 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -3504,7 +3504,8 @@ "kernelConfigs": { "type": "object", "required": [ - "outcomes" + "outcomes", + "selectedConfigs" ], "properties": { "annotations": { @@ -3572,6 +3573,12 @@ } } }, + "selectedConfigs": { + "type": "array", + "items": { + "type": "string" + } + }, "strict": { "oneOf": [{"type": "string"},{"type": "boolean"}] } diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 0b945a566..1bc4f9654 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -17734,7 +17734,8 @@ "kernelConfigs": { "type": "object", "required": [ - "outcomes" + "outcomes", + "selectedConfigs" ], "properties": { "annotations": { @@ -17802,6 +17803,12 @@ } } }, + "selectedConfigs": { + "type": "array", + "items": { + "type": "string" + } + }, "strict": { "oneOf": [{"type": "string"},{"type": "boolean"}] } From 6c02bd8963bfc9309b58744d13df680f6aa50f1b Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Fri, 24 May 2024 13:31:43 +1000 Subject: [PATCH 11/11] run make check-schemas --- pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index c3d84e9cb..3f397a6eb 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -2854,6 +2854,11 @@ func (in *JsonCompare) DeepCopy() *JsonCompare { func (in *KernelConfigsAnalyze) DeepCopyInto(out *KernelConfigsAnalyze) { *out = *in in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta) + if in.SelectedConfigs != nil { + in, out := &in.SelectedConfigs, &out.SelectedConfigs + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Outcomes != nil { in, out := &in.Outcomes, &out.Outcomes *out = make([]*Outcome, len(*in))