From 21f27430667ff65638fd05d486bb841ac28f98ba Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 24 Jul 2024 11:05:08 +1000 Subject: [PATCH 1/4] add schema for Journald Host Collector --- .../crds/troubleshoot.sh_hostcollectors.yaml | 27 ++++++++++++ .../crds/troubleshoot.sh_hostpreflights.yaml | 27 ++++++++++++ .../crds/troubleshoot.sh_supportbundles.yaml | 27 ++++++++++++ .../v1beta2/hostcollector_shared.go | 14 +++++++ .../v1beta2/zz_generated.deepcopy.go | 26 ++++++++++++ .../supportbundle-troubleshoot-v1beta2.json | 41 +++++++++++++++++++ 6 files changed, 162 insertions(+) diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 7e97a27fd..0fede10c8 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1458,6 +1458,33 @@ spec: exclude: type: BoolString type: object + journald: + properties: + collectorName: + type: string + dmesg: + type: boolean + exclude: + type: BoolString + lines: + type: integer + output: + type: string + reverse: + type: boolean + since: + type: string + system: + type: boolean + units: + items: + type: string + type: array + until: + type: string + utc: + type: boolean + type: object kernelConfigs: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index 980eb7faf..3a8ac9bb0 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1458,6 +1458,33 @@ spec: exclude: type: BoolString type: object + journald: + properties: + collectorName: + type: string + dmesg: + type: boolean + exclude: + type: BoolString + lines: + type: integer + output: + type: string + reverse: + type: boolean + since: + type: string + system: + type: boolean + units: + items: + type: string + type: array + until: + type: string + utc: + type: boolean + type: object kernelConfigs: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 6b6bca7db..9cefe528a 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20057,6 +20057,33 @@ spec: exclude: type: BoolString type: object + journald: + properties: + collectorName: + type: string + dmesg: + type: boolean + exclude: + type: BoolString + lines: + type: integer + output: + type: string + reverse: + type: boolean + since: + type: string + system: + type: boolean + units: + items: + type: string + type: array + until: + type: string + utc: + type: boolean + type: object kernelConfigs: properties: collectorName: diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index f7d31b8ee..ccde92496 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -199,6 +199,19 @@ type HostKernelConfigs struct { HostCollectorMeta `json:",inline" yaml:",inline"` } +type HostJournald struct { + HostCollectorMeta `json:",inline" yaml:",inline"` + System bool `json:"system,omitempty" yaml:"system,omitempty"` + Dmesg bool `json:"dmesg,omitempty" yaml:"dmesg,omitempty"` + Units []string `json:"units,omitempty" yaml:"units,omitempty"` + Since string `json:"since,omitempty" yaml:"since,omitempty"` + Until string `json:"until,omitempty" yaml:"until,omitempty"` + Output string `json:"output,omitempty" yaml:"output,omitempty"` + Lines int `json:"lines,omitempty" yaml:"lines,omitempty"` + Reverse bool `json:"reverse,omitempty" yaml:"reverse,omitempty"` + Utc bool `json:"utc,omitempty" yaml:"utc,omitempty"` +} + type HostCollect struct { CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"` Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"` @@ -224,6 +237,7 @@ type HostCollect struct { HostRun *HostRun `json:"run,omitempty" yaml:"run,omitempty"` HostCopy *HostCopy `json:"copy,omitempty" yaml:"copy,omitempty"` HostKernelConfigs *HostKernelConfigs `json:"kernelConfigs,omitempty" yaml:"kernelConfigs,omitempty"` + HostJournald *HostJournald `json:"journald,omitempty" yaml:"journald,omitempty"` HostCGroups *HostCGroups `json:"cgroups,omitempty" yaml:"cgroups,omitempty"` } diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 1a003f03a..4ea1bd9a1 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -2093,6 +2093,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) { *out = new(HostKernelConfigs) (*in).DeepCopyInto(*out) } + if in.HostJournald != nil { + in, out := &in.HostJournald, &out.HostJournald + *out = new(HostJournald) + (*in).DeepCopyInto(*out) + } if in.HostCGroups != nil { in, out := &in.HostCGroups, &out.HostCGroups *out = new(HostCGroups) @@ -2288,6 +2293,27 @@ 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 *HostJournald) DeepCopyInto(out *HostJournald) { + *out = *in + in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta) + if in.Units != nil { + in, out := &in.Units, &out.Units + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostJournald. +func (in *HostJournald) DeepCopy() *HostJournald { + if in == nil { + return nil + } + out := new(HostJournald) + in.DeepCopyInto(out) + 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 diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index c60a21683..e3b346a46 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -19094,6 +19094,47 @@ } } }, + "journald": { + "type": "object", + "properties": { + "collectorName": { + "type": "string" + }, + "dmesg": { + "type": "boolean" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "lines": { + "type": "integer" + }, + "output": { + "type": "string" + }, + "reverse": { + "type": "boolean" + }, + "since": { + "type": "string" + }, + "system": { + "type": "boolean" + }, + "units": { + "type": "array", + "items": { + "type": "string" + } + }, + "until": { + "type": "string" + }, + "utc": { + "type": "boolean" + } + } + }, "kernelConfigs": { "type": "object", "properties": { From 3770fd0b0ad71848902e2eac0075671d5c175058 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 24 Jul 2024 12:24:34 +1000 Subject: [PATCH 2/4] implement journald host collector --- .../crds/troubleshoot.sh_hostcollectors.yaml | 2 + .../crds/troubleshoot.sh_hostpreflights.yaml | 2 + .../crds/troubleshoot.sh_supportbundles.yaml | 2 + .../v1beta2/hostcollector_shared.go | 1 + pkg/collect/host_journald.go | 156 ++++++++++++++++++ pkg/collect/host_journald_test.go | 44 +++++ .../supportbundle-troubleshoot-v1beta2.json | 3 + 7 files changed, 210 insertions(+) create mode 100644 pkg/collect/host_journald.go create mode 100644 pkg/collect/host_journald_test.go diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 0fede10c8..36a2bfe30 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1476,6 +1476,8 @@ spec: type: string system: type: boolean + timeout: + type: string units: items: type: string diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index 3a8ac9bb0..1417b32bd 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1476,6 +1476,8 @@ spec: type: string system: type: boolean + timeout: + type: string units: items: type: string diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 9cefe528a..84828d7a8 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20075,6 +20075,8 @@ spec: type: string system: type: boolean + timeout: + type: string units: items: type: string diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index ccde92496..bc6255f7a 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -210,6 +210,7 @@ type HostJournald struct { Lines int `json:"lines,omitempty" yaml:"lines,omitempty"` Reverse bool `json:"reverse,omitempty" yaml:"reverse,omitempty"` Utc bool `json:"utc,omitempty" yaml:"utc,omitempty"` + Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` } type HostCollect struct { diff --git a/pkg/collect/host_journald.go b/pkg/collect/host_journald.go new file mode 100644 index 000000000..655c17309 --- /dev/null +++ b/pkg/collect/host_journald.go @@ -0,0 +1,156 @@ +package collect + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os/exec" + "path/filepath" + "strconv" + "time" + + "github.com/pkg/errors" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "k8s.io/klog/v2" +) + +type CollectHostJournald struct { + hostCollector *troubleshootv1beta2.HostJournald + BundlePath string +} + +const HostJournaldPath = `host-collectors/journald/` + +func (c *CollectHostJournald) Title() string { + return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "journald") +} + +func (c *CollectHostJournald) IsExcluded() (bool, error) { + return isExcluded(c.hostCollector.Exclude) +} + +func (c *CollectHostJournald) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { + + // collector name check + collectorName := c.hostCollector.CollectorName + if collectorName == "" { + return nil, errors.New("collector name is required") + } + + // timeout check + timeout, err := getTimeout(c.hostCollector.Timeout) + if err != nil { + return nil, errors.Wrap(err, "failed to parse timeout") + } + + // set timeout context + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + // prepare command options + cmdOptions, err := generateOptions(c.hostCollector) + if err != nil { + return nil, errors.Wrap(err, "failed to generate journalctl options") + } + + // run journalctl and capture output + klog.V(2).Infof("Running journalctl with options: %v", cmdOptions) + var stdout, stderr bytes.Buffer + cmd := exec.CommandContext(ctx, "journalctl", cmdOptions...) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + cmdInfo := HostRunInfo{ + Command: cmd.String(), + ExitCode: "0", + } + + if err := cmd.Run(); err != nil { + klog.V(2).Infof("journalctl command failed: %v", err) + if err == context.DeadlineExceeded { + cmdInfo.ExitCode = "124" + cmdInfo.Error = fmt.Sprintf("command timed out after %s", timeout.String()) + } else if exitError, ok := err.(*exec.ExitError); ok { + cmdInfo.ExitCode = strconv.Itoa(exitError.ExitCode()) + cmdInfo.Error = stderr.String() + } else { + return nil, errors.Wrap(err, "failed to run journalctl") + } + } + + output := NewResult() + + // write info file + infoJsonBytes, err := json.Marshal(cmdInfo) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal journalctl info result") + } + infoFileName := getOutputInfoFile(collectorName) + output.SaveResult(c.BundlePath, infoFileName, bytes.NewBuffer(infoJsonBytes)) + + // write actual journalctl output + outputFileName := getOutputFile(collectorName) + klog.V(2).Infof("Saving journalctl output to %q in bundle", outputFileName) + output.SaveResult(c.BundlePath, outputFileName, bytes.NewBuffer(stdout.Bytes())) + + return output, nil +} + +func generateOptions(jd *troubleshootv1beta2.HostJournald) ([]string, error) { + options := []string{} + + if jd.System { + options = append(options, "--system") + } + + if jd.Dmesg { + options = append(options, "--dmesg") + } + + for _, unit := range jd.Units { + options = append(options, "-u", unit) + } + + if jd.Since != "" { + options = append(options, "--since", jd.Since) + } + + if jd.Until != "" { + options = append(options, "--until", jd.Until) + } + + if jd.Output != "" { + options = append(options, "--output", jd.Output) + } + + if jd.Lines > 0 { + options = append(options, "-n", strconv.Itoa(jd.Lines)) + } + + if jd.Reverse { + options = append(options, "--reverse") + } + + if jd.Utc { + options = append(options, "--utc") + } + + return options, nil +} + +func getOutputFile(collectorName string) string { + return filepath.Join(HostJournaldPath, collectorName+".txt") +} + +func getOutputInfoFile(collectorName string) string { + return filepath.Join(HostJournaldPath, collectorName+"-info.json") +} + +func getTimeout(timeout string) (time.Duration, error) { + if timeout == "" { + return 30 * time.Second, nil + } + + return time.ParseDuration(timeout) +} diff --git a/pkg/collect/host_journald_test.go b/pkg/collect/host_journald_test.go new file mode 100644 index 000000000..6d02de2ba --- /dev/null +++ b/pkg/collect/host_journald_test.go @@ -0,0 +1,44 @@ +package collect + +import ( + "reflect" + "testing" + + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" +) + +func TestGenerateOptions(t *testing.T) { + jd := &troubleshootv1beta2.HostJournald{ + System: true, + Dmesg: true, + Units: []string{"unit1", "unit2"}, + Since: "2022-01-01", + Until: "2022-01-31", + Output: "json", + Lines: 100, + Reverse: true, + Utc: true, + } + + expectedOptions := []string{ + "--system", + "--dmesg", + "-u", "unit1", + "-u", "unit2", + "--since", "2022-01-01", + "--until", "2022-01-31", + "--output", "json", + "-n", "100", + "--reverse", + "--utc", + } + + options, err := generateOptions(jd) + if err != nil { + t.Fatalf("generateOptions failed with error: %v", err) + } + + if !reflect.DeepEqual(options, expectedOptions) { + t.Errorf("generateOptions returned incorrect options.\nExpected: %v\nActual: %v", expectedOptions, options) + } +} diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index e3b346a46..cd22fae94 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -19121,6 +19121,9 @@ "system": { "type": "boolean" }, + "timeout": { + "type": "string" + }, "units": { "type": "array", "items": { From cda4b3458e9103bb9447971a6b7d4e50fd4a4cc3 Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 24 Jul 2024 12:35:06 +1000 Subject: [PATCH 3/4] update host collector --- 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 48565e803..aa1cdf5a1 100644 --- a/pkg/collect/host_collector.go +++ b/pkg/collect/host_collector.go @@ -63,6 +63,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str return &CollectHostCopy{collector.HostCopy, bundlePath}, true case collector.HostKernelConfigs != nil: return &CollectHostKernelConfigs{collector.HostKernelConfigs, bundlePath}, true + case collector.HostJournald != nil: + return &CollectHostJournald{collector.HostJournald, bundlePath}, true case collector.HostCGroups != nil: return &CollectHostCGroups{collector.HostCGroups, bundlePath}, true default: From f453367d7a1e53d4f0567c610d03ebcf9fa38bba Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 24 Jul 2024 14:35:51 +1000 Subject: [PATCH 4/4] add --no-pager --- pkg/collect/host_journald.go | 3 +++ pkg/collect/host_journald_test.go | 1 + 2 files changed, 4 insertions(+) diff --git a/pkg/collect/host_journald.go b/pkg/collect/host_journald.go index 655c17309..c85caebd4 100644 --- a/pkg/collect/host_journald.go +++ b/pkg/collect/host_journald.go @@ -136,6 +136,9 @@ func generateOptions(jd *troubleshootv1beta2.HostJournald) ([]string, error) { options = append(options, "--utc") } + // opinionated on --no-pager + options = append(options, "--no-pager") + return options, nil } diff --git a/pkg/collect/host_journald_test.go b/pkg/collect/host_journald_test.go index 6d02de2ba..a0da70aac 100644 --- a/pkg/collect/host_journald_test.go +++ b/pkg/collect/host_journald_test.go @@ -31,6 +31,7 @@ func TestGenerateOptions(t *testing.T) { "-n", "100", "--reverse", "--utc", + "--no-pager", } options, err := generateOptions(jd)