From a1593fc48bb164a52c0fdfa9699e700ff066fac5 Mon Sep 17 00:00:00 2001 From: mdaigavane-px Date: Mon, 7 Oct 2024 18:53:42 +0530 Subject: [PATCH] [PWX-38984] Added ListNamespacesUsingLabelSelector and GetPersistentVolumeClaimsUsingLabelSelector that uses MatchExpressions along with MatchLabels for querying (#411) * [PWX-38984] Add label-based namespace and PVC selectors using MatchExpressions This commit introduces two new functions: - ListNamespacesUsingLabelSelector - GetPersistentVolumeClaimsUsingLabelSelector These functions use MatchExpressions alongside MatchLabels for better filtering. --------- Signed-off-by: Manali Daigavane Co-authored-by: Manali Daigavane --- k8s/core/namespaces.go | 23 +++++ k8s/core/namespaces_test.go | 135 +++++++++++++++++++++++++ k8s/core/persistentvolumeclaims.go | 16 +++ k8s/core/persistenvolumeclaims_test.go | 78 ++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 k8s/core/namespaces_test.go create mode 100644 k8s/core/persistenvolumeclaims_test.go diff --git a/k8s/core/namespaces.go b/k8s/core/namespaces.go index f1ebbc66..8cbae902 100644 --- a/k8s/core/namespaces.go +++ b/k8s/core/namespaces.go @@ -10,9 +10,13 @@ import ( // NamespaceOps is an interface to perform namespace operations type NamespaceOps interface { // ListNamespaces returns all the namespaces + // Deprecated: Use ListNamespacesUsingLabelSelector instead. ListNamespaces(labelSelector map[string]string) (*corev1.NamespaceList, error) // ListNamespacesV2 returns all the namespaces when the labelSlector is a String + // Deprecated: Use ListNamespacesUsingLabelSelector instead. ListNamespacesV2(labelSelector string) (*corev1.NamespaceList, error) + // ListNamespacesUsingLabelSelector returns all the namespaces when the labelSlector is of type metav1.LabelSelector + ListNamespacesUsingLabelSelector(labelSelector metav1.LabelSelector) (*corev1.NamespaceList, error) // GetNamespace returns a namespace object for given name GetNamespace(name string) (*corev1.Namespace, error) // CreateNamespace creates a namespace with given name and metadata @@ -24,6 +28,7 @@ type NamespaceOps interface { } // ListNamespaces returns all the namespaces +// Deprecated: Use ListNamespacesUsingLabelSelector instead. func (c *Client) ListNamespaces(labelSelector map[string]string) (*corev1.NamespaceList, error) { if err := c.initClient(); err != nil { return nil, err @@ -35,6 +40,7 @@ func (c *Client) ListNamespaces(labelSelector map[string]string) (*corev1.Namesp } // ListNamespacesV2 returns all the namespaces when the labelSlector is a String +// Deprecated: Use ListNamespacesUsingLabelSelector instead. func (c *Client) ListNamespacesV2(labelSelector string) (*corev1.NamespaceList, error) { if err := c.initClient(); err != nil { return nil, err @@ -45,6 +51,23 @@ func (c *Client) ListNamespacesV2(labelSelector string) (*corev1.NamespaceList, }) } +// ListNamespacesUsingLabelSelector list namespaces using the provided LabelSelector. +func (c *Client) ListNamespacesUsingLabelSelector(labelSelector metav1.LabelSelector) (*corev1.NamespaceList, error) { + if err := c.initClient(); err != nil { + return nil, err + } + + namespaceLabelSelector, err := metav1.LabelSelectorAsSelector(&labelSelector) + if err != nil { + return nil, err + } + + // List namespaces based on the label selector + return c.kubernetes.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{ + LabelSelector: namespaceLabelSelector.String(), + }) +} + // GetNamespace returns a namespace object for given name func (c *Client) GetNamespace(name string) (*corev1.Namespace, error) { if err := c.initClient(); err != nil { diff --git a/k8s/core/namespaces_test.go b/k8s/core/namespaces_test.go new file mode 100644 index 00000000..dda25802 --- /dev/null +++ b/k8s/core/namespaces_test.go @@ -0,0 +1,135 @@ +package core + +import ( + "context" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/events" + "k8s.io/client-go/tools/record" +) + +// MockClient returns a mock Client for testing +func MockClient() *Client { + // Create a fake Kubernetes client + fakeClientset := fake.NewSimpleClientset() + + // Create the mock client struct + return &Client{ + config: &rest.Config{}, // Mock rest.Config if needed + kubernetes: fakeClientset, // Use fake clientset as Kubernetes interface + eventRecordersLegacy: make(map[string]record.EventRecorder), // Mock legacy recorders + eventBroadcasterLegacy: nil, // No-op for legacy broadcaster + eventRecordersNew: make(map[string]events.EventRecorder), // Mock new recorders + eventBroadcasterNew: nil, // No-op for new broadcaster + eventRecordersLock: sync.Mutex{}, // Mock sync lock + } +} + +// Setup cluster ad write a test to list namespaces +func TestListNamespacesV3(t *testing.T) { + // Create a mock Client + client := MockClient() + SetInstance(client) + + // Add namespace to the fake clientset + _, err := client.kubernetes.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace-1", + Labels: map[string]string{"app": "test-app", "env": "test"}, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = client.kubernetes.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace-2", + Labels: map[string]string{"app": "test-app", "foo": "bar"}, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + // Test 1 + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test-app", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "env", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"test"}, + }, + }, + } + namespaceList, err := client.ListNamespacesUsingLabelSelector(labelSelector) + require.NoError(t, err) + assert.Len(t, namespaceList.Items, 1) + assert.Equal(t, "test-namespace-1", namespaceList.Items[0].Name) + + // Test 2 + labelSelector = metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test-app", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar"}, + }, + }, + } + namespaceList, err = client.ListNamespacesUsingLabelSelector(labelSelector) + require.NoError(t, err) + assert.Len(t, namespaceList.Items, 1) + + // Test 3 + labelSelector = metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpDoesNotExist, + }, + }, + } + namespaceList, err = client.ListNamespacesUsingLabelSelector(labelSelector) + require.NoError(t, err) + assert.Len(t, namespaceList.Items, 1) + + // Test 4 + labelSelector = metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpExists, + }, + }, + } + namespaceList, err = client.ListNamespacesUsingLabelSelector(labelSelector) + require.NoError(t, err) + assert.Len(t, namespaceList.Items, 1) + + // Test 5 + labelSelector = metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test-app", + }, + } + namespaceList, err = client.ListNamespacesUsingLabelSelector(labelSelector) + require.NoError(t, err) + assert.Len(t, namespaceList.Items, 2) + + //Test 6 + labelSelector = metav1.LabelSelector{} + namespaceList, err = client.ListNamespacesUsingLabelSelector(labelSelector) + require.NoError(t, err) + assert.Len(t, namespaceList.Items, 2) + +} diff --git a/k8s/core/persistentvolumeclaims.go b/k8s/core/persistentvolumeclaims.go index e0baaf34..edc1970c 100644 --- a/k8s/core/persistentvolumeclaims.go +++ b/k8s/core/persistentvolumeclaims.go @@ -29,7 +29,10 @@ type PersistentVolumeClaimOps interface { // GetPersistentVolumeClaim returns the PVC for given name and namespace GetPersistentVolumeClaim(pvcName string, namespace string) (*corev1.PersistentVolumeClaim, error) // GetPersistentVolumeClaims returns all PVCs in given namespace and that match the optional labelSelector + // Deprecated: Use GetPersistentVolumeClaimsUsingLabelSelector instead. GetPersistentVolumeClaims(namespace string, labelSelector map[string]string) (*corev1.PersistentVolumeClaimList, error) + // GetPersistentVolumeClaimsUsingLabelSelector returns all PVCs in given namespace and that match the optional labelSelector of type metav1.LabelSelector + GetPersistentVolumeClaimsUsingLabelSelector(namespace string, labelSelector metav1.LabelSelector) (*corev1.PersistentVolumeClaimList, error) // CreatePersistentVolume creates the given PV CreatePersistentVolume(pv *corev1.PersistentVolume) (*corev1.PersistentVolume, error) // GetPersistentVolume returns the PV for given name @@ -177,12 +180,25 @@ func (c *Client) GetPersistentVolumeClaim(pvcName string, namespace string) (*co } // GetPersistentVolumeClaims returns all PVCs in given namespace and that match the optional labelSelector +// Deprecated: Use GetPersistentVolumeClaimsUsingLabelSelector instead. func (c *Client) GetPersistentVolumeClaims(namespace string, labelSelector map[string]string) (*corev1.PersistentVolumeClaimList, error) { return c.getPVCsWithListOptions(namespace, metav1.ListOptions{ LabelSelector: mapToCSV(labelSelector), }) } +// GetPersistentVolumeClaimsUsingLabelSelector returns all PVCs in the given namespace and that match the optional labelSelector of type metav1.LabelSelector +func (c *Client) GetPersistentVolumeClaimsUsingLabelSelector(namespace string, labelSelector metav1.LabelSelector) (*corev1.PersistentVolumeClaimList, error) { + pvcLabelSelector, err := metav1.LabelSelectorAsSelector(&labelSelector) + if err != nil { + return nil, err + } + + return c.getPVCsWithListOptions(namespace, metav1.ListOptions{ + LabelSelector: pvcLabelSelector.String(), + }) +} + func (c *Client) getPVCsWithListOptions(namespace string, listOpts metav1.ListOptions) (*corev1.PersistentVolumeClaimList, error) { if err := c.initClient(); err != nil { return nil, err diff --git a/k8s/core/persistenvolumeclaims_test.go b/k8s/core/persistenvolumeclaims_test.go new file mode 100644 index 00000000..10ba4aea --- /dev/null +++ b/k8s/core/persistenvolumeclaims_test.go @@ -0,0 +1,78 @@ +package core + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetPersistentVolumeClaimsV2(t *testing.T) { + // Create a mock Client + client := MockClient() + SetInstance(client) + + // Add PVC to the fake clientset + _, err := client.kubernetes.CoreV1().PersistentVolumeClaims("test-namespace-1").Create(context.TODO(), &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pvc-1", + Namespace: "test-namespace-1", + Labels: map[string]string{"app": "test-app", "env": "test"}, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = client.kubernetes.CoreV1().PersistentVolumeClaims("test-namespace-1").Create(context.TODO(), &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pvc-2", + Namespace: "test-namespace-1", + Labels: map[string]string{"app": "test-app", "foo": "bar"}, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + // Test 1 + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test-app", + }, + } + pvcList, err := client.GetPersistentVolumeClaimsUsingLabelSelector("test-namespace-1", labelSelector) + require.NoError(t, err) + assert.Len(t, pvcList.Items, 2) + + // Test 2 + labelSelector = metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test-app", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "env", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"test"}, + }, + }, + } + pvcList, err = client.GetPersistentVolumeClaimsUsingLabelSelector("test-namespace-1", labelSelector) + require.NoError(t, err) + assert.Len(t, pvcList.Items, 1) + assert.Equal(t, "test-pvc-1", pvcList.Items[0].Name) + + // Test 3 + labelSelector = metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpDoesNotExist, + }, + }, + } + pvcList, err = client.GetPersistentVolumeClaimsUsingLabelSelector("test-namespace-1", labelSelector) + require.NoError(t, err) + assert.Len(t, pvcList.Items, 1) + assert.Equal(t, "test-pvc-1", pvcList.Items[0].Name) +}