Skip to content

Commit b88bc8d

Browse files
diamonwigginsnvanthaobanjoh
authored
Refactor Multi Node Analyzers (#1646)
* initial refactor of host os analyzer * refactor remote collect analysis --------- Signed-off-by: Evans Mungai <[email protected]> Co-authored-by: Gerard Nguyen <[email protected]> Co-authored-by: Evans Mungai <[email protected]>
1 parent 9c24ab6 commit b88bc8d

11 files changed

+1128
-765
lines changed

pkg/analyze/collected_contents.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package analyzer
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/pkg/errors"
8+
"github.com/replicatedhq/troubleshoot/pkg/constants"
9+
)
10+
11+
type collectedContent struct {
12+
NodeName string
13+
Data collectorData
14+
}
15+
16+
type collectorData interface{}
17+
18+
type nodeNames struct {
19+
Nodes []string `json:"nodes"`
20+
}
21+
22+
func retrieveCollectedContents(
23+
getCollectedFileContents func(string) ([]byte, error),
24+
localPath string, remoteNodeBaseDir string, remoteFileName string,
25+
) ([]collectedContent, error) {
26+
var collectedContents []collectedContent
27+
28+
// Try to retrieve local data first
29+
if contents, err := getCollectedFileContents(localPath); err == nil {
30+
collectedContents = append(collectedContents, collectedContent{NodeName: "", Data: contents})
31+
// Return immediately if local content is available
32+
return collectedContents, nil
33+
}
34+
35+
// Local data not available, move to remote collection
36+
nodeListContents, err := getCollectedFileContents(constants.NODE_LIST_FILE)
37+
if err != nil {
38+
return nil, errors.Wrap(err, "failed to get node list")
39+
}
40+
41+
var nodeNames nodeNames
42+
if err := json.Unmarshal(nodeListContents, &nodeNames); err != nil {
43+
return nil, errors.Wrap(err, "failed to unmarshal node names")
44+
}
45+
46+
// Collect data for each node
47+
for _, node := range nodeNames.Nodes {
48+
nodeFilePath := fmt.Sprintf("%s/%s/%s", remoteNodeBaseDir, node, remoteFileName)
49+
nodeContents, err := getCollectedFileContents(nodeFilePath)
50+
if err != nil {
51+
return nil, errors.Wrapf(err, "failed to retrieve content for node %s", node)
52+
}
53+
54+
collectedContents = append(collectedContents, collectedContent{NodeName: node, Data: nodeContents})
55+
}
56+
57+
return collectedContents, nil
58+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package analyzer
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/replicatedhq/troubleshoot/pkg/constants"
8+
"github.com/replicatedhq/troubleshoot/pkg/types"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestRetrieveCollectedContents(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
getCollectedFileContents func(string) ([]byte, error) // Mock function
17+
localPath string
18+
remoteNodeBaseDir string
19+
remoteFileName string
20+
expectedResult []collectedContent
21+
expectedError string
22+
}{
23+
{
24+
name: "successfully retrieve local content",
25+
getCollectedFileContents: func(path string) ([]byte, error) {
26+
if path == "localPath" {
27+
return []byte("localContent"), nil
28+
}
29+
return nil, &types.NotFoundError{Name: path}
30+
},
31+
localPath: "localPath",
32+
remoteNodeBaseDir: "remoteBaseDir",
33+
remoteFileName: "remoteFileName",
34+
expectedResult: []collectedContent{
35+
{
36+
NodeName: "",
37+
Data: []byte("localContent"),
38+
},
39+
},
40+
expectedError: "",
41+
},
42+
{
43+
name: "local content not found, retrieve remote node content successfully",
44+
getCollectedFileContents: func(path string) ([]byte, error) {
45+
if path == constants.NODE_LIST_FILE {
46+
nodeNames := nodeNames{Nodes: []string{"node1", "node2"}}
47+
return json.Marshal(nodeNames)
48+
}
49+
if path == "remoteBaseDir/node1/remoteFileName" {
50+
return []byte("remoteContent1"), nil
51+
}
52+
if path == "remoteBaseDir/node2/remoteFileName" {
53+
return []byte("remoteContent2"), nil
54+
}
55+
return nil, &types.NotFoundError{Name: path}
56+
},
57+
localPath: "localPath",
58+
remoteNodeBaseDir: "remoteBaseDir",
59+
remoteFileName: "remoteFileName",
60+
expectedResult: []collectedContent{
61+
{
62+
NodeName: "node1",
63+
Data: []byte("remoteContent1"),
64+
},
65+
{
66+
NodeName: "node2",
67+
Data: []byte("remoteContent2"),
68+
},
69+
},
70+
expectedError: "",
71+
},
72+
{
73+
name: "fail to retrieve local content and node list",
74+
getCollectedFileContents: func(path string) ([]byte, error) {
75+
return nil, &types.NotFoundError{Name: path}
76+
},
77+
localPath: "localPath",
78+
remoteNodeBaseDir: "remoteBaseDir",
79+
remoteFileName: "remoteFileName",
80+
expectedResult: nil,
81+
expectedError: "failed to get node list",
82+
},
83+
{
84+
name: "fail to retrieve content for one of the nodes",
85+
getCollectedFileContents: func(path string) ([]byte, error) {
86+
if path == constants.NODE_LIST_FILE {
87+
nodeNames := nodeNames{Nodes: []string{"node1", "node2"}}
88+
return json.Marshal(nodeNames)
89+
}
90+
if path == "remoteBaseDir/node1/remoteFileName" {
91+
return []byte("remoteContent1"), nil
92+
}
93+
if path == "remoteBaseDir/node2/remoteFileName" {
94+
return nil, &types.NotFoundError{Name: path}
95+
}
96+
return nil, &types.NotFoundError{Name: path}
97+
},
98+
localPath: "localPath",
99+
remoteNodeBaseDir: "remoteBaseDir",
100+
remoteFileName: "remoteFileName",
101+
expectedResult: nil,
102+
expectedError: "failed to retrieve content for node node2",
103+
},
104+
{
105+
name: "fail to unmarshal node list",
106+
getCollectedFileContents: func(path string) ([]byte, error) {
107+
if path == constants.NODE_LIST_FILE {
108+
return []byte("invalidJSON"), nil
109+
}
110+
return nil, &types.NotFoundError{Name: path}
111+
},
112+
localPath: "localPath",
113+
remoteNodeBaseDir: "remoteBaseDir",
114+
remoteFileName: "remoteFileName",
115+
expectedResult: nil,
116+
expectedError: "failed to unmarshal node names",
117+
},
118+
}
119+
120+
for _, test := range tests {
121+
t.Run(test.name, func(t *testing.T) {
122+
result, err := retrieveCollectedContents(
123+
test.getCollectedFileContents,
124+
test.localPath,
125+
test.remoteNodeBaseDir,
126+
test.remoteFileName,
127+
)
128+
129+
if test.expectedError != "" {
130+
require.Error(t, err)
131+
assert.Contains(t, err.Error(), test.expectedError)
132+
} else {
133+
require.NoError(t, err)
134+
assert.Equal(t, test.expectedResult, result)
135+
}
136+
})
137+
}
138+
}

