From d8fad956f45a4dd668647379bb0295e169faeac6 Mon Sep 17 00:00:00 2001 From: Naveen Thangaraj <166512789+naveenthangaraj03@users.noreply.github.com> Date: Thu, 3 Oct 2024 23:03:50 +0530 Subject: [PATCH] feat: error from events for STS analyzer (#1256) * error from events for sts analyzer Signed-off-by: naveenthangaraj03 * Signedoff this commit Signed-off-by: naveenthangaraj03 --------- Signed-off-by: naveenthangaraj03 Co-authored-by: Aris Boutselis --- pkg/analyzer/statefulset.go | 36 +++++++ pkg/analyzer/statefulset_test.go | 162 +++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) diff --git a/pkg/analyzer/statefulset.go b/pkg/analyzer/statefulset.go index 64b5d8b307..a85d116ff5 100644 --- a/pkg/analyzer/statefulset.go +++ b/pkg/analyzer/statefulset.go @@ -19,6 +19,7 @@ import ( "github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -93,6 +94,41 @@ func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { } } } + if sts.Spec.Replicas != nil && *(sts.Spec.Replicas) != sts.Status.AvailableReplicas { + for i := int32(0); i < *(sts.Spec.Replicas); i++ { + podName := sts.Name + "-" + fmt.Sprint(i) + pod, err := a.Client.GetClient().CoreV1().Pods(sts.Namespace).Get(a.Context, podName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) && i == 0 { + evt, err := util.FetchLatestEvent(a.Context, a.Client, sts.Namespace, sts.Name) + if err != nil || evt == nil || evt.Type == "Normal" { + break + } + failures = append(failures, common.Failure{ + Text: evt.Message, + Sensitive: []common.Sensitive{}, + }) + } + break + } + if pod.Status.Phase != "Running" { + failures = append(failures, common.Failure{ + Text: fmt.Sprintf("Statefulset pod %s in the namespace %s is not in running state.", pod.Name, pod.Namespace), + Sensitive: []common.Sensitive{ + { + Unmasked: sts.Namespace, + Masked: util.MaskString(pod.Name), + }, + { + Unmasked: serviceName, + Masked: util.MaskString(pod.Namespace), + }, + }, + }) + break + } + } + } if len(failures) > 0 { preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = common.PreAnalysis{ StatefulSet: sts, diff --git a/pkg/analyzer/statefulset_test.go b/pkg/analyzer/statefulset_test.go index 6aa8dd8e23..780765c6d7 100644 --- a/pkg/analyzer/statefulset_test.go +++ b/pkg/analyzer/statefulset_test.go @@ -240,3 +240,165 @@ func TestStatefulSetAnalyzerLabelSelectorFiltering(t *testing.T) { assert.Equal(t, 1, len(results)) assert.Equal(t, "default/example1", results[0].Name) } + +func TestStatefulSetAnalyzerReplica(t *testing.T) { + replicas := int32(3) + pods := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-0", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-1", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-2", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + } + clientset := fake.NewSimpleClientset( + &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "default", + }, + Spec: appsv1.StatefulSetSpec{ + Replicas: &replicas, + }, + Status: appsv1.StatefulSetStatus{ + AvailableReplicas: 3, + }, + }, + pods[0], pods[1], pods[2], + ) + statefulSetAnalyzer := StatefulSetAnalyzer{} + + config := common.Analyzer{ + Client: &kubernetes.Client{ + Client: clientset, + }, + Context: context.Background(), + Namespace: "default", + } + analysisResults, err := statefulSetAnalyzer.Analyze(config) + if err != nil { + t.Error(err) + } + assert.Equal(t, len(analysisResults), 1) +} + +func TestStatefulSetAnalyzerUnavailableReplicas(t *testing.T) { + replicas := int32(3) + clientset := fake.NewSimpleClientset( + &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "default", + }, + Spec: appsv1.StatefulSetSpec{ + Replicas: &replicas, + }, + Status: appsv1.StatefulSetStatus{ + AvailableReplicas: 0, + }, + }) + statefulSetAnalyzer := StatefulSetAnalyzer{} + + config := common.Analyzer{ + Client: &kubernetes.Client{ + Client: clientset, + }, + Context: context.Background(), + Namespace: "default", + } + analysisResults, err := statefulSetAnalyzer.Analyze(config) + if err != nil { + t.Error(err) + } + assert.Equal(t, len(analysisResults), 1) +} + +func TestStatefulSetAnalyzerUnavailableReplicaWithPodInitialized(t *testing.T) { + replicas := int32(3) + pods := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-0", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-1", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodPending, + }, + }, + } + clientset := fake.NewSimpleClientset( + &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "default", + }, + Spec: appsv1.StatefulSetSpec{ + Replicas: &replicas, + }, + Status: appsv1.StatefulSetStatus{ + AvailableReplicas: 1, + }, + }, + pods[0], pods[1], + ) + statefulSetAnalyzer := StatefulSetAnalyzer{} + + config := common.Analyzer{ + Client: &kubernetes.Client{ + Client: clientset, + }, + Context: context.Background(), + Namespace: "default", + } + analysisResults, err := statefulSetAnalyzer.Analyze(config) + if err != nil { + t.Error(err) + } + + var errorFound bool + want := "Statefulset pod example-1 in the namespace default is not in running state." + + for _, analysis := range analysisResults { + for _, got := range analysis.Error { + if want == got.Text { + errorFound = true + } + } + if errorFound { + break + } + } + if !errorFound { + t.Errorf("Error expected: '%v', not found in StatefulSet's analysis results", want) + } +}