Repository: kubernetes/kubernetes (language: go)

Issue:
endpointslicemirroring controller not create endpointslice when the endpoints are recreate
### What happened?

* create a service without selector, and manual create endpoints for this service
* endpointslicemirroring controller will create an endpointslice for this service.
* after endpoint slice created, and kube-controller-manager restart.
* during the kube-controller-manager restart, the endpoint was deleted and recreated
* after kube-controller-manager restart,  garbagecollector controller delete this endpointslice and endpointslice not recreate until endpoint/service update event or kube-controller-manager restart

### What did you expect to happen?

endpointslice will be recreated

### How can we reproduce it (as minimally and precisely as possible)?

* create a service without selector, and manual create endpoints for this service
* endpointslicemirroring controller will create an endpointslice for this service.
* after endpoint slice created, and kube-controller-manager restart.
* during the kube-controller-manager restart, the endpoint was deleted and recreated


### Anything else we need to know?

_No response_

### Kubernetes version

<details>

```console
$ kubectl version
# paste output here
v 1.30.0
```

</details>


### Cloud provider

<details>

</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
kind 1.30.0
# paste output here

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>



Discussion / hints:
/sig network
/assign

Fix patch:
diff --git a/pkg/controller/endpointslicemirroring/reconciler.go b/pkg/controller/endpointslicemirroring/reconciler.go
index 73016516c69de..03044376e6146 100644
--- a/pkg/controller/endpointslicemirroring/reconciler.go
+++ b/pkg/controller/endpointslicemirroring/reconciler.go
@@ -207,7 +207,11 @@ func (r *reconciler) reconcileByPortMapping(
 		totals = totalChanges(existingSlices[0], desiredSet)
 		if totals.added == 0 && totals.updated == 0 && totals.removed == 0 &&
 			apiequality.Semantic.DeepEqual(endpoints.Labels, compareLabels) &&
-			apiequality.Semantic.DeepEqual(compareAnnotations, existingSlices[0].Annotations) {
+			apiequality.Semantic.DeepEqual(compareAnnotations, existingSlices[0].Annotations) &&
+			!needRebuildExistingSlices(endpoints, existingSlices[0]) {
+			if !r.endpointSliceTracker.Has(existingSlices[0]) {
+				r.endpointSliceTracker.Update(existingSlices[0]) // Always ensure each EndpointSlice is being tracked.
+			}
 			return slices, totals
 		}
 	}
@@ -334,3 +338,13 @@ func totalChanges(existingSlice *discovery.EndpointSlice, desiredSet endpointsli
 	totals.added = desiredSet.Len() - existingMatches
 	return totals
 }
+
+func needRebuildExistingSlices(endpoints *corev1.Endpoints, existingSlice *discovery.EndpointSlice) bool {
+	for index := range existingSlice.OwnerReferences {
+		owner := existingSlice.OwnerReferences[index]
+		if owner.Kind == "Endpoints" && owner.Name == endpoints.Name && owner.UID != endpoints.UID {
+			return true
+		}
+	}
+	return false
+}


Test patch:
diff --git a/pkg/controller/endpointslicemirroring/reconciler_test.go b/pkg/controller/endpointslicemirroring/reconciler_test.go
index bc8158a82a8e1..3b0387a303306 100644
--- a/pkg/controller/endpointslicemirroring/reconciler_test.go
+++ b/pkg/controller/endpointslicemirroring/reconciler_test.go
@@ -24,6 +24,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	discovery "k8s.io/api/discovery/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/tools/record"
@@ -48,6 +49,7 @@ func TestReconcile(t *testing.T) {
 		subsets                  []corev1.EndpointSubset
 		epLabels                 map[string]string
 		epAnnotations            map[string]string
+		uid                      string
 		endpointsDeletionPending bool
 		maxEndpointsPerSubset    int32
 		existingEndpointSlices   []*discovery.EndpointSlice
@@ -219,9 +221,19 @@ func TestReconcile(t *testing.T) {
 				Hostname: "pod-1",
 			}},
 		}},
+		uid: "d8f2c1f6-5285-4b3c-b3c1-9b89f9e7ed7a",
 		existingEndpointSlices: []*discovery.EndpointSlice{{
 			ObjectMeta: metav1.ObjectMeta{
-				Name: "test-ep-1",
+				Name:      "test-ep-1",
+				Namespace: "test",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "v1",
+						Kind:       "Endpoints",
+						Name:       "test-ep",
+						UID:        "d8f2c1f6-5285-4b3c-b3c1-9b89f9e7ed7a",
+					},
+				},
 			},
 			AddressType: discovery.AddressTypeIPv4,
 			Ports: []discovery.EndpointPort{{
@@ -237,6 +249,47 @@ func TestReconcile(t *testing.T) {
 		}},
 		expectedNumSlices:     1,
 		expectedClientActions: 0,
+	}, {
+		testName: "Endpoints with 1 subset, port, and address and existing slice with same fields but different OwnerReferences",
+		subsets: []corev1.EndpointSubset{{
+			Ports: []corev1.EndpointPort{{
+				Name:     "http",
+				Port:     80,
+				Protocol: corev1.ProtocolTCP,
+			}},
+			Addresses: []corev1.EndpointAddress{{
+				IP:       "10.0.0.1",
+				Hostname: "pod-1",
+			}},
+		}},
+		uid: "d8f2c1f6-5285-4b3c-b3c1-9b89f9e7ed7a",
+		existingEndpointSlices: []*discovery.EndpointSlice{{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-ep-1",
+				Namespace: "test",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "v1",
+						Kind:       "Endpoints",
+						Name:       "test-ep",
+						UID:        "fb91e798-1875-4857-b5eb-e2c878157b4d",
+					},
+				},
+			},
+			AddressType: discovery.AddressTypeIPv4,
+			Ports: []discovery.EndpointPort{{
+				Name:     pointer.String("http"),
+				Port:     pointer.Int32(80),
+				Protocol: &protoTCP,
+			}},
+			Endpoints: []discovery.Endpoint{{
+				Addresses:  []string{"10.0.0.1"},
+				Hostname:   pointer.String("pod-1"),
+				Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
+			}},
+		}},
+		expectedNumSlices:     1,
+		expectedClientActions: 1,
 	}, {
 		testName: "Endpoints with 1 subset, port, and address and existing slice with an additional annotation",
 		subsets: []corev1.EndpointSubset{{
@@ -1012,7 +1065,7 @@ func TestReconcile(t *testing.T) {
 			setupMetrics()
 			namespace := "test"
 			endpoints := corev1.Endpoints{
-				ObjectMeta: metav1.ObjectMeta{Name: "test-ep", Namespace: namespace, Labels: tc.epLabels, Annotations: tc.epAnnotations},
+				ObjectMeta: metav1.ObjectMeta{Name: "test-ep", Namespace: namespace, Labels: tc.epLabels, Annotations: tc.epAnnotations, UID: types.UID(tc.uid)},
 				Subsets:    tc.subsets,
 			}