forked from k8sgpt-ai/k8sgpt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add node analyzer (k8sgpt-ai#272)
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
- Loading branch information
Dominik Augustin
authored
Apr 14, 2023
1 parent
a8f8070
commit 6247a1c
Showing
5 changed files
with
188 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package analyzer | ||
|
||
import ( | ||
"fmt" | ||
v1 "k8s.io/api/core/v1" | ||
|
||
"github.com/k8sgpt-ai/k8sgpt/pkg/common" | ||
"github.com/k8sgpt-ai/k8sgpt/pkg/util" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
type NodeAnalyzer struct{} | ||
|
||
func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { | ||
|
||
list, err := a.Client.GetClient().CoreV1().Nodes().List(a.Context, metav1.ListOptions{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var preAnalysis = map[string]common.PreAnalysis{} | ||
|
||
for _, node := range list.Items { | ||
var failures []common.Failure | ||
for _, nodeCondition := range node.Status.Conditions { | ||
// https://kubernetes.io/docs/concepts/architecture/nodes/#condition | ||
switch nodeCondition.Type { | ||
case v1.NodeReady: | ||
if nodeCondition.Status == v1.ConditionTrue { | ||
break | ||
} | ||
failures = addNodeConditionFailure(failures, node.Name, nodeCondition) | ||
default: | ||
if nodeCondition.Status != v1.ConditionFalse { | ||
failures = addNodeConditionFailure(failures, node.Name, nodeCondition) | ||
} | ||
} | ||
} | ||
|
||
if len(failures) > 0 { | ||
preAnalysis[fmt.Sprintf("%s", node.Name)] = common.PreAnalysis{ | ||
Node: node, | ||
FailureDetails: failures, | ||
} | ||
} | ||
} | ||
|
||
for key, value := range preAnalysis { | ||
var currentAnalysis = common.Result{ | ||
Kind: "Node", | ||
Name: key, | ||
Error: value.FailureDetails, | ||
} | ||
|
||
parent, _ := util.GetParent(a.Client, value.Node.ObjectMeta) | ||
currentAnalysis.ParentObject = parent | ||
a.Results = append(a.Results, currentAnalysis) | ||
} | ||
|
||
return a.Results, err | ||
} | ||
|
||
func addNodeConditionFailure(failures []common.Failure, nodeName string, nodeCondition v1.NodeCondition) []common.Failure { | ||
failures = append(failures, common.Failure{ | ||
Text: fmt.Sprintf("%s has condition of type %s, reason %s: %s", nodeName, nodeCondition.Type, nodeCondition.Reason, nodeCondition.Message), | ||
Sensitive: []common.Sensitive{ | ||
{ | ||
Unmasked: nodeName, | ||
Masked: util.MaskString(nodeName), | ||
}, | ||
}, | ||
}) | ||
return failures | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package analyzer | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/k8sgpt-ai/k8sgpt/pkg/common" | ||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" | ||
"github.com/magiconair/properties/assert" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
func TestNodeAnalyzerNodeReady(t *testing.T) { | ||
clientset := fake.NewSimpleClientset(&v1.Node{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "node1", | ||
}, | ||
Status: v1.NodeStatus{ | ||
Conditions: []v1.NodeCondition{ | ||
{ | ||
Type: v1.NodeReady, | ||
Status: v1.ConditionTrue, | ||
Reason: "KubeletReady", | ||
Message: "kubelet is posting ready status", | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
config := common.Analyzer{ | ||
Client: &kubernetes.Client{ | ||
Client: clientset, | ||
}, | ||
Context: context.Background(), | ||
} | ||
nodeAnalyzer := NodeAnalyzer{} | ||
var analysisResults []common.Result | ||
analysisResults, err := nodeAnalyzer.Analyze(config) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
assert.Equal(t, len(analysisResults), 0) | ||
} | ||
|
||
func TestNodeAnalyzerNodeDiskPressure(t *testing.T) { | ||
clientset := fake.NewSimpleClientset(&v1.Node{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "node1", | ||
}, | ||
Status: v1.NodeStatus{ | ||
Conditions: []v1.NodeCondition{ | ||
{ | ||
Type: v1.NodeDiskPressure, | ||
Status: v1.ConditionTrue, | ||
Reason: "KubeletHasDiskPressure", | ||
Message: "kubelet has disk pressure", | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
config := common.Analyzer{ | ||
Client: &kubernetes.Client{ | ||
Client: clientset, | ||
}, | ||
Context: context.Background(), | ||
} | ||
nodeAnalyzer := NodeAnalyzer{} | ||
var analysisResults []common.Result | ||
analysisResults, err := nodeAnalyzer.Analyze(config) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
assert.Equal(t, len(analysisResults), 1) | ||
} | ||
|
||
// A cloud provider may set their own condition and/or a new status might be introduced | ||
// In such cases a failure is assumed and the code shouldn't break, although it might be a false positive | ||
func TestNodeAnalyzerNodeUnknownType(t *testing.T) { | ||
clientset := fake.NewSimpleClientset(&v1.Node{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "node1", | ||
}, | ||
Status: v1.NodeStatus{ | ||
Conditions: []v1.NodeCondition{ | ||
{ | ||
Type: "UnknownNodeConditionType", | ||
Status: "CompletelyUnknown", | ||
Reason: "KubeletHasTheUnknown", | ||
Message: "kubelet has the unknown", | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
config := common.Analyzer{ | ||
Client: &kubernetes.Client{ | ||
Client: clientset, | ||
}, | ||
Context: context.Background(), | ||
} | ||
nodeAnalyzer := NodeAnalyzer{} | ||
var analysisResults []common.Result | ||
analysisResults, err := nodeAnalyzer.Analyze(config) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
assert.Equal(t, len(analysisResults), 1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters