Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: for extension API server panics with: Apparently introduces this panic with . What you expected to happen: to do not panic. How to reproduce it (as minimally and precisely as possible): extension API server with - for example the endpoint that the apiserver panics with Anything else we need to know?: Environment: Kubernetes version (use ): / Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Network plugin and version (if this is a network-related bug): Others:
/kind regression
/sig api-machinery
/assign
cc
Oops - sorry for that. It seems it is basically this comment: that I blindly applied without e2e retesting (the problem seem to be lack of tests, which is a bit surprising because bugs in my previous implementations were caught by tests). ^^ BTW - this has been cherrypicked to previous releases: 16: 17: 18: so it's actually even more critical...
I can file the automated cherry-pick PRs once the PR against the master branch is merged. :)
Reopening - since we need to cherrypick it still.
I now opened the cherry-picks: 16 - 17 - 18 -
Fix and picks are merged

Discussion:
srcs = ["healthz_test.go"], embed = [":go_default_library"], deps = [ <ins> "//staging/src/k8s.io/api/core/v1:go_default_library", </ins> "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library", "//staging/src/k8s.io/component-base/metrics/legacyregistry:go_default_library",

"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server/httplog:go_default_library", <del> "//staging/src/k8s.io/client-go/informers:go_default_library", </del> "//vendor/k8s.io/klog/v2:go_default_library", ], )

"bytes" "fmt" "net/http" <ins> "reflect" </ins> "strings" "sync" "sync/atomic"

"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/apiserver/pkg/server/httplog" <del> "k8s.io/client-go/informers" </del> "k8s.io/klog/v2" )

return fmt.Errorf("logging blocked") } <ins> type cacheSyncWaiter interface { WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool } </ins> type informerSync struct { <del> sharedInformerFactory informers.SharedInformerFactory </del> <ins> cacheSyncWaiter cacheSyncWaiter </ins> } var _ HealthChecker = &informerSync{} <del> // NewInformerSyncHealthz returns a new HealthChecker that will pass only if all informers in the given sharedInformerFactory sync. func NewInformerSyncHealthz(sharedInformerFactory informers.SharedInformerFactory) HealthChecker { </del> <ins> // NewInformerSyncHealthz returns a new HealthChecker that will pass only if all informers in the given cacheSyncWaiter sync. func NewInformerSyncHealthz(cacheSyncWaiter cacheSyncWaiter) HealthChecker { </ins> return &informerSync{ <del> sharedInformerFactory: sharedInformerFactory, </del> <ins> cacheSyncWaiter: cacheSyncWaiter, </ins> } }

// Close stopCh to force checking if informers are synced now. close(stopCh) <del> var informersByStarted map[bool][]string for informerType, started := range i.sharedInformerFactory.WaitForCacheSync(stopCh) { </del> <ins> informersByStarted := make(map[bool][]string) for informerType, started := range i.cacheSyncWaiter.WaitForCacheSync(stopCh) { </ins> informersByStarted[started] = append(informersByStarted[started], informerType.String()) }

"strings" "testing" <ins> corev1 "k8s.io/api/core/v1" </ins> "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/component-base/metrics/legacyregistry"

URL:    url, } } <ins> func TestInformerSyncHealthChecker(t *testing.T) { t.Run("test that check returns nil when all informers are started", func(t *testing.T) { healthChecker := NewInformerSyncHealthz(cacheSyncWaiterStub{ startedByInformerType: map[reflect.Type]bool{ reflect.TypeOf(corev1.Pod{}): true, }, }) err := healthChecker.Check(nil) if err != nil { t.Errorf("Got %v, expected no error", err) } }) t.Run("test that check returns err when there is not started informer", func(t *testing.T) { healthChecker := NewInformerSyncHealthz(cacheSyncWaiterStub{ startedByInformerType: map[reflect.Type]bool{ reflect.TypeOf(corev1.Pod{}):     true, reflect.TypeOf(corev1.Service{}): false, reflect.TypeOf(corev1.Node{}):    true, }, }) err := healthChecker.Check(nil) if err == nil { t.Errorf("expected error, got: %v", err) } }) } type cacheSyncWaiterStub struct { startedByInformerType map[reflect.Type]bool } // WaitForCacheSync is a stub implementation of the corresponding func // that simply returns the value passed during stub initialization. func (s cacheSyncWaiterStub) WaitForCacheSync(_ <-chan struct{}) map[reflect.Type]bool { return s.startedByInformerType } </ins>