Skip to content

Commit 969ecab

Browse files
committed
Remove all references to v1.Endpoints from non-network e2e tests
kube-proxy does not look at Endpoints ever, so it is incorrect for a test to assume that there is any correlation between whether Endpoints exist and whether a Service is working. Tests should only be using the v1.Endpoints API if they are explicitly testing the behavior of v1.Endpoints, the Endpoints controller, or the EndpointSlice mirroring controller. There is no reason for any non SIG Network tests to be testing any of those things, so there should be no references to v1.Endpoints in test/e2e outside of test/e2e/network. Also, simplify some pointlessly complicated e2eservice code.
1 parent da5bf27 commit 969ecab

File tree

7 files changed

+57
-191
lines changed

7 files changed

+57
-191
lines changed

test/e2e/apimachinery/aggregator.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
appsv1 "k8s.io/api/apps/v1"
3030
v1 "k8s.io/api/core/v1"
31+
discoveryv1 "k8s.io/api/discovery/v1"
3132
rbacv1 "k8s.io/api/rbac/v1"
3233
apierrors "k8s.io/apimachinery/pkg/api/errors"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -763,9 +764,9 @@ func validateErrorWithDebugInfo(ctx context.Context, f *framework.Framework, err
763764
msg := fmt.Sprintf(msg, fields...)
764765
msg += fmt.Sprintf(" but received unexpected error:\n%v", err)
765766
client := f.ClientSet
766-
ep, err := client.CoreV1().Endpoints(namespace).Get(ctx, "sample-api", metav1.GetOptions{})
767+
slices, err := client.DiscoveryV1().EndpointSlices(namespace).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, "sample-api")})
767768
if err == nil {
768-
msg += fmt.Sprintf("\nFound endpoints for sample-api:\n%v", ep)
769+
msg += fmt.Sprintf("\nFound endpoint slices for sample-api:\n%v", slices)
769770
}
770771
pds, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
771772
if err == nil {

test/e2e/framework/endpoints/.import-restrictions

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/e2e/framework/service/jig.go

Lines changed: 34 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,13 @@ import (
3333
policyv1 "k8s.io/api/policy/v1"
3434
apierrors "k8s.io/apimachinery/pkg/api/errors"
3535
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36-
"k8s.io/apimachinery/pkg/fields"
3736
"k8s.io/apimachinery/pkg/labels"
38-
"k8s.io/apimachinery/pkg/runtime"
3937
"k8s.io/apimachinery/pkg/util/intstr"
4038
utilnet "k8s.io/apimachinery/pkg/util/net"
4139
"k8s.io/apimachinery/pkg/util/sets"
4240
"k8s.io/apimachinery/pkg/util/uuid"
4341
"k8s.io/apimachinery/pkg/util/wait"
44-
"k8s.io/apimachinery/pkg/watch"
4542
clientset "k8s.io/client-go/kubernetes"
46-
"k8s.io/client-go/tools/cache"
4743
"k8s.io/kubernetes/test/e2e/framework"
4844
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
4945
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
@@ -404,42 +400,47 @@ func (j *TestJig) GetEndpointNodeNames(ctx context.Context) (sets.String, error)
404400
if err != nil {
405401
return nil, err
406402
}
407-
endpoints, err := j.Client.CoreV1().Endpoints(j.Namespace).Get(ctx, j.Name, metav1.GetOptions{})
403+
slices, err := j.Client.DiscoveryV1().EndpointSlices(j.Namespace).List(ctx, metav1.ListOptions{LabelSelector: discoveryv1.LabelServiceName + "=" + j.Name})
408404
if err != nil {
409-
return nil, fmt.Errorf("get endpoints for service %s/%s failed (%s)", j.Namespace, j.Name, err)
410-
}
411-
if len(endpoints.Subsets) == 0 {
412-
return nil, fmt.Errorf("endpoint has no subsets, cannot determine node addresses")
405+
return nil, fmt.Errorf("list endpointslices for service %s/%s failed (%w)", j.Namespace, j.Name, err)
413406
}
414407
epNodes := sets.NewString()
415-
for _, ss := range endpoints.Subsets {
416-
for _, e := range ss.Addresses {
417-
if e.NodeName != nil {
418-
epNodes.Insert(*e.NodeName)
408+
for _, slice := range slices.Items {
409+
for _, ep := range slice.Endpoints {
410+
if ep.NodeName != nil {
411+
epNodes.Insert(*ep.NodeName)
419412
}
420413
}
421414
}
415+
if len(epNodes) == 0 {
416+
return nil, fmt.Errorf("EndpointSlice has no endpoints, cannot determine node addresses")
417+
}
422418
return epNodes, nil
423419
}
424420

425-
// WaitForEndpointOnNode waits for a service endpoint on the given node.
421+
// WaitForEndpointOnNode waits for a service endpoint on the given node (which must be the service's only endpoint).
426422
func (j *TestJig) WaitForEndpointOnNode(ctx context.Context, nodeName string) error {
427423
return wait.PollUntilContextTimeout(ctx, framework.Poll, KubeProxyLagTimeout, true, func(ctx context.Context) (bool, error) {
428-
endpoints, err := j.Client.CoreV1().Endpoints(j.Namespace).Get(ctx, j.Name, metav1.GetOptions{})
424+
slices, err := j.Client.DiscoveryV1().EndpointSlices(j.Namespace).List(ctx, metav1.ListOptions{LabelSelector: "kubernetes.io/service-name=" + j.Name})
429425
if err != nil {
430-
framework.Logf("Get endpoints for service %s/%s failed (%s)", j.Namespace, j.Name, err)
426+
framework.Logf("List endpointslices for service %s/%s failed (%s)", j.Namespace, j.Name, err)
427+
return false, nil
428+
}
429+
if len(slices.Items) == 0 {
430+
framework.Logf("Expected 1 EndpointSlice for service %s/%s, got 0", j.Namespace, j.Name)
431431
return false, nil
432432
}
433-
if len(endpoints.Subsets) == 0 {
434-
framework.Logf("Expect endpoints with subsets, got none.")
433+
slice := slices.Items[0]
434+
if len(slice.Endpoints) == 0 {
435+
framework.Logf("Expected EndpointSlice with Endpoints, got none.")
435436
return false, nil
436437
}
437-
// TODO: Handle multiple endpoints
438-
if len(endpoints.Subsets[0].Addresses) == 0 {
438+
endpoint := slice.Endpoints[0]
439+
if len(endpoint.Addresses) == 0 || (endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready) {
439440
framework.Logf("Expected Ready endpoints - found none")
440441
return false, nil
441442
}
442-
epHostName := *endpoints.Subsets[0].Addresses[0].NodeName
443+
epHostName := *endpoint.NodeName
443444
framework.Logf("Pod for service %s/%s is on node %s", j.Namespace, j.Name, epHostName)
444445
if epHostName != nodeName {
445446
framework.Logf("Found endpoint on wrong node, expected %v, got %v", nodeName, epHostName)
@@ -451,91 +452,18 @@ func (j *TestJig) WaitForEndpointOnNode(ctx context.Context, nodeName string) er
451452

452453
// waitForAvailableEndpoint waits for at least 1 endpoint to be available till timeout
453454
func (j *TestJig) waitForAvailableEndpoint(ctx context.Context, timeout time.Duration) error {
454-
ctx, cancel := context.WithTimeout(ctx, timeout)
455-
defer cancel()
456-
//Wait for endpoints to be created, this may take longer time if service backing pods are taking longer time to run
457-
endpointSelector := fields.OneTermEqualSelector("metadata.name", j.Name)
458-
endpointAvailable := false
459-
endpointSliceAvailable := false
460-
461-
var controller cache.Controller
462-
_, controller = cache.NewInformer(
463-
&cache.ListWatch{
464-
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
465-
options.FieldSelector = endpointSelector.String()
466-
obj, err := j.Client.CoreV1().Endpoints(j.Namespace).List(ctx, options)
467-
return runtime.Object(obj), err
468-
},
469-
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
470-
options.FieldSelector = endpointSelector.String()
471-
return j.Client.CoreV1().Endpoints(j.Namespace).Watch(ctx, options)
472-
},
473-
},
474-
&v1.Endpoints{},
475-
0,
476-
cache.ResourceEventHandlerFuncs{
477-
AddFunc: func(obj interface{}) {
478-
if e, ok := obj.(*v1.Endpoints); ok {
479-
if len(e.Subsets) > 0 && len(e.Subsets[0].Addresses) > 0 {
480-
endpointAvailable = true
481-
}
482-
}
483-
},
484-
UpdateFunc: func(old, cur interface{}) {
485-
if e, ok := cur.(*v1.Endpoints); ok {
486-
if len(e.Subsets) > 0 && len(e.Subsets[0].Addresses) > 0 {
487-
endpointAvailable = true
488-
}
489-
}
490-
},
491-
},
492-
)
493-
494-
go controller.Run(ctx.Done())
495-
496-
var esController cache.Controller
497-
_, esController = cache.NewInformer(
498-
&cache.ListWatch{
499-
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
500-
options.LabelSelector = "kubernetes.io/service-name=" + j.Name
501-
obj, err := j.Client.DiscoveryV1().EndpointSlices(j.Namespace).List(ctx, options)
502-
return runtime.Object(obj), err
503-
},
504-
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
505-
options.LabelSelector = "kubernetes.io/service-name=" + j.Name
506-
return j.Client.DiscoveryV1().EndpointSlices(j.Namespace).Watch(ctx, options)
507-
},
508-
},
509-
&discoveryv1.EndpointSlice{},
510-
0,
511-
cache.ResourceEventHandlerFuncs{
512-
AddFunc: func(obj interface{}) {
513-
if es, ok := obj.(*discoveryv1.EndpointSlice); ok {
514-
// TODO: currently we only consider addresses in 1 slice, but services with
515-
// a large number of endpoints (>1000) may have multiple slices. Some slices
516-
// with only a few addresses. We should check the addresses in all slices.
517-
if len(es.Endpoints) > 0 && len(es.Endpoints[0].Addresses) > 0 {
518-
endpointSliceAvailable = true
519-
}
520-
}
521-
},
522-
UpdateFunc: func(old, cur interface{}) {
523-
if es, ok := cur.(*discoveryv1.EndpointSlice); ok {
524-
// TODO: currently we only consider addresses in 1 slice, but services with
525-
// a large number of endpoints (>1000) may have multiple slices. Some slices
526-
// with only a few addresses. We should check the addresses in all slices.
527-
if len(es.Endpoints) > 0 && len(es.Endpoints[0].Addresses) > 0 {
528-
endpointSliceAvailable = true
529-
}
530-
}
531-
},
532-
},
533-
)
534-
535-
go esController.Run(ctx.Done())
536-
537-
err := wait.PollUntilContextCancel(ctx, 1*time.Second, false, func(ctx context.Context) (bool, error) {
538-
return endpointAvailable && endpointSliceAvailable, nil
455+
err := wait.PollUntilContextTimeout(ctx, framework.Poll, timeout, true, func(ctx context.Context) (bool, error) {
456+
slices, err := j.Client.DiscoveryV1().EndpointSlices(j.Namespace).List(ctx, metav1.ListOptions{LabelSelector: "kubernetes.io/service-name=" + j.Name})
457+
if err != nil || len(slices.Items) == 0 {
458+
// Retry
459+
return false, nil
460+
}
461+
for _, es := range slices.Items {
462+
if len(es.Endpoints) > 0 && len(es.Endpoints[0].Addresses) > 0 {
463+
return true, nil
464+
}
465+
}
466+
return false, nil
539467
})
540468
if err != nil {
541469
return fmt.Errorf("no subset of available IP address found for the endpoint %s within timeout %v", j.Name, timeout)

test/e2e/framework/util.go

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ import (
5353
"k8s.io/client-go/tools/clientcmd"
5454
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
5555
watchtools "k8s.io/client-go/tools/watch"
56-
netutils "k8s.io/utils/net"
5756
)
5857

5958
// DEPRECATED constants. Use the timeouts in framework.Framework instead.
@@ -411,29 +410,12 @@ func CheckTestingNSDeletedExcept(ctx context.Context, c clientset.Interface, ski
411410
return fmt.Errorf("Waiting for terminating namespaces to be deleted timed out")
412411
}
413412

414-
// WaitForServiceEndpointsNum waits until the amount of endpoints that implement service to expectNum.
415-
// Some components use EndpointSlices other Endpoints, we must verify that both objects meet the requirements.
413+
// WaitForServiceEndpointsNum waits until there are EndpointSlices for serviceName
414+
// containing a total of expectNum endpoints. (If the service is dual-stack, expectNum
415+
// must count the endpoints of both IP families.)
416416
func WaitForServiceEndpointsNum(ctx context.Context, c clientset.Interface, namespace, serviceName string, expectNum int, interval, timeout time.Duration) error {
417417
return wait.PollUntilContextTimeout(ctx, interval, timeout, false, func(ctx context.Context) (bool, error) {
418418
Logf("Waiting for amount of service:%s endpoints to be %d", serviceName, expectNum)
419-
endpoint, err := c.CoreV1().Endpoints(namespace).Get(ctx, serviceName, metav1.GetOptions{})
420-
if err != nil {
421-
Logf("Unexpected error trying to get Endpoints for %s : %v", serviceName, err)
422-
return false, nil
423-
}
424-
425-
if countEndpointsNum(endpoint) != expectNum {
426-
Logf("Unexpected number of Endpoints, got %d, expected %d", countEndpointsNum(endpoint), expectNum)
427-
return false, nil
428-
}
429-
430-
// Endpoints are single family but EndpointSlices can have dual stack addresses,
431-
// so we verify the number of addresses that matches the same family on both.
432-
addressType := discoveryv1.AddressTypeIPv4
433-
if isIPv6Endpoint(endpoint) {
434-
addressType = discoveryv1.AddressTypeIPv6
435-
}
436-
437419
esList, err := c.DiscoveryV1().EndpointSlices(namespace).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, serviceName)})
438420
if err != nil {
439421
Logf("Unexpected error trying to get EndpointSlices for %s : %v", serviceName, err)
@@ -445,44 +427,18 @@ func WaitForServiceEndpointsNum(ctx context.Context, c clientset.Interface, name
445427
return false, nil
446428
}
447429

448-
if countEndpointsSlicesNum(esList, addressType) != expectNum {
449-
Logf("Unexpected number of Endpoints on Slices, got %d, expected %d", countEndpointsSlicesNum(esList, addressType), expectNum)
430+
if countEndpointsSlicesNum(esList) != expectNum {
431+
Logf("Unexpected number of Endpoints on Slices, got %d, expected %d", countEndpointsSlicesNum(esList), expectNum)
450432
return false, nil
451433
}
452434
return true, nil
453435
})
454436
}
455437

456-
func countEndpointsNum(e *v1.Endpoints) int {
457-
num := 0
458-
for _, sub := range e.Subsets {
459-
num += len(sub.Addresses)
460-
}
461-
return num
462-
}
463-
464-
// isIPv6Endpoint returns true if the Endpoint uses IPv6 addresses
465-
func isIPv6Endpoint(e *v1.Endpoints) bool {
466-
for _, sub := range e.Subsets {
467-
for _, addr := range sub.Addresses {
468-
if len(addr.IP) == 0 {
469-
continue
470-
}
471-
// Endpoints are single family, so it is enough to check only one address
472-
return netutils.IsIPv6String(addr.IP)
473-
}
474-
}
475-
// default to IPv4 an Endpoint without IP addresses
476-
return false
477-
}
478-
479-
func countEndpointsSlicesNum(epList *discoveryv1.EndpointSliceList, addressType discoveryv1.AddressType) int {
438+
func countEndpointsSlicesNum(epList *discoveryv1.EndpointSliceList) int {
480439
// EndpointSlices can contain the same address on multiple Slices
481440
addresses := sets.Set[string]{}
482441
for _, epSlice := range epList.Items {
483-
if epSlice.AddressType != addressType {
484-
continue
485-
}
486442
for _, ep := range epSlice.Endpoints {
487443
if len(ep.Addresses) > 0 {
488444
addresses.Insert(ep.Addresses[0])

test/e2e/kubectl/kubectl.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"sigs.k8s.io/yaml"
4444

4545
v1 "k8s.io/api/core/v1"
46+
discoveryv1 "k8s.io/api/discovery/v1"
4647
rbacv1 "k8s.io/api/rbac/v1"
4748
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
4849
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -66,7 +67,7 @@ import (
6667
"k8s.io/kubernetes/test/e2e/framework"
6768
e2eauth "k8s.io/kubernetes/test/e2e/framework/auth"
6869
e2edebug "k8s.io/kubernetes/test/e2e/framework/debug"
69-
e2eendpoints "k8s.io/kubernetes/test/e2e/framework/endpoints"
70+
e2eendpointslice "k8s.io/kubernetes/test/e2e/framework/endpointslice"
7071
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
7172
e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
7273
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
@@ -1538,10 +1539,10 @@ metadata:
15381539
})
15391540
validateService := func(name string, servicePort int, timeout time.Duration) {
15401541
err := wait.Poll(framework.Poll, timeout, func() (bool, error) {
1541-
ep, err := c.CoreV1().Endpoints(ns).Get(ctx, name, metav1.GetOptions{})
1542+
slices, err := c.DiscoveryV1().EndpointSlices(ns).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, name)})
15421543
if err != nil {
15431544
// log the real error
1544-
framework.Logf("Get endpoints failed (interval %v): %v", framework.Poll, err)
1545+
framework.Logf("List endpointslices failed (interval %v): %v", framework.Poll, err)
15451546

15461547
// if the error is API not found or could not find default credentials or TLS handshake timeout, try again
15471548
if apierrors.IsNotFound(err) ||
@@ -1552,7 +1553,7 @@ metadata:
15521553
return false, err
15531554
}
15541555

1555-
uidToPort := e2eendpoints.GetContainerPortsByPodUID(ep)
1556+
uidToPort := e2eendpointslice.GetContainerPortsByPodUID(slices.Items)
15561557
if len(uidToPort) == 0 {
15571558
framework.Logf("No endpoint found, retrying")
15581559
return false, nil

test/e2e/framework/endpoints/ports.go renamed to test/e2e/network/endpoints.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,15 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package endpoints
17+
package network
1818

1919
import (
2020
v1 "k8s.io/api/core/v1"
21-
"k8s.io/apimachinery/pkg/types"
2221
)
2322

24-
// PortsByPodUID is a map that maps pod UID to container ports.
25-
type PortsByPodUID map[types.UID][]int
26-
27-
// FullPortsByPodUID is a map that maps pod UID to container ports.
28-
type FullPortsByPodUID map[types.UID][]v1.ContainerPort
29-
30-
// GetContainerPortsByPodUID returns a PortsByPodUID map on the given endpoints.
31-
func GetContainerPortsByPodUID(ep *v1.Endpoints) PortsByPodUID {
32-
m := PortsByPodUID{}
23+
// getContainerPortsByPodUID returns a portsByPodUID map on the given endpoints.
24+
func getContainerPortsByPodUID(ep *v1.Endpoints) portsByPodUID {
25+
m := portsByPodUID{}
3326
for _, ss := range ep.Subsets {
3427
for _, port := range ss.Ports {
3528
for _, addr := range ss.Addresses {
@@ -44,9 +37,9 @@ func GetContainerPortsByPodUID(ep *v1.Endpoints) PortsByPodUID {
4437
return m
4538
}
4639

47-
// GetFullContainerPortsByPodUID returns a FullPortsByPodUID map on the given endpoints with all the port data.
48-
func GetFullContainerPortsByPodUID(ep *v1.Endpoints) FullPortsByPodUID {
49-
m := FullPortsByPodUID{}
40+
// getFullContainerPortsByPodUID returns a fullPortsByPodUID map on the given endpoints with all the port data.
41+
func getFullContainerPortsByPodUID(ep *v1.Endpoints) fullPortsByPodUID {
42+
m := fullPortsByPodUID{}
5043
for _, ss := range ep.Subsets {
5144
for _, port := range ss.Ports {
5245
containerPort := v1.ContainerPort{

0 commit comments

Comments
 (0)