mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
Refactor Multi Node Analyzers (#1646)
* initial refactor of host os analyzer * refactor remote collect analysis --------- Signed-off-by: Evans Mungai <evans@replicated.com> Co-authored-by: Gerard Nguyen <gerard@replicated.com> Co-authored-by: Evans Mungai <evans@replicated.com>
This commit is contained in:
58
pkg/analyze/collected_contents.go
Normal file
58
pkg/analyze/collected_contents.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
)
|
||||
|
||||
type collectedContent struct {
|
||||
NodeName string
|
||||
Data collectorData
|
||||
}
|
||||
|
||||
type collectorData interface{}
|
||||
|
||||
type nodeNames struct {
|
||||
Nodes []string `json:"nodes"`
|
||||
}
|
||||
|
||||
func retrieveCollectedContents(
|
||||
getCollectedFileContents func(string) ([]byte, error),
|
||||
localPath string, remoteNodeBaseDir string, remoteFileName string,
|
||||
) ([]collectedContent, error) {
|
||||
var collectedContents []collectedContent
|
||||
|
||||
// Try to retrieve local data first
|
||||
if contents, err := getCollectedFileContents(localPath); err == nil {
|
||||
collectedContents = append(collectedContents, collectedContent{NodeName: "", Data: contents})
|
||||
// Return immediately if local content is available
|
||||
return collectedContents, nil
|
||||
}
|
||||
|
||||
// Local data not available, move to remote collection
|
||||
nodeListContents, err := getCollectedFileContents(constants.NODE_LIST_FILE)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get node list")
|
||||
}
|
||||
|
||||
var nodeNames nodeNames
|
||||
if err := json.Unmarshal(nodeListContents, &nodeNames); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal node names")
|
||||
}
|
||||
|
||||
// Collect data for each node
|
||||
for _, node := range nodeNames.Nodes {
|
||||
nodeFilePath := fmt.Sprintf("%s/%s/%s", remoteNodeBaseDir, node, remoteFileName)
|
||||
nodeContents, err := getCollectedFileContents(nodeFilePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to retrieve content for node %s", node)
|
||||
}
|
||||
|
||||
collectedContents = append(collectedContents, collectedContent{NodeName: node, Data: nodeContents})
|
||||
}
|
||||
|
||||
return collectedContents, nil
|
||||
}
|
||||
138
pkg/analyze/collected_contents_test.go
Normal file
138
pkg/analyze/collected_contents_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRetrieveCollectedContents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
getCollectedFileContents func(string) ([]byte, error) // Mock function
|
||||
localPath string
|
||||
remoteNodeBaseDir string
|
||||
remoteFileName string
|
||||
expectedResult []collectedContent
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successfully retrieve local content",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == "localPath" {
|
||||
return []byte("localContent"), nil
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: []collectedContent{
|
||||
{
|
||||
NodeName: "",
|
||||
Data: []byte("localContent"),
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "local content not found, retrieve remote node content successfully",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
nodeNames := nodeNames{Nodes: []string{"node1", "node2"}}
|
||||
return json.Marshal(nodeNames)
|
||||
}
|
||||
if path == "remoteBaseDir/node1/remoteFileName" {
|
||||
return []byte("remoteContent1"), nil
|
||||
}
|
||||
if path == "remoteBaseDir/node2/remoteFileName" {
|
||||
return []byte("remoteContent2"), nil
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: []collectedContent{
|
||||
{
|
||||
NodeName: "node1",
|
||||
Data: []byte("remoteContent1"),
|
||||
},
|
||||
{
|
||||
NodeName: "node2",
|
||||
Data: []byte("remoteContent2"),
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "fail to retrieve local content and node list",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to get node list",
|
||||
},
|
||||
{
|
||||
name: "fail to retrieve content for one of the nodes",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
nodeNames := nodeNames{Nodes: []string{"node1", "node2"}}
|
||||
return json.Marshal(nodeNames)
|
||||
}
|
||||
if path == "remoteBaseDir/node1/remoteFileName" {
|
||||
return []byte("remoteContent1"), nil
|
||||
}
|
||||
if path == "remoteBaseDir/node2/remoteFileName" {
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to retrieve content for node node2",
|
||||
},
|
||||
{
|
||||
name: "fail to unmarshal node list",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
return []byte("invalidJSON"), nil
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to unmarshal node names",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := retrieveCollectedContents(
|
||||
test.getCollectedFileContents,
|
||||
test.localPath,
|
||||
test.remoteNodeBaseDir,
|
||||
test.remoteFileName,
|
||||
)
|
||||
|
||||
if test.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package analyzer
|
||||
|
||||
import troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type HostAnalyzer interface {
|
||||
Title() string
|
||||
@@ -83,3 +88,102 @@ func (c *resultCollector) get(title string) []*AnalyzeResult {
|
||||
}
|
||||
return []*AnalyzeResult{{Title: title, IsWarn: true, Message: "no results"}}
|
||||
}
|
||||
|
||||
func analyzeHostCollectorResults(collectedContent []collectedContent, outcomes []*troubleshootv1beta2.Outcome, checkCondition func(string, collectorData) (bool, error), title string) ([]*AnalyzeResult, error) {
|
||||
var results []*AnalyzeResult
|
||||
for _, content := range collectedContent {
|
||||
currentTitle := title
|
||||
if content.NodeName != "" {
|
||||
currentTitle = fmt.Sprintf("%s - Node %s", title, content.NodeName)
|
||||
}
|
||||
|
||||
analyzeResult, err := evaluateOutcomes(outcomes, checkCondition, content.Data, currentTitle)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to evaluate outcomes")
|
||||
}
|
||||
if analyzeResult != nil {
|
||||
results = append(results, analyzeResult...)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func evaluateOutcomes(outcomes []*troubleshootv1beta2.Outcome, checkCondition func(string, collectorData) (bool, error), data collectorData, title string) ([]*AnalyzeResult, error) {
|
||||
var results []*AnalyzeResult
|
||||
|
||||
for _, outcome := range outcomes {
|
||||
result := AnalyzeResult{
|
||||
Title: title,
|
||||
}
|
||||
|
||||
switch {
|
||||
case outcome.Fail != nil:
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
isMatch, err := checkCondition(outcome.Fail.When, data)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
case outcome.Warn != nil:
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
isMatch, err := checkCondition(outcome.Warn.When, data)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
case outcome.Pass != nil:
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
isMatch, err := checkCondition(outcome.Pass.When, data)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
299
pkg/analyze/host_analyzer_test.go
Normal file
299
pkg/analyze/host_analyzer_test.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostCollectorResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
collectedContent []collectedContent
|
||||
checkCondition func(string, collectorData) (bool, error)
|
||||
expectResult []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "pass if ubuntu >= 00.1.2",
|
||||
collectedContent: []collectedContent{
|
||||
{
|
||||
NodeName: "node1",
|
||||
Data: 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
osInfo := data.(collect.HostOSInfo)
|
||||
return osInfo.Platform == "ubuntu" && osInfo.PlatformVersion >= "00.1.2", nil
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info - Node node1",
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches ubuntu >= 00.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail if ubuntu <= 11.04",
|
||||
collectedContent: []collectedContent{
|
||||
{
|
||||
NodeName: "node1",
|
||||
Data: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "11.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
},
|
||||
{
|
||||
NodeName: "node2",
|
||||
Data: 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
osInfo := data.(collect.HostOSInfo)
|
||||
return osInfo.Platform == "ubuntu" && osInfo.PlatformVersion <= "11.04", nil
|
||||
},
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "title does not include node name if empty",
|
||||
collectedContent: []collectedContent{
|
||||
{
|
||||
NodeName: "",
|
||||
Data: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "20.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu >= 20.04",
|
||||
Message: "supported distribution matches ubuntu >= 20.04",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "unsupported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
osInfo := data.(collect.HostOSInfo)
|
||||
return osInfo.Platform == "ubuntu" && osInfo.PlatformVersion >= "20.04", nil
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info", // Ensuring the title does not include node name if it's empty
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches ubuntu >= 20.04",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// Call the new analyzeHostCollectorResults function with the test data
|
||||
result, err := analyzeHostCollectorResults(test.collectedContent, test.outcomes, test.checkCondition, "Host OS Info")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateOutcomes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
checkCondition func(string, collectorData) (bool, error)
|
||||
data collectorData
|
||||
expectedResult []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "fail condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "failCondition",
|
||||
Message: "failure condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Return true if the condition being checked matches "failCondition"
|
||||
return when == "failCondition", nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsFail: true,
|
||||
Message: "failure condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "warn condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "warnCondition",
|
||||
Message: "warning condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Return true if the condition being checked matches "warnCondition"
|
||||
return when == "warnCondition", nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsWarn: true,
|
||||
Message: "warning condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "passCondition",
|
||||
Message: "pass condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Return true if the condition being checked matches "passCondition"
|
||||
return when == "passCondition", nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsPass: true,
|
||||
Message: "pass condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "failCondition",
|
||||
Message: "failure condition met",
|
||||
},
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "warnCondition",
|
||||
Message: "warning condition met",
|
||||
},
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "passCondition",
|
||||
Message: "pass condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Always return false to simulate no condition matching
|
||||
return false, nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: nil, // No condition matches, so we expect no results
|
||||
},
|
||||
{
|
||||
name: "error in checkCondition",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "failCondition",
|
||||
Message: "failure condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Simulate an error occurring during condition evaluation
|
||||
return false, errors.New("mock error")
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsFail: false, // Error occurred, so no success flag
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := evaluateOutcomes(test.outcomes, test.checkCondition, test.data, "Test Title")
|
||||
|
||||
if test.name == "error in checkCondition" {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package analyzer
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -26,95 +27,40 @@ func (a *AnalyzeHostMemory) IsExcluded() (bool, error) {
|
||||
func (a *AnalyzeHostMemory) Analyze(
|
||||
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
|
||||
) ([]*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
result := AnalyzeResult{Title: a.Title()}
|
||||
|
||||
contents, err := getCollectedFileContents(collect.HostMemoryPath)
|
||||
// Use the generic function to collect both local and remote data
|
||||
collectedContents, err := retrieveCollectedContents(
|
||||
getCollectedFileContents,
|
||||
collect.HostMemoryPath, // Local path
|
||||
collect.NodeInfoBaseDir, // Remote base directory
|
||||
collect.HostMemoryFileName, // Remote file name
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
return []*AnalyzeResult{&result}, err
|
||||
}
|
||||
|
||||
memoryInfo := collect.MemoryInfo{}
|
||||
if err := json.Unmarshal(contents, &memoryInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal memory info")
|
||||
results, err := analyzeHostCollectorResults(collectedContents, a.hostAnalyzer.Outcomes, a.CheckCondition, a.Title())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to analyze OS version")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
}
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostMemoryConditionalToActual(outcome.Fail.When, memoryInfo.Total)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostMemoryConditionalToActual(outcome.Warn.When, memoryInfo.Total)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostMemoryConditionalToActual(outcome.Pass.When, memoryInfo.Total)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func compareHostMemoryConditionalToActual(conditional string, total uint64) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
// checkCondition checks the condition of the when clause
|
||||
func (a *AnalyzeHostMemory) CheckCondition(when string, data collectorData) (bool, error) {
|
||||
rawData, ok := data.([]byte)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("expected data to be []uint8 (raw bytes), got: %v", reflect.TypeOf(data))
|
||||
}
|
||||
|
||||
var memInfo collect.MemoryInfo
|
||||
if err := json.Unmarshal(rawData, &memInfo); err != nil {
|
||||
return false, fmt.Errorf("failed to unmarshal data into MemoryInfo: %v", err)
|
||||
}
|
||||
|
||||
parts := strings.Split(when, " ")
|
||||
if len(parts) != 2 {
|
||||
return false, fmt.Errorf("Expected 2 parts in conditional, got %d", len(parts))
|
||||
}
|
||||
@@ -129,19 +75,23 @@ func compareHostMemoryConditionalToActual(conditional string, total uint64) (res
|
||||
if !ok {
|
||||
return false, fmt.Errorf("could not parse quantity %q", desired)
|
||||
}
|
||||
if desiredInt < 0 {
|
||||
return false, fmt.Errorf("desired value must be a positive integer, got %d", desiredInt)
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return total < uint64(desiredInt), nil
|
||||
return memInfo.Total < uint64(desiredInt), nil
|
||||
case "<=":
|
||||
return total <= uint64(desiredInt), nil
|
||||
return memInfo.Total <= uint64(desiredInt), nil
|
||||
case ">":
|
||||
return total > uint64(desiredInt), nil
|
||||
return memInfo.Total > uint64(desiredInt), nil
|
||||
case ">=":
|
||||
return total >= uint64(desiredInt), nil
|
||||
return memInfo.Total >= uint64(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return total == uint64(desiredInt), nil
|
||||
return memInfo.Total == uint64(desiredInt), nil
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported operator: %q", operator)
|
||||
}
|
||||
|
||||
return false, errors.New("unknown operator")
|
||||
}
|
||||
|
||||
@@ -2,91 +2,110 @@ package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_doCompareHostMemory(t *testing.T) {
|
||||
func TestAnalyzeHostMemoryCheckCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
actual uint64
|
||||
expected bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "< 16Gi when actual is 8Gi",
|
||||
conditional: "< 16Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
actual: 8 * 1024 * 1024 * 1024, // 8GiB
|
||||
expected: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "< 8Gi when actual is 8Gi",
|
||||
conditional: "< 8Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
actual: 8 * 1024 * 1024 * 1024, // 8GiB
|
||||
expected: false,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "<= 8Gi when actual is 8Gi",
|
||||
conditional: "<= 8Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
actual: 8 * 1024 * 1024 * 1024, // 8GiB
|
||||
expected: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "<= 8Gi when actual is 16Gi",
|
||||
conditional: "<= 8Gi",
|
||||
actual: 16 * 1024 * 1024 * 1024,
|
||||
actual: 16 * 1024 * 1024 * 1024, // 16GiB
|
||||
expected: false,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "== 8Gi when actual is 16Gi",
|
||||
conditional: "== 8Gi",
|
||||
actual: 16 * 1024 * 1024 * 1024,
|
||||
actual: 16 * 1024 * 1024 * 1024, // 16GiB
|
||||
expected: false,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "== 8Gi when actual is 8Gi",
|
||||
conditional: "== 8Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
actual: 8 * 1024 * 1024 * 1024, // 8GiB
|
||||
expected: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "== 8000000000 when actual is 8000000000",
|
||||
conditional: "== 8000000000",
|
||||
actual: 8 * 1000 * 1000 * 1000,
|
||||
actual: 8 * 1000 * 1000 * 1000, // 8GB in decimal
|
||||
expected: true,
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
// Create the AnalyzeHostMemory object
|
||||
analyzeHostMemory := AnalyzeHostMemory{}
|
||||
|
||||
actual, err := compareHostMemoryConditionalToActual(test.conditional, test.actual)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
// Simulate the memory info as JSON-encoded data
|
||||
memInfo := collect.MemoryInfo{
|
||||
Total: test.actual,
|
||||
}
|
||||
rawData, err := json.Marshal(memInfo)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call the CheckCondition method
|
||||
result, err := analyzeHostMemory.CheckCondition(test.conditional, rawData)
|
||||
if test.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeHostMemory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
memoryInfo *collect.MemoryInfo
|
||||
hostAnalyzer *troubleshootv1beta2.MemoryAnalyze
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
name string
|
||||
hostAnalyzer *troubleshootv1beta2.MemoryAnalyze
|
||||
getCollectedFileContents func(string) ([]byte, error)
|
||||
expectedResults []*AnalyzeResult
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Pass on memory available",
|
||||
memoryInfo: &collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
name: "Pass on memory available (local)",
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
@@ -97,19 +116,27 @@ func TestAnalyzeHostMemory(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
// Simulate local memory content retrieval
|
||||
if path == collect.HostMemoryPath {
|
||||
memoryInfo := collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024, // 8GiB
|
||||
}
|
||||
return json.Marshal(memoryInfo)
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
expectedResults: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Amount of Memory",
|
||||
IsPass: true,
|
||||
Message: "System has at least 4Gi of memory",
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Fail on memory available",
|
||||
memoryInfo: &collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
name: "Fail on memory available (remote node)",
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
@@ -120,27 +147,33 @@ func TestAnalyzeHostMemory(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
// Simulate remote node list and memory content retrieval
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
nodeNames := nodeNames{Nodes: []string{"node1"}}
|
||||
return json.Marshal(nodeNames)
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostMemoryFileName) {
|
||||
memoryInfo := collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024, // 8GiB for remote node
|
||||
}
|
||||
return json.Marshal(memoryInfo)
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
expectedResults: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Amount of Memory",
|
||||
Title: "Amount of Memory - Node node1",
|
||||
IsFail: true,
|
||||
Message: "System requires at least 16Gi of memory",
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Warn on memory available",
|
||||
memoryInfo: &collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
name: "Warn on memory available (remote node)",
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 4Gi",
|
||||
Message: "System requires at least 4Gi of memory",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "<= 8Gi",
|
||||
@@ -149,97 +182,123 @@ func TestAnalyzeHostMemory(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
// Simulate remote node list and memory content retrieval
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
nodeNames := nodeNames{Nodes: []string{"node1"}}
|
||||
return json.Marshal(nodeNames)
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostMemoryFileName) {
|
||||
memoryInfo := collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024, // 8GiB for remote node
|
||||
}
|
||||
return json.Marshal(memoryInfo)
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
expectedResults: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Amount of Memory",
|
||||
Title: "Amount of Memory - Node node1",
|
||||
IsWarn: true,
|
||||
Message: "System performs best with more than 8Gi of memory",
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.memoryInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostMemory{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostMemoryAnalyze(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
memoryInfo collect.MemoryInfo
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
results []*AnalyzeResult
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fix for empty pass predicate",
|
||||
memoryInfo: collect.MemoryInfo{
|
||||
Total: 16 * 1024 * 1024 * 1024,
|
||||
},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 8Gi",
|
||||
Message: "oops",
|
||||
name: "Pass on empty pass predicate (local)",
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 8Gi",
|
||||
Message: "System requires at least 8Gi of memory",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "",
|
||||
Message: "it passed",
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "",
|
||||
Message: "Memory is sufficient",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
results: []*AnalyzeResult{
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
// Simulate local memory content retrieval
|
||||
if path == collect.HostMemoryPath {
|
||||
memoryInfo := collect.MemoryInfo{
|
||||
Total: 16 * 1024 * 1024 * 1024, // 16GiB
|
||||
}
|
||||
return json.Marshal(memoryInfo)
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
expectedResults: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Amount of Memory",
|
||||
IsPass: true,
|
||||
Message: "Memory is sufficient",
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Fix for empty pass predicate",
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 8Gi",
|
||||
Message: "oops",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "",
|
||||
Message: "it passed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
// Simulate local memory content retrieval
|
||||
if path == collect.HostMemoryPath {
|
||||
memoryInfo := collect.MemoryInfo{
|
||||
Total: 16 * 1024 * 1024 * 1024, // 16GiB
|
||||
}
|
||||
return json.Marshal(memoryInfo)
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
expectedResults: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Amount of Memory",
|
||||
IsPass: true,
|
||||
Message: "it passed",
|
||||
Title: "Memory Test",
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fn := func(_ string) ([]byte, error) {
|
||||
return json.Marshal(&tc.memoryInfo)
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// Set up the AnalyzeHostMemory object
|
||||
analyzeHostMemory := AnalyzeHostMemory{
|
||||
hostAnalyzer: test.hostAnalyzer,
|
||||
}
|
||||
|
||||
analyzer := AnalyzeHostMemory{
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "Memory Test",
|
||||
},
|
||||
Outcomes: tc.outcomes,
|
||||
},
|
||||
// Call the Analyze function
|
||||
results, err := analyzeHostMemory.Analyze(test.getCollectedFileContents, nil)
|
||||
|
||||
// Check for errors and compare results
|
||||
if test.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedResults, results)
|
||||
}
|
||||
results, err := analyzer.Analyze(fn, nil)
|
||||
if tc.wantErr {
|
||||
require.NotNil(t, err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.results, results)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package analyzer
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -10,18 +11,12 @@ import (
|
||||
"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 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")
|
||||
}
|
||||
@@ -33,71 +28,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()
|
||||
result := AnalyzeResult{Title: a.Title()}
|
||||
|
||||
// check if the host os info file exists (local mode)
|
||||
contents, err := getCollectedFileContents(collect.HostOSInfoPath)
|
||||
// Use the generic function to collect both local and remote data
|
||||
collectedContents, err := retrieveCollectedContents(
|
||||
getCollectedFileContents,
|
||||
collect.HostOSInfoPath, // Local path
|
||||
collect.NodeInfoBaseDir, // Remote base directory
|
||||
collect.HostInfoFileName, // Remote file name
|
||||
)
|
||||
if err != nil {
|
||||
// check if the node list file exists (remote mode)
|
||||
contents, err := getCollectedFileContents(constants.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
|
||||
return []*AnalyzeResult{&result}, err
|
||||
}
|
||||
|
||||
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...)
|
||||
results, err := analyzeHostCollectorResults(collectedContents, a.hostAnalyzer.Outcomes, a.CheckCondition, a.Title())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to analyze OS version")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// checkCondition checks the condition of the when clause
|
||||
func (a *AnalyzeHostOS) CheckCondition(when string, data collectorData) (bool, error) {
|
||||
rawData, ok := data.([]byte)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("expected data to be []uint8 (raw bytes), got: %v", reflect.TypeOf(data))
|
||||
}
|
||||
|
||||
var osInfo collect.HostOSInfo
|
||||
if err := json.Unmarshal(rawData, &osInfo); err != nil {
|
||||
return false, fmt.Errorf("failed to unmarshal data into HostOSInfo: %v", err)
|
||||
}
|
||||
|
||||
parts := strings.Split(when, " ")
|
||||
if len(parts) < 3 {
|
||||
return false, errors.New("when condition must have at least 3 parts")
|
||||
}
|
||||
expectedVer := fixVersion(parts[2])
|
||||
toleratedVer, err := semver.ParseTolerant(expectedVer)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse version: %s", expectedVer)
|
||||
}
|
||||
when = fmt.Sprintf("%s %v", parts[1], toleratedVer)
|
||||
whenRange, err := semver.ParseRange(when)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse version range: %s", when)
|
||||
}
|
||||
|
||||
// Match the kernel version regardless of the platform
|
||||
if parts[0] == "kernelVersion" {
|
||||
fixedKernelVer := fixVersion(osInfo.KernelVersion)
|
||||
toleratedKernelVer, err := semver.ParseTolerant(fixedKernelVer)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
|
||||
}
|
||||
if whenRange(toleratedKernelVer) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Match platform version and kernel version, such as "centos-8.2-kernel == 8.2"
|
||||
platform := parts[0]
|
||||
kernelInfo := fmt.Sprintf("%s-%s-kernel", osInfo.Platform, osInfo.PlatformVersion)
|
||||
if len(strings.Split(platform, "-")) == 3 && strings.Split(platform, "-")[2] == "kernel" {
|
||||
if platform == kernelInfo {
|
||||
fixedKernelVer := fixVersion(osInfo.KernelVersion)
|
||||
toleratedKernelVer, err := semver.ParseTolerant(fixedKernelVer)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
|
||||
}
|
||||
if whenRange(toleratedKernelVer) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
} else if platform == osInfo.Platform {
|
||||
fixedDistVer := fixVersion(osInfo.PlatformVersion)
|
||||
toleratedDistVer, err := semver.ParseTolerant(fixedDistVer)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse tolerant: %v", fixedDistVer)
|
||||
}
|
||||
if whenRange(toleratedDistVer) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var rx = regexp.MustCompile(`^[0-9]+\.?[0-9]*\.?[0-9]*`)
|
||||
|
||||
func fixVersion(versionStr string) string {
|
||||
@@ -113,106 +130,3 @@ func fixVersion(versionStr string) string {
|
||||
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,
|
||||
}
|
||||
when := ""
|
||||
message := ""
|
||||
uri := ""
|
||||
|
||||
if outcome.Fail != nil {
|
||||
result.IsFail = true
|
||||
when = outcome.Fail.When
|
||||
message = outcome.Fail.Message
|
||||
uri = outcome.Fail.URI
|
||||
} else if outcome.Warn != nil {
|
||||
result.IsWarn = true
|
||||
when = outcome.Warn.When
|
||||
message = outcome.Warn.Message
|
||||
uri = outcome.Warn.URI
|
||||
} else if outcome.Pass != nil {
|
||||
result.IsPass = true
|
||||
when = outcome.Pass.When
|
||||
message = outcome.Pass.Message
|
||||
uri = outcome.Pass.URI
|
||||
} else {
|
||||
return nil, errors.New("empty outcome")
|
||||
}
|
||||
|
||||
result.Message = message
|
||||
result.URI = uri
|
||||
// When is usually empty as the final case and should be treated as true
|
||||
if when == "" {
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(when, " ")
|
||||
if len(parts) < 3 {
|
||||
return []*AnalyzeResult{&result}, errors.New("when condition must have at least 3 parts")
|
||||
}
|
||||
expectedVer := fixVersion(parts[2])
|
||||
toleratedVer, err := semver.ParseTolerant(expectedVer)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse version: %s", expectedVer)
|
||||
}
|
||||
when = fmt.Sprintf("%s %v", parts[1], toleratedVer)
|
||||
whenRange, err := semver.ParseRange(when)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse version range: %s", when)
|
||||
}
|
||||
|
||||
// Match the kernel version regardless of the platform
|
||||
// e.g "kernelVersion == 4.15"
|
||||
if parts[0] == "kernelVersion" {
|
||||
fixedKernelVer := fixVersion(osInfo.KernelVersion)
|
||||
toleratedKernelVer, err := semver.ParseTolerant(fixedKernelVer)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
|
||||
}
|
||||
if whenRange(toleratedKernelVer) {
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Match the platform version and and kernel version passed in as
|
||||
// "<platform>-<kernelVersion>-kernel" e.g "centos-8.2-kernel == 8.2"
|
||||
platform := parts[0]
|
||||
kernelInfo := fmt.Sprintf("%s-%s-kernel", osInfo.Platform, osInfo.PlatformVersion)
|
||||
if len(strings.Split(platform, "-")) == 3 && strings.Split(platform, "-")[2] == "kernel" {
|
||||
if platform == kernelInfo {
|
||||
fixedKernelVer := fixVersion(osInfo.KernelVersion)
|
||||
toleratedKernelVer, err := semver.ParseTolerant(fixedKernelVer)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
|
||||
}
|
||||
if whenRange(toleratedKernelVer) {
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
// Match the platform version
|
||||
// e.g "centos == 8.2"
|
||||
} else if platform == osInfo.Platform {
|
||||
fixedDistVer := fixVersion(osInfo.PlatformVersion)
|
||||
toleratedDistVer, err := semver.ParseTolerant(fixedDistVer)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedDistVer)
|
||||
}
|
||||
if whenRange(toleratedDistVer) {
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -2,30 +2,106 @@ package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostOS(t *testing.T) {
|
||||
func TestAnalyzeHostOSCheckCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hostInfo collect.HostOSInfo
|
||||
hostAnalyzer *troubleshootv1beta2.HostOSAnalyze
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
name string
|
||||
conditional string
|
||||
osInfo collect.HostOSInfo
|
||||
expected bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "pass if ubuntu >= 0.1.2",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "00.1.2",
|
||||
Platform: "ubuntu",
|
||||
name: "kernelVersion == 4.15 when actual is 4.15",
|
||||
conditional: "kernelVersion == 4.15",
|
||||
osInfo: collect.HostOSInfo{
|
||||
KernelVersion: "4.15.0",
|
||||
},
|
||||
expected: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "kernelVersion < 4.15 when actual is 4.16",
|
||||
conditional: "kernelVersion < 4.15",
|
||||
osInfo: collect.HostOSInfo{
|
||||
KernelVersion: "4.16.0",
|
||||
},
|
||||
expected: false,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "centos == 8.2 when actual is 8.2",
|
||||
conditional: "centos == 8.2",
|
||||
osInfo: collect.HostOSInfo{
|
||||
Platform: "centos",
|
||||
PlatformVersion: "8.2",
|
||||
},
|
||||
expected: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "ubuntu == 20.04 when actual is 18.04",
|
||||
conditional: "ubuntu == 20.04",
|
||||
osInfo: collect.HostOSInfo{
|
||||
Platform: "ubuntu",
|
||||
PlatformVersion: "18.04",
|
||||
},
|
||||
expected: false,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid conditional format",
|
||||
conditional: "invalid conditional",
|
||||
osInfo: collect.HostOSInfo{
|
||||
Platform: "ubuntu",
|
||||
PlatformVersion: "18.04",
|
||||
},
|
||||
expected: false,
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// Create the AnalyzeHostOS object
|
||||
analyzeHostOS := AnalyzeHostOS{}
|
||||
|
||||
// Simulate the OS info as JSON-encoded data
|
||||
rawData, err := json.Marshal(test.osInfo)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call the CheckCondition method
|
||||
result, err := analyzeHostOS.CheckCondition(test.conditional, rawData)
|
||||
if test.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeHostOS(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hostAnalyzer *troubleshootv1beta2.HostOSAnalyze
|
||||
getCollectedFileContents func(string) ([]byte, error)
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "successfully retrieve local content and analyze",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
@@ -41,6 +117,17 @@ func TestAnalyzeHostOS(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == collect.HostOSInfoPath {
|
||||
return json.Marshal(collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "00.1.2",
|
||||
Platform: "ubuntu",
|
||||
})
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
@@ -48,22 +135,16 @@ func TestAnalyzeHostOS(t *testing.T) {
|
||||
Message: "supported distribution matches ubuntu >= 00.1.2",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "pass if ubuntu >= 1.0.2",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "1.0.2",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
name: "local content not found, retrieve and analyze remote content",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu >= 1.0.2",
|
||||
Message: "supported distribution matches ubuntu >= 1.0.2",
|
||||
When: "ubuntu == 18.04",
|
||||
Message: "supported remote ubuntu 18.04",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -73,53 +154,75 @@ func TestAnalyzeHostOS(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
return json.Marshal(nodeNames{Nodes: []string{"node1"}})
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostInfoFileName) {
|
||||
return json.Marshal(collect.HostOSInfo{
|
||||
Name: "nodehost",
|
||||
KernelVersion: "4.15.0-1034-aws",
|
||||
PlatformVersion: "18.04",
|
||||
Platform: "ubuntu",
|
||||
})
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
Title: "Host OS Info - Node node1",
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches ubuntu >= 1.0.2",
|
||||
Message: "supported remote ubuntu 18.04",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "pass if ubuntu >= 1.2.0",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "1.2.0",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
name: "fail to retrieve both local and remote content",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu >= 1.0.2",
|
||||
Message: "supported distribution matches ubuntu >= 1.2.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "unsupported distribution",
|
||||
Message: "failed analysis",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches ubuntu >= 1.2.0",
|
||||
Title: "Host OS Info",
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "pass if ubuntu-1.2.0-kernel >= 1.2.0",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "1.2.0-1034-gcp",
|
||||
PlatformVersion: "1.2.0",
|
||||
Platform: "centos",
|
||||
name: "error during remote content analysis",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "analysis failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
return json.Marshal(nodeNames{Nodes: []string{"node1"}})
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostInfoFileName) {
|
||||
return nil, errors.New("file not found")
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
result: nil,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "pass if centos-1.2.0-kernel >= 1.2.0",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
@@ -135,338 +238,139 @@ func TestAnalyzeHostOS(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
return json.Marshal(nodeNames{Nodes: []string{"node1"}})
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostInfoFileName) {
|
||||
return json.Marshal(collect.HostOSInfo{
|
||||
Name: "nodehost",
|
||||
KernelVersion: "1.2.0-1034-aws",
|
||||
PlatformVersion: "1.2.0",
|
||||
Platform: "centos",
|
||||
})
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
Title: "Host OS Info - Node node1",
|
||||
IsPass: true,
|
||||
Message: "supported kernel matches centos-1.2.0-kernel >= 1.2.0",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "pass if ubuntu-0.1.2-kernel >= 0.1.2",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "0.01.2-1034-gcp",
|
||||
PlatformVersion: "8.2",
|
||||
Platform: "centos",
|
||||
},
|
||||
name: "warn if ubuntu <= 16.04",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "centos-8.2-kernel >= 0.01.2",
|
||||
Message: "supported kernel matches centos-8.2-kernel >= 0.01.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "unsupported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsPass: true,
|
||||
Message: "supported kernel matches centos-8.2-kernel >= 0.01.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "fail if ubuntu <= 11.04",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "11.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu <= 11.04",
|
||||
Message: "unsupported ubuntu version 11.04",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "supported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsFail: true,
|
||||
Message: "unsupported ubuntu version 11.04",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail if none of the kernel distribution versions match",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "my-host",
|
||||
KernelVersion: "4.4",
|
||||
PlatformVersion: "18.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "centos-18.04-kernel > 4.15",
|
||||
Message: "supported distribution matches centos-18.04-kernel >= 4.15",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu-18.04-kernel > 4.15",
|
||||
Message: "supported distribution matches ubuntu-18.04-kernel >= 4.15",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu-16.04-kernel == 4.15",
|
||||
Message: "supported distribution matches ubuntu-16.04-kernel == 4.15 ",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "None matched, centos-18.04-kernel >= 4.15,ubuntu-18.04-kernel >= 4.15, supported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsFail: true,
|
||||
Message: "None matched, centos-18.04-kernel >= 4.15,ubuntu-18.04-kernel >= 4.15, supported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test if centos kernel > 4.15",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "my-host",
|
||||
KernelVersion: "4.15",
|
||||
PlatformVersion: "18.04",
|
||||
Platform: "centos",
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "centos-18.04-kernel >= 4.15",
|
||||
Message: "supported distribution matches centos-18.04-kernel >= 4.15",
|
||||
When: "ubuntu <= 16.04",
|
||||
Message: "System performs best with Ubuntu version higher than 16.04",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu-18.04-kernel > 4.15",
|
||||
Message: "supported distribution matches ubuntu-18.04-kernel >= 4.15",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu-16.04-kernel == 4.15",
|
||||
Message: "supported distribution matches ubuntu-16.04-kernel == 4.15 ",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "None matched, centos-18.04-kernel >= 4.15,ubuntu-18.04-kernel >= 4.15, supported distribution",
|
||||
When: "ubuntu > 16.04",
|
||||
Message: "Ubuntu version is sufficient",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches centos-18.04-kernel >= 4.15",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test ubuntu 16 kernel >= 4.15-abc",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "my-host",
|
||||
KernelVersion: "4.14-abc",
|
||||
PlatformVersion: "16.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu-16.04-kernel >= 4.14",
|
||||
Message: "supported distribution match 4.14",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsPass: true,
|
||||
Message: "supported distribution match 4.14",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "test kernelVersion >= 6.4.9-abc",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "my-host",
|
||||
KernelVersion: "6.5.0-1024-gcp",
|
||||
PlatformVersion: "22.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "kernelVersion >= 6.4.9-abc",
|
||||
Message: "supported kernel version >= 6.4.9-abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsPass: true,
|
||||
Message: "supported kernel version >= 6.4.9-abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.hostInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostOS{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == collect.HostOSInfoPath {
|
||||
return json.Marshal(collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "00.1.2",
|
||||
KernelVersion: "4.15.0-1234-gcp",
|
||||
PlatformVersion: "16.04",
|
||||
Platform: "ubuntu",
|
||||
})
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsWarn: true,
|
||||
Message: "System performs best with Ubuntu version higher than 16.04",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "analyze multiple nodes with different OS info",
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu == 18.04",
|
||||
Message: "supported ubuntu version",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "unsupported ubuntu version",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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",
|
||||
},
|
||||
},
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
return json.Marshal(nodeNames{Nodes: []string{"node1", "node2"}})
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node1/%s", collect.NodeInfoBaseDir, collect.HostInfoFileName) {
|
||||
return json.Marshal(collect.HostOSInfo{
|
||||
Name: "nodehost",
|
||||
KernelVersion: "4.15.0-1034-aws",
|
||||
PlatformVersion: "18.04",
|
||||
Platform: "ubuntu",
|
||||
})
|
||||
}
|
||||
if path == fmt.Sprintf("%s/node2/%s", collect.NodeInfoBaseDir, collect.HostInfoFileName) {
|
||||
return json.Marshal(collect.HostOSInfo{
|
||||
Name: "nodehost",
|
||||
KernelVersion: "4.15.0-1034-aws",
|
||||
PlatformVersion: "16.04",
|
||||
Platform: "ubuntu",
|
||||
})
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
result: []*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",
|
||||
Message: "supported ubuntu version",
|
||||
},
|
||||
{
|
||||
Title: "Host OS Info - Node node2",
|
||||
IsFail: true,
|
||||
Message: "unsupported ubuntu version 11.04",
|
||||
Message: "unsupported ubuntu version",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
// Set up the AnalyzeHostOS object with the custom hostAnalyzer per test
|
||||
analyzeHostOS := AnalyzeHostOS{
|
||||
hostAnalyzer: test.hostAnalyzer,
|
||||
}
|
||||
|
||||
// Call the Analyze function
|
||||
results, err := analyzeHostOS.Analyze(test.getCollectedFileContents, nil)
|
||||
|
||||
// Check for errors and compare results
|
||||
if test.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.result, results)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type MemoryInfo struct {
|
||||
}
|
||||
|
||||
const HostMemoryPath = `host-collectors/system/memory.json`
|
||||
const HostMemoryFileName = `memory.json`
|
||||
|
||||
type CollectHostMemory struct {
|
||||
hostCollector *troubleshootv1beta2.Memory
|
||||
|
||||
@@ -23,21 +23,10 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type FilteredCollector struct {
|
||||
Spec troubleshootv1beta2.HostCollect
|
||||
Collector collect.HostCollector
|
||||
}
|
||||
|
||||
func runHostCollectors(ctx context.Context, hostCollectors []*troubleshootv1beta2.HostCollect, additionalRedactors *troubleshootv1beta2.Redactor, bundlePath string, opts SupportBundleCreateOpts) (collect.CollectorResult, error) {
|
||||
collectSpecs := append([]*troubleshootv1beta2.HostCollect{}, hostCollectors...)
|
||||
collectedData := make(map[string][]byte)
|
||||
|
||||
// Filter out excluded collectors
|
||||
filteredCollectors, err := filterHostCollectors(ctx, collectSpecs, bundlePath, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.RunHostCollectorsInPod {
|
||||
if err := checkRemoteCollectorRBAC(ctx, opts.KubernetesRestConfig, "Remote Host Collectors", opts.Namespace); err != nil {
|
||||
if rbacErr, ok := err.(*RBACPermissionError); ok {
|
||||
@@ -52,11 +41,11 @@ func runHostCollectors(ctx context.Context, hostCollectors []*troubleshootv1beta
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := collectRemoteHost(ctx, filteredCollectors, bundlePath, opts, collectedData); err != nil {
|
||||
if err := collectRemoteHost(ctx, collectSpecs, bundlePath, opts, collectedData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := collectHost(ctx, filteredCollectors, opts, collectedData); err != nil {
|
||||
if err := collectHost(ctx, collectSpecs, bundlePath, opts, collectedData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -219,27 +208,38 @@ func getAnalysisFile(analyzeResults []*analyze.AnalyzeResult) (io.Reader, error)
|
||||
}
|
||||
|
||||
// collectRemoteHost runs remote host collectors sequentially
|
||||
func collectRemoteHost(ctx context.Context, filteredCollectors []FilteredCollector, bundlePath string, opts SupportBundleCreateOpts, collectedData map[string][]byte) error {
|
||||
func collectRemoteHost(ctx context.Context, collectSpecs []*troubleshootv1beta2.HostCollect, bundlePath string, opts SupportBundleCreateOpts, collectedData map[string][]byte) error {
|
||||
opts.KubernetesRestConfig.QPS = constants.DEFAULT_CLIENT_QPS
|
||||
opts.KubernetesRestConfig.Burst = constants.DEFAULT_CLIENT_BURST
|
||||
opts.KubernetesRestConfig.UserAgent = fmt.Sprintf("%s/%s", constants.DEFAULT_CLIENT_USER_AGENT, version.Version())
|
||||
|
||||
// Run remote collectors sequentially
|
||||
for _, c := range filteredCollectors {
|
||||
collector := c.Collector
|
||||
spec := c.Spec
|
||||
|
||||
// Send progress event: starting the collector
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Running host collector...", collector.Title())
|
||||
for _, spec := range collectSpecs {
|
||||
collector, ok := collect.GetHostCollector(spec, bundlePath)
|
||||
if !ok {
|
||||
opts.ProgressChan <- "Host collector not found"
|
||||
continue
|
||||
}
|
||||
|
||||
// Start a span for tracing
|
||||
_, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, collector.Title())
|
||||
span.SetAttributes(attribute.String("type", reflect.TypeOf(collector).String()))
|
||||
|
||||
isExcluded, _ := collector.IsExcluded()
|
||||
if isExcluded {
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Excluding host collector", collector.Title())
|
||||
span.SetAttributes(attribute.Bool(constants.EXCLUDED, true))
|
||||
span.End()
|
||||
continue
|
||||
}
|
||||
|
||||
// Send progress event: starting the collector
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Running host collector...", collector.Title())
|
||||
|
||||
// Parameters for remote collection
|
||||
params := &collect.RemoteCollectParams{
|
||||
ProgressChan: opts.ProgressChan,
|
||||
HostCollector: &spec,
|
||||
HostCollector: spec,
|
||||
BundlePath: bundlePath,
|
||||
ClientConfig: opts.KubernetesRestConfig,
|
||||
Image: "replicated/troubleshoot:latest",
|
||||
@@ -273,10 +273,14 @@ func collectRemoteHost(ctx context.Context, filteredCollectors []FilteredCollect
|
||||
}
|
||||
|
||||
// collectHost runs host collectors sequentially
|
||||
func collectHost(ctx context.Context, filteredCollectors []FilteredCollector, opts SupportBundleCreateOpts, collectedData map[string][]byte) error {
|
||||
func collectHost(ctx context.Context, collectSpecs []*troubleshootv1beta2.HostCollect, bundlePath string, opts SupportBundleCreateOpts, collectedData map[string][]byte) error {
|
||||
// Run local collectors sequentially
|
||||
for _, c := range filteredCollectors {
|
||||
collector := c.Collector
|
||||
for _, spec := range collectSpecs {
|
||||
collector, ok := collect.GetHostCollector(spec, bundlePath)
|
||||
if !ok {
|
||||
opts.ProgressChan <- "Host collector not found"
|
||||
continue
|
||||
}
|
||||
|
||||
// Send progress event: starting the collector
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Running host collector...", collector.Title())
|
||||
@@ -285,6 +289,14 @@ func collectHost(ctx context.Context, filteredCollectors []FilteredCollector, op
|
||||
_, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, collector.Title())
|
||||
span.SetAttributes(attribute.String("type", reflect.TypeOf(collector).String()))
|
||||
|
||||
isExcluded, _ := collector.IsExcluded()
|
||||
if isExcluded {
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Excluding host collector", collector.Title())
|
||||
span.SetAttributes(attribute.Bool(constants.EXCLUDED, true))
|
||||
span.End()
|
||||
continue
|
||||
}
|
||||
|
||||
// Run local collector sequentially
|
||||
result, err := collector.Collect(opts.ProgressChan)
|
||||
if err != nil {
|
||||
@@ -324,34 +336,3 @@ func getGlobalRedactors(additionalRedactors *troubleshootv1beta2.Redactor) []*tr
|
||||
}
|
||||
return []*troubleshootv1beta2.Redact{}
|
||||
}
|
||||
|
||||
// filterHostCollectors filters out excluded collectors and returns a list of collectors to run
|
||||
func filterHostCollectors(ctx context.Context, collectSpecs []*troubleshootv1beta2.HostCollect, bundlePath string, opts SupportBundleCreateOpts) ([]FilteredCollector, error) {
|
||||
var filteredCollectors []FilteredCollector
|
||||
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector, ok := collect.GetHostCollector(desiredCollector, bundlePath)
|
||||
if !ok {
|
||||
opts.ProgressChan <- "Host collector not found"
|
||||
continue
|
||||
}
|
||||
|
||||
_, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, collector.Title())
|
||||
span.SetAttributes(attribute.String("type", reflect.TypeOf(collector).String()))
|
||||
|
||||
isExcluded, _ := collector.IsExcluded()
|
||||
if isExcluded {
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Excluding host collector", collector.Title())
|
||||
span.SetAttributes(attribute.Bool(constants.EXCLUDED, true))
|
||||
span.End()
|
||||
continue
|
||||
}
|
||||
|
||||
filteredCollectors = append(filteredCollectors, FilteredCollector{
|
||||
Spec: *desiredCollector,
|
||||
Collector: collector,
|
||||
})
|
||||
}
|
||||
|
||||
return filteredCollectors, nil
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package supportbundle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
v1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
||||
func Test_filterHostCollectors(t *testing.T) {
|
||||
otel.SetTracerProvider(noop.NewTracerProvider())
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
collectSpecs []*v1beta2.HostCollect
|
||||
bundlePath string
|
||||
opts SupportBundleCreateOpts
|
||||
expectedResult []FilteredCollector
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "nil host collectors spec",
|
||||
collectSpecs: []*v1beta2.HostCollect{},
|
||||
bundlePath: "/tmp",
|
||||
opts: SupportBundleCreateOpts{
|
||||
ProgressChan: make(chan interface{}, 10),
|
||||
},
|
||||
expectedResult: []FilteredCollector{},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
filtered, err := filterHostCollectors(context.TODO(), tc.collectSpecs, tc.bundlePath, tc.opts)
|
||||
if err != tc.expectedError {
|
||||
t.Fatalf("expected error %v, got %v", tc.expectedError, err)
|
||||
}
|
||||
if len(filtered) != len(tc.expectedResult) {
|
||||
t.Fatalf("expected %d filtered collectors, got %d", len(tc.expectedResult), len(filtered))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user