diff --git a/backend/cmd/headlamp_test.go b/backend/cmd/headlamp_test.go index ec3aa07fe3b..1ecf616c9fc 100644 --- a/backend/cmd/headlamp_test.go +++ b/backend/cmd/headlamp_test.go @@ -1769,6 +1769,49 @@ func TestCacheMiddleware_CacheHitAndCacheMiss_RealK8s(t *testing.T) { assert.Equal(t, "true", secondFromCache, "second request should be from cache") } +// Integration test to verify CacheMiddleware does not cause excessive goroutine usage with a real K8s API server. +func TestCacheMiddleware_GoRoutines(t *testing.T) { + if os.Getenv("HEADLAMP_RUN_INTEGRATION_TESTS") != strconv.FormatBool(istrue) { + t.Skip("skipping integration test") + } + + start := runtime.NumGoroutine() + + c, clusterName := newRealK8sHeadlampConfig(t) + handler := createHeadlampHandler(c) + + ts := httptest.NewServer(handler) + defer ts.Close() + + apiPath := "/clusters/" + clusterName + "/api/v1/pods" + ctx := context.Background() + + resp, err := httpRequestWithContext(ctx, ts.URL+apiPath, "GET") + require.NoError(t, err) + + defer resp.Body.Close() + + time.Sleep(3 * time.Second) + + running := runtime.NumGoroutine() + + time.Sleep(3 * time.Second) + + after := runtime.NumGoroutine() + + expected := false + afterExpected := false + + if (running >= 50 && running < 200) && (after >= 50 && after < 200) { + expected = true + afterExpected = true + } + + assert.Equal(t, 3, start) + assert.Equal(t, running >= 50 && running < 200, expected) + assert.Equal(t, after >= 50 && after < 200, afterExpected) +} + // TestCacheMiddleware_CacheInvalidation_RealK8s tests cache invalidation with a // real Kubernetes API server. Creates a ConfigMap, invalidates via DELETE, then // verifies the next GET fetches fresh data. Requires HEADLAMP_RUN_INTEGRATION_TESTS=true diff --git a/backend/pkg/k8cache/cacheInvalidation.go b/backend/pkg/k8cache/cacheInvalidation.go index 0d64b8beea7..faa826554e0 100644 --- a/backend/pkg/k8cache/cacheInvalidation.go +++ b/backend/pkg/k8cache/cacheInvalidation.go @@ -135,7 +135,43 @@ func returnGVRList(apiResourceLists []*metav1.APIResourceList) []schema.GroupVer } } - return gvrList + // filtering the gvrList to make sure spawning go routines for only important + // resources which are required to be watched and cached, + // this will help to reduce the performance issue and resource utilization. + filtered := filterImportantResources(gvrList) + + return filtered +} + +// filterImportantResources filters the provided list of GroupVersionResources to +// include only those that are deemed important for caching and watching. +// This helps reduce the number of resources we watch and cache, +// improving performance and resource utilization. +func filterImportantResources(gvrList []schema.GroupVersionResource) []schema.GroupVersionResource { + allowed := map[string]struct{}{ + "pods": {}, + "services": {}, + "deployments": {}, + "replicasets": {}, + "statefulsets": {}, + "daemonsets": {}, + "nodes": {}, + "configmaps": {}, + "secrets": {}, + "jobs": {}, + "cronjobs": {}, + } + + filtered := make([]schema.GroupVersionResource, 0, len(allowed)) + + for _, gvr := range gvrList { + if _, ok := allowed[gvr.Resource]; ok { + filtered = append(filtered, gvr) + } + } + + // return the filtered list of GroupVersionResources that are important for caching and watching + return filtered } // Corrected CheckForChanges.