diff --git a/go.mod b/go.mod index 7d2fac4..b3a1962 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/zapr v1.2.3 // indirect diff --git a/go.sum b/go.sum index bd7f340..cbcc252 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= diff --git a/internal/controller/pvc_controller_test.go b/internal/controller/pvc_controller_test.go new file mode 100644 index 0000000..56ee326 --- /dev/null +++ b/internal/controller/pvc_controller_test.go @@ -0,0 +1,199 @@ +package controller + +import ( + "fmt" + "github.com/AppsFlyer/local-pvc-releaser/internal/exporters" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2/klogr" + "os" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "testing" +) + +func getReconciler() *PVCReconciler { + fakeClient, err := getFakeClient() + if err != nil { + fmt.Println("unable to create fake client. exiting..") + os.Exit(1) + } + logr := klogr.New() + + collector := exporters.NewCollector() + reconciler := &PVCReconciler{ + Client: fakeClient, + Scheme: fakeClient.Scheme(), + Logger: &logr, + DryRun: false, + Collector: collector, + PvcSelector: true, + PvcAnoCustomKey: "appsflyer.com/local-pvc-releaser", + PvcAnoCustomValue: "enabled", + } + + return reconciler +} + +func getFakeClient(initObjs ...client.Object) (client.WithWatch, error) { + scheme := runtime.NewScheme() + if err := corev1.AddToScheme(scheme); err != nil { + return nil, err + } + return fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build(), nil +} + +func TestFilterPVCListByPV(t *testing.T) { + reconciler := getReconciler() + + // Create a sample PVC list + pvcList := &v1.PersistentVolumeClaimList{ + Items: []v1.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "pv1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc2"}, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "pv2", + }, + }, + }, + } + + // Create a sample PV + pvExist := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "pv1"}, + } + + pvNil := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "pv3"}, + } + + // Simulate successful filtering + result := reconciler.FilterPVCListByPV(pvcList, pvExist) + + // Check if the result is as expected + if result == nil { + t.Error("Expected a non-nil result, got nil") + } + + if result.Name != "pvc1" { + t.Errorf("Expected result to be pvc1, got %s", result.Name) + } + + // Simulate non existed pv + result = reconciler.FilterPVCListByPV(pvcList, pvNil) + + // Check if the result is as expected + if result != nil { + t.Error("Expected nil result, got non-nil object as the filter return matched value") + } +} + +func TestFilterPVListByNodeName(t *testing.T) { + reconciler := getReconciler() + + // Create a sample PV list + pvList := &v1.PersistentVolumeList{ + Items: []v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pv1"}, + Spec: v1.PersistentVolumeSpec{ + NodeAffinity: &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "kubernetes.io/hostname", + Operator: v1.NodeSelectorOpIn, + Values: []string{"node1"}, + }, + }, + }, + }, + }, + }, + }, + Status: v1.PersistentVolumeStatus{ + Phase: v1.VolumeBound, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pv2"}, + Spec: v1.PersistentVolumeSpec{ + NodeAffinity: &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "kubernetes.io/hostname", + Operator: v1.NodeSelectorOpIn, + Values: []string{"node2"}, + }, + }, + }, + }, + }, + }, + }, + Status: v1.PersistentVolumeStatus{ + Phase: v1.VolumeBound, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pv3"}, + Spec: v1.PersistentVolumeSpec{ + NodeAffinity: &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "kubernetes.io/hostname", + Operator: v1.NodeSelectorOpIn, + Values: []string{"node3"}, + }, + }, + }, + }, + }, + }, + }, + Status: v1.PersistentVolumeStatus{ + Phase: v1.VolumeBound, + }, + }, + // Add more PVs as needed for additional test cases + }, + } + + // Call the function for matched results + nodeName := "node2" + result := reconciler.FilterPVListByNodeName(pvList, nodeName) + + // Check if the result is as expected + if len(result) != 1 { + t.Errorf("Expected 1 related PV, got %d", len(result)) + } + + if result[0].Name != "pv2" { + t.Errorf("Expected result to be pv2, got %s", result[0].Name) + } + + // Call the function with empty PV list + nodeNameNil := "" + result = reconciler.FilterPVListByNodeName(&v1.PersistentVolumeList{}, nodeNameNil) + + // Check if the result is as expected + if len(result) != 0 { + t.Errorf("Expected 0 related PV, got %d", len(result)) + } +}