Change:
} } <ins> func TestEndpointSliceMirroringSelectorTransition(t *testing.T) { controlPlaneConfig := framework.NewIntegrationTestControlPlaneConfig() _, server, closeFn := framework.RunAnAPIServer(controlPlaneConfig) defer closeFn() config := restclient.Config{Host: server.URL} client, err := clientset.NewForConfig(&config) if err != nil { t.Fatalf("Error creating clientset: %v", err) } resyncPeriod := 12 * time.Hour informers := informers.NewSharedInformerFactory(client, resyncPeriod) epsmController := endpointslicemirroring.NewController( informers.Core().V1().Endpoints(), informers.Discovery().V1().EndpointSlices(), informers.Core().V1().Services(), int32(100), client, 1*time.Second) // Start informer and controllers stopCh := make(chan struct{}) defer close(stopCh) informers.Start(stopCh) go epsmController.Run(1, stopCh) testCases := []struct { testName               string startingSelector       map[string]string startingMirroredSlices int endingSelector         map[string]string endingMirroredSlices   int }{ { testName:               "nil -> {foo: bar} selector", startingSelector:       nil, startingMirroredSlices: 1, endingSelector:         map[string]string{"foo": "bar"}, endingMirroredSlices:   0, }, { testName:               "{foo: bar} -> nil selector", startingSelector:       map[string]string{"foo": "bar"}, startingMirroredSlices: 0, endingSelector:         nil, endingMirroredSlices:   1, }, { testName:               "{} -> {foo: bar} selector", startingSelector:       map[string]string{}, startingMirroredSlices: 1, endingSelector:         map[string]string{"foo": "bar"}, endingMirroredSlices:   0, }, { testName:               "{foo: bar} -> {} selector", startingSelector:       map[string]string{"foo": "bar"}, startingMirroredSlices: 0, endingSelector:         map[string]string{}, endingMirroredSlices:   1, }, } for i, tc := range testCases { t.Run(tc.testName, func(t *testing.T) { ns := framework.CreateTestingNamespace(fmt.Sprintf("test-endpointslice-mirroring-%d", i), server, t) defer framework.DeleteTestingNamespace(ns, server, t) meta := metav1.ObjectMeta{Name: "test-123", Namespace: ns.Name} service := &corev1.Service{ ObjectMeta: meta, Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{{ Port: int32(80), }}, Selector: tc.startingSelector, }, } customEndpoints := &corev1.Endpoints{ ObjectMeta: meta, Subsets: []corev1.EndpointSubset{{ Ports: []corev1.EndpointPort{{ Port: 80, }}, Addresses: []corev1.EndpointAddress{{ IP: "10.0.0.1", }}, }}, } _, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) if err != nil { t.Fatalf("Error creating service: %v", err) } _, err = client.CoreV1().Endpoints(ns.Name).Create(context.TODO(), customEndpoints, metav1.CreateOptions{}) if err != nil { t.Fatalf("Error creating endpoints: %v", err) } // verify the expected number of mirrored slices exist err = waitForMirroredSlices(t, client, ns.Name, service.Name, tc.startingMirroredSlices) if err != nil { t.Fatalf("Timed out waiting for initial mirrored slices to match expectations: %v", err) } service.Spec.Selector = tc.endingSelector _, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) if err != nil { t.Fatalf("Error updating service: %v", err) } // verify the expected number of mirrored slices exist err = waitForMirroredSlices(t, client, ns.Name, service.Name, tc.endingMirroredSlices) if err != nil { t.Fatalf("Timed out waiting for final mirrored slices to match expectations: %v", err) } }) } } func waitForMirroredSlices(t *testing.T, client *kubernetes.Clientset, nsName, svcName string, num int) error { t.Helper() return wait.PollImmediate(1*time.Second, wait.ForeverTestTimeout, func() (bool, error) { lSelector := discovery.LabelServiceName + "=" + svcName lSelector += "," + discovery.LabelManagedBy + "=endpointslicemirroring-controller.k8s.io" esList, err := client.DiscoveryV1().EndpointSlices(nsName).List(context.TODO(), metav1.ListOptions{LabelSelector: lSelector}) if err != nil { t.Logf("Error listing EndpointSlices: %v", err) return false, err } if len(esList.Items) != num { t.Logf("Expected %d slices to be mirrored, got %d", num, len(esList.Items)) return false, nil } return true, nil }) } </ins> // isSubset check if all the elements in a exist in b func isSubset(a, b map[string]string) bool { if len(a) > len(b) {

Discussion:
with Selector is created is removed Endpoints and EndpointSlice controllers do not clean up Endpoint(Slice)s mirroring controller starts mirroring the Endpoints resource originally created by the Endpoints controller is back to the Service backing Service change created by mirroring controller still references original IP from step 4 At least one of the following: Endpoints and EndpointSlices to be cleaned up when the Service has the selector removed () mirrored EndpointSlice to be removed when the Service has a selector back See "What Happened" No response Most noticeable in Kubernetes 1.19+ since that's when kube-proxy starts reading from EndpointSlices by default. Any No response No response No response No response
/sig network /triage accepted /priority important-soon /cc
/assign