Change:
The controllers can create a second EndpointSlice after an immediate sync if the first EndpointSlice they created is not in the cache yet. Here's what can happen: gets event that triggers sync for Service syncs and creates EndpointSlice gets event that triggers sync for same Service syncs and creates EndpointSlice (the slice created in 2 has not made it to the cache yet) A single EndpointSlice to be created. Run integration tests hundreds of times that involve 2 consecutive events that will trigger a sync. Example: /assign /sig network
/triage accepted
/cc
When this bug is fixed, we'll need to rollback
just amend it in the PR that fix the bug, so we merge all together

Discussion:
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" <ins> "k8s.io/apimachinery/pkg/util/rand" </ins> "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers"

} func newController(nodeNames []string, batchPeriod time.Duration) (*fake.Clientset, *endpointSliceController) { <del> client := newClientset() </del> <ins> client := fake.NewSimpleClientset() </ins> informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) nodeInformer := informerFactory.Core().V1().Nodes() indexer := nodeInformer.Informer().GetIndexer()

indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}}) } <ins> esInformer := informerFactory.Discovery().V1().EndpointSlices() esIndexer := esInformer.Informer().GetIndexer() // These reactors are required to mock functionality that would be covered // automatically if we weren't using the fake client. client.PrependReactor("create", "endpointslices", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) { endpointSlice := action.(k8stesting.CreateAction).GetObject().(*discovery.EndpointSlice) if endpointSlice.ObjectMeta.GenerateName != "" { endpointSlice.ObjectMeta.Name = fmt.Sprintf("%s-%s", endpointSlice.ObjectMeta.GenerateName, rand.String(8)) endpointSlice.ObjectMeta.GenerateName = "" } endpointSlice.Generation = 1 esIndexer.Add(endpointSlice) return false, endpointSlice, nil })) client.PrependReactor("update", "endpointslices", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) { endpointSlice := action.(k8stesting.CreateAction).GetObject().(*discovery.EndpointSlice) endpointSlice.Generation++ esIndexer.Update(endpointSlice) return false, endpointSlice, nil })) </ins> esController := NewController( informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Services(), nodeInformer, <del> informerFactory.Discovery().V1().EndpointSlices(), </del> <ins> esInformer, </ins> int32(100), client, batchPeriod)

return !ok || endpointSlice.Generation > g } <del> // StaleSlices returns true if one or more of the provided EndpointSlices // have older generations than the corresponding tracked ones or if the tracker // is expecting one or more of the provided EndpointSlices to be deleted. </del> <ins> // StaleSlices returns true if any of the following are true: // 1. One or more of the provided EndpointSlices have older generations than the //    corresponding tracked ones. // 2. The tracker is expecting one or more of the provided EndpointSlices to be //    deleted. // 3. The tracker is tracking EndpointSlices that have not been provided. </ins> func (est *endpointSliceTracker) StaleSlices(service *v1.Service, endpointSlices []*discovery.EndpointSlice) bool { est.lock.Lock() defer est.lock.Unlock()

if !ok { return false } <ins> providedSlices := map[types.UID]int64{} </ins> for _, endpointSlice := range endpointSlices { <ins> providedSlices[endpointSlice.UID] = endpointSlice.Generation </ins> g, ok := gfs[endpointSlice.UID] if ok && (g == deletionExpected || g > endpointSlice.Generation) { return true } } <ins> for uid, generation := range gfs { if generation == deletionExpected { continue } _, ok := providedSlices[uid] if !ok { return true } } </ins> return false }

serviceParam: &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "svc1", Namespace: "ns1"}}, slicesParam:  []*discovery.EndpointSlice{epSlice1NewerGen}, expectNewer:  false, <ins> }, { name: "slice in params is expected to be deleted", tracker: &endpointSliceTracker{ generationsByService: map[types.NamespacedName]generationsBySlice{ {Name: "svc1", Namespace: "ns1"}: { epSlice1.UID: deletionExpected, }, }, }, serviceParam: &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "svc1", Namespace: "ns1"}}, slicesParam:  []*discovery.EndpointSlice{epSlice1}, expectNewer:  true, }, { name: "slice in tracker but not in params", tracker: &endpointSliceTracker{ generationsByService: map[types.NamespacedName]generationsBySlice{ {Name: "svc1", Namespace: "ns1"}: { epSlice1.UID: epSlice1.Generation, }, }, }, serviceParam: &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "svc1", Namespace: "ns1"}}, slicesParam:  []*discovery.EndpointSlice{}, expectNewer:  true, </ins> }} for _, tc := range testCases {