Merge pull request #98116 from aojea/mirror_dual
slice mirroring controller should mirror annotations (but endpoints.kubernetes.io/last-change-trigger-time annotation) and labels
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -196,9 +197,14 @@ func (r *reconciler) reconcileByPortMapping(
|
||||
// if >0 existing slices, mark all but 1 for deletion.
|
||||
slices.toDelete = existingSlices[1:]
|
||||
|
||||
// Return early if first slice matches desired endpoints.
|
||||
// generated slices must mirror all endpoints annotations but EndpointsLastChangeTriggerTime
|
||||
compareAnnotations := cloneAndRemoveKeys(endpoints.Annotations, corev1.EndpointsLastChangeTriggerTime)
|
||||
compareLabels := cloneAndRemoveKeys(existingSlices[0].Labels, discovery.LabelManagedBy, discovery.LabelServiceName)
|
||||
// Return early if first slice matches desired endpoints, labels and annotations
|
||||
totals = totalChanges(existingSlices[0], desiredSet)
|
||||
if totals.added == 0 && totals.updated == 0 && totals.removed == 0 {
|
||||
if totals.added == 0 && totals.updated == 0 && totals.removed == 0 &&
|
||||
apiequality.Semantic.DeepEqual(endpoints.Labels, compareLabels) &&
|
||||
apiequality.Semantic.DeepEqual(compareAnnotations, existingSlices[0].Annotations) {
|
||||
return slices, totals
|
||||
}
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ func TestReconcile(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
subsets []corev1.EndpointSubset
|
||||
epLabels map[string]string
|
||||
endpointsDeletionPending bool
|
||||
maxEndpointsPerSubset int32
|
||||
existingEndpointSlices []*discovery.EndpointSlice
|
||||
@@ -105,6 +106,102 @@ func TestReconcile(t *testing.T) {
|
||||
existingEndpointSlices: []*discovery.EndpointSlice{},
|
||||
expectedNumSlices: 0,
|
||||
expectedClientActions: 0,
|
||||
}, {
|
||||
testName: "Endpoints with 1 subset, port, and address and existing slice with same fields",
|
||||
subsets: []corev1.EndpointSubset{{
|
||||
Ports: []corev1.EndpointPort{{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
Addresses: []corev1.EndpointAddress{{
|
||||
IP: "10.0.0.1",
|
||||
Hostname: "pod-1",
|
||||
}},
|
||||
}},
|
||||
existingEndpointSlices: []*discovery.EndpointSlice{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ep-1",
|
||||
},
|
||||
AddressType: discovery.AddressTypeIPv4,
|
||||
Ports: []discovery.EndpointPort{{
|
||||
Name: utilpointer.StringPtr("http"),
|
||||
Port: utilpointer.Int32Ptr(80),
|
||||
Protocol: &protoTCP,
|
||||
}},
|
||||
Endpoints: []discovery.Endpoint{{
|
||||
Addresses: []string{"10.0.0.1"},
|
||||
Hostname: utilpointer.StringPtr("pod-1"),
|
||||
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
|
||||
}},
|
||||
}},
|
||||
expectedNumSlices: 1,
|
||||
expectedClientActions: 0,
|
||||
}, {
|
||||
testName: "Endpoints with 1 subset, port, and address and existing slice with an additional annotation",
|
||||
subsets: []corev1.EndpointSubset{{
|
||||
Ports: []corev1.EndpointPort{{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
Addresses: []corev1.EndpointAddress{{
|
||||
IP: "10.0.0.1",
|
||||
Hostname: "pod-1",
|
||||
}},
|
||||
}},
|
||||
existingEndpointSlices: []*discovery.EndpointSlice{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ep-1",
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
},
|
||||
AddressType: discovery.AddressTypeIPv4,
|
||||
Ports: []discovery.EndpointPort{{
|
||||
Name: utilpointer.StringPtr("http"),
|
||||
Port: utilpointer.Int32Ptr(80),
|
||||
Protocol: &protoTCP,
|
||||
}},
|
||||
Endpoints: []discovery.Endpoint{{
|
||||
Addresses: []string{"10.0.0.1"},
|
||||
Hostname: utilpointer.StringPtr("pod-1"),
|
||||
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
|
||||
}},
|
||||
}},
|
||||
expectedNumSlices: 1,
|
||||
expectedClientActions: 1,
|
||||
}, {
|
||||
testName: "Endpoints with 1 subset, port, label and address and existing slice with same fields but the label",
|
||||
subsets: []corev1.EndpointSubset{{
|
||||
Ports: []corev1.EndpointPort{{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
Addresses: []corev1.EndpointAddress{{
|
||||
IP: "10.0.0.1",
|
||||
Hostname: "pod-1",
|
||||
}},
|
||||
}},
|
||||
epLabels: map[string]string{"foo": "bar"},
|
||||
existingEndpointSlices: []*discovery.EndpointSlice{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ep-1",
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
},
|
||||
AddressType: discovery.AddressTypeIPv4,
|
||||
Ports: []discovery.EndpointPort{{
|
||||
Name: utilpointer.StringPtr("http"),
|
||||
Port: utilpointer.Int32Ptr(80),
|
||||
Protocol: &protoTCP,
|
||||
}},
|
||||
Endpoints: []discovery.Endpoint{{
|
||||
Addresses: []string{"10.0.0.1"},
|
||||
Hostname: utilpointer.StringPtr("pod-1"),
|
||||
Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
|
||||
}},
|
||||
}},
|
||||
expectedNumSlices: 1,
|
||||
expectedClientActions: 1,
|
||||
}, {
|
||||
testName: "Endpoints with 1 subset, 2 ports, and 2 addresses",
|
||||
subsets: []corev1.EndpointSubset{{
|
||||
@@ -641,7 +738,7 @@ func TestReconcile(t *testing.T) {
|
||||
setupMetrics()
|
||||
namespace := "test"
|
||||
endpoints := corev1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-ep", Namespace: namespace},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-ep", Namespace: namespace, Labels: tc.epLabels},
|
||||
Subsets: tc.subsets,
|
||||
}
|
||||
|
||||
|
@@ -69,6 +69,7 @@ func newEndpointSlice(endpoints *corev1.Endpoints, ports []discovery.EndpointPor
|
||||
epSlice := &discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{},
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
Namespace: endpoints.Namespace,
|
||||
},
|
||||
@@ -77,13 +78,23 @@ func newEndpointSlice(endpoints *corev1.Endpoints, ports []discovery.EndpointPor
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
}
|
||||
|
||||
// clone all labels
|
||||
for label, val := range endpoints.Labels {
|
||||
epSlice.Labels[label] = val
|
||||
}
|
||||
|
||||
// overwrite specific labels
|
||||
epSlice.Labels[discovery.LabelServiceName] = endpoints.Name
|
||||
epSlice.Labels[discovery.LabelManagedBy] = controllerName
|
||||
|
||||
// clone all annotations but EndpointsLastChangeTriggerTime
|
||||
for annotation, val := range endpoints.Annotations {
|
||||
if annotation == corev1.EndpointsLastChangeTriggerTime {
|
||||
continue
|
||||
}
|
||||
epSlice.Annotations[annotation] = val
|
||||
}
|
||||
|
||||
if sliceName == "" {
|
||||
epSlice.GenerateName = getEndpointSlicePrefix(endpoints.Name)
|
||||
} else {
|
||||
@@ -228,3 +239,22 @@ func hasLeaderElection(annotations map[string]string) bool {
|
||||
_, ok := annotations[resourcelock.LeaderElectionRecordAnnotationKey]
|
||||
return ok
|
||||
}
|
||||
|
||||
// cloneAndRemoveKeys is a copy of CloneAndRemoveLabels
|
||||
// it is used here for annotations and labels
|
||||
func cloneAndRemoveKeys(a map[string]string, keys ...string) map[string]string {
|
||||
if len(keys) == 0 {
|
||||
// Don't need to remove a key.
|
||||
return a
|
||||
}
|
||||
// Clone.
|
||||
newMap := map[string]string{}
|
||||
for k, v := range a {
|
||||
newMap[k] = v
|
||||
}
|
||||
// remove keys
|
||||
for _, key := range keys {
|
||||
delete(newMap, key)
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -38,42 +39,153 @@ func TestNewEndpointSlice(t *testing.T) {
|
||||
|
||||
ports := []discovery.EndpointPort{{Name: &portName, Protocol: &protocol}}
|
||||
addrType := discovery.AddressTypeIPv4
|
||||
gvk := schema.GroupVersionKind{Version: "v1", Kind: "Endpoints"}
|
||||
|
||||
endpoints := v1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "test",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
Subsets: []v1.EndpointSubset{{
|
||||
Ports: []v1.EndpointPort{{Port: 80}},
|
||||
}},
|
||||
}
|
||||
|
||||
gvk := schema.GroupVersionKind{Version: "v1", Kind: "Endpoints"}
|
||||
ownerRef := metav1.NewControllerRef(&endpoints, gvk)
|
||||
|
||||
expectedSlice := discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
discovery.LabelServiceName: endpoints.Name,
|
||||
discovery.LabelManagedBy: controllerName,
|
||||
testCases := []struct {
|
||||
name string
|
||||
tweakEndpoint func(ep *corev1.Endpoints)
|
||||
expectedSlice discovery.EndpointSlice
|
||||
}{
|
||||
{
|
||||
name: "create slice from endpoints",
|
||||
tweakEndpoint: func(ep *corev1.Endpoints) {
|
||||
},
|
||||
expectedSlice: discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
discovery.LabelServiceName: endpoints.Name,
|
||||
discovery.LabelManagedBy: controllerName,
|
||||
},
|
||||
Annotations: map[string]string{},
|
||||
GenerateName: fmt.Sprintf("%s-", endpoints.Name),
|
||||
Namespace: endpoints.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
},
|
||||
Ports: ports,
|
||||
AddressType: addrType,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create slice from endpoints with annotations",
|
||||
tweakEndpoint: func(ep *corev1.Endpoints) {
|
||||
annotations := map[string]string{"foo": "bar"}
|
||||
ep.Annotations = annotations
|
||||
},
|
||||
expectedSlice: discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
discovery.LabelServiceName: endpoints.Name,
|
||||
discovery.LabelManagedBy: controllerName,
|
||||
},
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
GenerateName: fmt.Sprintf("%s-", endpoints.Name),
|
||||
Namespace: endpoints.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
},
|
||||
Ports: ports,
|
||||
AddressType: addrType,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create slice from endpoints with labels",
|
||||
tweakEndpoint: func(ep *corev1.Endpoints) {
|
||||
labels := map[string]string{"foo": "bar"}
|
||||
ep.Labels = labels
|
||||
},
|
||||
expectedSlice: discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
discovery.LabelServiceName: endpoints.Name,
|
||||
discovery.LabelManagedBy: controllerName,
|
||||
},
|
||||
Annotations: map[string]string{},
|
||||
GenerateName: fmt.Sprintf("%s-", endpoints.Name),
|
||||
Namespace: endpoints.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
},
|
||||
Ports: ports,
|
||||
AddressType: addrType,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create slice from endpoints with labels and annotations",
|
||||
tweakEndpoint: func(ep *corev1.Endpoints) {
|
||||
labels := map[string]string{"foo": "bar"}
|
||||
ep.Labels = labels
|
||||
annotations := map[string]string{"foo2": "bar2"}
|
||||
ep.Annotations = annotations
|
||||
},
|
||||
expectedSlice: discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
discovery.LabelServiceName: endpoints.Name,
|
||||
discovery.LabelManagedBy: controllerName,
|
||||
},
|
||||
Annotations: map[string]string{"foo2": "bar2"},
|
||||
GenerateName: fmt.Sprintf("%s-", endpoints.Name),
|
||||
Namespace: endpoints.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
},
|
||||
Ports: ports,
|
||||
AddressType: addrType,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create slice from endpoints with labels and annotations triggertime",
|
||||
tweakEndpoint: func(ep *corev1.Endpoints) {
|
||||
labels := map[string]string{"foo": "bar"}
|
||||
ep.Labels = labels
|
||||
annotations := map[string]string{
|
||||
"foo2": "bar2",
|
||||
corev1.EndpointsLastChangeTriggerTime: "date",
|
||||
}
|
||||
ep.Annotations = annotations
|
||||
},
|
||||
expectedSlice: discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
discovery.LabelServiceName: endpoints.Name,
|
||||
discovery.LabelManagedBy: controllerName,
|
||||
},
|
||||
Annotations: map[string]string{"foo2": "bar2"},
|
||||
GenerateName: fmt.Sprintf("%s-", endpoints.Name),
|
||||
Namespace: endpoints.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
},
|
||||
Ports: ports,
|
||||
AddressType: addrType,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
},
|
||||
GenerateName: fmt.Sprintf("%s-", endpoints.Name),
|
||||
OwnerReferences: []metav1.OwnerReference{*ownerRef},
|
||||
Namespace: endpoints.Namespace,
|
||||
},
|
||||
Ports: ports,
|
||||
AddressType: addrType,
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
}
|
||||
generatedSlice := newEndpointSlice(&endpoints, ports, addrType, "")
|
||||
|
||||
assert.EqualValues(t, expectedSlice, *generatedSlice)
|
||||
|
||||
if len(endpoints.Labels) > 1 {
|
||||
t.Errorf("Expected Endpoints labels to not be modified, got %+v", endpoints.Labels)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ep := endpoints.DeepCopy()
|
||||
tc.tweakEndpoint(ep)
|
||||
generatedSlice := newEndpointSlice(ep, ports, addrType, "")
|
||||
assert.EqualValues(t, tc.expectedSlice, *generatedSlice)
|
||||
if len(endpoints.Labels) > 1 {
|
||||
t.Errorf("Expected Endpoints labels to not be modified, got %+v", endpoints.Labels)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user