diff --git a/pkg/project/auth/cache.go b/pkg/project/auth/cache.go index 0f0438f9e..110e2b784 100644 --- a/pkg/project/auth/cache.go +++ b/pkg/project/auth/cache.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "sync" + "sync/atomic" "time" "k8s.io/klog/v2" @@ -107,6 +108,41 @@ func (rs *statelessSkipSynchronizer) SkipSynchronize(prevState string, versioned return skip, currentState } +func newOverridableSkipSynchronizer() *overridableSkipSynchronizer { + return &overridableSkipSynchronizer{ + skipSynchronizer: &statelessSkipSynchronizer{}, + } +} + +var ( + bTrue = true + bFalse = false + pTrue = &bTrue + pFalse = &bFalse +) + +// overridableSkipSynchronizer wraps a base skipSynchronizer with the ability to +// override the next SkipSynchronize call. +type overridableSkipSynchronizer struct { + override atomic.Pointer[bool] + skipSynchronizer +} + +func (o *overridableSkipSynchronizer) SkipSynchronize(prevState string, versionedObjects ...LastSyncResourceVersioner) (bool, string) { + if override := o.override.Swap(nil); override != nil { + return *override, prevState + } + return o.skipSynchronizer.SkipSynchronize(prevState, versionedObjects...) +} + +func (o *overridableSkipSynchronizer) ForceDoNotSkip() { + o.override.Store(pFalse) +} + +func (o *overridableSkipSynchronizer) ForceSkip() { + o.override.Store(pTrue) +} + type neverSkipSynchronizer struct{} func (s *neverSkipSynchronizer) SkipSynchronize(prevState string, versionedObjects ...LastSyncResourceVersioner) (bool, string) { @@ -244,6 +280,14 @@ func NewAuthorizationCache( watchers: []CacheWatcher{}, } + skipSyncer := newOverridableSkipSynchronizer() + if _, err := informers.Roles().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{DeleteFunc: func(obj interface{}) { skipSyncer.ForceDoNotSkip() }}); err != nil { + utilruntime.HandleError(err) + } + if _, err := informers.RoleBindings().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{DeleteFunc: func(obj interface{}) { skipSyncer.ForceDoNotSkip() }}); err != nil { + utilruntime.HandleError(err) + } + ac.skip = skipSyncer ac.lastSyncResourceVersioner = namespaceLastSyncResourceVersioner ac.syncHandler = ac.syncRequest return ac @@ -251,8 +295,6 @@ func NewAuthorizationCache( // Run begins watching and synchronizing the cache func (ac *AuthorizationCache) Run(period time.Duration) { - ac.skip = &statelessSkipSynchronizer{} - go utilwait.Forever(func() { ac.synchronize() }, period) } diff --git a/pkg/project/auth/cache_test.go b/pkg/project/auth/cache_test.go index 4e0da05aa..9d1038f58 100644 --- a/pkg/project/auth/cache_test.go +++ b/pkg/project/auth/cache_test.go @@ -79,6 +79,7 @@ func (mr *mockReviewer) Review(name string) (Review, error) { } func validateList(t *testing.T, lister Lister, user user.Info, expectedSet sets.String) { + t.Helper() namespaceList, err := lister.List(user, labels.Everything()) if err != nil { t.Errorf("Unexpected error %v", err) @@ -135,6 +136,7 @@ func TestSyncNamespace(t *testing.T) { reviewer, informers.Rbac().V1(), ) + authorizationCache.skip = &neverSkipSynchronizer{} // we prime the data we need here since we are not running reflectors for i := range namespaceList.Items { nsIndexer.Add(&namespaceList.Items[i])