Skip to content

Commit

Permalink
feat(analyzer): enable host os info analyzer to support multiple nodes (
Browse files Browse the repository at this point in the history
  • Loading branch information
DexterYan authored Sep 25, 2024
1 parent 83f02f4 commit 142015c
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 33 deletions.
111 changes: 83 additions & 28 deletions pkg/analyze/host_os_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ type AnalyzeHostOS struct {
hostAnalyzer *troubleshootv1beta2.HostOSAnalyze
}

type NodeOSInfo struct {
NodeName string
collect.HostOSInfo
}

func (a *AnalyzeHostOS) Title() string {
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Host OS Info")
}
Expand All @@ -27,30 +32,93 @@ func (a *AnalyzeHostOS) IsExcluded() (bool, error) {
func (a *AnalyzeHostOS) Analyze(
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
) ([]*AnalyzeResult, error) {

var nodesOSInfo []NodeOSInfo
result := AnalyzeResult{}
result.Title = a.Title()

// check if the host os info file exists (local mode)
contents, err := getCollectedFileContents(collect.HostOSInfoPath)
if err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to get collected file")
//check if the node list file exists (remote mode)
contents, err := getCollectedFileContents(collect.NODE_LIST_FILE)
if err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to get collected file")
}

var nodes collect.HostOSInfoNodes
if err := json.Unmarshal(contents, &nodes); err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to unmarshal host os info nodes")
}

// iterate over each node and analyze the host os info
for _, node := range nodes.Nodes {
contents, err := getCollectedFileContents(collect.NodeInfoBaseDir + "/" + node + "/" + collect.HostInfoFileName)
if err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to get collected file")
}

var osInfo collect.HostOSInfo
if err := json.Unmarshal(contents, &osInfo); err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to unmarshal host os info")
}

nodesOSInfo = append(nodesOSInfo, NodeOSInfo{NodeName: node, HostOSInfo: osInfo})
}

results, err := analyzeOSVersionResult(nodesOSInfo, a.hostAnalyzer.Outcomes, a.Title())
if err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to analyze os version result")
}
return results, nil
}

var osInfo collect.HostOSInfo
if err := json.Unmarshal(contents, &osInfo); err != nil {
return []*AnalyzeResult{&result}, errors.Wrap(err, "failed to unmarshal host os info")
}
nodesOSInfo = append(nodesOSInfo, NodeOSInfo{NodeName: "", HostOSInfo: osInfo})
return analyzeOSVersionResult(nodesOSInfo, a.hostAnalyzer.Outcomes, a.Title())
}

func analyzeOSVersionResult(nodesOSInfo []NodeOSInfo, outcomes []*troubleshootv1beta2.Outcome, title string) ([]*AnalyzeResult, error) {
var results []*AnalyzeResult
for _, osInfo := range nodesOSInfo {
if title == "" {
title = "Host OS Info"
}

analyzeResult, err := analyzeByOutcomes(outcomes, osInfo, title)
if err != nil {
return nil, errors.Wrap(err, "failed to analyze condition")
}
results = append(results, analyzeResult...)
}

return analyzeOSVersionResult(osInfo, a.hostAnalyzer.Outcomes, a.Title())
return results, nil
}

func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv1beta2.Outcome, title string) ([]*AnalyzeResult, error) {
var rx = regexp.MustCompile(`^[0-9]+\.?[0-9]*\.?[0-9]*`)

if title == "" {
title = "Host OS Info"
func fixVersion(versionStr string) string {

splitStr := strings.Split(versionStr, ".")
for i := 0; i < len(splitStr); i++ {
if splitStr[i] != "0" {
splitStr[i] = strings.TrimPrefix(splitStr[i], "0")
}
}
fixTrailZero := strings.Join(splitStr, ".")
version := rx.FindString(fixTrailZero)
version = strings.TrimRight(version, ".")
return version
}

func analyzeByOutcomes(outcomes []*troubleshootv1beta2.Outcome, osInfo NodeOSInfo, title string) ([]*AnalyzeResult, error) {
var results []*AnalyzeResult
for _, outcome := range outcomes {
if osInfo.NodeName != "" {
title = fmt.Sprintf("%s - Node %s", title, osInfo.NodeName)
}

result := AnalyzeResult{
Title: title,
Expand Down Expand Up @@ -82,7 +150,8 @@ func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv
result.URI = uri
// When is usually empty as the final case and should be treated as true
if when == "" {
return []*AnalyzeResult{&result}, nil
results = append(results, &result)
return results, nil
}

parts := strings.Split(when, " ")
Expand All @@ -109,7 +178,8 @@ func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv
return []*AnalyzeResult{}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
}
if whenRange(toleratedKernelVer) {
return []*AnalyzeResult{&result}, nil
results = append(results, &result)
return results, nil
}
}

Expand All @@ -125,7 +195,8 @@ func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
}
if whenRange(toleratedKernelVer) {
return []*AnalyzeResult{&result}, nil
results = append(results, &result)
return results, nil
}
}
// Match the platform version
Expand All @@ -137,26 +208,10 @@ func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedDistVer)
}
if whenRange(toleratedDistVer) {
return []*AnalyzeResult{&result}, nil
results = append(results, &result)
return results, nil
}
}
}

return []*AnalyzeResult{}, nil
}

var rx = regexp.MustCompile(`^[0-9]+\.?[0-9]*\.?[0-9]*`)

