k8s.io/kubernetes@v1.29.3/pkg/proxy/topology_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package proxy
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    27  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    28  	"k8s.io/kubernetes/pkg/features"
    29  )
    30  
    31  func checkExpectedEndpoints(expected sets.Set[string], actual []Endpoint) error {
    32  	var errs []error
    33  
    34  	expectedCopy := sets.New[string](expected.UnsortedList()...)
    35  	for _, ep := range actual {
    36  		if !expectedCopy.Has(ep.String()) {
    37  			errs = append(errs, fmt.Errorf("unexpected endpoint %v", ep))
    38  		}
    39  		expectedCopy.Delete(ep.String())
    40  	}
    41  	if len(expectedCopy) > 0 {
    42  		errs = append(errs, fmt.Errorf("missing endpoints %v", expectedCopy.UnsortedList()))
    43  	}
    44  
    45  	return kerrors.NewAggregate(errs)
    46  }
    47  
    48  func TestCategorizeEndpoints(t *testing.T) {
    49  	testCases := []struct {
    50  		name         string
    51  		hintsEnabled bool
    52  		pteEnabled   bool
    53  		nodeLabels   map[string]string
    54  		serviceInfo  ServicePort
    55  		endpoints    []Endpoint
    56  
    57  		// We distinguish `nil` ("service doesn't use this kind of endpoints") from
    58  		// `sets.Set[string]()` ("service uses this kind of endpoints but has no endpoints").
    59  		// allEndpoints can be left unset if only one of clusterEndpoints and
    60  		// localEndpoints is set, and allEndpoints is identical to it.
    61  		// onlyRemoteEndpoints should be true if CategorizeEndpoints returns true for
    62  		// hasAnyEndpoints despite allEndpoints being empty.
    63  		clusterEndpoints    sets.Set[string]
    64  		localEndpoints      sets.Set[string]
    65  		allEndpoints        sets.Set[string]
    66  		onlyRemoteEndpoints bool
    67  	}{{
    68  		name:         "hints enabled, hints annotation == auto",
    69  		hintsEnabled: true,
    70  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
    71  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
    72  		endpoints: []Endpoint{
    73  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
    74  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
    75  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
    76  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
    77  		},
    78  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.6:80"),
    79  		localEndpoints:   nil,
    80  	}, {
    81  		name:         "hints, hints annotation == disabled, hints ignored",
    82  		hintsEnabled: true,
    83  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
    84  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "disabled"},
    85  		endpoints: []Endpoint{
    86  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
    87  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
    88  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
    89  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
    90  		},
    91  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.5:80", "10.1.2.6:80"),
    92  		localEndpoints:   nil,
    93  	}, {
    94  		name:         "hints disabled, hints annotation == auto",
    95  		hintsEnabled: false,
    96  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
    97  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
    98  		endpoints: []Endpoint{
    99  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   100  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   101  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   102  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   103  		},
   104  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.5:80", "10.1.2.6:80"),
   105  		localEndpoints:   nil,
   106  	}, {
   107  
   108  		name:         "hints, hints annotation == aUto (wrong capitalization), hints no longer ignored",
   109  		hintsEnabled: true,
   110  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   111  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "aUto"},
   112  		endpoints: []Endpoint{
   113  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   114  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   115  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   116  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   117  		},
   118  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.6:80"),
   119  		localEndpoints:   nil,
   120  	}, {
   121  		name:         "hints, hints annotation empty, hints ignored",
   122  		hintsEnabled: true,
   123  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   124  		serviceInfo:  &BaseServicePortInfo{},
   125  		endpoints: []Endpoint{
   126  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   127  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   128  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   129  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   130  		},
   131  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.5:80", "10.1.2.6:80"),
   132  		localEndpoints:   nil,
   133  	}, {
   134  		name:         "externalTrafficPolicy: Local, topology ignored for Local endpoints",
   135  		hintsEnabled: true,
   136  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   137  		serviceInfo:  &BaseServicePortInfo{externalPolicyLocal: true, nodePort: 8080, hintsAnnotation: "auto"},
   138  		endpoints: []Endpoint{
   139  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true, isLocal: true},
   140  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true, isLocal: true},
   141  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   142  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   143  		},
   144  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.6:80"),
   145  		localEndpoints:   sets.New[string]("10.1.2.3:80", "10.1.2.4:80"),
   146  		allEndpoints:     sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.6:80"),
   147  	}, {
   148  		name:         "internalTrafficPolicy: Local, topology ignored for Local endpoints",
   149  		hintsEnabled: true,
   150  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   151  		serviceInfo:  &BaseServicePortInfo{internalPolicyLocal: true, hintsAnnotation: "auto", externalPolicyLocal: false, nodePort: 8080},
   152  		endpoints: []Endpoint{
   153  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true, isLocal: true},
   154  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true, isLocal: true},
   155  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   156  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   157  		},
   158  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.6:80"),
   159  		localEndpoints:   sets.New[string]("10.1.2.3:80", "10.1.2.4:80"),
   160  		allEndpoints:     sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.6:80"),
   161  	}, {
   162  		name:         "empty node labels",
   163  		hintsEnabled: true,
   164  		nodeLabels:   map[string]string{},
   165  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   166  		endpoints: []Endpoint{
   167  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   168  		},
   169  		clusterEndpoints: sets.New[string]("10.1.2.3:80"),
   170  		localEndpoints:   nil,
   171  	}, {
   172  		name:         "empty zone label",
   173  		hintsEnabled: true,
   174  		nodeLabels:   map[string]string{v1.LabelTopologyZone: ""},
   175  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   176  		endpoints: []Endpoint{
   177  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   178  		},
   179  		clusterEndpoints: sets.New[string]("10.1.2.3:80"),
   180  		localEndpoints:   nil,
   181  	}, {
   182  		name:         "node in different zone, no endpoint filtering",
   183  		hintsEnabled: true,
   184  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-b"},
   185  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   186  		endpoints: []Endpoint{
   187  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   188  		},
   189  		clusterEndpoints: sets.New[string]("10.1.2.3:80"),
   190  		localEndpoints:   nil,
   191  	}, {
   192  		name:         "normal endpoint filtering, auto annotation",
   193  		hintsEnabled: true,
   194  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   195  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   196  		endpoints: []Endpoint{
   197  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   198  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   199  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   200  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   201  		},
   202  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.6:80"),
   203  		localEndpoints:   nil,
   204  	}, {
   205  		name:         "unready endpoint",
   206  		hintsEnabled: true,
   207  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   208  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   209  		endpoints: []Endpoint{
   210  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   211  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   212  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   213  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: false},
   214  		},
   215  		clusterEndpoints: sets.New[string]("10.1.2.3:80"),
   216  		localEndpoints:   nil,
   217  	}, {
   218  		name:         "only unready endpoints in same zone (should not filter)",
   219  		hintsEnabled: true,
   220  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   221  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   222  		endpoints: []Endpoint{
   223  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: false},
   224  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   225  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   226  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: false},
   227  		},
   228  		clusterEndpoints: sets.New[string]("10.1.2.4:80", "10.1.2.5:80"),
   229  		localEndpoints:   nil,
   230  	}, {
   231  		name:         "normal endpoint filtering, Auto annotation",
   232  		hintsEnabled: true,
   233  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   234  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "Auto"},
   235  		endpoints: []Endpoint{
   236  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   237  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   238  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   239  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   240  		},
   241  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.6:80"),
   242  		localEndpoints:   nil,
   243  	}, {
   244  		name:         "hintsAnnotation empty, no filtering applied",
   245  		hintsEnabled: true,
   246  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   247  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: ""},
   248  		endpoints: []Endpoint{
   249  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   250  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   251  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   252  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   253  		},
   254  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.5:80", "10.1.2.6:80"),
   255  		localEndpoints:   nil,
   256  	}, {
   257  		name:         "hintsAnnotation disabled, no filtering applied",
   258  		hintsEnabled: true,
   259  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   260  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "disabled"},
   261  		endpoints: []Endpoint{
   262  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   263  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   264  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   265  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   266  		},
   267  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.5:80", "10.1.2.6:80"),
   268  		localEndpoints:   nil,
   269  	}, {
   270  		name:         "missing hints, no filtering applied",
   271  		hintsEnabled: true,
   272  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   273  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   274  		endpoints: []Endpoint{
   275  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   276  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b"), ready: true},
   277  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: nil, ready: true},
   278  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-a"), ready: true},
   279  		},
   280  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.5:80", "10.1.2.6:80"),
   281  		localEndpoints:   nil,
   282  	}, {
   283  		name:         "multiple hints per endpoint, filtering includes any endpoint with zone included",
   284  		hintsEnabled: true,
   285  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-c"},
   286  		serviceInfo:  &BaseServicePortInfo{hintsAnnotation: "auto"},
   287  		endpoints: []Endpoint{
   288  			&BaseEndpointInfo{endpoint: "10.1.2.3:80", zoneHints: sets.New[string]("zone-a", "zone-b", "zone-c"), ready: true},
   289  			&BaseEndpointInfo{endpoint: "10.1.2.4:80", zoneHints: sets.New[string]("zone-b", "zone-c"), ready: true},
   290  			&BaseEndpointInfo{endpoint: "10.1.2.5:80", zoneHints: sets.New[string]("zone-b", "zone-d"), ready: true},
   291  			&BaseEndpointInfo{endpoint: "10.1.2.6:80", zoneHints: sets.New[string]("zone-c"), ready: true},
   292  		},
   293  		clusterEndpoints: sets.New[string]("10.1.2.3:80", "10.1.2.4:80", "10.1.2.6:80"),
   294  		localEndpoints:   nil,
   295  	}, {
   296  		name:         "conflicting topology and localness require merging allEndpoints",
   297  		hintsEnabled: true,
   298  		nodeLabels:   map[string]string{v1.LabelTopologyZone: "zone-a"},
   299  		serviceInfo:  &BaseServicePortInfo{internalPolicyLocal: false, externalPolicyLocal: true, nodePort: 8080, hintsAnnotation: "auto"},
   300  		endpoints: []Endpoint{
   301  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", zoneHints: sets.New[string]("zone-a"), ready: true, isLocal: true},
   302  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", zoneHints: sets.New[string]("zone-b"), ready: true, isLocal: true},
   303  			&BaseEndpointInfo{endpoint: "10.0.0.2:80", zoneHints: sets.New[string]("zone-a"), ready: true, isLocal: false},
   304  			&BaseEndpointInfo{endpoint: "10.0.0.3:80", zoneHints: sets.New[string]("zone-b"), ready: true, isLocal: false},
   305  		},
   306  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.2:80"),
   307  		localEndpoints:   sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   308  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.1:80", "10.0.0.2:80"),
   309  	}, {
   310  		name:             "internalTrafficPolicy: Local, with empty endpoints",
   311  		serviceInfo:      &BaseServicePortInfo{internalPolicyLocal: true},
   312  		endpoints:        []Endpoint{},
   313  		clusterEndpoints: nil,
   314  		localEndpoints:   sets.New[string](),
   315  	}, {
   316  		name:        "internalTrafficPolicy: Local, but all endpoints are remote",
   317  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true},
   318  		endpoints: []Endpoint{
   319  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: false},
   320  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: false},
   321  		},
   322  		clusterEndpoints:    nil,
   323  		localEndpoints:      sets.New[string](),
   324  		onlyRemoteEndpoints: true,
   325  	}, {
   326  		name:        "internalTrafficPolicy: Local, all endpoints are local",
   327  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true},
   328  		endpoints: []Endpoint{
   329  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: true},
   330  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: true},
   331  		},
   332  		clusterEndpoints: nil,
   333  		localEndpoints:   sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   334  	}, {
   335  		name:        "internalTrafficPolicy: Local, some endpoints are local",
   336  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true},
   337  		endpoints: []Endpoint{
   338  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: true},
   339  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: false},
   340  		},
   341  		clusterEndpoints: nil,
   342  		localEndpoints:   sets.New[string]("10.0.0.0:80"),
   343  	}, {
   344  		name:        "Cluster traffic policy, endpoints not Ready",
   345  		serviceInfo: &BaseServicePortInfo{},
   346  		endpoints: []Endpoint{
   347  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: false},
   348  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: false},
   349  		},
   350  		clusterEndpoints: sets.New[string](),
   351  		localEndpoints:   nil,
   352  	}, {
   353  		name:        "Cluster traffic policy, some endpoints are Ready",
   354  		serviceInfo: &BaseServicePortInfo{},
   355  		endpoints: []Endpoint{
   356  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: false},
   357  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true},
   358  		},
   359  		clusterEndpoints: sets.New[string]("10.0.0.1:80"),
   360  		localEndpoints:   nil,
   361  	}, {
   362  		name:        "Cluster traffic policy, all endpoints are terminating",
   363  		pteEnabled:  true,
   364  		serviceInfo: &BaseServicePortInfo{},
   365  		endpoints: []Endpoint{
   366  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: false, serving: true, terminating: true, isLocal: true},
   367  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: false, serving: true, terminating: true, isLocal: false},
   368  		},
   369  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   370  		localEndpoints:   nil,
   371  	}, {
   372  		name:        "iTP: Local, eTP: Cluster, some endpoints local",
   373  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true, externalPolicyLocal: false, nodePort: 8080},
   374  		endpoints: []Endpoint{
   375  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: true},
   376  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: false},
   377  		},
   378  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   379  		localEndpoints:   sets.New[string]("10.0.0.0:80"),
   380  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   381  	}, {
   382  		name:        "iTP: Cluster, eTP: Local, some endpoints local",
   383  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: false, externalPolicyLocal: true, nodePort: 8080},
   384  		endpoints: []Endpoint{
   385  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: true},
   386  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: false},
   387  		},
   388  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   389  		localEndpoints:   sets.New[string]("10.0.0.0:80"),
   390  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   391  	}, {
   392  		name:        "iTP: Local, eTP: Local, some endpoints local",
   393  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true, externalPolicyLocal: true, nodePort: 8080},
   394  		endpoints: []Endpoint{
   395  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: true},
   396  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: false},
   397  		},
   398  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   399  		localEndpoints:   sets.New[string]("10.0.0.0:80"),
   400  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   401  	}, {
   402  		name:        "iTP: Local, eTP: Local, all endpoints remote",
   403  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true, externalPolicyLocal: true, nodePort: 8080},
   404  		endpoints: []Endpoint{
   405  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: false},
   406  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: false},
   407  		},
   408  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   409  		localEndpoints:   sets.New[string](),
   410  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   411  	}, {
   412  		name:        "iTP: Local, eTP: Local, all endpoints remote and terminating",
   413  		pteEnabled:  true,
   414  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true, externalPolicyLocal: true, nodePort: 8080},
   415  		endpoints: []Endpoint{
   416  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: false, serving: true, terminating: true, isLocal: false},
   417  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: false, serving: true, terminating: true, isLocal: false},
   418  		},
   419  		clusterEndpoints:    sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   420  		localEndpoints:      sets.New[string](),
   421  		allEndpoints:        sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   422  		onlyRemoteEndpoints: true,
   423  	}, {
   424  		name:        "iTP: Cluster, eTP: Local, with terminating endpoints",
   425  		pteEnabled:  true,
   426  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: false, externalPolicyLocal: true, nodePort: 8080},
   427  		endpoints: []Endpoint{
   428  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: false},
   429  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: false, serving: false, isLocal: true},
   430  			&BaseEndpointInfo{endpoint: "10.0.0.2:80", ready: false, serving: true, terminating: true, isLocal: true},
   431  			&BaseEndpointInfo{endpoint: "10.0.0.3:80", ready: false, serving: true, terminating: true, isLocal: false},
   432  		},
   433  		clusterEndpoints: sets.New[string]("10.0.0.0:80"),
   434  		localEndpoints:   sets.New[string]("10.0.0.2:80"),
   435  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.2:80"),
   436  	}, {
   437  		name:        "externalTrafficPolicy ignored if not externally accessible",
   438  		serviceInfo: &BaseServicePortInfo{externalPolicyLocal: true},
   439  		endpoints: []Endpoint{
   440  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: false},
   441  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: true},
   442  		},
   443  		clusterEndpoints: sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   444  		localEndpoints:   nil,
   445  		allEndpoints:     sets.New[string]("10.0.0.0:80", "10.0.0.1:80"),
   446  	}, {
   447  		name:        "no cluster endpoints for iTP:Local internal-only service",
   448  		serviceInfo: &BaseServicePortInfo{internalPolicyLocal: true},
   449  		endpoints: []Endpoint{
   450  			&BaseEndpointInfo{endpoint: "10.0.0.0:80", ready: true, isLocal: false},
   451  			&BaseEndpointInfo{endpoint: "10.0.0.1:80", ready: true, isLocal: true},
   452  		},
   453  		clusterEndpoints: nil,
   454  		localEndpoints:   sets.New[string]("10.0.0.1:80"),
   455  		allEndpoints:     sets.New[string]("10.0.0.1:80"),
   456  	}}
   457  
   458  	for _, tc := range testCases {
   459  		t.Run(tc.name, func(t *testing.T) {
   460  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TopologyAwareHints, tc.hintsEnabled)()
   461  
   462  			clusterEndpoints, localEndpoints, allEndpoints, hasAnyEndpoints := CategorizeEndpoints(tc.endpoints, tc.serviceInfo, tc.nodeLabels)
   463  
   464  			if tc.clusterEndpoints == nil && clusterEndpoints != nil {
   465  				t.Errorf("expected no cluster endpoints but got %v", clusterEndpoints)
   466  			} else {
   467  				err := checkExpectedEndpoints(tc.clusterEndpoints, clusterEndpoints)
   468  				if err != nil {
   469  					t.Errorf("error with cluster endpoints: %v", err)
   470  				}
   471  			}
   472  
   473  			if tc.localEndpoints == nil && localEndpoints != nil {
   474  				t.Errorf("expected no local endpoints but got %v", localEndpoints)
   475  			} else {
   476  				err := checkExpectedEndpoints(tc.localEndpoints, localEndpoints)
   477  				if err != nil {
   478  					t.Errorf("error with local endpoints: %v", err)
   479  				}
   480  			}
   481  
   482  			var expectedAllEndpoints sets.Set[string]
   483  			if tc.clusterEndpoints != nil && tc.localEndpoints == nil {
   484  				expectedAllEndpoints = tc.clusterEndpoints
   485  			} else if tc.localEndpoints != nil && tc.clusterEndpoints == nil {
   486  				expectedAllEndpoints = tc.localEndpoints
   487  			} else {
   488  				expectedAllEndpoints = tc.allEndpoints
   489  			}
   490  			err := checkExpectedEndpoints(expectedAllEndpoints, allEndpoints)
   491  			if err != nil {
   492  				t.Errorf("error with allEndpoints: %v", err)
   493  			}
   494  
   495  			expectedHasAnyEndpoints := len(expectedAllEndpoints) > 0 || tc.onlyRemoteEndpoints
   496  			if expectedHasAnyEndpoints != hasAnyEndpoints {
   497  				t.Errorf("expected hasAnyEndpoints=%v, got %v", expectedHasAnyEndpoints, hasAnyEndpoints)
   498  			}
   499  		})
   500  	}
   501  }