pkg/analyze/host_analyzer.go

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package analyzer
22

3-
import troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
3+
import (
4+
"fmt"
5+
6+
"github.com/pkg/errors"
7+
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
8+
)
49

510
type HostAnalyzer interface {
611
Title() string
@@ -83,3 +88,102 @@ func (c *resultCollector) get(title string) []*AnalyzeResult {
8388
}
8489
return []*AnalyzeResult{{Title: title, IsWarn: true, Message: "no results"}}
8590
}
91+
92+
func analyzeHostCollectorResults(collectedContent []collectedContent, outcomes []*troubleshootv1beta2.Outcome, checkCondition func(string, collectorData) (bool, error), title string) ([]*AnalyzeResult, error) {
93+
var results []*AnalyzeResult
94+
for _, content := range collectedContent {
95+
currentTitle := title
96+
if content.NodeName != "" {
97+
currentTitle = fmt.Sprintf("%s - Node %s", title, content.NodeName)
98+
}
99+
100+
analyzeResult, err := evaluateOutcomes(outcomes, checkCondition, content.Data, currentTitle)
101+
if err != nil {
102+
return nil, errors.Wrap(err, "failed to evaluate outcomes")
103+
}
104+
if analyzeResult != nil {
105+
results = append(results, analyzeResult...)
106+
}
107+
}
108+
return results, nil
109+
}
110+
111+
func evaluateOutcomes(outcomes []*troubleshootv1beta2.Outcome, checkCondition func(string, collectorData) (bool, error), data collectorData, title string) ([]*AnalyzeResult, error) {
112+
var results []*AnalyzeResult
113+
114+
for _, outcome := range outcomes {
115+
result := AnalyzeResult{
116+
Title: title,
117+
}
118+
119+
switch {
120+
case outcome.Fail != nil:
121+
if outcome.Fail.When == "" {
122+
result.IsFail = true
123+
result.Message = outcome.Fail.Message
124+
result.URI = outcome.Fail.URI
125+
results = append(results, &result)
126+
return results, nil
127+
}
128+
129+
isMatch, err := checkCondition(outcome.Fail.When, data)
130+
if err != nil {
131+
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
132+
}
133+
134+
if isMatch {
135+
result.IsFail = true
136+
result.Message = outcome.Fail.Message
137+
result.URI = outcome.Fail.URI
138+
results = append(results, &result)
139+
return results, nil
140+
}
141+
142+
case outcome.Warn != nil:
143+
if outcome.Warn.When == "" {
144+
result.IsWarn = true
145+
result.Message = outcome.Warn.Message
146+
result.URI = outcome.Warn.URI
147+
results = append(results, &result)
148+
return results, nil
149+
}
150+
151+
isMatch, err := checkCondition(outcome.Warn.When, data)
152+
if err != nil {
153+
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
154+
}
155+
156+
if isMatch {
157+
result.IsWarn = true
158+
result.Message = outcome.Warn.Message
159+
result.URI = outcome.Warn.URI
160+
results = append(results, &result)
161+
return results, nil
162+
}
163+
164+
case outcome.Pass != nil:
165+
if outcome.Pass.When == "" {
166+
result.IsPass = true
167+
result.Message = outcome.Pass.Message
168+
result.URI = outcome.Pass.URI
169+
results = append(results, &result)
170+
return results, nil
171+
}
172+
173+
isMatch, err := checkCondition(outcome.Pass.When, data)
174+
if err != nil {
175+
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
176+
}
177+
178+
if isMatch {
179+
result.IsPass = true
180+
result.Message = outcome.Pass.Message
181+
result.URI = outcome.Pass.URI
182+
results = append(results, &result)
183+
return results, nil
184+
}
185+
}
186+
}
187+
188+
return nil, nil
189+
}

0 commit comments

Comments
 (0)