func fixVersion(versionStr string) string {

splitStr := strings.Split(versionStr, ".")
for i := 0; i < len(splitStr); i++ {
if splitStr[i] != "0" {
splitStr[i] = strings.TrimPrefix(splitStr[i], "0")
}
}
fixTrailZero := strings.Join(splitStr, ".")
version := rx.FindString(fixTrailZero)
version = strings.TrimRight(version, ".")
return version
return results, nil
}
99 changes: 99 additions & 0 deletions pkg/analyze/host_os_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,102 @@ func TestAnalyzeHostOS(t *testing.T) {
})
}
}

func TestAnalyzeOSVersionResult(t *testing.T) {
tests := []struct {
name string
outcomes []*troubleshootv1beta2.Outcome
nodeOSInfo []NodeOSInfo
expectResult []*AnalyzeResult
}{
{
name: "pass if ubuntu >= 0.1.2",
nodeOSInfo: []NodeOSInfo{
{
NodeName: "node1",
HostOSInfo: collect.HostOSInfo{
Name: "myhost",
KernelVersion: "5.4.0-1034-gcp",
PlatformVersion: "00.1.2",
Platform: "ubuntu",
},
},
},
outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "ubuntu >= 00.1.2",
Message: "supported distribution matches ubuntu >= 00.1.2",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "unsupported distribution",
},
},
},
expectResult: []*AnalyzeResult{
{
Title: "Host OS Info - Node node1",
IsPass: true,
Message: "supported distribution matches ubuntu >= 00.1.2",
},
},
},
{
name: "fail if ubuntu <= 11.04",
nodeOSInfo: []NodeOSInfo{
{
NodeName: "node1",
HostOSInfo: collect.HostOSInfo{
Name: "myhost",
KernelVersion: "5.4.0-1034-gcp",
PlatformVersion: "11.04",
Platform: "ubuntu",
},
},
{
NodeName: "node2",
HostOSInfo: collect.HostOSInfo{
Name: "myhost",
KernelVersion: "5.4.0-1034-gcp",
PlatformVersion: "11.04",
Platform: "ubuntu",
},
},
},
outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "ubuntu <= 11.04",
Message: "unsupported ubuntu version 11.04",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "supported distribution",
},
},
},
expectResult: []*AnalyzeResult{
{
Title: "Host OS Info - Node node1",
IsFail: true,
Message: "unsupported ubuntu version 11.04",
},
{
Title: "Host OS Info - Node node2",
IsFail: true,
Message: "unsupported ubuntu version 11.04",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := analyzeOSVersionResult(test.nodeOSInfo, test.outcomes, "Host OS Info")
require.NoError(t, err)
assert.Equal(t, test.expectResult, result)
})
}
}
2 changes: 2 additions & 0 deletions pkg/collect/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"k8s.io/client-go/rest"
)

const NODE_LIST_FILE = "host-collectors/system/node_list.json"

var (
// ErrCollectorNotFound is returned when an undefined host collector is
// specified by the user.
Expand Down
27 changes: 23 additions & 4 deletions pkg/collect/host_os_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package collect
import (
"bytes"
"encoding/json"
"fmt"
"os"

"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
Expand All @@ -17,7 +19,13 @@ type HostOSInfo struct {
Platform string `json:"platform"`
}

type HostOSInfoNodes struct {
Nodes []string `json:"nodes"`
}

const HostOSInfoPath = `host-collectors/system/hostos_info.json`
const NodeInfoBaseDir = `host-collectors/system`
const HostInfoFileName = `hostos_info.json`

type CollectHostOS struct {
hostCollector *troubleshootv1beta2.HostOS
Expand Down Expand Up @@ -87,9 +95,10 @@ func (c *CollectHostOS) RemoteCollect(progressChan chan<- interface{}) (map[stri
}

output := NewResult()
nodes := []string{}

// save the first result we find in the node and save it
for _, result := range results.AllCollectedData {
for node, result := range results.AllCollectedData {
var nodeResult map[string]string
if err := json.Unmarshal(result, &nodeResult); err != nil {
return nil, errors.Wrap(err, "failed to marshal node results")
Expand All @@ -105,10 +114,20 @@ func (c *CollectHostOS) RemoteCollect(progressChan chan<- interface{}) (map[stri
if err != nil {
return nil, errors.Wrap(err, "failed to marshal host os info")
}
output.SaveResult(c.BundlePath, HostOSInfoPath, bytes.NewBuffer(b))
return output, nil
nodes = append(nodes, node)
output.SaveResult(c.BundlePath, fmt.Sprintf("host-collectors/system/%s/%s", node, HostInfoFileName), bytes.NewBuffer(b))
}
}

return nil, errors.New("failed to find host os info")
// check if NODE_LIST_FILE exists
_, err = os.Stat(NODE_LIST_FILE)
// if it not exists, save the nodes list
if err != nil {
nodesBytes, err := json.MarshalIndent(HostOSInfoNodes{Nodes: nodes}, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal host os info nodes")
}
output.SaveResult(c.BundlePath, NODE_LIST_FILE, bytes.NewBuffer(nodesBytes))
}
return output, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestHostOSRemoteCollector(t *testing.T) {
}{
{
paths: []string{
"hostos_info.json",
"node_list.json",
},
expectType: "file",
},
Expand Down

0 comments on commit 142015c

Please sign in to comment.