Skip to content

Commit

Permalink
[PWX-38984] Added ListNamespacesUsingLabelSelector and GetPersistentV…
Browse files Browse the repository at this point in the history
…olumeClaimsUsingLabelSelector 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 <[email protected]>
Co-authored-by: Manali Daigavane <[email protected]>
  • Loading branch information
mdaigavane-px and Manali Daigavane authored Oct 7, 2024
1 parent 96756af commit a1593fc
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
23 changes: 23 additions & 0 deletions k8s/core/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down
135 changes: 135 additions & 0 deletions k8s/core/namespaces_test.go
Original file line number Diff line number Diff line change
@@ -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)

}
16 changes: 16 additions & 0 deletions k8s/core/persistentvolumeclaims.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
78 changes: 78 additions & 0 deletions k8s/core/persistenvolumeclaims_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit a1593fc

Please sign in to comment.