diff --git a/pkg/analyzer/pvc.go b/pkg/analyzer/pvc.go index 3942efcc6b..c7544e13a8 100644 --- a/pkg/analyzer/pvc.go +++ b/pkg/analyzer/pvc.go @@ -18,6 +18,7 @@ import ( "github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/util" + appsv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -43,7 +44,7 @@ func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { var failures []common.Failure // Check for empty rs - if pvc.Status.Phase == "Pending" { + if pvc.Status.Phase == appsv1.ClaimPending { // parse the event log and append details evt, err := FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name) diff --git a/pkg/analyzer/pvc_test.go b/pkg/analyzer/pvc_test.go new file mode 100644 index 0000000000..31273bcdd8 --- /dev/null +++ b/pkg/analyzer/pvc_test.go @@ -0,0 +1,217 @@ +/* +Copyright 2024 The K8sGPT Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package analyzer + +import ( + "context" + "testing" + + "github.com/k8sgpt-ai/k8sgpt/pkg/common" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestPersistentVolumeClaimAnalyzer(t *testing.T) { + tests := []struct { + name string + config common.Analyzer + expectations []string + }{ + { + name: "PV1 and PVC5 report failures", + config: common.Analyzer{ + Client: &kubernetes.Client{ + Client: fake.NewSimpleClientset( + &appsv1.Event{ + // This is the latest event. + ObjectMeta: metav1.ObjectMeta{ + Name: "Event1", + Namespace: "default", + }, + Reason: "ProvisioningFailed", + Message: "PVC provisioning failed", + }, + &appsv1.Event{ + ObjectMeta: metav1.ObjectMeta{ + // This event won't get selected. + Name: "Event2", + Namespace: "test", + }, + }, + &appsv1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "Event3", + Namespace: "default", + }, + }, + &appsv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC1", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + Phase: appsv1.ClaimPending, + }, + }, + &appsv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC2", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + // Won't contribute to failures. + Phase: appsv1.ClaimBound, + }, + }, + &appsv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC3", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + // Won't contribute to failures. + Phase: appsv1.ClaimLost, + }, + }, + &appsv1.PersistentVolumeClaim{ + // PVCs in namespace other than "default" won't be discovered. + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC4", + Namespace: "test", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + Phase: appsv1.ClaimLost, + }, + }, + &appsv1.PersistentVolumeClaim{ + // PVCs in namespace other than "default" won't be discovered. + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC5", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + Phase: appsv1.ClaimPending, + }, + }, + ), + }, + Context: context.Background(), + Namespace: "default", + }, + expectations: []string{ + "default/PVC1", + "default/PVC5", + }, + }, + { + name: "no event", + config: common.Analyzer{ + Client: &kubernetes.Client{ + Client: fake.NewSimpleClientset( + &appsv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC1", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + Phase: appsv1.ClaimPending, + }, + }, + ), + }, + Context: context.Background(), + Namespace: "default", + }, + }, + { + name: "event other than privision failure", + config: common.Analyzer{ + Client: &kubernetes.Client{ + Client: fake.NewSimpleClientset( + &appsv1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "Event1", + Namespace: "default", + }, + // Any reason other than ProvisioningFailed won't result in failure. + Reason: "UnknownReason", + }, + &appsv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC1", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + Phase: appsv1.ClaimPending, + }, + }, + ), + }, + Context: context.Background(), + Namespace: "default", + }, + }, + { + name: "event without error message", + config: common.Analyzer{ + Client: &kubernetes.Client{ + Client: fake.NewSimpleClientset( + &appsv1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "Event1", + Namespace: "default", + }, + // Event without any error message won't result in failure. + Reason: "ProvisioningFailed", + }, + &appsv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "PVC1", + Namespace: "default", + }, + Status: appsv1.PersistentVolumeClaimStatus{ + Phase: appsv1.ClaimPending, + }, + }, + ), + }, + Context: context.Background(), + Namespace: "default", + }, + }, + } + + pvcAnalyzer := PvcAnalyzer{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + results, err := pvcAnalyzer.Analyze(tt.config) + require.NoError(t, err) + + if tt.expectations == nil { + require.Equal(t, 0, len(results)) + } else { + for i, expectation := range tt.expectations { + require.Equal(t, expectation, results[i].Name) + for _, failure := range results[i].Error { + require.Equal(t, "PVC provisioning failed", failure.Text) + } + } + } + }) + } +} diff --git a/pkg/analyzer/rs_test.go b/pkg/analyzer/rs_test.go index 6ec95e909b..42d6d3df84 100644 --- a/pkg/analyzer/rs_test.go +++ b/pkg/analyzer/rs_test.go @@ -142,10 +142,10 @@ func TestReplicaSetAnalyzer(t *testing.T) { }, } - for i, result := range results { - require.Equal(t, expectations[i].name, result.Name) - for j, failure := range result.Error { - require.Equal(t, expectations[i].failuresText[j], failure.Text) + for i, expectation := range expectations { + require.Equal(t, expectation.name, results[i].Name) + for j, failure := range results[i].Error { + require.Equal(t, expectation.failuresText[j], failure.Text) } } }