From 2947f5ce4fa2bf529fe9dae94e9ed96afbe19f38 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Thu, 29 Oct 2020 23:45:54 -0400 Subject: [PATCH] endpointslice controller: refactor TestSyncServiceFull to use test tables Signed-off-by: Andrew Sy Kim --- .../endpointslice_controller_test.go | 319 ++++++++++++++---- 1 file changed, 257 insertions(+), 62 deletions(-) diff --git a/pkg/controller/endpointslice/endpointslice_controller_test.go b/pkg/controller/endpointslice/endpointslice_controller_test.go index bc2d113cb5e..39a34b469b3 100644 --- a/pkg/controller/endpointslice/endpointslice_controller_test.go +++ b/pkg/controller/endpointslice/endpointslice_controller_test.go @@ -33,12 +33,15 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/controller" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" + "k8s.io/kubernetes/pkg/features" utilpointer "k8s.io/utils/pointer" ) @@ -335,76 +338,268 @@ func TestOnEndpointSliceUpdate(t *testing.T) { assert.Equal(t, 1, esController.queue.Len()) } -// Ensure SyncService handles a variety of protocols and IPs appropriately. -func TestSyncServiceFull(t *testing.T) { - client, esController := newController([]string{"node-1"}, time.Duration(0)) - namespace := metav1.NamespaceDefault - serviceName := "all-the-protocols" +func TestSyncService(t *testing.T) { + creationTimestamp := metav1.Now() + deletionTimestamp := metav1.Now() - pod1 := newPod(1, namespace, true, 0, false) - pod1.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.4"}} - esController.podStore.Add(pod1) - - pod2 := newPod(2, namespace, true, 0, false) - pod2.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.5"}, {IP: "1234::5678:0000:0000:9abc:def0"}} - esController.podStore.Add(pod2) - - // create service with all protocols and multiple ports - serviceCreateTime := time.Now() - service := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: serviceName, - Namespace: namespace, - CreationTimestamp: metav1.NewTime(serviceCreateTime), - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - {Name: "tcp-example", TargetPort: intstr.FromInt(80), Protocol: v1.ProtocolTCP}, - {Name: "udp-example", TargetPort: intstr.FromInt(161), Protocol: v1.ProtocolUDP}, - {Name: "sctp-example", TargetPort: intstr.FromInt(3456), Protocol: v1.ProtocolSCTP}, + testcases := []struct { + name string + service *v1.Service + pods []*v1.Pod + expectedEndpointPorts []discovery.EndpointPort + expectedEndpoints []discovery.Endpoint + terminatingGateEnabled bool + }{ + { + name: "pods with multiple IPs and Service with ipFamilies=ipv4", + service: &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "default", + CreationTimestamp: creationTimestamp, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + {Name: "tcp-example", TargetPort: intstr.FromInt(80), Protocol: v1.ProtocolTCP}, + {Name: "udp-example", TargetPort: intstr.FromInt(161), Protocol: v1.ProtocolUDP}, + {Name: "sctp-example", TargetPort: intstr.FromInt(3456), Protocol: v1.ProtocolSCTP}, + }, + Selector: map[string]string{"foo": "bar"}, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, + }, + }, + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "pod0", + Labels: map[string]string{"foo": "bar"}, + DeletionTimestamp: nil, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Name: "container-1", + }}, + NodeName: "node-1", + }, + Status: v1.PodStatus{ + PodIP: "10.0.0.1", + PodIPs: []v1.PodIP{{ + IP: "10.0.0.1", + }}, + Conditions: []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "pod1", + Labels: map[string]string{"foo": "bar"}, + DeletionTimestamp: nil, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Name: "container-1", + }}, + NodeName: "node-1", + }, + Status: v1.PodStatus{ + PodIP: "10.0.0.2", + PodIPs: []v1.PodIP{ + { + IP: "10.0.0.2", + }, + { + IP: "fd08::5678:0000:0000:9abc:def0", + }, + }, + Conditions: []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + }, + }, + }, + }, + expectedEndpointPorts: []discovery.EndpointPort{ + { + Name: utilpointer.StringPtr("sctp-example"), + Protocol: protoPtr(v1.ProtocolSCTP), + Port: utilpointer.Int32Ptr(int32(3456)), + }, + { + Name: utilpointer.StringPtr("udp-example"), + Protocol: protoPtr(v1.ProtocolUDP), + Port: utilpointer.Int32Ptr(int32(161)), + }, + { + Name: utilpointer.StringPtr("tcp-example"), + Protocol: protoPtr(v1.ProtocolTCP), + Port: utilpointer.Int32Ptr(int32(80)), + }, + }, + expectedEndpoints: []discovery.Endpoint{ + { + Conditions: discovery.EndpointConditions{ + Ready: utilpointer.BoolPtr(true), + }, + Addresses: []string{"10.0.0.1"}, + TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod0"}, + Topology: map[string]string{"kubernetes.io/hostname": "node-1"}, + }, + { + Conditions: discovery.EndpointConditions{ + Ready: utilpointer.BoolPtr(true), + }, + Addresses: []string{"10.0.0.2"}, + TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod1"}, + Topology: map[string]string{"kubernetes.io/hostname": "node-1"}, + }, + }, + }, + { + name: "pods with multiple IPs and Service with ipFamilies=ipv6", + service: &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "default", + CreationTimestamp: creationTimestamp, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + {Name: "tcp-example", TargetPort: intstr.FromInt(80), Protocol: v1.ProtocolTCP}, + {Name: "udp-example", TargetPort: intstr.FromInt(161), Protocol: v1.ProtocolUDP}, + {Name: "sctp-example", TargetPort: intstr.FromInt(3456), Protocol: v1.ProtocolSCTP}, + }, + Selector: map[string]string{"foo": "bar"}, + IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, + }, + }, + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "pod0", + Labels: map[string]string{"foo": "bar"}, + DeletionTimestamp: nil, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Name: "container-1", + }}, + NodeName: "node-1", + }, + Status: v1.PodStatus{ + PodIP: "10.0.0.1", + PodIPs: []v1.PodIP{{ + IP: "10.0.0.1", + }}, + Conditions: []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "pod1", + Labels: map[string]string{"foo": "bar"}, + DeletionTimestamp: nil, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Name: "container-1", + }}, + NodeName: "node-1", + }, + Status: v1.PodStatus{ + PodIP: "10.0.0.2", + PodIPs: []v1.PodIP{ + { + IP: "10.0.0.2", + }, + { + IP: "fd08::5678:0000:0000:9abc:def0", + }, + }, + Conditions: []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + }, + }, + }, + }, + expectedEndpointPorts: []discovery.EndpointPort{ + { + Name: utilpointer.StringPtr("sctp-example"), + Protocol: protoPtr(v1.ProtocolSCTP), + Port: utilpointer.Int32Ptr(int32(3456)), + }, + { + Name: utilpointer.StringPtr("udp-example"), + Protocol: protoPtr(v1.ProtocolUDP), + Port: utilpointer.Int32Ptr(int32(161)), + }, + { + Name: utilpointer.StringPtr("tcp-example"), + Protocol: protoPtr(v1.ProtocolTCP), + Port: utilpointer.Int32Ptr(int32(80)), + }, + }, + expectedEndpoints: []discovery.Endpoint{ + { + Conditions: discovery.EndpointConditions{ + Ready: utilpointer.BoolPtr(true), + }, + Addresses: []string{"fd08::5678:0000:0000:9abc:def0"}, + TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: "default", Name: "pod1"}, + Topology: map[string]string{"kubernetes.io/hostname": "node-1"}, + }, }, - Selector: map[string]string{"foo": "bar"}, - IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, }, } - esController.serviceStore.Add(service) - _, err := esController.client.CoreV1().Services(namespace).Create(context.TODO(), service, metav1.CreateOptions{}) - assert.Nil(t, err, "Expected no error creating service") - // run through full sync service loop - err = esController.syncService(fmt.Sprintf("%s/%s", namespace, serviceName)) - assert.NoError(t, err) + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testcase.terminatingGateEnabled)() - // last action should be to create endpoint slice - expectActions(t, client.Actions(), 1, "create", "endpointslices") - sliceList, err := client.DiscoveryV1beta1().EndpointSlices(namespace).List(context.TODO(), metav1.ListOptions{}) - assert.Nil(t, err, "Expected no error fetching endpoint slices") - assert.Len(t, sliceList.Items, 1, "Expected 1 endpoint slices") + client, esController := newController([]string{"node-1"}, time.Duration(0)) - // ensure all attributes of endpoint slice match expected state - slice := sliceList.Items[0] - assert.Len(t, slice.Endpoints, 1, "Expected 1 endpoints in first slice") - assert.Equal(t, slice.Annotations["endpoints.kubernetes.io/last-change-trigger-time"], serviceCreateTime.Format(time.RFC3339Nano)) - assert.EqualValues(t, []discovery.EndpointPort{{ - Name: utilpointer.StringPtr("sctp-example"), - Protocol: protoPtr(v1.ProtocolSCTP), - Port: utilpointer.Int32Ptr(int32(3456)), - }, { - Name: utilpointer.StringPtr("udp-example"), - Protocol: protoPtr(v1.ProtocolUDP), - Port: utilpointer.Int32Ptr(int32(161)), - }, { - Name: utilpointer.StringPtr("tcp-example"), - Protocol: protoPtr(v1.ProtocolTCP), - Port: utilpointer.Int32Ptr(int32(80)), - }}, slice.Ports) + for _, pod := range testcase.pods { + esController.podStore.Add(pod) + } + esController.serviceStore.Add(testcase.service) - assert.ElementsMatch(t, []discovery.Endpoint{{ - Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, - Addresses: []string{"1234::5678:0000:0000:9abc:def0"}, - TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: namespace, Name: pod2.Name}, - Topology: map[string]string{"kubernetes.io/hostname": "node-1"}, - }}, slice.Endpoints) + _, err := esController.client.CoreV1().Services(testcase.service.Namespace).Create(context.TODO(), testcase.service, metav1.CreateOptions{}) + assert.Nil(t, err, "Expected no error creating service") + + err = esController.syncService(fmt.Sprintf("%s/%s", testcase.service.Namespace, testcase.service.Name)) + assert.Nil(t, err) + + // last action should be to create endpoint slice + expectActions(t, client.Actions(), 1, "create", "endpointslices") + sliceList, err := client.DiscoveryV1beta1().EndpointSlices(testcase.service.Namespace).List(context.TODO(), metav1.ListOptions{}) + assert.Nil(t, err, "Expected no error fetching endpoint slices") + assert.Len(t, sliceList.Items, 1, "Expected 1 endpoint slices") + + // ensure all attributes of endpoint slice match expected state + slice := sliceList.Items[0] + assert.Equal(t, slice.Annotations["endpoints.kubernetes.io/last-change-trigger-time"], creationTimestamp.Format(time.RFC3339Nano)) + assert.EqualValues(t, testcase.expectedEndpointPorts, slice.Ports) + assert.ElementsMatch(t, testcase.expectedEndpoints, slice.Endpoints) + }) + } } // TestPodAddsBatching verifies that endpoint updates caused by pod addition are batched together.