Add e2e parallel

Signed-off-by: faizanahmad055 <faizan.ahmad55@outlook.com>
This commit is contained in:
faizanahmad055
2026-05-11 21:59:32 +02:00
parent 96ac8d1daf
commit caebfd98f9
10 changed files with 485 additions and 243 deletions

View File

@@ -2,7 +2,6 @@ package controller
import (
"fmt"
"slices"
"sync"
"sync/atomic"
"time"
@@ -48,11 +47,46 @@ type Controller struct {
// read by the informer event handlers, so they must be atomic.
var secretControllerInitialized atomic.Bool
var configmapControllerInitialized atomic.Bool
var selectedNamespacesCache []string
// selectedNamespacesCache holds an immutable snapshot of the set of namespace
// names that match the namespace label selector. Written exclusively by the
// namespace controller's informer goroutine; read concurrently by configmap/
// secret controller informer goroutines. Using atomic.Value with an immutable
// map[string]struct{} snapshot avoids mutexes and prevents data races.
var selectedNamespacesCache atomic.Value // always stores map[string]struct{}
// loadSelectedNamespaces returns the current namespace snapshot (never nil).
func loadSelectedNamespaces() map[string]struct{} {
if v := selectedNamespacesCache.Load(); v != nil {
return v.(map[string]struct{})
}
return map[string]struct{}{}
}
// storeSelectedNamespaces replaces the current snapshot with one built from ns.
// It is the only mutator of selectedNamespacesCache and is called only from
// the namespace controller's informer goroutine (or from tests for setup).
func storeSelectedNamespaces(ns []string) {
m := make(map[string]struct{}, len(ns))
for _, n := range ns {
m[n] = struct{}{}
}
selectedNamespacesCache.Store(m)
}
// loadSelectedNamespacesList returns the current namespace names as a slice.
// Intended for use in tests where slice-based assertions are more convenient.
func loadSelectedNamespacesList() []string {
m := loadSelectedNamespaces()
result := make([]string, 0, len(m))
for k := range m {
result = append(result, k)
}
return result
}
// NewController for initializing a Controller
func NewController(client kubernetes.Interface, resource string, namespace string, ignoredNamespaces []string, namespaceLabelSelector string, resourceLabelSelector string, collectors metrics.Collectors) (*Controller,
error) {
func NewController(client kubernetes.Interface, resource string, namespace string, ignoredNamespaces []string, namespaceLabelSelector string, resourceLabelSelector string, collectors metrics.Collectors) (*Controller, error) {
if options.SyncAfterRestart {
secretControllerInitialized.Store(true)
configmapControllerInitialized.Store(true)
@@ -155,36 +189,45 @@ func (c *Controller) resourceInSelectedNamespaces(raw interface{}) bool {
return true
}
namespaces := loadSelectedNamespaces()
var ns string
switch object := raw.(type) {
case *v1.ConfigMap:
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
return true
}
ns = object.GetNamespace()
case *v1.Secret:
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
return true
}
ns = object.GetNamespace()
case *csiv1.SecretProviderClassPodStatus:
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
return true
}
ns = object.GetNamespace()
default:
return false
}
return false
_, ok := namespaces[ns]
return ok
}
func (c *Controller) addSelectedNamespaceToCache(namespace v1.Namespace) {
selectedNamespacesCache = append(selectedNamespacesCache, namespace.GetName())
old := loadSelectedNamespaces()
next := make(map[string]struct{}, len(old)+1)
for k := range old {
next[k] = struct{}{}
}
next[namespace.GetName()] = struct{}{}
selectedNamespacesCache.Store(next)
logrus.Infof("added namespace to be watched: %s", namespace.GetName())
}
func (c *Controller) removeSelectedNamespaceFromCache(namespace v1.Namespace) {
for i, v := range selectedNamespacesCache {
if v == namespace.GetName() {
selectedNamespacesCache = append(selectedNamespacesCache[:i], selectedNamespacesCache[i+1:]...)
logrus.Infof("removed namespace from watch: %s", namespace.GetName())
return
}
old := loadSelectedNamespaces()
if _, ok := old[namespace.GetName()]; !ok {
return
}
next := make(map[string]struct{}, len(old))
for k := range old {
next[k] = struct{}{}
}
delete(next, namespace.GetName())
selectedNamespacesCache.Store(next)
logrus.Infof("removed namespace from watch: %s", namespace.GetName())
}
// Update function to add an old object and a new object to the queue in case of updating a resource
@@ -319,6 +362,9 @@ func (c *Controller) processNextItem() bool {
rh, ok := resourceHandler.(handler.ResourceHandler)
if !ok {
logrus.Errorf("Invalid resource handler type: %T", resourceHandler)
// Clear rate-limiter state so the item doesn't leak memory in the queue.
c.queue.Forget(resourceHandler)
c.collectors.RecordError("invalid_handler_type")
return true
}
err := rh.Handle()

View File

@@ -45,7 +45,7 @@ func (m *mockResourceHandler) GetEnqueueTime() time.Time {
func resetGlobalState() {
secretControllerInitialized.Store(false)
configmapControllerInitialized.Store(false)
selectedNamespacesCache = []string{}
storeSelectedNamespaces([]string{})
}
// newTestController creates a controller for testing without starting informers
@@ -223,12 +223,12 @@ func TestResourceInSelectedNamespaces(t *testing.T) {
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
resetGlobalState()
selectedNamespacesCache = tt.cachedNamespaces
resetGlobalState()
storeSelectedNamespaces(tt.cachedNamespaces)
c := newTestController([]string{}, tt.namespaceSelector)
result := c.resourceInSelectedNamespaces(tt.resource)
assert.Equal(t, tt.expected, result)
c := newTestController([]string{}, tt.namespaceSelector)
result := c.resourceInSelectedNamespaces(tt.resource)
assert.Equal(t, tt.expected, result)
},
)
}
@@ -244,17 +244,17 @@ func TestAddSelectedNamespaceToCache(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "namespace-1"},
}
c.addSelectedNamespaceToCache(ns1)
assert.Contains(t, selectedNamespacesCache, "namespace-1")
assert.Len(t, selectedNamespacesCache, 1)
assert.Contains(t, loadSelectedNamespaces(), "namespace-1")
assert.Len(t, loadSelectedNamespaces(), 1)
// Add second namespace
ns2 := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "namespace-2"},
}
c.addSelectedNamespaceToCache(ns2)
assert.Contains(t, selectedNamespacesCache, "namespace-1")
assert.Contains(t, selectedNamespacesCache, "namespace-2")
assert.Len(t, selectedNamespacesCache, 2)
assert.Contains(t, loadSelectedNamespaces(), "namespace-1")
assert.Contains(t, loadSelectedNamespaces(), "namespace-2")
assert.Len(t, loadSelectedNamespaces(), 2)
}
func TestRemoveSelectedNamespaceFromCache(t *testing.T) {
@@ -293,16 +293,16 @@ func TestRemoveSelectedNamespaceFromCache(t *testing.T) {
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
resetGlobalState()
selectedNamespacesCache = tt.initialCache
resetGlobalState()
storeSelectedNamespaces(tt.initialCache)
c := newTestController([]string{}, "env=prod")
ns := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: tt.namespaceToRemove},
}
c.removeSelectedNamespaceFromCache(ns)
c := newTestController([]string{}, "env=prod")
ns := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: tt.namespaceToRemove},
}
c.removeSelectedNamespaceFromCache(ns)
assert.Equal(t, tt.expectedCache, selectedNamespacesCache)
assert.ElementsMatch(t, tt.expectedCache, loadSelectedNamespacesList())
},
)
}
@@ -500,10 +500,10 @@ func TestUpdateHandler(t *testing.T) {
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
resetGlobalState()
if tt.cachedNamespaces != nil {
selectedNamespacesCache = tt.cachedNamespaces
}
resetGlobalState()
if tt.cachedNamespaces != nil {
storeSelectedNamespaces(tt.cachedNamespaces)
}
c := newTestController(tt.ignoredNamespaces, tt.namespaceSelector)
c.Update(tt.oldResource, tt.newResource)
@@ -675,13 +675,13 @@ func TestAddHandlerWithNamespaceEvent(t *testing.T) {
c.Add(ns)
assert.Contains(t, selectedNamespacesCache, "new-namespace")
assert.Contains(t, loadSelectedNamespaces(), "new-namespace")
assert.Equal(t, 0, c.queue.Len(), "Namespace add should not queue anything")
}
func TestDeleteHandlerWithNamespaceEvent(t *testing.T) {
resetGlobalState()
selectedNamespacesCache = []string{"ns-1", "ns-to-delete", "ns-2"}
storeSelectedNamespaces([]string{"ns-1", "ns-to-delete", "ns-2"})
c := newTestController([]string{}, "env=prod")
options.ReloadOnDelete = "true"
@@ -694,9 +694,9 @@ func TestDeleteHandlerWithNamespaceEvent(t *testing.T) {
c.Delete(ns)
assert.NotContains(t, selectedNamespacesCache, "ns-to-delete")
assert.Contains(t, selectedNamespacesCache, "ns-1")
assert.Contains(t, selectedNamespacesCache, "ns-2")
assert.NotContains(t, loadSelectedNamespaces(), "ns-to-delete")
assert.Contains(t, loadSelectedNamespaces(), "ns-1")
assert.Contains(t, loadSelectedNamespaces(), "ns-2")
assert.Equal(t, 0, c.queue.Len(), "Namespace delete should not queue anything")
}