Skip to content

Commit

Permalink
feat: [sc-108689] troubleshoot: journald collector (#1586)
Browse files Browse the repository at this point in the history
* add schema for Journald Host Collector

* implement journald host collector

* update host collector

* add --no-pager
  • Loading branch information
nvanthao authored Jul 28, 2024
1 parent 01d5804 commit a57171a
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 0 deletions.
29 changes: 29 additions & 0 deletions config/crds/troubleshoot.sh_hostcollectors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,35 @@ 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
timeout:
type: string
units:
items:
type: string
type: array
until:
type: string
utc:
type: boolean
type: object
kernelConfigs:
properties:
collectorName:
Expand Down
29 changes: 29 additions & 0 deletions config/crds/troubleshoot.sh_hostpreflights.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,35 @@ 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
timeout:
type: string
units:
items:
type: string
type: array
until:
type: string
utc:
type: boolean
type: object
kernelConfigs:
properties:
collectorName:
Expand Down
29 changes: 29 additions & 0 deletions config/crds/troubleshoot.sh_supportbundles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20057,6 +20057,35 @@ 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
timeout:
type: string
units:
items:
type: string
type: array
until:
type: string
utc:
type: boolean
type: object
kernelConfigs:
properties:
collectorName:
Expand Down
15 changes: 15 additions & 0 deletions pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ 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"`
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
}

type HostCollect struct {
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
Expand All @@ -224,6 +238,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"`
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/collect/host_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
159 changes: 159 additions & 0 deletions pkg/collect/host_journald.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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")
}

// opinionated on --no-pager
options = append(options, "--no-pager")

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)
}
45 changes: 45 additions & 0 deletions pkg/collect/host_journald_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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",
"--no-pager",
}

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)
}
}
Loading

0 comments on commit a57171a

Please sign in to comment.