diff --git a/pkg/analyze/host_memory_test.go b/pkg/analyze/host_memory_test.go index 2b107855b..b8283b0e7 100644 --- a/pkg/analyze/host_memory_test.go +++ b/pkg/analyze/host_memory_test.go @@ -2,10 +2,13 @@ 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" ) @@ -102,7 +105,7 @@ func TestAnalyzeHostMemory(t *testing.T) { expectedError string }{ { - name: "Pass on memory available", + name: "Pass on memory available (local)", hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{ Outcomes: []*troubleshootv1beta2.Outcome{ { @@ -113,11 +116,15 @@ func TestAnalyzeHostMemory(t *testing.T) { }, }, }, - getCollectedFileContents: func(filename string) ([]byte, error) { - memoryInfo := collect.MemoryInfo{ - Total: 8 * 1024 * 1024 * 1024, // 8GiB + 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 json.Marshal(memoryInfo) + return nil, errors.New("file not found") }, expectedResults: []*AnalyzeResult{ { @@ -129,7 +136,7 @@ func TestAnalyzeHostMemory(t *testing.T) { expectedError: "", }, { - name: "Fail on memory available", + name: "Fail on memory available (remote node)", hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{ Outcomes: []*troubleshootv1beta2.Outcome{ { @@ -140,15 +147,23 @@ func TestAnalyzeHostMemory(t *testing.T) { }, }, }, - getCollectedFileContents: func(filename string) ([]byte, error) { - memoryInfo := collect.MemoryInfo{ - Total: 8 * 1024 * 1024 * 1024, // 8GiB + 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) } - return json.Marshal(memoryInfo) + 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", }, @@ -156,7 +171,7 @@ func TestAnalyzeHostMemory(t *testing.T) { expectedError: "", }, { - name: "Warn on memory available", + name: "Warn on memory available (remote node)", hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{ Outcomes: []*troubleshootv1beta2.Outcome{ { @@ -167,15 +182,23 @@ func TestAnalyzeHostMemory(t *testing.T) { }, }, }, - getCollectedFileContents: func(filename string) ([]byte, error) { - memoryInfo := collect.MemoryInfo{ - Total: 8 * 1024 * 1024 * 1024, // 8GiB + 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) } - return json.Marshal(memoryInfo) + 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", }, @@ -183,7 +206,7 @@ func TestAnalyzeHostMemory(t *testing.T) { expectedError: "", }, { - name: "Pass on empty pass predicate", + name: "Pass on empty pass predicate (local)", hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{ Outcomes: []*troubleshootv1beta2.Outcome{ { @@ -200,11 +223,15 @@ func TestAnalyzeHostMemory(t *testing.T) { }, }, }, - getCollectedFileContents: func(filename string) ([]byte, error) { - memoryInfo := collect.MemoryInfo{ - Total: 16 * 1024 * 1024 * 1024, // 16GiB + 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 json.Marshal(memoryInfo) + return nil, errors.New("file not found") }, expectedResults: []*AnalyzeResult{ { @@ -215,6 +242,43 @@ func TestAnalyzeHostMemory(t *testing.T) { }, 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", + }, + }, + expectedError: "", + }, } for _, test := range tests { diff --git a/pkg/analyze/host_os_info.go b/pkg/analyze/host_os_info.go index 7f09d02ec..885cc48ce 100644 --- a/pkg/analyze/host_os_info.go +++ b/pkg/analyze/host_os_info.go @@ -77,7 +77,6 @@ func (a *AnalyzeHostOS) CheckCondition(when string, data CollectorData) (bool, e } // 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) @@ -89,8 +88,7 @@ func (a *AnalyzeHostOS) CheckCondition(when string, data CollectorData) (bool, e } } - // Match the platform version and and kernel version passed in as - // "--kernel" e.g "centos-8.2-kernel == 8.2" + // 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" { @@ -104,8 +102,6 @@ func (a *AnalyzeHostOS) CheckCondition(when string, data CollectorData) (bool, e return true, nil } } - // Match the platform version - // e.g "centos == 8.2" } else if platform == osInfo.Platform { fixedDistVer := fixVersion(osInfo.PlatformVersion) toleratedDistVer, err := semver.ParseTolerant(fixedDistVer) diff --git a/pkg/analyze/host_os_info_test.go b/pkg/analyze/host_os_info_test.go index cbf484c11..701180947 100644 --- a/pkg/analyze/host_os_info_test.go +++ b/pkg/analyze/host_os_info_test.go @@ -2,6 +2,7 @@ package analyzer import ( "encoding/json" + "fmt" "testing" "github.com/pkg/errors" @@ -58,15 +59,6 @@ func TestAnalyzeHostOSCheckCondition(t *testing.T) { expected: false, expectErr: false, }, - { - name: "kernelVersion == 5.10 when actual is 5.10.42", - conditional: "kernelVersion == 5.10", - osInfo: collect.HostOSInfo{ - KernelVersion: "5.10.42", - }, - expected: true, - expectErr: false, - }, { name: "invalid conditional format", conditional: "invalid conditional", @@ -103,11 +95,10 @@ func TestAnalyzeHostOSCheckCondition(t *testing.T) { func TestAnalyzeHostOS(t *testing.T) { tests := []struct { name string - hostAnalyzer *troubleshootv1beta2.HostOSAnalyze // Different types of analyzers per test case - getCollectedFileContents func(string) ([]byte, error) // Mock function - findFiles getChildCollectedFileContents - expectedResults []*AnalyzeResult - expectedError string + hostAnalyzer *troubleshootv1beta2.HostOSAnalyze + getCollectedFileContents func(string) ([]byte, error) + result []*AnalyzeResult + expectErr bool }{ { name: "successfully retrieve local content and analyze", @@ -115,26 +106,36 @@ func TestAnalyzeHostOS(t *testing.T) { Outcomes: []*troubleshootv1beta2.Outcome{ { Pass: &troubleshootv1beta2.SingleOutcome{ - When: "os == localOS", - Message: "local content analyzed", + 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 == collect.HostOSInfoPath { - return []byte(`{"Name": "localOS"}`), nil + 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") }, - expectedResults: []*AnalyzeResult{ + result: []*AnalyzeResult{ { Title: "Host OS Info", IsPass: true, - Message: "local content analyzed", + Message: "supported distribution matches ubuntu >= 00.1.2", }, }, - expectedError: "", + expectErr: false, }, { name: "local content not found, retrieve and analyze remote content", @@ -142,30 +143,39 @@ func TestAnalyzeHostOS(t *testing.T) { Outcomes: []*troubleshootv1beta2.Outcome{ { Pass: &troubleshootv1beta2.SingleOutcome{ - When: "os == remoteOS", - Message: "remote content analyzed", + When: "ubuntu == 18.04", + Message: "supported remote ubuntu 18.04", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "unsupported distribution", }, }, }, }, getCollectedFileContents: func(path string) ([]byte, error) { if path == constants.NODE_LIST_FILE { - nodeNames := NodeNames{Nodes: []string{"node1"}} - return json.Marshal(nodeNames) + return json.Marshal(NodeNames{Nodes: []string{"node1"}}) } - if path == "remoteBaseDir/node1/remoteFileName" { - return []byte(`{"Name": "remoteOS"}`), nil + 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") }, - expectedResults: []*AnalyzeResult{ + result: []*AnalyzeResult{ { Title: "Host OS Info - Node node1", IsPass: true, - Message: "remote content analyzed", + Message: "supported remote ubuntu 18.04", }, }, - expectedError: "", + expectErr: false, }, { name: "fail to retrieve both local and remote content", @@ -181,15 +191,15 @@ func TestAnalyzeHostOS(t *testing.T) { getCollectedFileContents: func(path string) ([]byte, error) { return nil, errors.New("file not found") }, - expectedResults: []*AnalyzeResult{ + result: []*AnalyzeResult{ { Title: "Host OS Info", }, }, - expectedError: "failed to get node list", + expectErr: true, }, { - name: "error during content analysis", + name: "error during remote content analysis", hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{ Outcomes: []*troubleshootv1beta2.Outcome{ { @@ -201,16 +211,146 @@ func TestAnalyzeHostOS(t *testing.T) { }, getCollectedFileContents: func(path string) ([]byte, error) { if path == constants.NODE_LIST_FILE { - nodeNames := NodeNames{Nodes: []string{"node1"}} - return json.Marshal(nodeNames) + return json.Marshal(NodeNames{Nodes: []string{"node1"}}) } - if path == "remoteBaseDir/node1/remoteFileName" { - return []byte(`{"Name": "remoteOS"}`), nil + 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") }, - expectedResults: nil, - expectedError: "failed to analyze OS version", + result: nil, + expectErr: true, + }, + { + name: "pass if centos-1.2.0-kernel >= 1.2.0", + hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "centos-1.2.0-kernel >= 1.2.0", + Message: "supported kernel matches centos-1.2.0-kernel >= 1.2.0", + }, + }, + { + 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"}}) + } + 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 - Node node1", + IsPass: true, + Message: "supported kernel matches centos-1.2.0-kernel >= 1.2.0", + }, + }, + expectErr: false, + }, + { + name: "warn if ubuntu <= 16.04", + hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Warn: &troubleshootv1beta2.SingleOutcome{ + When: "ubuntu <= 16.04", + Message: "System performs best with Ubuntu version higher than 16.04", + }, + }, + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "ubuntu > 16.04", + Message: "Ubuntu version is sufficient", + }, + }, + }, + }, + getCollectedFileContents: func(path string) ([]byte, error) { + if path == collect.HostOSInfoPath { + return json.Marshal(collect.HostOSInfo{ + Name: "myhost", + 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", + }, + }, + }, + }, + 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") + }, + result: []*AnalyzeResult{ + { + Title: "Host OS Info - Node node1", + IsPass: true, + Message: "supported ubuntu version", + }, + { + Title: "Host OS Info - Node node2", + IsFail: true, + Message: "unsupported ubuntu version", + }, + }, + expectErr: false, }, } @@ -222,15 +362,14 @@ func TestAnalyzeHostOS(t *testing.T) { } // Call the Analyze function - results, err := analyzeHostOS.Analyze(test.getCollectedFileContents, test.findFiles) + results, err := analyzeHostOS.Analyze(test.getCollectedFileContents, nil) // Check for errors and compare results - if test.expectedError != "" { + if test.expectErr { require.Error(t, err) - assert.Contains(t, err.Error(), test.expectedError) } else { require.NoError(t, err) - assert.Equal(t, test.expectedResults, results) + assert.Equal(t, test.result, results) } }) }