k8s.io/kubernetes@v1.29.3/pkg/controller/endpoint/endpoints_controller_test.go (about) 1 /* 2 Copyright 2014 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 endpoint 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "net/http/httptest" 24 "reflect" 25 "strconv" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 v1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apimachinery/pkg/util/intstr" 35 "k8s.io/apimachinery/pkg/util/wait" 36 "k8s.io/client-go/informers" 37 clientset "k8s.io/client-go/kubernetes" 38 "k8s.io/client-go/kubernetes/fake" 39 clientscheme "k8s.io/client-go/kubernetes/scheme" 40 restclient "k8s.io/client-go/rest" 41 "k8s.io/client-go/tools/cache" 42 utiltesting "k8s.io/client-go/util/testing" 43 endptspkg "k8s.io/kubernetes/pkg/api/v1/endpoints" 44 api "k8s.io/kubernetes/pkg/apis/core" 45 controllerpkg "k8s.io/kubernetes/pkg/controller" 46 utilnet "k8s.io/utils/net" 47 "k8s.io/utils/pointer" 48 ) 49 50 var alwaysReady = func() bool { return true } 51 var neverReady = func() bool { return false } 52 var emptyNodeName string 53 var triggerTime = time.Date(2018, 01, 01, 0, 0, 0, 0, time.UTC) 54 var triggerTimeString = triggerTime.Format(time.RFC3339Nano) 55 var oldTriggerTimeString = triggerTime.Add(-time.Hour).Format(time.RFC3339Nano) 56 57 var ipv4only = []v1.IPFamily{v1.IPv4Protocol} 58 var ipv6only = []v1.IPFamily{v1.IPv6Protocol} 59 var ipv4ipv6 = []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol} 60 var ipv6ipv4 = []v1.IPFamily{v1.IPv6Protocol, v1.IPv4Protocol} 61 62 func testPod(namespace string, id int, nPorts int, isReady bool, ipFamilies []v1.IPFamily) *v1.Pod { 63 p := &v1.Pod{ 64 TypeMeta: metav1.TypeMeta{APIVersion: "v1"}, 65 ObjectMeta: metav1.ObjectMeta{ 66 Namespace: namespace, 67 Name: fmt.Sprintf("pod%d", id), 68 Labels: map[string]string{"foo": "bar"}, 69 ResourceVersion: fmt.Sprint(id), 70 }, 71 Spec: v1.PodSpec{ 72 Containers: []v1.Container{{Ports: []v1.ContainerPort{}}}, 73 }, 74 Status: v1.PodStatus{ 75 Conditions: []v1.PodCondition{ 76 { 77 Type: v1.PodReady, 78 Status: v1.ConditionTrue, 79 }, 80 }, 81 }, 82 } 83 if !isReady { 84 p.Status.Conditions[0].Status = v1.ConditionFalse 85 } 86 for j := 0; j < nPorts; j++ { 87 p.Spec.Containers[0].Ports = append(p.Spec.Containers[0].Ports, 88 v1.ContainerPort{Name: fmt.Sprintf("port%d", j), ContainerPort: int32(8080 + j)}) 89 } 90 for _, family := range ipFamilies { 91 var ip string 92 if family == v1.IPv4Protocol { 93 ip = fmt.Sprintf("1.2.3.%d", 4+id) 94 } else { 95 ip = fmt.Sprintf("2000::%d", 4+id) 96 } 97 p.Status.PodIPs = append(p.Status.PodIPs, v1.PodIP{IP: ip}) 98 } 99 p.Status.PodIP = p.Status.PodIPs[0].IP 100 101 return p 102 } 103 104 func addPods(store cache.Store, namespace string, nPods int, nPorts int, nNotReady int, ipFamilies []v1.IPFamily) { 105 for i := 0; i < nPods+nNotReady; i++ { 106 isReady := i < nPods 107 pod := testPod(namespace, i, nPorts, isReady, ipFamilies) 108 store.Add(pod) 109 } 110 } 111 112 func addNotReadyPodsWithSpecifiedRestartPolicyAndPhase(store cache.Store, namespace string, nPods int, nPorts int, restartPolicy v1.RestartPolicy, podPhase v1.PodPhase) { 113 for i := 0; i < nPods; i++ { 114 p := &v1.Pod{ 115 TypeMeta: metav1.TypeMeta{APIVersion: "v1"}, 116 ObjectMeta: metav1.ObjectMeta{ 117 Namespace: namespace, 118 Name: fmt.Sprintf("pod%d", i), 119 Labels: map[string]string{"foo": "bar"}, 120 }, 121 Spec: v1.PodSpec{ 122 RestartPolicy: restartPolicy, 123 Containers: []v1.Container{{Ports: []v1.ContainerPort{}}}, 124 }, 125 Status: v1.PodStatus{ 126 PodIP: fmt.Sprintf("1.2.3.%d", 4+i), 127 Phase: podPhase, 128 Conditions: []v1.PodCondition{ 129 { 130 Type: v1.PodReady, 131 Status: v1.ConditionFalse, 132 }, 133 }, 134 }, 135 } 136 for j := 0; j < nPorts; j++ { 137 p.Spec.Containers[0].Ports = append(p.Spec.Containers[0].Ports, 138 v1.ContainerPort{Name: fmt.Sprintf("port%d", j), ContainerPort: int32(8080 + j)}) 139 } 140 store.Add(p) 141 } 142 } 143 144 func makeTestServer(t *testing.T, namespace string) (*httptest.Server, *utiltesting.FakeHandler) { 145 fakeEndpointsHandler := utiltesting.FakeHandler{ 146 StatusCode: http.StatusOK, 147 ResponseBody: runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{}), 148 } 149 mux := http.NewServeMux() 150 if namespace == "" { 151 t.Fatal("namespace cannot be empty") 152 } 153 mux.Handle("/api/v1/namespaces/"+namespace+"/endpoints", &fakeEndpointsHandler) 154 mux.Handle("/api/v1/namespaces/"+namespace+"/endpoints/", &fakeEndpointsHandler) 155 mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { 156 t.Errorf("unexpected request: %v", req.RequestURI) 157 http.Error(res, "", http.StatusNotFound) 158 }) 159 return httptest.NewServer(mux), &fakeEndpointsHandler 160 } 161 162 // makeBlockingEndpointDeleteTestServer will signal the blockNextAction channel on endpoint "POST" & "DELETE" requests. All 163 // block endpoint "DELETE" requestsi will wait on a blockDelete signal to delete endpoint. If controller is nil, a error will 164 // be sent in the response. 165 func makeBlockingEndpointDeleteTestServer(t *testing.T, controller *endpointController, endpoint *v1.Endpoints, blockDelete, blockNextAction chan struct{}, namespace string) *httptest.Server { 166 167 handlerFunc := func(res http.ResponseWriter, req *http.Request) { 168 if controller == nil { 169 res.WriteHeader(http.StatusInternalServerError) 170 res.Write([]byte("controller has not been set yet")) 171 return 172 } 173 174 if req.Method == "POST" { 175 controller.endpointsStore.Add(endpoint) 176 blockNextAction <- struct{}{} 177 } 178 179 if req.Method == "DELETE" { 180 go func() { 181 // Delay the deletion of endoints to make endpoint cache out of sync 182 <-blockDelete 183 controller.endpointsStore.Delete(endpoint) 184 controller.onEndpointsDelete(endpoint) 185 }() 186 blockNextAction <- struct{}{} 187 } 188 189 res.WriteHeader(http.StatusOK) 190 res.Write([]byte(runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{}))) 191 } 192 193 mux := http.NewServeMux() 194 mux.HandleFunc("/api/v1/namespaces/"+namespace+"/endpoints", handlerFunc) 195 mux.HandleFunc("/api/v1/namespaces/"+namespace+"/endpoints/", handlerFunc) 196 mux.HandleFunc("/api/v1/namespaces/"+namespace+"/events", func(res http.ResponseWriter, req *http.Request) {}) 197 mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { 198 t.Errorf("unexpected request: %v", req.RequestURI) 199 http.Error(res, "", http.StatusNotFound) 200 }) 201 return httptest.NewServer(mux) 202 203 } 204 205 type endpointController struct { 206 *Controller 207 podStore cache.Store 208 serviceStore cache.Store 209 endpointsStore cache.Store 210 } 211 212 func newController(url string, batchPeriod time.Duration) *endpointController { 213 client := clientset.NewForConfigOrDie(&restclient.Config{Host: url, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}) 214 informerFactory := informers.NewSharedInformerFactory(client, controllerpkg.NoResyncPeriodFunc()) 215 endpoints := NewEndpointController(informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Services(), 216 informerFactory.Core().V1().Endpoints(), client, batchPeriod) 217 endpoints.podsSynced = alwaysReady 218 endpoints.servicesSynced = alwaysReady 219 endpoints.endpointsSynced = alwaysReady 220 return &endpointController{ 221 endpoints, 222 informerFactory.Core().V1().Pods().Informer().GetStore(), 223 informerFactory.Core().V1().Services().Informer().GetStore(), 224 informerFactory.Core().V1().Endpoints().Informer().GetStore(), 225 } 226 } 227 228 func newFakeController(batchPeriod time.Duration) (*fake.Clientset, *endpointController) { 229 client := fake.NewSimpleClientset() 230 informerFactory := informers.NewSharedInformerFactory(client, controllerpkg.NoResyncPeriodFunc()) 231 232 eController := NewEndpointController( 233 informerFactory.Core().V1().Pods(), 234 informerFactory.Core().V1().Services(), 235 informerFactory.Core().V1().Endpoints(), 236 client, 237 batchPeriod) 238 239 eController.podsSynced = alwaysReady 240 eController.servicesSynced = alwaysReady 241 eController.endpointsSynced = alwaysReady 242 243 return client, &endpointController{ 244 eController, 245 informerFactory.Core().V1().Pods().Informer().GetStore(), 246 informerFactory.Core().V1().Services().Informer().GetStore(), 247 informerFactory.Core().V1().Endpoints().Informer().GetStore(), 248 } 249 } 250 251 func TestSyncEndpointsItemsPreserveNoSelector(t *testing.T) { 252 ns := metav1.NamespaceDefault 253 testServer, endpointsHandler := makeTestServer(t, ns) 254 defer testServer.Close() 255 endpoints := newController(testServer.URL, 0*time.Second) 256 endpoints.endpointsStore.Add(&v1.Endpoints{ 257 ObjectMeta: metav1.ObjectMeta{ 258 Name: "foo", 259 Namespace: ns, 260 ResourceVersion: "1", 261 }, 262 Subsets: []v1.EndpointSubset{{ 263 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 264 Ports: []v1.EndpointPort{{Port: 1000}}, 265 }}, 266 }) 267 endpoints.serviceStore.Add(&v1.Service{ 268 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 269 Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 80}}}, 270 }) 271 endpoints.syncService(context.TODO(), ns+"/foo") 272 endpointsHandler.ValidateRequestCount(t, 0) 273 } 274 275 func TestSyncEndpointsExistingNilSubsets(t *testing.T) { 276 ns := metav1.NamespaceDefault 277 testServer, endpointsHandler := makeTestServer(t, ns) 278 defer testServer.Close() 279 endpoints := newController(testServer.URL, 0*time.Second) 280 endpoints.endpointsStore.Add(&v1.Endpoints{ 281 ObjectMeta: metav1.ObjectMeta{ 282 Name: "foo", 283 Namespace: ns, 284 ResourceVersion: "1", 285 }, 286 Subsets: nil, 287 }) 288 endpoints.serviceStore.Add(&v1.Service{ 289 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 290 Spec: v1.ServiceSpec{ 291 Selector: map[string]string{"foo": "bar"}, 292 Ports: []v1.ServicePort{{Port: 80}}, 293 }, 294 }) 295 endpoints.syncService(context.TODO(), ns+"/foo") 296 endpointsHandler.ValidateRequestCount(t, 0) 297 } 298 299 func TestSyncEndpointsExistingEmptySubsets(t *testing.T) { 300 ns := metav1.NamespaceDefault 301 testServer, endpointsHandler := makeTestServer(t, ns) 302 defer testServer.Close() 303 endpoints := newController(testServer.URL, 0*time.Second) 304 endpoints.endpointsStore.Add(&v1.Endpoints{ 305 ObjectMeta: metav1.ObjectMeta{ 306 Name: "foo", 307 Namespace: ns, 308 ResourceVersion: "1", 309 }, 310 Subsets: []v1.EndpointSubset{}, 311 }) 312 endpoints.serviceStore.Add(&v1.Service{ 313 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 314 Spec: v1.ServiceSpec{ 315 Selector: map[string]string{"foo": "bar"}, 316 Ports: []v1.ServicePort{{Port: 80}}, 317 }, 318 }) 319 endpoints.syncService(context.TODO(), ns+"/foo") 320 endpointsHandler.ValidateRequestCount(t, 0) 321 } 322 323 func TestSyncEndpointsWithPodResourceVersionUpdateOnly(t *testing.T) { 324 ns := metav1.NamespaceDefault 325 testServer, endpointsHandler := makeTestServer(t, ns) 326 defer testServer.Close() 327 pod0 := testPod(ns, 0, 1, true, ipv4only) 328 pod1 := testPod(ns, 1, 1, false, ipv4only) 329 endpoints := newController(testServer.URL, 0*time.Second) 330 endpoints.endpointsStore.Add(&v1.Endpoints{ 331 ObjectMeta: metav1.ObjectMeta{ 332 Name: "foo", 333 Namespace: ns, 334 ResourceVersion: "1", 335 }, 336 Subsets: []v1.EndpointSubset{{ 337 Addresses: []v1.EndpointAddress{ 338 { 339 IP: pod0.Status.PodIPs[0].IP, 340 NodeName: &emptyNodeName, 341 TargetRef: &v1.ObjectReference{Kind: "Pod", Name: pod0.Name, Namespace: ns, ResourceVersion: "1"}, 342 }, 343 }, 344 NotReadyAddresses: []v1.EndpointAddress{ 345 { 346 IP: pod1.Status.PodIPs[0].IP, 347 NodeName: &emptyNodeName, 348 TargetRef: &v1.ObjectReference{Kind: "Pod", Name: pod1.Name, Namespace: ns, ResourceVersion: "2"}, 349 }, 350 }, 351 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 352 }}, 353 }) 354 endpoints.serviceStore.Add(&v1.Service{ 355 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 356 Spec: v1.ServiceSpec{ 357 Selector: map[string]string{"foo": "bar"}, 358 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 359 }, 360 }) 361 pod0.ResourceVersion = "3" 362 pod1.ResourceVersion = "4" 363 endpoints.podStore.Add(pod0) 364 endpoints.podStore.Add(pod1) 365 endpoints.syncService(context.TODO(), ns+"/foo") 366 endpointsHandler.ValidateRequestCount(t, 0) 367 } 368 369 func TestSyncEndpointsNewNoSubsets(t *testing.T) { 370 ns := metav1.NamespaceDefault 371 testServer, endpointsHandler := makeTestServer(t, ns) 372 defer testServer.Close() 373 endpoints := newController(testServer.URL, 0*time.Second) 374 endpoints.serviceStore.Add(&v1.Service{ 375 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 376 Spec: v1.ServiceSpec{ 377 Selector: map[string]string{"foo": "bar"}, 378 Ports: []v1.ServicePort{{Port: 80}}, 379 }, 380 }) 381 endpoints.syncService(context.TODO(), ns+"/foo") 382 endpointsHandler.ValidateRequestCount(t, 1) 383 } 384 385 func TestCheckLeftoverEndpoints(t *testing.T) { 386 ns := metav1.NamespaceDefault 387 testServer, _ := makeTestServer(t, ns) 388 defer testServer.Close() 389 endpoints := newController(testServer.URL, 0*time.Second) 390 endpoints.endpointsStore.Add(&v1.Endpoints{ 391 ObjectMeta: metav1.ObjectMeta{ 392 Name: "foo", 393 Namespace: ns, 394 ResourceVersion: "1", 395 }, 396 Subsets: []v1.EndpointSubset{{ 397 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 398 Ports: []v1.EndpointPort{{Port: 1000}}, 399 }}, 400 }) 401 endpoints.checkLeftoverEndpoints() 402 if e, a := 1, endpoints.queue.Len(); e != a { 403 t.Fatalf("Expected %v, got %v", e, a) 404 } 405 got, _ := endpoints.queue.Get() 406 if e, a := ns+"/foo", got; e != a { 407 t.Errorf("Expected %v, got %v", e, a) 408 } 409 } 410 411 func TestSyncEndpointsProtocolTCP(t *testing.T) { 412 ns := "other" 413 testServer, endpointsHandler := makeTestServer(t, ns) 414 defer testServer.Close() 415 endpoints := newController(testServer.URL, 0*time.Second) 416 endpoints.endpointsStore.Add(&v1.Endpoints{ 417 ObjectMeta: metav1.ObjectMeta{ 418 Name: "foo", 419 Namespace: ns, 420 ResourceVersion: "1", 421 }, 422 Subsets: []v1.EndpointSubset{{ 423 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 424 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, 425 }}, 426 }) 427 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 428 endpoints.serviceStore.Add(&v1.Service{ 429 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 430 Spec: v1.ServiceSpec{ 431 Selector: map[string]string{}, 432 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "TCP"}}, 433 }, 434 }) 435 endpoints.syncService(context.TODO(), ns+"/foo") 436 437 endpointsHandler.ValidateRequestCount(t, 1) 438 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 439 ObjectMeta: metav1.ObjectMeta{ 440 Name: "foo", 441 Namespace: ns, 442 ResourceVersion: "1", 443 Labels: map[string]string{ 444 v1.IsHeadlessService: "", 445 }, 446 }, 447 Subsets: []v1.EndpointSubset{{ 448 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 449 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 450 }}, 451 }) 452 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 453 } 454 455 func TestSyncEndpointsHeadlessServiceLabel(t *testing.T) { 456 ns := metav1.NamespaceDefault 457 testServer, endpointsHandler := makeTestServer(t, ns) 458 defer testServer.Close() 459 endpoints := newController(testServer.URL, 0*time.Second) 460 endpoints.endpointsStore.Add(&v1.Endpoints{ 461 ObjectMeta: metav1.ObjectMeta{ 462 Name: "foo", 463 Namespace: ns, 464 ResourceVersion: "1", 465 Labels: map[string]string{ 466 v1.IsHeadlessService: "", 467 }, 468 }, 469 Subsets: []v1.EndpointSubset{}, 470 }) 471 endpoints.serviceStore.Add(&v1.Service{ 472 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 473 Spec: v1.ServiceSpec{ 474 Selector: map[string]string{"foo": "bar"}, 475 Ports: []v1.ServicePort{{Port: 80}}, 476 }, 477 }) 478 endpoints.syncService(context.TODO(), ns+"/foo") 479 endpointsHandler.ValidateRequestCount(t, 0) 480 } 481 482 func TestSyncServiceExternalNameType(t *testing.T) { 483 serviceName := "testing-1" 484 namespace := metav1.NamespaceDefault 485 486 testCases := []struct { 487 desc string 488 service *v1.Service 489 }{ 490 { 491 desc: "External name with selector and ports should not receive endpoints", 492 service: &v1.Service{ 493 ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, 494 Spec: v1.ServiceSpec{ 495 Selector: map[string]string{"foo": "bar"}, 496 Ports: []v1.ServicePort{{Port: 80}}, 497 Type: v1.ServiceTypeExternalName, 498 }, 499 }, 500 }, 501 { 502 desc: "External name with ports should not receive endpoints", 503 service: &v1.Service{ 504 ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, 505 Spec: v1.ServiceSpec{ 506 Ports: []v1.ServicePort{{Port: 80}}, 507 Type: v1.ServiceTypeExternalName, 508 }, 509 }, 510 }, 511 { 512 desc: "External name with selector should not receive endpoints", 513 service: &v1.Service{ 514 ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, 515 Spec: v1.ServiceSpec{ 516 Selector: map[string]string{"foo": "bar"}, 517 Type: v1.ServiceTypeExternalName, 518 }, 519 }, 520 }, 521 { 522 desc: "External name without selector and ports should not receive endpoints", 523 service: &v1.Service{ 524 ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, 525 Spec: v1.ServiceSpec{ 526 Type: v1.ServiceTypeExternalName, 527 }, 528 }, 529 }, 530 } 531 532 for _, tc := range testCases { 533 t.Run(tc.desc, func(t *testing.T) { 534 testServer, endpointsHandler := makeTestServer(t, namespace) 535 536 defer testServer.Close() 537 endpoints := newController(testServer.URL, 0*time.Second) 538 err := endpoints.serviceStore.Add(tc.service) 539 if err != nil { 540 t.Fatalf("Error adding service to service store: %v", err) 541 } 542 err = endpoints.syncService(context.TODO(), namespace+"/"+serviceName) 543 if err != nil { 544 t.Fatalf("Error syncing service: %v", err) 545 } 546 endpointsHandler.ValidateRequestCount(t, 0) 547 }) 548 } 549 } 550 551 func TestSyncEndpointsProtocolUDP(t *testing.T) { 552 ns := "other" 553 testServer, endpointsHandler := makeTestServer(t, ns) 554 defer testServer.Close() 555 endpoints := newController(testServer.URL, 0*time.Second) 556 endpoints.endpointsStore.Add(&v1.Endpoints{ 557 ObjectMeta: metav1.ObjectMeta{ 558 Name: "foo", 559 Namespace: ns, 560 ResourceVersion: "1", 561 }, 562 Subsets: []v1.EndpointSubset{{ 563 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 564 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "UDP"}}, 565 }}, 566 }) 567 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 568 endpoints.serviceStore.Add(&v1.Service{ 569 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 570 Spec: v1.ServiceSpec{ 571 Selector: map[string]string{}, 572 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "UDP"}}, 573 }, 574 }) 575 endpoints.syncService(context.TODO(), ns+"/foo") 576 577 endpointsHandler.ValidateRequestCount(t, 1) 578 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 579 ObjectMeta: metav1.ObjectMeta{ 580 Name: "foo", 581 Namespace: ns, 582 ResourceVersion: "1", 583 Labels: map[string]string{ 584 v1.IsHeadlessService: "", 585 }, 586 }, 587 Subsets: []v1.EndpointSubset{{ 588 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 589 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "UDP"}}, 590 }}, 591 }) 592 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 593 } 594 595 func TestSyncEndpointsProtocolSCTP(t *testing.T) { 596 ns := "other" 597 testServer, endpointsHandler := makeTestServer(t, ns) 598 defer testServer.Close() 599 endpoints := newController(testServer.URL, 0*time.Second) 600 endpoints.endpointsStore.Add(&v1.Endpoints{ 601 ObjectMeta: metav1.ObjectMeta{ 602 Name: "foo", 603 Namespace: ns, 604 ResourceVersion: "1", 605 }, 606 Subsets: []v1.EndpointSubset{{ 607 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 608 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "SCTP"}}, 609 }}, 610 }) 611 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 612 endpoints.serviceStore.Add(&v1.Service{ 613 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 614 Spec: v1.ServiceSpec{ 615 Selector: map[string]string{}, 616 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "SCTP"}}, 617 }, 618 }) 619 endpoints.syncService(context.TODO(), ns+"/foo") 620 621 endpointsHandler.ValidateRequestCount(t, 1) 622 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 623 ObjectMeta: metav1.ObjectMeta{ 624 Name: "foo", 625 Namespace: ns, 626 ResourceVersion: "1", 627 Labels: map[string]string{ 628 v1.IsHeadlessService: "", 629 }, 630 }, 631 Subsets: []v1.EndpointSubset{{ 632 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 633 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "SCTP"}}, 634 }}, 635 }) 636 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 637 } 638 639 func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) { 640 ns := "other" 641 testServer, endpointsHandler := makeTestServer(t, ns) 642 defer testServer.Close() 643 endpoints := newController(testServer.URL, 0*time.Second) 644 endpoints.endpointsStore.Add(&v1.Endpoints{ 645 ObjectMeta: metav1.ObjectMeta{ 646 Name: "foo", 647 Namespace: ns, 648 ResourceVersion: "1", 649 }, 650 Subsets: []v1.EndpointSubset{}, 651 }) 652 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 653 endpoints.serviceStore.Add(&v1.Service{ 654 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 655 Spec: v1.ServiceSpec{ 656 Selector: map[string]string{}, 657 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 658 }, 659 }) 660 endpoints.syncService(context.TODO(), ns+"/foo") 661 662 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 663 ObjectMeta: metav1.ObjectMeta{ 664 Name: "foo", 665 Namespace: ns, 666 ResourceVersion: "1", 667 Labels: map[string]string{ 668 v1.IsHeadlessService: "", 669 }, 670 }, 671 Subsets: []v1.EndpointSubset{{ 672 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 673 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 674 }}, 675 }) 676 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 677 } 678 679 func TestSyncEndpointsItemsEmptySelectorSelectsAllNotReady(t *testing.T) { 680 ns := "other" 681 testServer, endpointsHandler := makeTestServer(t, ns) 682 defer testServer.Close() 683 endpoints := newController(testServer.URL, 0*time.Second) 684 endpoints.endpointsStore.Add(&v1.Endpoints{ 685 ObjectMeta: metav1.ObjectMeta{ 686 Name: "foo", 687 Namespace: ns, 688 ResourceVersion: "1", 689 }, 690 Subsets: []v1.EndpointSubset{}, 691 }) 692 addPods(endpoints.podStore, ns, 0, 1, 1, ipv4only) 693 endpoints.serviceStore.Add(&v1.Service{ 694 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 695 Spec: v1.ServiceSpec{ 696 Selector: map[string]string{}, 697 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 698 }, 699 }) 700 endpoints.syncService(context.TODO(), ns+"/foo") 701 702 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 703 ObjectMeta: metav1.ObjectMeta{ 704 Name: "foo", 705 Namespace: ns, 706 ResourceVersion: "1", 707 Labels: map[string]string{ 708 v1.IsHeadlessService: "", 709 }, 710 }, 711 Subsets: []v1.EndpointSubset{{ 712 NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 713 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 714 }}, 715 }) 716 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 717 } 718 719 func TestSyncEndpointsItemsEmptySelectorSelectsAllMixed(t *testing.T) { 720 ns := "other" 721 testServer, endpointsHandler := makeTestServer(t, ns) 722 defer testServer.Close() 723 endpoints := newController(testServer.URL, 0*time.Second) 724 endpoints.endpointsStore.Add(&v1.Endpoints{ 725 ObjectMeta: metav1.ObjectMeta{ 726 Name: "foo", 727 Namespace: ns, 728 ResourceVersion: "1", 729 }, 730 Subsets: []v1.EndpointSubset{}, 731 }) 732 addPods(endpoints.podStore, ns, 1, 1, 1, ipv4only) 733 endpoints.serviceStore.Add(&v1.Service{ 734 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 735 Spec: v1.ServiceSpec{ 736 Selector: map[string]string{}, 737 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 738 }, 739 }) 740 endpoints.syncService(context.TODO(), ns+"/foo") 741 742 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 743 ObjectMeta: metav1.ObjectMeta{ 744 Name: "foo", 745 Namespace: ns, 746 ResourceVersion: "1", 747 Labels: map[string]string{ 748 v1.IsHeadlessService: "", 749 }, 750 }, 751 Subsets: []v1.EndpointSubset{{ 752 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 753 NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.5", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod1", Namespace: ns}}}, 754 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 755 }}, 756 }) 757 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 758 } 759 760 func TestSyncEndpointsItemsPreexisting(t *testing.T) { 761 ns := "bar" 762 testServer, endpointsHandler := makeTestServer(t, ns) 763 defer testServer.Close() 764 endpoints := newController(testServer.URL, 0*time.Second) 765 endpoints.endpointsStore.Add(&v1.Endpoints{ 766 ObjectMeta: metav1.ObjectMeta{ 767 Name: "foo", 768 Namespace: ns, 769 ResourceVersion: "1", 770 }, 771 Subsets: []v1.EndpointSubset{{ 772 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 773 Ports: []v1.EndpointPort{{Port: 1000}}, 774 }}, 775 }) 776 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 777 endpoints.serviceStore.Add(&v1.Service{ 778 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 779 Spec: v1.ServiceSpec{ 780 Selector: map[string]string{"foo": "bar"}, 781 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 782 }, 783 }) 784 endpoints.syncService(context.TODO(), ns+"/foo") 785 786 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 787 ObjectMeta: metav1.ObjectMeta{ 788 Name: "foo", 789 Namespace: ns, 790 ResourceVersion: "1", 791 Labels: map[string]string{ 792 v1.IsHeadlessService: "", 793 }, 794 }, 795 Subsets: []v1.EndpointSubset{{ 796 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 797 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 798 }}, 799 }) 800 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 801 } 802 803 func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) { 804 ns := metav1.NamespaceDefault 805 testServer, endpointsHandler := makeTestServer(t, ns) 806 defer testServer.Close() 807 endpoints := newController(testServer.URL, 0*time.Second) 808 endpoints.endpointsStore.Add(&v1.Endpoints{ 809 ObjectMeta: metav1.ObjectMeta{ 810 ResourceVersion: "1", 811 Name: "foo", 812 Namespace: ns, 813 }, 814 Subsets: []v1.EndpointSubset{{ 815 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 816 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 817 }}, 818 }) 819 addPods(endpoints.podStore, metav1.NamespaceDefault, 1, 1, 0, ipv4only) 820 endpoints.serviceStore.Add(&v1.Service{ 821 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: metav1.NamespaceDefault}, 822 Spec: v1.ServiceSpec{ 823 Selector: map[string]string{"foo": "bar"}, 824 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 825 }, 826 }) 827 endpoints.syncService(context.TODO(), ns+"/foo") 828 endpointsHandler.ValidateRequestCount(t, 0) 829 } 830 831 func TestSyncEndpointsItems(t *testing.T) { 832 ns := "other" 833 testServer, endpointsHandler := makeTestServer(t, ns) 834 defer testServer.Close() 835 endpoints := newController(testServer.URL, 0*time.Second) 836 addPods(endpoints.podStore, ns, 3, 2, 0, ipv4only) 837 addPods(endpoints.podStore, "blah", 5, 2, 0, ipv4only) // make sure these aren't found! 838 839 endpoints.serviceStore.Add(&v1.Service{ 840 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 841 Spec: v1.ServiceSpec{ 842 Selector: map[string]string{"foo": "bar"}, 843 Ports: []v1.ServicePort{ 844 {Name: "port0", Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}, 845 {Name: "port1", Port: 88, Protocol: "TCP", TargetPort: intstr.FromInt32(8088)}, 846 }, 847 }, 848 }) 849 endpoints.syncService(context.TODO(), "other/foo") 850 851 expectedSubsets := []v1.EndpointSubset{{ 852 Addresses: []v1.EndpointAddress{ 853 {IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, 854 {IP: "1.2.3.5", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod1", Namespace: ns}}, 855 {IP: "1.2.3.6", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod2", Namespace: ns}}, 856 }, 857 Ports: []v1.EndpointPort{ 858 {Name: "port0", Port: 8080, Protocol: "TCP"}, 859 {Name: "port1", Port: 8088, Protocol: "TCP"}, 860 }, 861 }} 862 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 863 ObjectMeta: metav1.ObjectMeta{ 864 ResourceVersion: "", 865 Name: "foo", 866 Labels: map[string]string{ 867 v1.IsHeadlessService: "", 868 }, 869 }, 870 Subsets: endptspkg.SortSubsets(expectedSubsets), 871 }) 872 endpointsHandler.ValidateRequestCount(t, 1) 873 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints", "POST", &data) 874 } 875 876 func TestSyncEndpointsItemsWithLabels(t *testing.T) { 877 ns := "other" 878 testServer, endpointsHandler := makeTestServer(t, ns) 879 defer testServer.Close() 880 endpoints := newController(testServer.URL, 0*time.Second) 881 addPods(endpoints.podStore, ns, 3, 2, 0, ipv4only) 882 serviceLabels := map[string]string{"foo": "bar"} 883 endpoints.serviceStore.Add(&v1.Service{ 884 ObjectMeta: metav1.ObjectMeta{ 885 Name: "foo", 886 Namespace: ns, 887 Labels: serviceLabels, 888 }, 889 Spec: v1.ServiceSpec{ 890 Selector: map[string]string{"foo": "bar"}, 891 Ports: []v1.ServicePort{ 892 {Name: "port0", Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}, 893 {Name: "port1", Port: 88, Protocol: "TCP", TargetPort: intstr.FromInt32(8088)}, 894 }, 895 }, 896 }) 897 endpoints.syncService(context.TODO(), ns+"/foo") 898 899 expectedSubsets := []v1.EndpointSubset{{ 900 Addresses: []v1.EndpointAddress{ 901 {IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, 902 {IP: "1.2.3.5", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod1", Namespace: ns}}, 903 {IP: "1.2.3.6", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod2", Namespace: ns}}, 904 }, 905 Ports: []v1.EndpointPort{ 906 {Name: "port0", Port: 8080, Protocol: "TCP"}, 907 {Name: "port1", Port: 8088, Protocol: "TCP"}, 908 }, 909 }} 910 911 serviceLabels[v1.IsHeadlessService] = "" 912 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 913 ObjectMeta: metav1.ObjectMeta{ 914 ResourceVersion: "", 915 Name: "foo", 916 Labels: serviceLabels, 917 }, 918 Subsets: endptspkg.SortSubsets(expectedSubsets), 919 }) 920 endpointsHandler.ValidateRequestCount(t, 1) 921 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints", "POST", &data) 922 } 923 924 func TestSyncEndpointsItemsPreexistingLabelsChange(t *testing.T) { 925 ns := "bar" 926 testServer, endpointsHandler := makeTestServer(t, ns) 927 defer testServer.Close() 928 endpoints := newController(testServer.URL, 0*time.Second) 929 endpoints.endpointsStore.Add(&v1.Endpoints{ 930 ObjectMeta: metav1.ObjectMeta{ 931 Name: "foo", 932 Namespace: ns, 933 ResourceVersion: "1", 934 Labels: map[string]string{ 935 "foo": "bar", 936 }, 937 }, 938 Subsets: []v1.EndpointSubset{{ 939 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 940 Ports: []v1.EndpointPort{{Port: 1000}}, 941 }}, 942 }) 943 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 944 serviceLabels := map[string]string{"baz": "blah"} 945 endpoints.serviceStore.Add(&v1.Service{ 946 ObjectMeta: metav1.ObjectMeta{ 947 Name: "foo", 948 Namespace: ns, 949 Labels: serviceLabels, 950 }, 951 Spec: v1.ServiceSpec{ 952 Selector: map[string]string{"foo": "bar"}, 953 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 954 }, 955 }) 956 endpoints.syncService(context.TODO(), ns+"/foo") 957 958 serviceLabels[v1.IsHeadlessService] = "" 959 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 960 ObjectMeta: metav1.ObjectMeta{ 961 Name: "foo", 962 Namespace: ns, 963 ResourceVersion: "1", 964 Labels: serviceLabels, 965 }, 966 Subsets: []v1.EndpointSubset{{ 967 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 968 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 969 }}, 970 }) 971 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 972 } 973 974 func TestWaitsForAllInformersToBeSynced2(t *testing.T) { 975 var tests = []struct { 976 podsSynced func() bool 977 servicesSynced func() bool 978 endpointsSynced func() bool 979 shouldUpdateEndpoints bool 980 }{ 981 {neverReady, alwaysReady, alwaysReady, false}, 982 {alwaysReady, neverReady, alwaysReady, false}, 983 {alwaysReady, alwaysReady, neverReady, false}, 984 {alwaysReady, alwaysReady, alwaysReady, true}, 985 } 986 987 for _, test := range tests { 988 func() { 989 ns := "other" 990 testServer, endpointsHandler := makeTestServer(t, ns) 991 defer testServer.Close() 992 endpoints := newController(testServer.URL, 0*time.Second) 993 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 994 995 service := &v1.Service{ 996 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 997 Spec: v1.ServiceSpec{ 998 Selector: map[string]string{}, 999 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "TCP"}}, 1000 }, 1001 } 1002 endpoints.serviceStore.Add(service) 1003 endpoints.onServiceUpdate(service) 1004 endpoints.podsSynced = test.podsSynced 1005 endpoints.servicesSynced = test.servicesSynced 1006 endpoints.endpointsSynced = test.endpointsSynced 1007 endpoints.workerLoopPeriod = 10 * time.Millisecond 1008 stopCh := make(chan struct{}) 1009 defer close(stopCh) 1010 go endpoints.Run(context.TODO(), 1) 1011 1012 // cache.WaitForNamedCacheSync has a 100ms poll period, and the endpoints worker has a 10ms period. 1013 // To ensure we get all updates, including unexpected ones, we need to wait at least as long as 1014 // a single cache sync period and worker period, with some fudge room. 1015 time.Sleep(150 * time.Millisecond) 1016 if test.shouldUpdateEndpoints { 1017 // Ensure the work queue has been processed by looping for up to a second to prevent flakes. 1018 wait.PollImmediate(50*time.Millisecond, 1*time.Second, func() (bool, error) { 1019 return endpoints.queue.Len() == 0, nil 1020 }) 1021 endpointsHandler.ValidateRequestCount(t, 1) 1022 } else { 1023 endpointsHandler.ValidateRequestCount(t, 0) 1024 } 1025 }() 1026 } 1027 } 1028 1029 func TestSyncEndpointsHeadlessService(t *testing.T) { 1030 ns := "headless" 1031 testServer, endpointsHandler := makeTestServer(t, ns) 1032 defer testServer.Close() 1033 endpoints := newController(testServer.URL, 0*time.Second) 1034 endpoints.endpointsStore.Add(&v1.Endpoints{ 1035 ObjectMeta: metav1.ObjectMeta{ 1036 Name: "foo", 1037 Namespace: ns, 1038 ResourceVersion: "1", 1039 }, 1040 Subsets: []v1.EndpointSubset{{ 1041 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 1042 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, 1043 }}, 1044 }) 1045 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 1046 service := &v1.Service{ 1047 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns, Labels: map[string]string{"a": "b"}}, 1048 Spec: v1.ServiceSpec{ 1049 Selector: map[string]string{}, 1050 ClusterIP: api.ClusterIPNone, 1051 Ports: []v1.ServicePort{}, 1052 }, 1053 } 1054 originalService := service.DeepCopy() 1055 endpoints.serviceStore.Add(service) 1056 endpoints.syncService(context.TODO(), ns+"/foo") 1057 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1058 ObjectMeta: metav1.ObjectMeta{ 1059 Name: "foo", 1060 Namespace: ns, 1061 ResourceVersion: "1", 1062 Labels: map[string]string{ 1063 "a": "b", 1064 v1.IsHeadlessService: "", 1065 }, 1066 }, 1067 Subsets: []v1.EndpointSubset{{ 1068 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 1069 Ports: []v1.EndpointPort{}, 1070 }}, 1071 }) 1072 if !reflect.DeepEqual(originalService, service) { 1073 t.Fatalf("syncing endpoints changed service: %s", cmp.Diff(service, originalService)) 1074 } 1075 endpointsHandler.ValidateRequestCount(t, 1) 1076 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1077 } 1078 1079 func TestSyncEndpointsItemsExcludeNotReadyPodsWithRestartPolicyNeverAndPhaseFailed(t *testing.T) { 1080 ns := "other" 1081 testServer, endpointsHandler := makeTestServer(t, ns) 1082 defer testServer.Close() 1083 endpoints := newController(testServer.URL, 0*time.Second) 1084 endpoints.endpointsStore.Add(&v1.Endpoints{ 1085 ObjectMeta: metav1.ObjectMeta{ 1086 Name: "foo", 1087 Namespace: ns, 1088 ResourceVersion: "1", 1089 Labels: map[string]string{ 1090 "foo": "bar", 1091 }, 1092 }, 1093 Subsets: []v1.EndpointSubset{}, 1094 }) 1095 addNotReadyPodsWithSpecifiedRestartPolicyAndPhase(endpoints.podStore, ns, 1, 1, v1.RestartPolicyNever, v1.PodFailed) 1096 endpoints.serviceStore.Add(&v1.Service{ 1097 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1098 Spec: v1.ServiceSpec{ 1099 Selector: map[string]string{"foo": "bar"}, 1100 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 1101 }, 1102 }) 1103 endpoints.syncService(context.TODO(), ns+"/foo") 1104 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1105 ObjectMeta: metav1.ObjectMeta{ 1106 Name: "foo", 1107 Namespace: ns, 1108 ResourceVersion: "1", 1109 Labels: map[string]string{ 1110 v1.IsHeadlessService: "", 1111 }, 1112 }, 1113 Subsets: []v1.EndpointSubset{}, 1114 }) 1115 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1116 } 1117 1118 func TestSyncEndpointsItemsExcludeNotReadyPodsWithRestartPolicyNeverAndPhaseSucceeded(t *testing.T) { 1119 ns := "other" 1120 testServer, endpointsHandler := makeTestServer(t, ns) 1121 defer testServer.Close() 1122 endpoints := newController(testServer.URL, 0*time.Second) 1123 endpoints.endpointsStore.Add(&v1.Endpoints{ 1124 ObjectMeta: metav1.ObjectMeta{ 1125 Name: "foo", 1126 Namespace: ns, 1127 ResourceVersion: "1", 1128 Labels: map[string]string{ 1129 "foo": "bar", 1130 }, 1131 }, 1132 Subsets: []v1.EndpointSubset{}, 1133 }) 1134 addNotReadyPodsWithSpecifiedRestartPolicyAndPhase(endpoints.podStore, ns, 1, 1, v1.RestartPolicyNever, v1.PodSucceeded) 1135 endpoints.serviceStore.Add(&v1.Service{ 1136 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1137 Spec: v1.ServiceSpec{ 1138 Selector: map[string]string{"foo": "bar"}, 1139 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 1140 }, 1141 }) 1142 endpoints.syncService(context.TODO(), ns+"/foo") 1143 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1144 ObjectMeta: metav1.ObjectMeta{ 1145 Name: "foo", 1146 Namespace: ns, 1147 ResourceVersion: "1", 1148 Labels: map[string]string{ 1149 v1.IsHeadlessService: "", 1150 }, 1151 }, 1152 Subsets: []v1.EndpointSubset{}, 1153 }) 1154 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1155 } 1156 1157 func TestSyncEndpointsItemsExcludeNotReadyPodsWithRestartPolicyOnFailureAndPhaseSucceeded(t *testing.T) { 1158 ns := "other" 1159 testServer, endpointsHandler := makeTestServer(t, ns) 1160 defer testServer.Close() 1161 endpoints := newController(testServer.URL, 0*time.Second) 1162 endpoints.endpointsStore.Add(&v1.Endpoints{ 1163 ObjectMeta: metav1.ObjectMeta{ 1164 Name: "foo", 1165 Namespace: ns, 1166 ResourceVersion: "1", 1167 Labels: map[string]string{ 1168 "foo": "bar", 1169 }, 1170 }, 1171 Subsets: []v1.EndpointSubset{}, 1172 }) 1173 addNotReadyPodsWithSpecifiedRestartPolicyAndPhase(endpoints.podStore, ns, 1, 1, v1.RestartPolicyOnFailure, v1.PodSucceeded) 1174 endpoints.serviceStore.Add(&v1.Service{ 1175 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1176 Spec: v1.ServiceSpec{ 1177 Selector: map[string]string{"foo": "bar"}, 1178 Ports: []v1.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}}, 1179 }, 1180 }) 1181 endpoints.syncService(context.TODO(), ns+"/foo") 1182 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1183 ObjectMeta: metav1.ObjectMeta{ 1184 Name: "foo", 1185 Namespace: ns, 1186 ResourceVersion: "1", 1187 Labels: map[string]string{ 1188 v1.IsHeadlessService: "", 1189 }, 1190 }, 1191 Subsets: []v1.EndpointSubset{}, 1192 }) 1193 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1194 } 1195 1196 func TestSyncEndpointsHeadlessWithoutPort(t *testing.T) { 1197 ns := metav1.NamespaceDefault 1198 testServer, endpointsHandler := makeTestServer(t, ns) 1199 defer testServer.Close() 1200 endpoints := newController(testServer.URL, 0*time.Second) 1201 endpoints.serviceStore.Add(&v1.Service{ 1202 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1203 Spec: v1.ServiceSpec{ 1204 Selector: map[string]string{"foo": "bar"}, 1205 ClusterIP: "None", 1206 Ports: nil, 1207 }, 1208 }) 1209 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 1210 endpoints.syncService(context.TODO(), ns+"/foo") 1211 endpointsHandler.ValidateRequestCount(t, 1) 1212 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1213 ObjectMeta: metav1.ObjectMeta{ 1214 Name: "foo", 1215 Labels: map[string]string{ 1216 v1.IsHeadlessService: "", 1217 }, 1218 }, 1219 Subsets: []v1.EndpointSubset{{ 1220 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 1221 Ports: nil, 1222 }}, 1223 }) 1224 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints", "POST", &data) 1225 } 1226 1227 func TestPodToEndpointAddressForService(t *testing.T) { 1228 ipv4 := v1.IPv4Protocol 1229 ipv6 := v1.IPv6Protocol 1230 1231 testCases := []struct { 1232 name string 1233 ipFamilies []v1.IPFamily 1234 service v1.Service 1235 expectedEndpointFamily v1.IPFamily 1236 expectError bool 1237 }{ 1238 { 1239 name: "v4 service, in a single stack cluster", 1240 ipFamilies: ipv4only, 1241 service: v1.Service{ 1242 Spec: v1.ServiceSpec{ 1243 ClusterIP: "10.0.0.1", 1244 }, 1245 }, 1246 expectedEndpointFamily: ipv4, 1247 }, 1248 { 1249 name: "v4 service, in a dual stack cluster", 1250 ipFamilies: ipv4ipv6, 1251 service: v1.Service{ 1252 Spec: v1.ServiceSpec{ 1253 ClusterIP: "10.0.0.1", 1254 }, 1255 }, 1256 expectedEndpointFamily: ipv4, 1257 }, 1258 { 1259 name: "v4 service, in a dual stack ipv6-primary cluster", 1260 ipFamilies: ipv6ipv4, 1261 service: v1.Service{ 1262 Spec: v1.ServiceSpec{ 1263 ClusterIP: "10.0.0.1", 1264 }, 1265 }, 1266 expectedEndpointFamily: ipv4, 1267 }, 1268 { 1269 name: "v4 headless service, in a single stack cluster", 1270 ipFamilies: ipv4only, 1271 service: v1.Service{ 1272 Spec: v1.ServiceSpec{ 1273 ClusterIP: v1.ClusterIPNone, 1274 }, 1275 }, 1276 expectedEndpointFamily: ipv4, 1277 }, 1278 { 1279 name: "v4 headless service, in a dual stack cluster", 1280 ipFamilies: ipv4ipv6, 1281 service: v1.Service{ 1282 Spec: v1.ServiceSpec{ 1283 ClusterIP: v1.ClusterIPNone, 1284 IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, 1285 }, 1286 }, 1287 expectedEndpointFamily: ipv4, 1288 }, 1289 { 1290 name: "v4 legacy headless service, in a dual stack cluster", 1291 ipFamilies: ipv4ipv6, 1292 service: v1.Service{ 1293 Spec: v1.ServiceSpec{ 1294 ClusterIP: v1.ClusterIPNone, 1295 }, 1296 }, 1297 expectedEndpointFamily: ipv4, 1298 }, 1299 { 1300 name: "v4 legacy headless service, in a dual stack ipv6-primary cluster", 1301 ipFamilies: ipv6ipv4, 1302 service: v1.Service{ 1303 Spec: v1.ServiceSpec{ 1304 ClusterIP: v1.ClusterIPNone, 1305 }, 1306 }, 1307 expectedEndpointFamily: ipv6, 1308 }, 1309 { 1310 name: "v6 service, in a dual stack cluster", 1311 ipFamilies: ipv4ipv6, 1312 service: v1.Service{ 1313 Spec: v1.ServiceSpec{ 1314 ClusterIP: "3000::1", 1315 }, 1316 }, 1317 expectedEndpointFamily: ipv6, 1318 }, 1319 { 1320 name: "v6 headless service, in a single stack cluster", 1321 ipFamilies: ipv6only, 1322 service: v1.Service{ 1323 Spec: v1.ServiceSpec{ 1324 ClusterIP: v1.ClusterIPNone, 1325 }, 1326 }, 1327 expectedEndpointFamily: ipv6, 1328 }, 1329 { 1330 name: "v6 headless service, in a dual stack cluster (connected to a new api-server)", 1331 ipFamilies: ipv4ipv6, 1332 service: v1.Service{ 1333 Spec: v1.ServiceSpec{ 1334 ClusterIP: v1.ClusterIPNone, 1335 IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, // <- set by a api-server defaulting logic 1336 }, 1337 }, 1338 expectedEndpointFamily: ipv6, 1339 }, 1340 { 1341 name: "v6 legacy headless service, in a dual stack cluster (connected to a old api-server)", 1342 ipFamilies: ipv4ipv6, 1343 service: v1.Service{ 1344 Spec: v1.ServiceSpec{ 1345 ClusterIP: v1.ClusterIPNone, // <- families are not set by api-server 1346 }, 1347 }, 1348 expectedEndpointFamily: ipv4, 1349 }, 1350 // in reality this is a misconfigured cluster 1351 // i.e user is not using dual stack and have PodIP == v4 and ServiceIP==v6 1352 // previously controller could assign wrong ip to endpoint address 1353 // with gate removed. this is no longer the case. this is *not* behavior change 1354 // because previously things would have failed in kube-proxy anyway (due to editing wrong iptables). 1355 { 1356 name: "v6 service, in a v4 only cluster.", 1357 ipFamilies: ipv4only, 1358 service: v1.Service{ 1359 Spec: v1.ServiceSpec{ 1360 ClusterIP: "3000::1", 1361 }, 1362 }, 1363 expectError: true, 1364 expectedEndpointFamily: ipv4, 1365 }, 1366 // but this will actually give an error 1367 { 1368 name: "v6 service, in a v4 only cluster", 1369 ipFamilies: ipv4only, 1370 service: v1.Service{ 1371 Spec: v1.ServiceSpec{ 1372 ClusterIP: "3000::1", 1373 }, 1374 }, 1375 expectError: true, 1376 }, 1377 } 1378 for _, tc := range testCases { 1379 t.Run(tc.name, func(t *testing.T) { 1380 podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc) 1381 ns := "test" 1382 addPods(podStore, ns, 1, 1, 0, tc.ipFamilies) 1383 pods := podStore.List() 1384 if len(pods) != 1 { 1385 t.Fatalf("podStore size: expected: %d, got: %d", 1, len(pods)) 1386 } 1387 pod := pods[0].(*v1.Pod) 1388 epa, err := podToEndpointAddressForService(&tc.service, pod) 1389 1390 if err != nil && !tc.expectError { 1391 t.Fatalf("podToEndpointAddressForService returned unexpected error %v", err) 1392 } 1393 1394 if err == nil && tc.expectError { 1395 t.Fatalf("podToEndpointAddressForService should have returned error but it did not") 1396 } 1397 1398 if err != nil && tc.expectError { 1399 return 1400 } 1401 1402 if utilnet.IsIPv6String(epa.IP) != (tc.expectedEndpointFamily == ipv6) { 1403 t.Fatalf("IP: expected %s, got: %s", tc.expectedEndpointFamily, epa.IP) 1404 } 1405 if *(epa.NodeName) != pod.Spec.NodeName { 1406 t.Fatalf("NodeName: expected: %s, got: %s", pod.Spec.NodeName, *(epa.NodeName)) 1407 } 1408 if epa.TargetRef.Kind != "Pod" { 1409 t.Fatalf("TargetRef.Kind: expected: %s, got: %s", "Pod", epa.TargetRef.Kind) 1410 } 1411 if epa.TargetRef.Namespace != pod.ObjectMeta.Namespace { 1412 t.Fatalf("TargetRef.Namespace: expected: %s, got: %s", pod.ObjectMeta.Namespace, epa.TargetRef.Namespace) 1413 } 1414 if epa.TargetRef.Name != pod.ObjectMeta.Name { 1415 t.Fatalf("TargetRef.Name: expected: %s, got: %s", pod.ObjectMeta.Name, epa.TargetRef.Name) 1416 } 1417 if epa.TargetRef.UID != pod.ObjectMeta.UID { 1418 t.Fatalf("TargetRef.UID: expected: %s, got: %s", pod.ObjectMeta.UID, epa.TargetRef.UID) 1419 } 1420 if epa.TargetRef.ResourceVersion != "" { 1421 t.Fatalf("TargetRef.ResourceVersion: expected empty, got: %s", epa.TargetRef.ResourceVersion) 1422 } 1423 }) 1424 } 1425 1426 } 1427 1428 func TestLastTriggerChangeTimeAnnotation(t *testing.T) { 1429 ns := "other" 1430 testServer, endpointsHandler := makeTestServer(t, ns) 1431 defer testServer.Close() 1432 endpoints := newController(testServer.URL, 0*time.Second) 1433 endpoints.endpointsStore.Add(&v1.Endpoints{ 1434 ObjectMeta: metav1.ObjectMeta{ 1435 Name: "foo", 1436 Namespace: ns, 1437 ResourceVersion: "1", 1438 }, 1439 Subsets: []v1.EndpointSubset{{ 1440 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 1441 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, 1442 }}, 1443 }) 1444 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 1445 endpoints.serviceStore.Add(&v1.Service{ 1446 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns, CreationTimestamp: metav1.NewTime(triggerTime)}, 1447 Spec: v1.ServiceSpec{ 1448 Selector: map[string]string{}, 1449 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "TCP"}}, 1450 }, 1451 }) 1452 endpoints.syncService(context.TODO(), ns+"/foo") 1453 1454 endpointsHandler.ValidateRequestCount(t, 1) 1455 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1456 ObjectMeta: metav1.ObjectMeta{ 1457 Name: "foo", 1458 Namespace: ns, 1459 ResourceVersion: "1", 1460 Annotations: map[string]string{ 1461 v1.EndpointsLastChangeTriggerTime: triggerTimeString, 1462 }, 1463 Labels: map[string]string{ 1464 v1.IsHeadlessService: "", 1465 }, 1466 }, 1467 Subsets: []v1.EndpointSubset{{ 1468 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 1469 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 1470 }}, 1471 }) 1472 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1473 } 1474 1475 func TestLastTriggerChangeTimeAnnotation_AnnotationOverridden(t *testing.T) { 1476 ns := "other" 1477 testServer, endpointsHandler := makeTestServer(t, ns) 1478 defer testServer.Close() 1479 endpoints := newController(testServer.URL, 0*time.Second) 1480 endpoints.endpointsStore.Add(&v1.Endpoints{ 1481 ObjectMeta: metav1.ObjectMeta{ 1482 Name: "foo", 1483 Namespace: ns, 1484 ResourceVersion: "1", 1485 Annotations: map[string]string{ 1486 v1.EndpointsLastChangeTriggerTime: oldTriggerTimeString, 1487 }, 1488 }, 1489 Subsets: []v1.EndpointSubset{{ 1490 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 1491 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, 1492 }}, 1493 }) 1494 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 1495 endpoints.serviceStore.Add(&v1.Service{ 1496 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns, CreationTimestamp: metav1.NewTime(triggerTime)}, 1497 Spec: v1.ServiceSpec{ 1498 Selector: map[string]string{}, 1499 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "TCP"}}, 1500 }, 1501 }) 1502 endpoints.syncService(context.TODO(), ns+"/foo") 1503 1504 endpointsHandler.ValidateRequestCount(t, 1) 1505 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1506 ObjectMeta: metav1.ObjectMeta{ 1507 Name: "foo", 1508 Namespace: ns, 1509 ResourceVersion: "1", 1510 Annotations: map[string]string{ 1511 v1.EndpointsLastChangeTriggerTime: triggerTimeString, 1512 }, 1513 Labels: map[string]string{ 1514 v1.IsHeadlessService: "", 1515 }, 1516 }, 1517 Subsets: []v1.EndpointSubset{{ 1518 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 1519 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 1520 }}, 1521 }) 1522 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1523 } 1524 1525 func TestLastTriggerChangeTimeAnnotation_AnnotationCleared(t *testing.T) { 1526 ns := "other" 1527 testServer, endpointsHandler := makeTestServer(t, ns) 1528 defer testServer.Close() 1529 endpoints := newController(testServer.URL, 0*time.Second) 1530 endpoints.endpointsStore.Add(&v1.Endpoints{ 1531 ObjectMeta: metav1.ObjectMeta{ 1532 Name: "foo", 1533 Namespace: ns, 1534 ResourceVersion: "1", 1535 Annotations: map[string]string{ 1536 v1.EndpointsLastChangeTriggerTime: triggerTimeString, 1537 }, 1538 }, 1539 Subsets: []v1.EndpointSubset{{ 1540 Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, 1541 Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, 1542 }}, 1543 }) 1544 // Neither pod nor service has trigger time, this should cause annotation to be cleared. 1545 addPods(endpoints.podStore, ns, 1, 1, 0, ipv4only) 1546 endpoints.serviceStore.Add(&v1.Service{ 1547 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1548 Spec: v1.ServiceSpec{ 1549 Selector: map[string]string{}, 1550 Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080), Protocol: "TCP"}}, 1551 }, 1552 }) 1553 endpoints.syncService(context.TODO(), ns+"/foo") 1554 1555 endpointsHandler.ValidateRequestCount(t, 1) 1556 data := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{ 1557 ObjectMeta: metav1.ObjectMeta{ 1558 Name: "foo", 1559 Namespace: ns, 1560 ResourceVersion: "1", 1561 Labels: map[string]string{ 1562 v1.IsHeadlessService: "", 1563 }, // Annotation not set anymore. 1564 }, 1565 Subsets: []v1.EndpointSubset{{ 1566 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, 1567 Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, 1568 }}, 1569 }) 1570 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "PUT", &data) 1571 } 1572 1573 // TestPodUpdatesBatching verifies that endpoint updates caused by pod updates are batched together. 1574 // This test uses real time.Sleep, as there is no easy way to mock time in endpoints controller now. 1575 // TODO(mborsz): Migrate this test to mock clock when possible. 1576 func TestPodUpdatesBatching(t *testing.T) { 1577 type podUpdate struct { 1578 delay time.Duration 1579 podName string 1580 podIP string 1581 } 1582 1583 tests := []struct { 1584 name string 1585 batchPeriod time.Duration 1586 podsCount int 1587 updates []podUpdate 1588 finalDelay time.Duration 1589 wantRequestCount int 1590 }{ 1591 { 1592 name: "three updates with no batching", 1593 batchPeriod: 0 * time.Second, 1594 podsCount: 10, 1595 updates: []podUpdate{ 1596 { 1597 // endpoints.Run needs ~100 ms to start processing updates. 1598 delay: 200 * time.Millisecond, 1599 podName: "pod0", 1600 podIP: "10.0.0.0", 1601 }, 1602 { 1603 delay: 100 * time.Millisecond, 1604 podName: "pod1", 1605 podIP: "10.0.0.1", 1606 }, 1607 { 1608 delay: 100 * time.Millisecond, 1609 podName: "pod2", 1610 podIP: "10.0.0.2", 1611 }, 1612 }, 1613 finalDelay: 3 * time.Second, 1614 wantRequestCount: 3, 1615 }, 1616 { 1617 name: "three updates in one batch", 1618 batchPeriod: 1 * time.Second, 1619 podsCount: 10, 1620 updates: []podUpdate{ 1621 { 1622 // endpoints.Run needs ~100 ms to start processing updates. 1623 delay: 200 * time.Millisecond, 1624 podName: "pod0", 1625 podIP: "10.0.0.0", 1626 }, 1627 { 1628 delay: 100 * time.Millisecond, 1629 podName: "pod1", 1630 podIP: "10.0.0.1", 1631 }, 1632 { 1633 delay: 100 * time.Millisecond, 1634 podName: "pod2", 1635 podIP: "10.0.0.2", 1636 }, 1637 }, 1638 finalDelay: 3 * time.Second, 1639 wantRequestCount: 1, 1640 }, 1641 { 1642 name: "three updates in two batches", 1643 batchPeriod: 1 * time.Second, 1644 podsCount: 10, 1645 updates: []podUpdate{ 1646 { 1647 // endpoints.Run needs ~100 ms to start processing updates. 1648 delay: 200 * time.Millisecond, 1649 podName: "pod0", 1650 podIP: "10.0.0.0", 1651 }, 1652 { 1653 delay: 100 * time.Millisecond, 1654 podName: "pod1", 1655 podIP: "10.0.0.1", 1656 }, 1657 { 1658 delay: 1 * time.Second, 1659 podName: "pod2", 1660 podIP: "10.0.0.2", 1661 }, 1662 }, 1663 finalDelay: 3 * time.Second, 1664 wantRequestCount: 2, 1665 }, 1666 } 1667 1668 for _, tc := range tests { 1669 t.Run(tc.name, func(t *testing.T) { 1670 ns := "other" 1671 resourceVersion := 1 1672 testServer, endpointsHandler := makeTestServer(t, ns) 1673 defer testServer.Close() 1674 endpoints := newController(testServer.URL, tc.batchPeriod) 1675 stopCh := make(chan struct{}) 1676 defer close(stopCh) 1677 endpoints.podsSynced = alwaysReady 1678 endpoints.servicesSynced = alwaysReady 1679 endpoints.endpointsSynced = alwaysReady 1680 endpoints.workerLoopPeriod = 10 * time.Millisecond 1681 1682 go endpoints.Run(context.TODO(), 1) 1683 1684 addPods(endpoints.podStore, ns, tc.podsCount, 1, 0, ipv4only) 1685 1686 endpoints.serviceStore.Add(&v1.Service{ 1687 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1688 Spec: v1.ServiceSpec{ 1689 Selector: map[string]string{"foo": "bar"}, 1690 Ports: []v1.ServicePort{{Port: 80}}, 1691 }, 1692 }) 1693 1694 for _, update := range tc.updates { 1695 time.Sleep(update.delay) 1696 1697 old, exists, err := endpoints.podStore.GetByKey(fmt.Sprintf("%s/%s", ns, update.podName)) 1698 if err != nil { 1699 t.Fatalf("Error while retrieving old value of %q: %v", update.podName, err) 1700 } 1701 if !exists { 1702 t.Fatalf("Pod %q doesn't exist", update.podName) 1703 } 1704 oldPod := old.(*v1.Pod) 1705 newPod := oldPod.DeepCopy() 1706 newPod.Status.PodIP = update.podIP 1707 newPod.Status.PodIPs[0].IP = update.podIP 1708 newPod.ResourceVersion = strconv.Itoa(resourceVersion) 1709 resourceVersion++ 1710 1711 endpoints.podStore.Update(newPod) 1712 endpoints.updatePod(oldPod, newPod) 1713 } 1714 1715 time.Sleep(tc.finalDelay) 1716 endpointsHandler.ValidateRequestCount(t, tc.wantRequestCount) 1717 }) 1718 } 1719 } 1720 1721 // TestPodAddsBatching verifies that endpoint updates caused by pod addition are batched together. 1722 // This test uses real time.Sleep, as there is no easy way to mock time in endpoints controller now. 1723 // TODO(mborsz): Migrate this test to mock clock when possible. 1724 func TestPodAddsBatching(t *testing.T) { 1725 type podAdd struct { 1726 delay time.Duration 1727 } 1728 1729 tests := []struct { 1730 name string 1731 batchPeriod time.Duration 1732 adds []podAdd 1733 finalDelay time.Duration 1734 wantRequestCount int 1735 }{ 1736 { 1737 name: "three adds with no batching", 1738 batchPeriod: 0 * time.Second, 1739 adds: []podAdd{ 1740 { 1741 // endpoints.Run needs ~100 ms to start processing updates. 1742 delay: 200 * time.Millisecond, 1743 }, 1744 { 1745 delay: 100 * time.Millisecond, 1746 }, 1747 { 1748 delay: 100 * time.Millisecond, 1749 }, 1750 }, 1751 finalDelay: 3 * time.Second, 1752 wantRequestCount: 3, 1753 }, 1754 { 1755 name: "three adds in one batch", 1756 batchPeriod: 1 * time.Second, 1757 adds: []podAdd{ 1758 { 1759 // endpoints.Run needs ~100 ms to start processing updates. 1760 delay: 200 * time.Millisecond, 1761 }, 1762 { 1763 delay: 100 * time.Millisecond, 1764 }, 1765 { 1766 delay: 100 * time.Millisecond, 1767 }, 1768 }, 1769 finalDelay: 3 * time.Second, 1770 wantRequestCount: 1, 1771 }, 1772 { 1773 name: "three adds in two batches", 1774 batchPeriod: 1 * time.Second, 1775 adds: []podAdd{ 1776 { 1777 // endpoints.Run needs ~100 ms to start processing updates. 1778 delay: 200 * time.Millisecond, 1779 }, 1780 { 1781 delay: 100 * time.Millisecond, 1782 }, 1783 { 1784 delay: 1 * time.Second, 1785 }, 1786 }, 1787 finalDelay: 3 * time.Second, 1788 wantRequestCount: 2, 1789 }, 1790 } 1791 1792 for _, tc := range tests { 1793 t.Run(tc.name, func(t *testing.T) { 1794 ns := "other" 1795 testServer, endpointsHandler := makeTestServer(t, ns) 1796 defer testServer.Close() 1797 endpoints := newController(testServer.URL, tc.batchPeriod) 1798 stopCh := make(chan struct{}) 1799 defer close(stopCh) 1800 endpoints.podsSynced = alwaysReady 1801 endpoints.servicesSynced = alwaysReady 1802 endpoints.endpointsSynced = alwaysReady 1803 endpoints.workerLoopPeriod = 10 * time.Millisecond 1804 1805 go endpoints.Run(context.TODO(), 1) 1806 1807 endpoints.serviceStore.Add(&v1.Service{ 1808 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1809 Spec: v1.ServiceSpec{ 1810 Selector: map[string]string{"foo": "bar"}, 1811 Ports: []v1.ServicePort{{Port: 80}}, 1812 }, 1813 }) 1814 1815 for i, add := range tc.adds { 1816 time.Sleep(add.delay) 1817 1818 p := testPod(ns, i, 1, true, ipv4only) 1819 endpoints.podStore.Add(p) 1820 endpoints.addPod(p) 1821 } 1822 1823 time.Sleep(tc.finalDelay) 1824 endpointsHandler.ValidateRequestCount(t, tc.wantRequestCount) 1825 }) 1826 } 1827 } 1828 1829 // TestPodDeleteBatching verifies that endpoint updates caused by pod deletion are batched together. 1830 // This test uses real time.Sleep, as there is no easy way to mock time in endpoints controller now. 1831 // TODO(mborsz): Migrate this test to mock clock when possible. 1832 func TestPodDeleteBatching(t *testing.T) { 1833 type podDelete struct { 1834 delay time.Duration 1835 podName string 1836 } 1837 1838 tests := []struct { 1839 name string 1840 batchPeriod time.Duration 1841 podsCount int 1842 deletes []podDelete 1843 finalDelay time.Duration 1844 wantRequestCount int 1845 }{ 1846 { 1847 name: "three deletes with no batching", 1848 batchPeriod: 0 * time.Second, 1849 podsCount: 10, 1850 deletes: []podDelete{ 1851 { 1852 // endpoints.Run needs ~100 ms to start processing updates. 1853 delay: 200 * time.Millisecond, 1854 podName: "pod0", 1855 }, 1856 { 1857 delay: 100 * time.Millisecond, 1858 podName: "pod1", 1859 }, 1860 { 1861 delay: 100 * time.Millisecond, 1862 podName: "pod2", 1863 }, 1864 }, 1865 finalDelay: 3 * time.Second, 1866 wantRequestCount: 3, 1867 }, 1868 { 1869 name: "three deletes in one batch", 1870 batchPeriod: 1 * time.Second, 1871 podsCount: 10, 1872 deletes: []podDelete{ 1873 { 1874 // endpoints.Run needs ~100 ms to start processing updates. 1875 delay: 200 * time.Millisecond, 1876 podName: "pod0", 1877 }, 1878 { 1879 delay: 100 * time.Millisecond, 1880 podName: "pod1", 1881 }, 1882 { 1883 delay: 100 * time.Millisecond, 1884 podName: "pod2", 1885 }, 1886 }, 1887 finalDelay: 3 * time.Second, 1888 wantRequestCount: 1, 1889 }, 1890 { 1891 name: "three deletes in two batches", 1892 batchPeriod: 1 * time.Second, 1893 podsCount: 10, 1894 deletes: []podDelete{ 1895 { 1896 // endpoints.Run needs ~100 ms to start processing updates. 1897 delay: 200 * time.Millisecond, 1898 podName: "pod0", 1899 }, 1900 { 1901 delay: 100 * time.Millisecond, 1902 podName: "pod1", 1903 }, 1904 { 1905 delay: 1 * time.Second, 1906 podName: "pod2", 1907 }, 1908 }, 1909 finalDelay: 3 * time.Second, 1910 wantRequestCount: 2, 1911 }, 1912 } 1913 1914 for _, tc := range tests { 1915 t.Run(tc.name, func(t *testing.T) { 1916 ns := "other" 1917 testServer, endpointsHandler := makeTestServer(t, ns) 1918 defer testServer.Close() 1919 endpoints := newController(testServer.URL, tc.batchPeriod) 1920 stopCh := make(chan struct{}) 1921 defer close(stopCh) 1922 endpoints.podsSynced = alwaysReady 1923 endpoints.servicesSynced = alwaysReady 1924 endpoints.endpointsSynced = alwaysReady 1925 endpoints.workerLoopPeriod = 10 * time.Millisecond 1926 1927 go endpoints.Run(context.TODO(), 1) 1928 1929 addPods(endpoints.podStore, ns, tc.podsCount, 1, 0, ipv4only) 1930 1931 endpoints.serviceStore.Add(&v1.Service{ 1932 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 1933 Spec: v1.ServiceSpec{ 1934 Selector: map[string]string{"foo": "bar"}, 1935 Ports: []v1.ServicePort{{Port: 80}}, 1936 }, 1937 }) 1938 1939 for _, update := range tc.deletes { 1940 time.Sleep(update.delay) 1941 1942 old, exists, err := endpoints.podStore.GetByKey(fmt.Sprintf("%s/%s", ns, update.podName)) 1943 if err != nil { 1944 t.Fatalf("Error while retrieving old value of %q: %v", update.podName, err) 1945 } 1946 if !exists { 1947 t.Fatalf("Pod %q doesn't exist", update.podName) 1948 } 1949 endpoints.podStore.Delete(old) 1950 endpoints.deletePod(old) 1951 } 1952 1953 time.Sleep(tc.finalDelay) 1954 endpointsHandler.ValidateRequestCount(t, tc.wantRequestCount) 1955 }) 1956 } 1957 } 1958 1959 func TestSyncEndpointsServiceNotFound(t *testing.T) { 1960 ns := metav1.NamespaceDefault 1961 testServer, endpointsHandler := makeTestServer(t, ns) 1962 defer testServer.Close() 1963 endpoints := newController(testServer.URL, 0) 1964 endpoints.endpointsStore.Add(&v1.Endpoints{ 1965 ObjectMeta: metav1.ObjectMeta{ 1966 Name: "foo", 1967 Namespace: ns, 1968 ResourceVersion: "1", 1969 }, 1970 }) 1971 endpoints.syncService(context.TODO(), ns+"/foo") 1972 endpointsHandler.ValidateRequestCount(t, 1) 1973 endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "DELETE", nil) 1974 } 1975 1976 func TestSyncServiceOverCapacity(t *testing.T) { 1977 testCases := []struct { 1978 name string 1979 startingAnnotation *string 1980 numExisting int 1981 numDesired int 1982 numDesiredNotReady int 1983 numExpectedReady int 1984 numExpectedNotReady int 1985 expectedAnnotation bool 1986 }{{ 1987 name: "empty", 1988 startingAnnotation: nil, 1989 numExisting: 0, 1990 numDesired: 0, 1991 numExpectedReady: 0, 1992 numExpectedNotReady: 0, 1993 expectedAnnotation: false, 1994 }, { 1995 name: "annotation added past capacity, < than maxCapacity of Ready Addresses", 1996 startingAnnotation: nil, 1997 numExisting: maxCapacity - 1, 1998 numDesired: maxCapacity - 3, 1999 numDesiredNotReady: 4, 2000 numExpectedReady: maxCapacity - 3, 2001 numExpectedNotReady: 3, 2002 expectedAnnotation: true, 2003 }, { 2004 name: "annotation added past capacity, maxCapacity of Ready Addresses ", 2005 startingAnnotation: nil, 2006 numExisting: maxCapacity - 1, 2007 numDesired: maxCapacity, 2008 numDesiredNotReady: 10, 2009 numExpectedReady: maxCapacity, 2010 numExpectedNotReady: 0, 2011 expectedAnnotation: true, 2012 }, { 2013 name: "annotation removed below capacity", 2014 startingAnnotation: pointer.String("truncated"), 2015 numExisting: maxCapacity - 1, 2016 numDesired: maxCapacity - 1, 2017 numDesiredNotReady: 0, 2018 numExpectedReady: maxCapacity - 1, 2019 numExpectedNotReady: 0, 2020 expectedAnnotation: false, 2021 }, { 2022 name: "annotation was set to warning previously, annotation removed at capacity", 2023 startingAnnotation: pointer.String("warning"), 2024 numExisting: maxCapacity, 2025 numDesired: maxCapacity, 2026 numDesiredNotReady: 0, 2027 numExpectedReady: maxCapacity, 2028 numExpectedNotReady: 0, 2029 expectedAnnotation: false, 2030 }, { 2031 name: "annotation was set to warning previously but still over capacity", 2032 startingAnnotation: pointer.String("warning"), 2033 numExisting: maxCapacity + 1, 2034 numDesired: maxCapacity + 1, 2035 numDesiredNotReady: 0, 2036 numExpectedReady: maxCapacity, 2037 numExpectedNotReady: 0, 2038 expectedAnnotation: true, 2039 }, { 2040 name: "annotation removed at capacity", 2041 startingAnnotation: pointer.String("truncated"), 2042 numExisting: maxCapacity, 2043 numDesired: maxCapacity, 2044 numDesiredNotReady: 0, 2045 numExpectedReady: maxCapacity, 2046 numExpectedNotReady: 0, 2047 expectedAnnotation: false, 2048 }, { 2049 name: "no endpoints change, annotation value corrected", 2050 startingAnnotation: pointer.String("invalid"), 2051 numExisting: maxCapacity + 1, 2052 numDesired: maxCapacity + 1, 2053 numDesiredNotReady: 0, 2054 numExpectedReady: maxCapacity, 2055 numExpectedNotReady: 0, 2056 expectedAnnotation: true, 2057 }} 2058 2059 for _, tc := range testCases { 2060 t.Run(tc.name, func(t *testing.T) { 2061 ns := "test" 2062 client, c := newFakeController(0 * time.Second) 2063 2064 addPods(c.podStore, ns, tc.numDesired, 1, tc.numDesiredNotReady, ipv4only) 2065 pods := c.podStore.List() 2066 2067 svc := &v1.Service{ 2068 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 2069 Spec: v1.ServiceSpec{ 2070 Selector: map[string]string{"foo": "bar"}, 2071 Ports: []v1.ServicePort{{Port: 80}}, 2072 }, 2073 } 2074 c.serviceStore.Add(svc) 2075 2076 subset := v1.EndpointSubset{} 2077 for i := 0; i < tc.numExisting; i++ { 2078 pod := pods[i].(*v1.Pod) 2079 epa, _ := podToEndpointAddressForService(svc, pod) 2080 subset.Addresses = append(subset.Addresses, *epa) 2081 } 2082 endpoints := &v1.Endpoints{ 2083 ObjectMeta: metav1.ObjectMeta{ 2084 Name: svc.Name, 2085 Namespace: ns, 2086 ResourceVersion: "1", 2087 Annotations: map[string]string{}, 2088 }, 2089 Subsets: []v1.EndpointSubset{subset}, 2090 } 2091 if tc.startingAnnotation != nil { 2092 endpoints.Annotations[v1.EndpointsOverCapacity] = *tc.startingAnnotation 2093 } 2094 c.endpointsStore.Add(endpoints) 2095 client.CoreV1().Endpoints(ns).Create(context.TODO(), endpoints, metav1.CreateOptions{}) 2096 2097 c.syncService(context.TODO(), fmt.Sprintf("%s/%s", ns, svc.Name)) 2098 2099 actualEndpoints, err := client.CoreV1().Endpoints(ns).Get(context.TODO(), endpoints.Name, metav1.GetOptions{}) 2100 if err != nil { 2101 t.Fatalf("unexpected error getting endpoints: %v", err) 2102 } 2103 2104 actualAnnotation, ok := actualEndpoints.Annotations[v1.EndpointsOverCapacity] 2105 if tc.expectedAnnotation { 2106 if !ok { 2107 t.Errorf("Expected EndpointsOverCapacity annotation to be set") 2108 } else if actualAnnotation != "truncated" { 2109 t.Errorf("Expected EndpointsOverCapacity annotation to be 'truncated', got %s", actualAnnotation) 2110 } 2111 } else { 2112 if ok { 2113 t.Errorf("Expected EndpointsOverCapacity annotation not to be set, got %s", actualAnnotation) 2114 } 2115 } 2116 numActualReady := 0 2117 numActualNotReady := 0 2118 for _, subset := range actualEndpoints.Subsets { 2119 numActualReady += len(subset.Addresses) 2120 numActualNotReady += len(subset.NotReadyAddresses) 2121 } 2122 if numActualReady != tc.numExpectedReady { 2123 t.Errorf("Unexpected number of actual ready Endpoints: got %d endpoints, want %d endpoints", numActualReady, tc.numExpectedReady) 2124 } 2125 if numActualNotReady != tc.numExpectedNotReady { 2126 t.Errorf("Unexpected number of actual not ready Endpoints: got %d endpoints, want %d endpoints", numActualNotReady, tc.numExpectedNotReady) 2127 } 2128 }) 2129 } 2130 } 2131 2132 func TestTruncateEndpoints(t *testing.T) { 2133 testCases := []struct { 2134 desc string 2135 // subsetsReady, subsetsNotReady, expectedReady, expectedNotReady 2136 // must all be the same length 2137 subsetsReady []int 2138 subsetsNotReady []int 2139 expectedReady []int 2140 expectedNotReady []int 2141 }{{ 2142 desc: "empty", 2143 subsetsReady: []int{}, 2144 subsetsNotReady: []int{}, 2145 expectedReady: []int{}, 2146 expectedNotReady: []int{}, 2147 }, { 2148 desc: "total endpoints < max capacity", 2149 subsetsReady: []int{50, 100, 100, 100, 100}, 2150 subsetsNotReady: []int{50, 100, 100, 100, 100}, 2151 expectedReady: []int{50, 100, 100, 100, 100}, 2152 expectedNotReady: []int{50, 100, 100, 100, 100}, 2153 }, { 2154 desc: "total endpoints = max capacity", 2155 subsetsReady: []int{100, 100, 100, 100, 100}, 2156 subsetsNotReady: []int{100, 100, 100, 100, 100}, 2157 expectedReady: []int{100, 100, 100, 100, 100}, 2158 expectedNotReady: []int{100, 100, 100, 100, 100}, 2159 }, { 2160 desc: "total ready endpoints < max capacity, but total endpoints > max capacity", 2161 subsetsReady: []int{90, 110, 50, 10, 20}, 2162 subsetsNotReady: []int{101, 200, 200, 201, 298}, 2163 expectedReady: []int{90, 110, 50, 10, 20}, 2164 expectedNotReady: []int{73, 144, 144, 145, 214}, 2165 }, { 2166 desc: "total ready endpoints > max capacity", 2167 subsetsReady: []int{205, 400, 402, 400, 693}, 2168 subsetsNotReady: []int{100, 200, 200, 200, 300}, 2169 expectedReady: []int{98, 191, 192, 191, 328}, 2170 expectedNotReady: []int{0, 0, 0, 0, 0}, 2171 }} 2172 2173 for _, tc := range testCases { 2174 t.Run(tc.desc, func(t *testing.T) { 2175 var subsets []v1.EndpointSubset 2176 for subsetIndex, numReady := range tc.subsetsReady { 2177 subset := v1.EndpointSubset{} 2178 for i := 0; i < numReady; i++ { 2179 subset.Addresses = append(subset.Addresses, v1.EndpointAddress{}) 2180 } 2181 2182 numNotReady := tc.subsetsNotReady[subsetIndex] 2183 for i := 0; i < numNotReady; i++ { 2184 subset.NotReadyAddresses = append(subset.NotReadyAddresses, v1.EndpointAddress{}) 2185 } 2186 subsets = append(subsets, subset) 2187 } 2188 2189 endpoints := &v1.Endpoints{Subsets: subsets} 2190 truncateEndpoints(endpoints) 2191 2192 for i, subset := range endpoints.Subsets { 2193 if len(subset.Addresses) != tc.expectedReady[i] { 2194 t.Errorf("Unexpected number of actual ready Endpoints for subset %d: got %d endpoints, want %d endpoints", i, len(subset.Addresses), tc.expectedReady[i]) 2195 } 2196 if len(subset.NotReadyAddresses) != tc.expectedNotReady[i] { 2197 t.Errorf("Unexpected number of actual not ready Endpoints for subset %d: got %d endpoints, want %d endpoints", i, len(subset.NotReadyAddresses), tc.expectedNotReady[i]) 2198 } 2199 } 2200 }) 2201 } 2202 } 2203 2204 func TestEndpointPortFromServicePort(t *testing.T) { 2205 http := pointer.String("http") 2206 testCases := map[string]struct { 2207 serviceAppProtocol *string 2208 expectedEndpointsAppProtocol *string 2209 }{ 2210 "empty app protocol": { 2211 serviceAppProtocol: nil, 2212 expectedEndpointsAppProtocol: nil, 2213 }, 2214 "http app protocol": { 2215 serviceAppProtocol: http, 2216 expectedEndpointsAppProtocol: http, 2217 }, 2218 } 2219 2220 for name, tc := range testCases { 2221 t.Run(name, func(t *testing.T) { 2222 epp := endpointPortFromServicePort(&v1.ServicePort{Name: "test", AppProtocol: tc.serviceAppProtocol}, 80) 2223 2224 if epp.AppProtocol != tc.expectedEndpointsAppProtocol { 2225 t.Errorf("Expected Endpoints AppProtocol to be %s, got %s", stringVal(tc.expectedEndpointsAppProtocol), stringVal(epp.AppProtocol)) 2226 } 2227 }) 2228 } 2229 } 2230 2231 // TestMultipleServiceChanges tests that endpoints that are not created because of an out of sync endpoints cache are eventually recreated 2232 // A service will be created. After the endpoints exist, the service will be deleted and the endpoints will not be deleted from the cache immediately. 2233 // After the service is recreated, the endpoints will be deleted replicating an out of sync cache. Expect that eventually the endpoints will be recreated. 2234 func TestMultipleServiceChanges(t *testing.T) { 2235 ns := metav1.NamespaceDefault 2236 expectedSubsets := []v1.EndpointSubset{{ 2237 Addresses: []v1.EndpointAddress{ 2238 {IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, 2239 }, 2240 }} 2241 endpoint := &v1.Endpoints{ 2242 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns, ResourceVersion: "1"}, 2243 Subsets: expectedSubsets, 2244 } 2245 2246 controller := &endpointController{} 2247 blockDelete := make(chan struct{}) 2248 blockNextAction := make(chan struct{}) 2249 stopChan := make(chan struct{}) 2250 testServer := makeBlockingEndpointDeleteTestServer(t, controller, endpoint, blockDelete, blockNextAction, ns) 2251 defer testServer.Close() 2252 2253 *controller = *newController(testServer.URL, 0*time.Second) 2254 addPods(controller.podStore, ns, 1, 1, 0, ipv4only) 2255 2256 go func() { controller.Run(context.TODO(), 1) }() 2257 2258 svc := &v1.Service{ 2259 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, 2260 Spec: v1.ServiceSpec{ 2261 Selector: map[string]string{"foo": "bar"}, 2262 ClusterIP: "None", 2263 Ports: nil, 2264 }, 2265 } 2266 2267 controller.serviceStore.Add(svc) 2268 controller.onServiceUpdate(svc) 2269 // blockNextAction should eventually unblock once server gets endpoint request. 2270 waitForChanReceive(t, 1*time.Second, blockNextAction, "Service Add should have caused a request to be sent to the test server") 2271 2272 controller.serviceStore.Delete(svc) 2273 controller.onServiceDelete(svc) 2274 waitForChanReceive(t, 1*time.Second, blockNextAction, "Service Delete should have caused a request to be sent to the test server") 2275 2276 // If endpoints cache has not updated before service update is registered 2277 // Services add will not trigger a Create endpoint request. 2278 controller.serviceStore.Add(svc) 2279 controller.onServiceUpdate(svc) 2280 2281 // Ensure the work queue has been processed by looping for up to a second to prevent flakes. 2282 wait.PollImmediate(50*time.Millisecond, 1*time.Second, func() (bool, error) { 2283 return controller.queue.Len() == 0, nil 2284 }) 2285 2286 // Cause test server to delete endpoints 2287 close(blockDelete) 2288 waitForChanReceive(t, 1*time.Second, blockNextAction, "Endpoint should have been recreated") 2289 2290 close(blockNextAction) 2291 close(stopChan) 2292 } 2293 2294 func TestSyncServiceAddresses(t *testing.T) { 2295 makeService := func(tolerateUnready bool) *v1.Service { 2296 return &v1.Service{ 2297 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "ns"}, 2298 Spec: v1.ServiceSpec{ 2299 Selector: map[string]string{"foo": "bar"}, 2300 PublishNotReadyAddresses: tolerateUnready, 2301 Type: v1.ServiceTypeClusterIP, 2302 ClusterIP: "1.1.1.1", 2303 Ports: []v1.ServicePort{{Port: 80}}, 2304 }, 2305 } 2306 } 2307 2308 makePod := func(phase v1.PodPhase, isReady bool, terminating bool) *v1.Pod { 2309 statusCondition := v1.ConditionFalse 2310 if isReady { 2311 statusCondition = v1.ConditionTrue 2312 } 2313 2314 now := metav1.Now() 2315 deletionTimestamp := &now 2316 if !terminating { 2317 deletionTimestamp = nil 2318 } 2319 return &v1.Pod{ 2320 ObjectMeta: metav1.ObjectMeta{ 2321 Namespace: "ns", 2322 Name: "fakepod", 2323 DeletionTimestamp: deletionTimestamp, 2324 Labels: map[string]string{"foo": "bar"}, 2325 }, 2326 Spec: v1.PodSpec{ 2327 Containers: []v1.Container{{Ports: []v1.ContainerPort{ 2328 {Name: "port1", ContainerPort: int32(8080)}, 2329 }}}, 2330 }, 2331 Status: v1.PodStatus{ 2332 Phase: phase, 2333 Conditions: []v1.PodCondition{ 2334 { 2335 Type: v1.PodReady, 2336 Status: statusCondition, 2337 }, 2338 }, 2339 PodIP: "10.1.1.1", 2340 PodIPs: []v1.PodIP{ 2341 {IP: "10.1.1.1"}, 2342 }, 2343 }, 2344 } 2345 } 2346 2347 testCases := []struct { 2348 name string 2349 pod *v1.Pod 2350 service *v1.Service 2351 expectedReady int 2352 expectedUnready int 2353 }{ 2354 { 2355 name: "pod running phase", 2356 pod: makePod(v1.PodRunning, true, false), 2357 service: makeService(false), 2358 expectedReady: 1, 2359 expectedUnready: 0, 2360 }, 2361 { 2362 name: "pod running phase being deleted", 2363 pod: makePod(v1.PodRunning, true, true), 2364 service: makeService(false), 2365 expectedReady: 0, 2366 expectedUnready: 0, 2367 }, 2368 { 2369 name: "pod unknown phase container ready", 2370 pod: makePod(v1.PodUnknown, true, false), 2371 service: makeService(false), 2372 expectedReady: 1, 2373 expectedUnready: 0, 2374 }, 2375 { 2376 name: "pod unknown phase container ready being deleted", 2377 pod: makePod(v1.PodUnknown, true, true), 2378 service: makeService(false), 2379 expectedReady: 0, 2380 expectedUnready: 0, 2381 }, 2382 { 2383 name: "pod pending phase container ready", 2384 pod: makePod(v1.PodPending, true, false), 2385 service: makeService(false), 2386 expectedReady: 1, 2387 expectedUnready: 0, 2388 }, 2389 { 2390 name: "pod pending phase container ready being deleted", 2391 pod: makePod(v1.PodPending, true, true), 2392 service: makeService(false), 2393 expectedReady: 0, 2394 expectedUnready: 0, 2395 }, 2396 { 2397 name: "pod unknown phase container not ready", 2398 pod: makePod(v1.PodUnknown, false, false), 2399 service: makeService(false), 2400 expectedReady: 0, 2401 expectedUnready: 1, 2402 }, 2403 { 2404 name: "pod pending phase container not ready", 2405 pod: makePod(v1.PodPending, false, false), 2406 service: makeService(false), 2407 expectedReady: 0, 2408 expectedUnready: 1, 2409 }, 2410 { 2411 name: "pod failed phase", 2412 pod: makePod(v1.PodFailed, false, false), 2413 service: makeService(false), 2414 expectedReady: 0, 2415 expectedUnready: 0, 2416 }, 2417 { 2418 name: "pod succeeded phase", 2419 pod: makePod(v1.PodSucceeded, false, false), 2420 service: makeService(false), 2421 expectedReady: 0, 2422 expectedUnready: 0, 2423 }, 2424 { 2425 name: "pod running phase and tolerate unready", 2426 pod: makePod(v1.PodRunning, false, false), 2427 service: makeService(true), 2428 expectedReady: 1, 2429 expectedUnready: 0, 2430 }, 2431 { 2432 name: "pod running phase and tolerate unready being deleted", 2433 pod: makePod(v1.PodRunning, false, true), 2434 service: makeService(true), 2435 expectedReady: 1, 2436 expectedUnready: 0, 2437 }, 2438 { 2439 name: "pod unknown phase and tolerate unready", 2440 pod: makePod(v1.PodUnknown, false, false), 2441 service: makeService(true), 2442 expectedReady: 1, 2443 expectedUnready: 0, 2444 }, 2445 { 2446 name: "pod unknown phase and tolerate unready being deleted", 2447 pod: makePod(v1.PodUnknown, false, true), 2448 service: makeService(true), 2449 expectedReady: 1, 2450 expectedUnready: 0, 2451 }, 2452 { 2453 name: "pod pending phase and tolerate unready", 2454 pod: makePod(v1.PodPending, false, false), 2455 service: makeService(true), 2456 expectedReady: 1, 2457 expectedUnready: 0, 2458 }, 2459 { 2460 name: "pod pending phase and tolerate unready being deleted", 2461 pod: makePod(v1.PodPending, false, true), 2462 service: makeService(true), 2463 expectedReady: 1, 2464 expectedUnready: 0, 2465 }, 2466 { 2467 name: "pod failed phase and tolerate unready", 2468 pod: makePod(v1.PodFailed, false, false), 2469 service: makeService(true), 2470 expectedReady: 0, 2471 expectedUnready: 0, 2472 }, 2473 { 2474 name: "pod succeeded phase and tolerate unready endpoints", 2475 pod: makePod(v1.PodSucceeded, false, false), 2476 service: makeService(true), 2477 expectedReady: 0, 2478 expectedUnready: 0, 2479 }, 2480 } 2481 2482 for _, tc := range testCases { 2483 t.Run(tc.name, func(t *testing.T) { 2484 ns := tc.service.Namespace 2485 client, c := newFakeController(0 * time.Second) 2486 2487 err := c.podStore.Add(tc.pod) 2488 if err != nil { 2489 t.Errorf("Unexpected error adding pod %v", err) 2490 } 2491 err = c.serviceStore.Add(tc.service) 2492 if err != nil { 2493 t.Errorf("Unexpected error adding service %v", err) 2494 } 2495 err = c.syncService(context.TODO(), fmt.Sprintf("%s/%s", ns, tc.service.Name)) 2496 if err != nil { 2497 t.Errorf("Unexpected error syncing service %v", err) 2498 } 2499 2500 endpoints, err := client.CoreV1().Endpoints(ns).Get(context.TODO(), tc.service.Name, metav1.GetOptions{}) 2501 if err != nil { 2502 t.Errorf("Unexpected error %v", err) 2503 } 2504 2505 readyEndpoints := 0 2506 unreadyEndpoints := 0 2507 for _, subset := range endpoints.Subsets { 2508 readyEndpoints += len(subset.Addresses) 2509 unreadyEndpoints += len(subset.NotReadyAddresses) 2510 } 2511 2512 if tc.expectedReady != readyEndpoints { 2513 t.Errorf("Expected %d ready endpoints, got %d", tc.expectedReady, readyEndpoints) 2514 } 2515 2516 if tc.expectedUnready != unreadyEndpoints { 2517 t.Errorf("Expected %d ready endpoints, got %d", tc.expectedUnready, unreadyEndpoints) 2518 } 2519 }) 2520 } 2521 } 2522 2523 func TestEndpointsDeletionEvents(t *testing.T) { 2524 ns := metav1.NamespaceDefault 2525 testServer, _ := makeTestServer(t, ns) 2526 defer testServer.Close() 2527 controller := newController(testServer.URL, 0) 2528 store := controller.endpointsStore 2529 ep1 := &v1.Endpoints{ 2530 ObjectMeta: metav1.ObjectMeta{ 2531 Name: "foo", 2532 Namespace: ns, 2533 ResourceVersion: "rv1", 2534 }, 2535 } 2536 2537 // Test Unexpected and Expected Deletes 2538 store.Delete(ep1) 2539 controller.onEndpointsDelete(ep1) 2540 2541 if controller.queue.Len() != 1 { 2542 t.Errorf("Expected one service to be in the queue, found %d", controller.queue.Len()) 2543 } 2544 } 2545 2546 func stringVal(str *string) string { 2547 if str == nil { 2548 return "nil" 2549 } 2550 return *str 2551 } 2552 2553 // waitForChanReceive blocks up to the timeout waiting for the receivingChan to receive 2554 func waitForChanReceive(t *testing.T, timeout time.Duration, receivingChan chan struct{}, errorMsg string) { 2555 timer := time.NewTimer(timeout) 2556 select { 2557 case <-timer.C: 2558 t.Errorf(errorMsg) 2559 case <-receivingChan: 2560 } 2561 } 2562 2563 func TestEndpointSubsetsEqualIgnoreResourceVersion(t *testing.T) { 2564 copyAndMutateEndpointSubset := func(orig *v1.EndpointSubset, mutator func(*v1.EndpointSubset)) *v1.EndpointSubset { 2565 newSubSet := orig.DeepCopy() 2566 mutator(newSubSet) 2567 return newSubSet 2568 } 2569 es1 := &v1.EndpointSubset{ 2570 Addresses: []v1.EndpointAddress{ 2571 { 2572 IP: "1.1.1.1", 2573 TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod1-1", Namespace: "ns", ResourceVersion: "1"}, 2574 }, 2575 }, 2576 NotReadyAddresses: []v1.EndpointAddress{ 2577 { 2578 IP: "1.1.1.2", 2579 TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod1-2", Namespace: "ns2", ResourceVersion: "2"}, 2580 }, 2581 }, 2582 Ports: []v1.EndpointPort{{Port: 8081, Protocol: "TCP"}}, 2583 } 2584 es2 := &v1.EndpointSubset{ 2585 Addresses: []v1.EndpointAddress{ 2586 { 2587 IP: "2.2.2.1", 2588 TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod2-1", Namespace: "ns", ResourceVersion: "3"}, 2589 }, 2590 }, 2591 NotReadyAddresses: []v1.EndpointAddress{ 2592 { 2593 IP: "2.2.2.2", 2594 TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod2-2", Namespace: "ns2", ResourceVersion: "4"}, 2595 }, 2596 }, 2597 Ports: []v1.EndpointPort{{Port: 8082, Protocol: "TCP"}}, 2598 } 2599 tests := []struct { 2600 name string 2601 subsets1 []v1.EndpointSubset 2602 subsets2 []v1.EndpointSubset 2603 expected bool 2604 }{ 2605 { 2606 name: "Subsets removed", 2607 subsets1: []v1.EndpointSubset{*es1, *es2}, 2608 subsets2: []v1.EndpointSubset{*es1}, 2609 expected: false, 2610 }, 2611 { 2612 name: "Ready Pod IP changed", 2613 subsets1: []v1.EndpointSubset{*es1, *es2}, 2614 subsets2: []v1.EndpointSubset{*copyAndMutateEndpointSubset(es1, func(es *v1.EndpointSubset) { 2615 es.Addresses[0].IP = "1.1.1.10" 2616 }), *es2}, 2617 expected: false, 2618 }, 2619 { 2620 name: "NotReady Pod IP changed", 2621 subsets1: []v1.EndpointSubset{*es1, *es2}, 2622 subsets2: []v1.EndpointSubset{*es1, *copyAndMutateEndpointSubset(es2, func(es *v1.EndpointSubset) { 2623 es.NotReadyAddresses[0].IP = "2.2.2.10" 2624 })}, 2625 expected: false, 2626 }, 2627 { 2628 name: "Pod ResourceVersion changed", 2629 subsets1: []v1.EndpointSubset{*es1, *es2}, 2630 subsets2: []v1.EndpointSubset{*es1, *copyAndMutateEndpointSubset(es2, func(es *v1.EndpointSubset) { 2631 es.Addresses[0].TargetRef.ResourceVersion = "100" 2632 })}, 2633 expected: true, 2634 }, 2635 { 2636 name: "Pod ResourceVersion removed", 2637 subsets1: []v1.EndpointSubset{*es1, *es2}, 2638 subsets2: []v1.EndpointSubset{*es1, *copyAndMutateEndpointSubset(es2, func(es *v1.EndpointSubset) { 2639 es.Addresses[0].TargetRef.ResourceVersion = "" 2640 })}, 2641 expected: true, 2642 }, 2643 { 2644 name: "Ports changed", 2645 subsets1: []v1.EndpointSubset{*es1, *es2}, 2646 subsets2: []v1.EndpointSubset{*es1, *copyAndMutateEndpointSubset(es1, func(es *v1.EndpointSubset) { 2647 es.Ports[0].Port = 8082 2648 })}, 2649 expected: false, 2650 }, 2651 } 2652 for _, tt := range tests { 2653 t.Run(tt.name, func(t *testing.T) { 2654 if got := endpointSubsetsEqualIgnoreResourceVersion(tt.subsets1, tt.subsets2); got != tt.expected { 2655 t.Errorf("semanticIgnoreResourceVersion.DeepEqual() = %v, expected %v", got, tt.expected) 2656 } 2657 }) 2658 } 2659 }