discovery/aggregated: Add invalidation callback support

Add callback mechanism to notify peer-aggregated discovery handler
when local discovery changes, enabling efficient cache invalidation.

Part of KEP-4020: Unknown Version Interoperability Proxy
This commit is contained in:
Richa Banker
2025-11-05 21:14:26 -08:00
parent 6b8f793888
commit c72f9f73d9

View File

@@ -85,6 +85,9 @@ type ResourceManager interface {
// The group from the least-numbered source is used
WithSource(source Source) ResourceManager
// AddInvalidationCallback adds a callback to be called when the discovery cache is invalidated.
AddInvalidationCallback(callback func())
http.Handler
}
@@ -116,6 +119,10 @@ func (rm resourceManager) WithSource(source Source) ResourceManager {
}
}
func (rm resourceManager) AddInvalidationCallback(callback func()) {
rm.resourceDiscoveryManager.AddInvalidationCallback(callback)
}
type groupKey struct {
name string
@@ -138,9 +145,10 @@ type resourceDiscoveryManager struct {
// Writes protected by the lock.
// List of all apigroups & resources indexed by the resource manager
lock sync.RWMutex
apiGroups map[groupKey]*apidiscoveryv2.APIGroupDiscovery
versionPriorities map[groupVersionKey]priorityInfo
lock sync.RWMutex
apiGroups map[groupKey]*apidiscoveryv2.APIGroupDiscovery
versionPriorities map[groupVersionKey]priorityInfo
invalidationCallback atomic.Pointer[func()]
}
type priorityInfo struct {
@@ -148,6 +156,17 @@ type priorityInfo struct {
VersionPriority int
}
func (rdm *resourceDiscoveryManager) AddInvalidationCallback(callback func()) {
rdm.invalidationCallback.Store(&callback)
}
func (rdm *resourceDiscoveryManager) invalidateCacheLocked() {
rdm.cache.Store(nil)
if callback := rdm.invalidationCallback.Load(); callback != nil {
(*callback)()
}
}
func NewResourceManager(path string) ResourceManager {
scheme := runtime.NewScheme()
utilruntime.Must(apidiscoveryv2.AddToScheme(scheme))
@@ -188,7 +207,7 @@ func (rdm *resourceDiscoveryManager) SetGroupVersionPriority(source Source, gv m
GroupPriorityMinimum: groupPriorityMinimum,
VersionPriority: versionPriority,
}
rdm.cache.Store(nil)
rdm.invalidateCacheLocked()
}
func (rdm *resourceDiscoveryManager) SetGroups(source Source, groups []apidiscoveryv2.APIGroupDiscovery) {
@@ -196,7 +215,7 @@ func (rdm *resourceDiscoveryManager) SetGroups(source Source, groups []apidiscov
defer rdm.lock.Unlock()
rdm.apiGroups = nil
rdm.cache.Store(nil)
rdm.invalidateCacheLocked()
for _, group := range groups {
for _, version := range group.Versions {
@@ -297,7 +316,7 @@ func (rdm *resourceDiscoveryManager) addGroupVersionLocked(source Source, groupN
}
// Reset response document so it is recreated lazily
rdm.cache.Store(nil)
rdm.invalidateCacheLocked()
}
func (rdm *resourceDiscoveryManager) RemoveGroupVersion(source Source, apiGroup metav1.GroupVersion) {
@@ -338,7 +357,7 @@ func (rdm *resourceDiscoveryManager) RemoveGroupVersion(source Source, apiGroup
}
// Reset response document so it is recreated lazily
rdm.cache.Store(nil)
rdm.invalidateCacheLocked()
}
func (rdm *resourceDiscoveryManager) RemoveGroup(source Source, groupName string) {
@@ -521,12 +540,22 @@ func (rdm *resourceDiscoveryManager) serveHTTP(resp http.ResponseWriter, req *ht
response := cache.cachedResponse
etag := cache.cachedResponseETag
mediaType, _, err := negotiation.NegotiateOutputMediaType(req, rdm.serializer, DiscoveryEndpointRestrictions)
writeDiscoveryResponse(&response, etag, rdm.serializer, resp, req)
}
func writeDiscoveryResponse(
resp *apidiscoveryv2.APIGroupDiscoveryList,
etag string,
serializer runtime.NegotiatedSerializer,
w http.ResponseWriter,
req *http.Request,
) {
mediaType, _, err := negotiation.NegotiateOutputMediaType(req, serializer, DiscoveryEndpointRestrictions)
if err != nil {
// Should never happen. wrapper.go will only proxy requests to this
// handler if the media type passes DiscoveryEndpointRestrictions
utilruntime.HandleError(err)
resp.WriteHeader(http.StatusInternalServerError)
w.WriteHeader(http.StatusInternalServerError)
return
}
var targetGV schema.GroupVersion
@@ -534,40 +563,39 @@ func (rdm *resourceDiscoveryManager) serveHTTP(resp http.ResponseWriter, req *ht
(mediaType.Convert.GroupVersion() != apidiscoveryv2.SchemeGroupVersion &&
mediaType.Convert.GroupVersion() != apidiscoveryv2beta1.SchemeGroupVersion) {
utilruntime.HandleError(fmt.Errorf("expected aggregated discovery group version, got group: %s, version %s", mediaType.Convert.Group, mediaType.Convert.Version))
resp.WriteHeader(http.StatusInternalServerError)
w.WriteHeader(http.StatusInternalServerError)
return
}
if mediaType.Convert.GroupVersion() == apidiscoveryv2beta1.SchemeGroupVersion &&
utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AggregatedDiscoveryRemoveBetaType) {
klog.Errorf("aggregated discovery version v2beta1 is removed. Please update to use v2")
resp.WriteHeader(http.StatusNotFound)
w.WriteHeader(http.StatusNotFound)
return
}
targetGV = mediaType.Convert.GroupVersion()
if len(etag) > 0 {
// Use proper e-tag headers if one is available
ServeHTTPWithETag(
&response,
resp,
etag,
targetGV,
rdm.serializer,
resp,
serializer,
w,
req,
)
} else {
// Default to normal response in rare case etag is
// not cached with the object for some reason.
responsewriters.WriteObjectNegotiated(
rdm.serializer,
serializer,
DiscoveryEndpointRestrictions,
targetGV,
resp,
w,
req,
http.StatusOK,
&response,
resp,
true,
)
}