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:
Kubernetes Prow Robot
2021-03-08 20:47:12 -08:00
committed by GitHub
5 changed files with 462 additions and 25 deletions

View File

@@ -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
}
}

View File

@@ -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,
}

View File

@@ -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
}

View File

@@ -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)
}
})
}
}