sigs.k8s.io/external-dns@v0.14.1/source/service_test.go (about) 1 /* 2 Copyright 2017 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 source 18 19 import ( 20 "context" 21 "net" 22 "sort" 23 "strings" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 "github.com/stretchr/testify/suite" 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/labels" 32 "k8s.io/client-go/kubernetes/fake" 33 34 "sigs.k8s.io/external-dns/endpoint" 35 ) 36 37 type ServiceSuite struct { 38 suite.Suite 39 sc Source 40 fooWithTargets *v1.Service 41 } 42 43 func (suite *ServiceSuite) SetupTest() { 44 fakeClient := fake.NewSimpleClientset() 45 46 suite.fooWithTargets = &v1.Service{ 47 Spec: v1.ServiceSpec{ 48 Type: v1.ServiceTypeLoadBalancer, 49 }, 50 ObjectMeta: metav1.ObjectMeta{ 51 Namespace: "default", 52 Name: "foo-with-targets", 53 Annotations: map[string]string{}, 54 }, 55 Status: v1.ServiceStatus{ 56 LoadBalancer: v1.LoadBalancerStatus{ 57 Ingress: []v1.LoadBalancerIngress{ 58 {IP: "8.8.8.8"}, 59 {Hostname: "foo"}, 60 }, 61 }, 62 }, 63 } 64 _, err := fakeClient.CoreV1().Services(suite.fooWithTargets.Namespace).Create(context.Background(), suite.fooWithTargets, metav1.CreateOptions{}) 65 suite.NoError(err, "should successfully create service") 66 67 suite.sc, err = NewServiceSource( 68 context.TODO(), 69 fakeClient, 70 "", 71 "", 72 "{{.Name}}", 73 false, 74 "", 75 false, 76 false, 77 false, 78 []string{}, 79 false, 80 labels.Everything(), 81 false, 82 ) 83 suite.NoError(err, "should initialize service source") 84 } 85 86 func (suite *ServiceSuite) TestResourceLabelIsSet() { 87 endpoints, _ := suite.sc.Endpoints(context.Background()) 88 for _, ep := range endpoints { 89 suite.Equal("service/default/foo-with-targets", ep.Labels[endpoint.ResourceLabelKey], "should set correct resource label") 90 } 91 } 92 93 func TestServiceSource(t *testing.T) { 94 t.Parallel() 95 96 suite.Run(t, new(ServiceSuite)) 97 t.Run("Interface", testServiceSourceImplementsSource) 98 t.Run("NewServiceSource", testServiceSourceNewServiceSource) 99 t.Run("Endpoints", testServiceSourceEndpoints) 100 t.Run("MultipleServices", testMultipleServicesEndpoints) 101 } 102 103 // testServiceSourceImplementsSource tests that serviceSource is a valid Source. 104 func testServiceSourceImplementsSource(t *testing.T) { 105 assert.Implements(t, (*Source)(nil), new(serviceSource)) 106 } 107 108 // testServiceSourceNewServiceSource tests that NewServiceSource doesn't return an error. 109 func testServiceSourceNewServiceSource(t *testing.T) { 110 t.Parallel() 111 112 for _, ti := range []struct { 113 title string 114 annotationFilter string 115 fqdnTemplate string 116 serviceTypesFilter []string 117 expectError bool 118 }{ 119 { 120 title: "invalid template", 121 expectError: true, 122 fqdnTemplate: "{{.Name", 123 }, 124 { 125 title: "valid empty template", 126 expectError: false, 127 }, 128 { 129 title: "valid template", 130 expectError: false, 131 fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com", 132 }, 133 { 134 title: "non-empty annotation filter label", 135 expectError: false, 136 annotationFilter: "kubernetes.io/ingress.class=nginx", 137 }, 138 { 139 title: "non-empty service types filter", 140 expectError: false, 141 serviceTypesFilter: []string{string(v1.ServiceTypeClusterIP)}, 142 }, 143 } { 144 ti := ti 145 t.Run(ti.title, func(t *testing.T) { 146 t.Parallel() 147 148 _, err := NewServiceSource( 149 context.TODO(), 150 fake.NewSimpleClientset(), 151 "", 152 ti.annotationFilter, 153 ti.fqdnTemplate, 154 false, 155 "", 156 false, 157 false, 158 false, 159 ti.serviceTypesFilter, 160 false, 161 labels.Everything(), 162 false, 163 ) 164 165 if ti.expectError { 166 assert.Error(t, err) 167 } else { 168 assert.NoError(t, err) 169 } 170 }) 171 } 172 } 173 174 // testServiceSourceEndpoints tests that various services generate the correct endpoints. 175 func testServiceSourceEndpoints(t *testing.T) { 176 t.Parallel() 177 178 for _, tc := range []struct { 179 title string 180 targetNamespace string 181 annotationFilter string 182 svcNamespace string 183 svcName string 184 svcType v1.ServiceType 185 compatibility string 186 fqdnTemplate string 187 combineFQDNAndAnnotation bool 188 ignoreHostnameAnnotation bool 189 labels map[string]string 190 annotations map[string]string 191 clusterIP string 192 externalIPs []string 193 lbs []string 194 serviceTypesFilter []string 195 expected []*endpoint.Endpoint 196 expectError bool 197 serviceLabelSelector string 198 resolveLoadBalancerHostname bool 199 }{ 200 { 201 title: "no annotated services return no endpoints", 202 svcNamespace: "testing", 203 svcName: "foo", 204 svcType: v1.ServiceTypeLoadBalancer, 205 labels: map[string]string{}, 206 annotations: map[string]string{}, 207 externalIPs: []string{}, 208 lbs: []string{"1.2.3.4"}, 209 serviceTypesFilter: []string{}, 210 expected: []*endpoint.Endpoint{}, 211 }, 212 { 213 title: "no annotated services return no endpoints when ignoring annotations", 214 svcNamespace: "testing", 215 svcName: "foo", 216 svcType: v1.ServiceTypeLoadBalancer, 217 ignoreHostnameAnnotation: true, 218 labels: map[string]string{}, 219 annotations: map[string]string{}, 220 externalIPs: []string{}, 221 lbs: []string{"1.2.3.4"}, 222 serviceTypesFilter: []string{}, 223 expected: []*endpoint.Endpoint{}, 224 }, 225 { 226 title: "annotated services return an endpoint with target IP", 227 svcNamespace: "testing", 228 svcName: "foo", 229 svcType: v1.ServiceTypeLoadBalancer, 230 labels: map[string]string{}, 231 annotations: map[string]string{ 232 hostnameAnnotationKey: "foo.example.org.", 233 }, 234 externalIPs: []string{}, 235 lbs: []string{"1.2.3.4"}, 236 serviceTypesFilter: []string{}, 237 expected: []*endpoint.Endpoint{ 238 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 239 }, 240 }, 241 { 242 title: "hostname annotation on services is ignored", 243 svcNamespace: "testing", 244 svcName: "foo", 245 svcType: v1.ServiceTypeLoadBalancer, 246 ignoreHostnameAnnotation: true, 247 labels: map[string]string{}, 248 annotations: map[string]string{ 249 hostnameAnnotationKey: "foo.example.org.", 250 }, 251 externalIPs: []string{}, 252 lbs: []string{"1.2.3.4"}, 253 serviceTypesFilter: []string{}, 254 expected: []*endpoint.Endpoint{}, 255 }, 256 { 257 title: "annotated ClusterIp aren't processed without explicit authorization", 258 svcNamespace: "testing", 259 svcName: "foo", 260 svcType: v1.ServiceTypeClusterIP, 261 labels: map[string]string{}, 262 annotations: map[string]string{ 263 hostnameAnnotationKey: "foo.example.org.", 264 }, 265 clusterIP: "1.2.3.4", 266 externalIPs: []string{}, 267 lbs: []string{}, 268 serviceTypesFilter: []string{}, 269 expected: []*endpoint.Endpoint{}, 270 }, 271 { 272 title: "FQDN template with multiple hostnames return an endpoint with target IP", 273 svcNamespace: "testing", 274 svcName: "foo", 275 svcType: v1.ServiceTypeLoadBalancer, 276 fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", 277 labels: map[string]string{}, 278 annotations: map[string]string{}, 279 externalIPs: []string{}, 280 lbs: []string{"1.2.3.4"}, 281 serviceTypesFilter: []string{}, 282 expected: []*endpoint.Endpoint{ 283 {DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 284 {DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 285 }, 286 }, 287 { 288 title: "FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations", 289 svcNamespace: "testing", 290 svcName: "foo", 291 svcType: v1.ServiceTypeLoadBalancer, 292 fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", 293 ignoreHostnameAnnotation: true, 294 labels: map[string]string{}, 295 annotations: map[string]string{}, 296 externalIPs: []string{}, 297 lbs: []string{"1.2.3.4"}, 298 serviceTypesFilter: []string{}, 299 expected: []*endpoint.Endpoint{ 300 {DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 301 {DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 302 }, 303 }, 304 { 305 title: "FQDN template and annotation both with multiple hostnames return an endpoint with target IP", 306 svcNamespace: "testing", 307 svcName: "foo", 308 svcType: v1.ServiceTypeLoadBalancer, 309 fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", 310 combineFQDNAndAnnotation: true, 311 labels: map[string]string{}, 312 annotations: map[string]string{ 313 hostnameAnnotationKey: "foo.example.org., bar.example.org.", 314 }, 315 externalIPs: []string{}, 316 lbs: []string{"1.2.3.4"}, 317 serviceTypesFilter: []string{}, 318 expected: []*endpoint.Endpoint{ 319 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 320 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 321 {DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 322 {DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 323 }, 324 }, 325 { 326 title: "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints", 327 svcNamespace: "testing", 328 svcName: "foo", 329 svcType: v1.ServiceTypeLoadBalancer, 330 fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", 331 combineFQDNAndAnnotation: true, 332 ignoreHostnameAnnotation: true, 333 labels: map[string]string{}, 334 annotations: map[string]string{ 335 hostnameAnnotationKey: "foo.example.org., bar.example.org.", 336 }, 337 externalIPs: []string{}, 338 lbs: []string{"1.2.3.4"}, 339 serviceTypesFilter: []string{}, 340 expected: []*endpoint.Endpoint{ 341 {DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 342 {DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 343 }, 344 }, 345 { 346 title: "annotated services with multiple hostnames return an endpoint with target IP", 347 svcNamespace: "testing", 348 svcName: "foo", 349 svcType: v1.ServiceTypeLoadBalancer, 350 labels: map[string]string{}, 351 annotations: map[string]string{ 352 hostnameAnnotationKey: "foo.example.org., bar.example.org.", 353 }, 354 externalIPs: []string{}, 355 lbs: []string{"1.2.3.4"}, 356 serviceTypesFilter: []string{}, 357 expected: []*endpoint.Endpoint{ 358 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 359 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 360 }, 361 }, 362 { 363 title: "annotated services with multiple hostnames and without trailing period return an endpoint with target IP", 364 svcNamespace: "testing", 365 svcName: "foo", 366 svcType: v1.ServiceTypeLoadBalancer, 367 labels: map[string]string{}, 368 annotations: map[string]string{ 369 hostnameAnnotationKey: "foo.example.org, bar.example.org", 370 }, 371 externalIPs: []string{}, 372 lbs: []string{"1.2.3.4"}, 373 serviceTypesFilter: []string{}, 374 expected: []*endpoint.Endpoint{ 375 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 376 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 377 }, 378 }, 379 { 380 title: "annotated services return an endpoint with target hostname", 381 svcNamespace: "testing", 382 svcName: "foo", 383 svcType: v1.ServiceTypeLoadBalancer, 384 labels: map[string]string{}, 385 annotations: map[string]string{ 386 hostnameAnnotationKey: "foo.example.org.", 387 }, 388 externalIPs: []string{}, 389 lbs: []string{"lb.example.com"}, // Kubernetes omits the trailing dot 390 serviceTypesFilter: []string{}, 391 expected: []*endpoint.Endpoint{ 392 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}}, 393 }, 394 }, 395 { 396 title: "annotated services return an endpoint with hostname then resolve hostname", 397 svcNamespace: "testing", 398 svcName: "foo", 399 svcType: v1.ServiceTypeLoadBalancer, 400 labels: map[string]string{}, 401 annotations: map[string]string{ 402 hostnameAnnotationKey: "foo.example.org.", 403 }, 404 externalIPs: []string{}, 405 lbs: []string{"example.com"}, // Use a resolvable hostname for testing. 406 serviceTypesFilter: []string{}, 407 resolveLoadBalancerHostname: true, 408 expected: []*endpoint.Endpoint{ 409 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"93.184.216.34"}}, 410 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2606:2800:220:1:248:1893:25c8:1946"}}, 411 }, 412 }, 413 { 414 title: "annotated services can omit trailing dot", 415 svcNamespace: "testing", 416 svcName: "foo", 417 svcType: v1.ServiceTypeLoadBalancer, 418 labels: map[string]string{}, 419 annotations: map[string]string{ 420 hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted 421 }, 422 externalIPs: []string{}, 423 lbs: []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot 424 serviceTypesFilter: []string{}, 425 expected: []*endpoint.Endpoint{ 426 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 427 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}}, 428 }, 429 }, 430 { 431 title: "our controller type is kops dns controller", 432 svcNamespace: "testing", 433 svcName: "foo", 434 svcType: v1.ServiceTypeLoadBalancer, 435 labels: map[string]string{}, 436 annotations: map[string]string{ 437 controllerAnnotationKey: controllerAnnotationValue, 438 hostnameAnnotationKey: "foo.example.org.", 439 }, 440 externalIPs: []string{}, 441 lbs: []string{"1.2.3.4"}, 442 serviceTypesFilter: []string{}, 443 expected: []*endpoint.Endpoint{ 444 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 445 }, 446 }, 447 { 448 title: "different controller types are ignored even (with template specified)", 449 svcNamespace: "testing", 450 svcName: "foo", 451 svcType: v1.ServiceTypeLoadBalancer, 452 fqdnTemplate: "{{.Name}}.ext-dns.test.com", 453 labels: map[string]string{}, 454 annotations: map[string]string{ 455 controllerAnnotationKey: "some-other-tool", 456 hostnameAnnotationKey: "foo.example.org.", 457 }, 458 externalIPs: []string{}, 459 lbs: []string{"1.2.3.4"}, 460 serviceTypesFilter: []string{}, 461 expected: []*endpoint.Endpoint{}, 462 }, 463 { 464 title: "services are found in target namespace", 465 targetNamespace: "testing", 466 svcNamespace: "testing", 467 svcName: "foo", 468 svcType: v1.ServiceTypeLoadBalancer, 469 labels: map[string]string{}, 470 annotations: map[string]string{ 471 hostnameAnnotationKey: "foo.example.org.", 472 }, 473 externalIPs: []string{}, 474 lbs: []string{"1.2.3.4"}, 475 serviceTypesFilter: []string{}, 476 expected: []*endpoint.Endpoint{ 477 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 478 }, 479 }, 480 { 481 title: "services that are not in target namespace are ignored", 482 targetNamespace: "testing", 483 svcNamespace: "other-testing", 484 svcName: "foo", 485 svcType: v1.ServiceTypeLoadBalancer, 486 labels: map[string]string{}, 487 annotations: map[string]string{ 488 hostnameAnnotationKey: "foo.example.org.", 489 }, 490 externalIPs: []string{}, 491 lbs: []string{"1.2.3.4"}, 492 serviceTypesFilter: []string{}, 493 expected: []*endpoint.Endpoint{}, 494 }, 495 { 496 title: "services are found in all namespaces", 497 svcNamespace: "other-testing", 498 svcName: "foo", 499 svcType: v1.ServiceTypeLoadBalancer, 500 labels: map[string]string{}, 501 annotations: map[string]string{ 502 hostnameAnnotationKey: "foo.example.org.", 503 }, 504 externalIPs: []string{}, 505 lbs: []string{"1.2.3.4"}, 506 serviceTypesFilter: []string{}, 507 expected: []*endpoint.Endpoint{ 508 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 509 }, 510 }, 511 { 512 title: "valid matching annotation filter expression", 513 annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", 514 svcNamespace: "testing", 515 svcName: "foo", 516 svcType: v1.ServiceTypeLoadBalancer, 517 labels: map[string]string{}, 518 annotations: map[string]string{ 519 hostnameAnnotationKey: "foo.example.org.", 520 "service.beta.kubernetes.io/external-traffic": "OnlyLocal", 521 }, 522 externalIPs: []string{}, 523 lbs: []string{"1.2.3.4"}, 524 serviceTypesFilter: []string{}, 525 expected: []*endpoint.Endpoint{ 526 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 527 }, 528 }, 529 { 530 title: "valid non-matching annotation filter expression", 531 annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", 532 svcNamespace: "testing", 533 svcName: "foo", 534 svcType: v1.ServiceTypeLoadBalancer, 535 labels: map[string]string{}, 536 annotations: map[string]string{ 537 hostnameAnnotationKey: "foo.example.org.", 538 "service.beta.kubernetes.io/external-traffic": "SomethingElse", 539 }, 540 externalIPs: []string{}, 541 lbs: []string{"1.2.3.4"}, 542 serviceTypesFilter: []string{}, 543 expected: []*endpoint.Endpoint{}, 544 }, 545 { 546 title: "invalid annotation filter expression", 547 annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)", 548 svcNamespace: "testing", 549 svcName: "foo", 550 svcType: v1.ServiceTypeLoadBalancer, 551 labels: map[string]string{}, 552 annotations: map[string]string{ 553 hostnameAnnotationKey: "foo.example.org.", 554 "service.beta.kubernetes.io/external-traffic": "OnlyLocal", 555 }, 556 externalIPs: []string{}, 557 lbs: []string{"1.2.3.4"}, 558 serviceTypesFilter: []string{}, 559 expected: []*endpoint.Endpoint{}, 560 expectError: true, 561 }, 562 { 563 title: "valid matching annotation filter label", 564 annotationFilter: "service.beta.kubernetes.io/external-traffic=Global", 565 svcNamespace: "testing", 566 svcName: "foo", 567 svcType: v1.ServiceTypeLoadBalancer, 568 labels: map[string]string{}, 569 annotations: map[string]string{ 570 hostnameAnnotationKey: "foo.example.org.", 571 "service.beta.kubernetes.io/external-traffic": "Global", 572 }, 573 externalIPs: []string{}, 574 lbs: []string{"1.2.3.4"}, 575 serviceTypesFilter: []string{}, 576 expected: []*endpoint.Endpoint{ 577 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 578 }, 579 }, 580 { 581 title: "valid non-matching annotation filter label", 582 annotationFilter: "service.beta.kubernetes.io/external-traffic=Global", 583 svcNamespace: "testing", 584 svcName: "foo", 585 svcType: v1.ServiceTypeLoadBalancer, 586 labels: map[string]string{}, 587 annotations: map[string]string{ 588 hostnameAnnotationKey: "foo.example.org.", 589 "service.beta.kubernetes.io/external-traffic": "OnlyLocal", 590 }, 591 externalIPs: []string{}, 592 lbs: []string{"1.2.3.4"}, 593 serviceTypesFilter: []string{}, 594 expected: []*endpoint.Endpoint{}, 595 }, 596 { 597 title: "no external entrypoints return no endpoints", 598 svcNamespace: "testing", 599 svcName: "foo", 600 svcType: v1.ServiceTypeLoadBalancer, 601 labels: map[string]string{}, 602 annotations: map[string]string{ 603 hostnameAnnotationKey: "foo.example.org.", 604 }, 605 externalIPs: []string{}, 606 lbs: []string{}, 607 serviceTypesFilter: []string{}, 608 expected: []*endpoint.Endpoint{}, 609 }, 610 { 611 title: "annotated service with externalIPs returns a single endpoint with multiple targets", 612 svcNamespace: "testing", 613 svcName: "foo", 614 svcType: v1.ServiceTypeLoadBalancer, 615 labels: map[string]string{}, 616 annotations: map[string]string{ 617 hostnameAnnotationKey: "foo.example.org.", 618 }, 619 externalIPs: []string{"10.2.3.4", "11.2.3.4"}, 620 lbs: []string{"1.2.3.4"}, 621 serviceTypesFilter: []string{}, 622 expected: []*endpoint.Endpoint{ 623 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}}, 624 }, 625 }, 626 { 627 title: "multiple external entrypoints return a single endpoint with multiple targets", 628 svcNamespace: "testing", 629 svcName: "foo", 630 svcType: v1.ServiceTypeLoadBalancer, 631 labels: map[string]string{}, 632 annotations: map[string]string{ 633 hostnameAnnotationKey: "foo.example.org.", 634 }, 635 externalIPs: []string{}, 636 lbs: []string{"1.2.3.4", "8.8.8.8"}, 637 serviceTypesFilter: []string{}, 638 expected: []*endpoint.Endpoint{ 639 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}}, 640 }, 641 }, 642 { 643 title: "services annotated with legacy mate annotations are ignored in default mode", 644 svcNamespace: "testing", 645 svcName: "foo", 646 svcType: v1.ServiceTypeLoadBalancer, 647 labels: map[string]string{}, 648 annotations: map[string]string{ 649 "zalando.org/dnsname": "foo.example.org.", 650 }, 651 externalIPs: []string{}, 652 lbs: []string{"1.2.3.4"}, 653 serviceTypesFilter: []string{}, 654 expected: []*endpoint.Endpoint{}, 655 }, 656 { 657 title: "services annotated with legacy mate annotations return an endpoint in compatibility mode", 658 svcNamespace: "testing", 659 svcName: "foo", 660 svcType: v1.ServiceTypeLoadBalancer, 661 compatibility: "mate", 662 labels: map[string]string{}, 663 annotations: map[string]string{ 664 "zalando.org/dnsname": "foo.example.org.", 665 }, 666 externalIPs: []string{}, 667 lbs: []string{"1.2.3.4"}, 668 serviceTypesFilter: []string{}, 669 expected: []*endpoint.Endpoint{ 670 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 671 }, 672 }, 673 { 674 title: "services annotated with legacy molecule annotations return an endpoint in compatibility mode", 675 svcNamespace: "testing", 676 svcName: "foo", 677 svcType: v1.ServiceTypeLoadBalancer, 678 compatibility: "molecule", 679 labels: map[string]string{ 680 "dns": "route53", 681 }, 682 annotations: map[string]string{ 683 "domainName": "foo.example.org., bar.example.org", 684 }, 685 externalIPs: []string{}, 686 lbs: []string{"1.2.3.4"}, 687 serviceTypesFilter: []string{}, 688 expected: []*endpoint.Endpoint{ 689 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 690 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 691 }, 692 }, 693 { 694 title: "load balancer services annotated with DNS Controller annotations return an endpoint with A and CNAME targets in compatibility mode", 695 svcNamespace: "testing", 696 svcName: "foo", 697 svcType: v1.ServiceTypeLoadBalancer, 698 compatibility: "kops-dns-controller", 699 labels: map[string]string{}, 700 annotations: map[string]string{ 701 kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org", 702 }, 703 externalIPs: []string{}, 704 lbs: []string{"1.2.3.4", "lb.example.com"}, 705 serviceTypesFilter: []string{}, 706 expected: []*endpoint.Endpoint{ 707 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 708 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}}, 709 }, 710 }, 711 { 712 title: "load balancer services annotated with DNS Controller annotations return an endpoint with both annotations in compatibility mode", 713 svcNamespace: "testing", 714 svcName: "foo", 715 svcType: v1.ServiceTypeLoadBalancer, 716 compatibility: "kops-dns-controller", 717 labels: map[string]string{}, 718 annotations: map[string]string{ 719 kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", 720 kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", 721 }, 722 externalIPs: []string{}, 723 lbs: []string{"1.2.3.4"}, 724 serviceTypesFilter: []string{}, 725 expected: []*endpoint.Endpoint{ 726 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 727 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 728 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 729 {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 730 }, 731 }, 732 { 733 title: "not annotated services with set fqdnTemplate return an endpoint with target IP", 734 svcNamespace: "testing", 735 svcName: "foo", 736 svcType: v1.ServiceTypeLoadBalancer, 737 fqdnTemplate: "{{.Name}}.bar.example.com", 738 labels: map[string]string{}, 739 annotations: map[string]string{}, 740 externalIPs: []string{}, 741 lbs: []string{"1.2.3.4", "elb.com"}, 742 serviceTypesFilter: []string{}, 743 expected: []*endpoint.Endpoint{ 744 {DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 745 {DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"elb.com"}}, 746 }, 747 }, 748 { 749 title: "annotated services with set fqdnTemplate annotation takes precedence", 750 svcNamespace: "testing", 751 svcName: "foo", 752 svcType: v1.ServiceTypeLoadBalancer, 753 fqdnTemplate: "{{.Name}}.bar.example.com", 754 labels: map[string]string{}, 755 annotations: map[string]string{ 756 hostnameAnnotationKey: "foo.example.org.", 757 }, 758 externalIPs: []string{}, 759 lbs: []string{"1.2.3.4", "elb.com"}, 760 serviceTypesFilter: []string{}, 761 expected: []*endpoint.Endpoint{ 762 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 763 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"elb.com"}}, 764 }, 765 }, 766 { 767 title: "compatibility annotated services with tmpl. compatibility takes precedence", 768 svcNamespace: "testing", 769 svcName: "foo", 770 svcType: v1.ServiceTypeLoadBalancer, 771 compatibility: "mate", 772 fqdnTemplate: "{{.Name}}.bar.example.com", 773 labels: map[string]string{}, 774 annotations: map[string]string{ 775 "zalando.org/dnsname": "mate.example.org.", 776 }, 777 externalIPs: []string{}, 778 lbs: []string{"1.2.3.4"}, 779 serviceTypesFilter: []string{}, 780 expected: []*endpoint.Endpoint{ 781 {DNSName: "mate.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 782 }, 783 }, 784 { 785 title: "not annotated services with unknown tmpl field should not return anything", 786 svcNamespace: "testing", 787 svcName: "foo", 788 svcType: v1.ServiceTypeLoadBalancer, 789 fqdnTemplate: "{{.Calibre}}.bar.example.com", 790 labels: map[string]string{}, 791 annotations: map[string]string{}, 792 externalIPs: []string{}, 793 lbs: []string{"1.2.3.4"}, 794 serviceTypesFilter: []string{}, 795 expected: []*endpoint.Endpoint{}, 796 expectError: true, 797 }, 798 { 799 title: "ttl not annotated should have RecordTTL.IsConfigured set to false", 800 svcNamespace: "testing", 801 svcName: "foo", 802 svcType: v1.ServiceTypeLoadBalancer, 803 labels: map[string]string{}, 804 annotations: map[string]string{ 805 hostnameAnnotationKey: "foo.example.org.", 806 }, 807 externalIPs: []string{}, 808 lbs: []string{"1.2.3.4"}, 809 serviceTypesFilter: []string{}, 810 expected: []*endpoint.Endpoint{ 811 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, 812 }, 813 }, 814 { 815 title: "ttl annotated but invalid should have RecordTTL.IsConfigured set to false", 816 svcNamespace: "testing", 817 svcName: "foo", 818 svcType: v1.ServiceTypeLoadBalancer, 819 labels: map[string]string{}, 820 annotations: map[string]string{ 821 hostnameAnnotationKey: "foo.example.org.", 822 ttlAnnotationKey: "foo", 823 }, 824 externalIPs: []string{}, 825 lbs: []string{"1.2.3.4"}, 826 serviceTypesFilter: []string{}, 827 expected: []*endpoint.Endpoint{ 828 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, 829 }, 830 }, 831 { 832 title: "ttl annotated and is valid should set Record.TTL", 833 svcNamespace: "testing", 834 svcName: "foo", 835 svcType: v1.ServiceTypeLoadBalancer, 836 labels: map[string]string{}, 837 annotations: map[string]string{ 838 hostnameAnnotationKey: "foo.example.org.", 839 ttlAnnotationKey: "10", 840 }, 841 externalIPs: []string{}, 842 lbs: []string{"1.2.3.4"}, 843 serviceTypesFilter: []string{}, 844 expected: []*endpoint.Endpoint{ 845 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)}, 846 }, 847 }, 848 { 849 title: "ttl annotated (in duration format) and is valid should set Record.TTL", 850 svcNamespace: "testing", 851 svcName: "foo", 852 svcType: v1.ServiceTypeLoadBalancer, 853 labels: map[string]string{}, 854 annotations: map[string]string{ 855 hostnameAnnotationKey: "foo.example.org.", 856 ttlAnnotationKey: "1m", 857 }, 858 externalIPs: []string{}, 859 lbs: []string{"1.2.3.4"}, 860 serviceTypesFilter: []string{}, 861 expected: []*endpoint.Endpoint{ 862 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(60)}, 863 }, 864 }, 865 { 866 title: "Negative ttl is not valid", 867 svcNamespace: "testing", 868 svcName: "foo", 869 svcType: v1.ServiceTypeLoadBalancer, 870 labels: map[string]string{}, 871 annotations: map[string]string{ 872 hostnameAnnotationKey: "foo.example.org.", 873 ttlAnnotationKey: "-10", 874 }, 875 externalIPs: []string{}, 876 lbs: []string{"1.2.3.4"}, 877 serviceTypesFilter: []string{}, 878 expected: []*endpoint.Endpoint{ 879 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, 880 }, 881 }, 882 { 883 title: "filter on service types should include matching services", 884 svcNamespace: "testing", 885 svcName: "foo", 886 svcType: v1.ServiceTypeLoadBalancer, 887 labels: map[string]string{}, 888 annotations: map[string]string{ 889 hostnameAnnotationKey: "foo.example.org.", 890 }, 891 externalIPs: []string{}, 892 lbs: []string{"1.2.3.4"}, 893 serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, 894 expected: []*endpoint.Endpoint{ 895 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 896 }, 897 }, 898 { 899 title: "filter on service types should exclude non-matching services", 900 svcNamespace: "testing", 901 svcName: "foo", 902 svcType: v1.ServiceTypeNodePort, 903 labels: map[string]string{}, 904 annotations: map[string]string{ 905 hostnameAnnotationKey: "foo.example.org.", 906 }, 907 externalIPs: []string{}, 908 lbs: []string{"1.2.3.4"}, 909 serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, 910 expected: []*endpoint.Endpoint{}, 911 }, 912 { 913 title: "internal-host annotated and host annotated clusterip services return an endpoint with Cluster IP", 914 svcNamespace: "testing", 915 svcName: "foo", 916 svcType: v1.ServiceTypeClusterIP, 917 labels: map[string]string{}, 918 annotations: map[string]string{ 919 hostnameAnnotationKey: "foo.example.org.", 920 internalHostnameAnnotationKey: "foo.internal.example.org.", 921 }, 922 clusterIP: "1.1.1.1", 923 externalIPs: []string{}, 924 lbs: []string{"1.2.3.4"}, 925 serviceTypesFilter: []string{}, 926 expected: []*endpoint.Endpoint{ 927 {DNSName: "foo.internal.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 928 }, 929 }, 930 { 931 title: "internal-host annotated loadbalancer services return an endpoint with Cluster IP", 932 svcNamespace: "testing", 933 svcName: "foo", 934 svcType: v1.ServiceTypeLoadBalancer, 935 labels: map[string]string{}, 936 annotations: map[string]string{ 937 internalHostnameAnnotationKey: "foo.internal.example.org.", 938 }, 939 clusterIP: "1.1.1.1", 940 externalIPs: []string{}, 941 lbs: []string{"1.2.3.4"}, 942 serviceTypesFilter: []string{}, 943 expected: []*endpoint.Endpoint{ 944 {DNSName: "foo.internal.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 945 }, 946 }, 947 { 948 title: "internal-host annotated and host annotated loadbalancer services return an endpoint with Cluster IP and an endpoint with lb IP", 949 svcNamespace: "testing", 950 svcName: "foo", 951 svcType: v1.ServiceTypeLoadBalancer, 952 labels: map[string]string{}, 953 annotations: map[string]string{ 954 hostnameAnnotationKey: "foo.example.org.", 955 internalHostnameAnnotationKey: "foo.internal.example.org.", 956 }, 957 clusterIP: "1.1.1.1", 958 externalIPs: []string{}, 959 lbs: []string{"1.2.3.4"}, 960 serviceTypesFilter: []string{}, 961 expected: []*endpoint.Endpoint{ 962 {DNSName: "foo.internal.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 963 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 964 }, 965 }, 966 { 967 title: "service with matching labels and fqdn filter should be included", 968 svcNamespace: "testing", 969 svcName: "fqdn", 970 svcType: v1.ServiceTypeLoadBalancer, 971 labels: map[string]string{ 972 "app": "web-external", 973 }, 974 clusterIP: "1.1.1.1", 975 externalIPs: []string{}, 976 lbs: []string{"1.2.3.4"}, 977 serviceTypesFilter: []string{}, 978 serviceLabelSelector: "app=web-external", 979 fqdnTemplate: "{{.Name}}.bar.example.com", 980 expected: []*endpoint.Endpoint{ 981 {DNSName: "fqdn.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 982 }, 983 }, 984 { 985 title: "service with matching labels and hostname annotation should be included", 986 svcNamespace: "testing", 987 svcName: "foo", 988 svcType: v1.ServiceTypeLoadBalancer, 989 labels: map[string]string{ 990 "app": "web-external", 991 }, 992 clusterIP: "1.1.1.1", 993 externalIPs: []string{}, 994 lbs: []string{"1.2.3.4"}, 995 serviceTypesFilter: []string{}, 996 serviceLabelSelector: "app=web-external", 997 annotations: map[string]string{hostnameAnnotationKey: "annotation.bar.example.com"}, 998 expected: []*endpoint.Endpoint{ 999 {DNSName: "annotation.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 1000 }, 1001 }, 1002 { 1003 title: "service without matching labels and fqdn filter should be excluded", 1004 svcNamespace: "testing", 1005 svcName: "fqdn", 1006 svcType: v1.ServiceTypeLoadBalancer, 1007 labels: map[string]string{ 1008 "app": "web-internal", 1009 }, 1010 clusterIP: "1.1.1.1", 1011 externalIPs: []string{}, 1012 lbs: []string{"1.2.3.4"}, 1013 serviceTypesFilter: []string{}, 1014 serviceLabelSelector: "app=web-external", 1015 fqdnTemplate: "{{.Name}}.bar.example.com", 1016 expected: []*endpoint.Endpoint{}, 1017 }, 1018 { 1019 title: "service without matching labels and hostname annotation should be excluded", 1020 svcNamespace: "testing", 1021 svcName: "foo", 1022 svcType: v1.ServiceTypeLoadBalancer, 1023 labels: map[string]string{ 1024 "app": "web-internal", 1025 }, 1026 clusterIP: "1.1.1.1", 1027 externalIPs: []string{}, 1028 lbs: []string{"1.2.3.4"}, 1029 serviceTypesFilter: []string{}, 1030 serviceLabelSelector: "app=web-external", 1031 annotations: map[string]string{hostnameAnnotationKey: "annotation.bar.example.com"}, 1032 expected: []*endpoint.Endpoint{}, 1033 }, 1034 { 1035 title: "dual-stack load-balancer service gets both addresses", 1036 svcNamespace: "testing", 1037 svcName: "foobar", 1038 svcType: v1.ServiceTypeLoadBalancer, 1039 labels: map[string]string{}, 1040 clusterIP: "1.1.1.2,2001:db8::2", 1041 externalIPs: []string{}, 1042 lbs: []string{"1.1.1.1", "2001:db8::1"}, 1043 serviceTypesFilter: []string{}, 1044 annotations: map[string]string{hostnameAnnotationKey: "foobar.example.org"}, 1045 expected: []*endpoint.Endpoint{ 1046 {DNSName: "foobar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 1047 {DNSName: "foobar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}}, 1048 }, 1049 }, 1050 { 1051 title: "IPv6-only load-balancer service gets IPv6 endpoint", 1052 svcNamespace: "testing", 1053 svcName: "foobar-v6", 1054 svcType: v1.ServiceTypeLoadBalancer, 1055 labels: map[string]string{}, 1056 clusterIP: "2001:db8::1", 1057 externalIPs: []string{}, 1058 lbs: []string{"2001:db8::2"}, 1059 serviceTypesFilter: []string{}, 1060 annotations: map[string]string{hostnameAnnotationKey: "foobar-v6.example.org"}, 1061 expected: []*endpoint.Endpoint{ 1062 {DNSName: "foobar-v6.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}}, 1063 }, 1064 }, 1065 } { 1066 tc := tc 1067 t.Run(tc.title, func(t *testing.T) { 1068 t.Parallel() 1069 1070 // Create a Kubernetes testing client 1071 kubernetes := fake.NewSimpleClientset() 1072 1073 // Create a service to test against 1074 ingresses := []v1.LoadBalancerIngress{} 1075 for _, lb := range tc.lbs { 1076 if net.ParseIP(lb) != nil { 1077 ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb}) 1078 } else { 1079 ingresses = append(ingresses, v1.LoadBalancerIngress{Hostname: lb}) 1080 } 1081 } 1082 1083 service := &v1.Service{ 1084 Spec: v1.ServiceSpec{ 1085 Type: tc.svcType, 1086 ClusterIP: tc.clusterIP, 1087 ExternalIPs: tc.externalIPs, 1088 }, 1089 ObjectMeta: metav1.ObjectMeta{ 1090 Namespace: tc.svcNamespace, 1091 Name: tc.svcName, 1092 Labels: tc.labels, 1093 Annotations: tc.annotations, 1094 }, 1095 Status: v1.ServiceStatus{ 1096 LoadBalancer: v1.LoadBalancerStatus{ 1097 Ingress: ingresses, 1098 }, 1099 }, 1100 } 1101 1102 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 1103 require.NoError(t, err) 1104 1105 var sourceLabel labels.Selector 1106 if tc.serviceLabelSelector != "" { 1107 sourceLabel, err = labels.Parse(tc.serviceLabelSelector) 1108 require.NoError(t, err) 1109 } else { 1110 sourceLabel = labels.Everything() 1111 } 1112 1113 // Create our object under test and get the endpoints. 1114 client, err := NewServiceSource( 1115 context.TODO(), 1116 kubernetes, 1117 tc.targetNamespace, 1118 tc.annotationFilter, 1119 tc.fqdnTemplate, 1120 tc.combineFQDNAndAnnotation, 1121 tc.compatibility, 1122 false, 1123 false, 1124 false, 1125 tc.serviceTypesFilter, 1126 tc.ignoreHostnameAnnotation, 1127 sourceLabel, 1128 tc.resolveLoadBalancerHostname, 1129 ) 1130 1131 require.NoError(t, err) 1132 1133 res, err := client.Endpoints(context.Background()) 1134 if tc.expectError { 1135 require.Error(t, err) 1136 } else { 1137 require.NoError(t, err) 1138 } 1139 1140 // Validate returned endpoints against desired endpoints. 1141 validateEndpoints(t, res, tc.expected) 1142 }) 1143 } 1144 } 1145 1146 // testMultipleServicesEndpoints tests that multiple services generate correct merged endpoints 1147 func testMultipleServicesEndpoints(t *testing.T) { 1148 t.Parallel() 1149 1150 for _, tc := range []struct { 1151 title string 1152 targetNamespace string 1153 annotationFilter string 1154 svcNamespace string 1155 svcName string 1156 svcType v1.ServiceType 1157 compatibility string 1158 fqdnTemplate string 1159 combineFQDNAndAnnotation bool 1160 ignoreHostnameAnnotation bool 1161 labels map[string]string 1162 clusterIP string 1163 services map[string]map[string]string 1164 serviceTypesFilter []string 1165 expected []*endpoint.Endpoint 1166 expectError bool 1167 }{ 1168 { 1169 "test service returns a correct end point", 1170 "", 1171 "", 1172 "testing", 1173 "foo", 1174 v1.ServiceTypeLoadBalancer, 1175 "", 1176 "", 1177 false, 1178 false, 1179 map[string]string{}, 1180 "", 1181 map[string]map[string]string{ 1182 "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, 1183 }, 1184 []string{}, 1185 []*endpoint.Endpoint{ 1186 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.4"}}, 1187 }, 1188 false, 1189 }, 1190 { 1191 "multiple services that share same DNS should be merged into one endpoint", 1192 "", 1193 "", 1194 "testing", 1195 "foo", 1196 v1.ServiceTypeLoadBalancer, 1197 "", 1198 "", 1199 false, 1200 false, 1201 map[string]string{}, 1202 "", 1203 map[string]map[string]string{ 1204 "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, 1205 "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"}, 1206 "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"}, 1207 }, 1208 []string{}, 1209 []*endpoint.Endpoint{ 1210 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5", "1.2.3.6"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.4"}}, 1211 }, 1212 false, 1213 }, 1214 { 1215 "test that services with different hostnames do not get merged together", 1216 "", 1217 "", 1218 "testing", 1219 "foo", 1220 v1.ServiceTypeLoadBalancer, 1221 "", 1222 "", 1223 false, 1224 false, 1225 map[string]string{}, 1226 "", 1227 map[string]map[string]string{ 1228 "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"}, 1229 "10.1.1.3": {hostnameAnnotationKey: "bar.example.org"}, 1230 "10.1.1.1": {hostnameAnnotationKey: "bar.example.org"}, 1231 "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, 1232 "10.1.1.2": {hostnameAnnotationKey: "bar.example.org"}, 1233 "20.1.1.1": {hostnameAnnotationKey: "foobar.example.org"}, 1234 "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"}, 1235 }, 1236 []string{}, 1237 []*endpoint.Endpoint{ 1238 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5", "1.2.3.6"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.4"}}, 1239 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.1.1", "10.1.1.2", "10.1.1.3"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo10.1.1.1"}}, 1240 {DNSName: "foobar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"20.1.1.1"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo20.1.1.1"}}, 1241 }, 1242 false, 1243 }, 1244 { 1245 "test that services with different set-identifier do not get merged together", 1246 "", 1247 "", 1248 "testing", 1249 "foo", 1250 v1.ServiceTypeLoadBalancer, 1251 "", 1252 "", 1253 false, 1254 false, 1255 map[string]string{}, 1256 "", 1257 map[string]map[string]string{ 1258 "a.elb.com": {hostnameAnnotationKey: "foo.example.org", SetIdentifierKey: "a"}, 1259 "b.elb.com": {hostnameAnnotationKey: "foo.example.org", SetIdentifierKey: "b"}, 1260 }, 1261 []string{}, 1262 []*endpoint.Endpoint{ 1263 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"a.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/fooa.elb.com"}, SetIdentifier: "a"}, 1264 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"b.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foob.elb.com"}, SetIdentifier: "b"}, 1265 }, 1266 false, 1267 }, 1268 } { 1269 tc := tc 1270 t.Run(tc.title, func(t *testing.T) { 1271 t.Parallel() 1272 1273 // Create a Kubernetes testing client 1274 kubernetes := fake.NewSimpleClientset() 1275 1276 // Create services to test against 1277 for lb, annotations := range tc.services { 1278 ingresses := []v1.LoadBalancerIngress{} 1279 ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb}) 1280 1281 service := &v1.Service{ 1282 Spec: v1.ServiceSpec{ 1283 Type: tc.svcType, 1284 ClusterIP: tc.clusterIP, 1285 }, 1286 ObjectMeta: metav1.ObjectMeta{ 1287 Namespace: tc.svcNamespace, 1288 Name: tc.svcName + lb, 1289 Labels: tc.labels, 1290 Annotations: annotations, 1291 }, 1292 Status: v1.ServiceStatus{ 1293 LoadBalancer: v1.LoadBalancerStatus{ 1294 Ingress: ingresses, 1295 }, 1296 }, 1297 } 1298 1299 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 1300 require.NoError(t, err) 1301 } 1302 1303 // Create our object under test and get the endpoints. 1304 client, err := NewServiceSource( 1305 context.TODO(), 1306 kubernetes, 1307 tc.targetNamespace, 1308 tc.annotationFilter, 1309 tc.fqdnTemplate, 1310 tc.combineFQDNAndAnnotation, 1311 tc.compatibility, 1312 false, 1313 false, 1314 false, 1315 tc.serviceTypesFilter, 1316 tc.ignoreHostnameAnnotation, 1317 labels.Everything(), 1318 false, 1319 ) 1320 require.NoError(t, err) 1321 1322 res, err := client.Endpoints(context.Background()) 1323 if tc.expectError { 1324 require.Error(t, err) 1325 } else { 1326 require.NoError(t, err) 1327 } 1328 1329 // Validate returned endpoints against desired endpoints. 1330 validateEndpoints(t, res, tc.expected) 1331 // Test that endpoint resourceLabelKey matches desired endpoint 1332 sort.SliceStable(res, func(i, j int) bool { 1333 return strings.Compare(res[i].DNSName, res[j].DNSName) < 0 1334 }) 1335 sort.SliceStable(tc.expected, func(i, j int) bool { 1336 return strings.Compare(tc.expected[i].DNSName, tc.expected[j].DNSName) < 0 1337 }) 1338 1339 for i := range res { 1340 if res[i].Labels[endpoint.ResourceLabelKey] != tc.expected[i].Labels[endpoint.ResourceLabelKey] { 1341 t.Errorf("expected %s, got %s", tc.expected[i].Labels[endpoint.ResourceLabelKey], res[i].Labels[endpoint.ResourceLabelKey]) 1342 } 1343 } 1344 }) 1345 } 1346 } 1347 1348 // testServiceSourceEndpoints tests that various services generate the correct endpoints. 1349 func TestClusterIpServices(t *testing.T) { 1350 t.Parallel() 1351 1352 for _, tc := range []struct { 1353 title string 1354 targetNamespace string 1355 annotationFilter string 1356 svcNamespace string 1357 svcName string 1358 svcType v1.ServiceType 1359 compatibility string 1360 fqdnTemplate string 1361 ignoreHostnameAnnotation bool 1362 labels map[string]string 1363 annotations map[string]string 1364 clusterIP string 1365 expected []*endpoint.Endpoint 1366 expectError bool 1367 labelSelector string 1368 }{ 1369 { 1370 title: "hostname annotated ClusterIp services return an endpoint with Cluster IP", 1371 svcNamespace: "testing", 1372 svcName: "foo", 1373 svcType: v1.ServiceTypeClusterIP, 1374 annotations: map[string]string{ 1375 hostnameAnnotationKey: "foo.example.org.", 1376 }, 1377 clusterIP: "1.2.3.4", 1378 expected: []*endpoint.Endpoint{ 1379 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 1380 }, 1381 }, 1382 { 1383 title: "target annotated ClusterIp services return an endpoint with the specified A", 1384 svcNamespace: "testing", 1385 svcName: "foo", 1386 svcType: v1.ServiceTypeClusterIP, 1387 annotations: map[string]string{ 1388 hostnameAnnotationKey: "foo.example.org.", 1389 targetAnnotationKey: "4.3.2.1", 1390 }, 1391 clusterIP: "1.2.3.4", 1392 expected: []*endpoint.Endpoint{ 1393 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}}, 1394 }, 1395 }, 1396 { 1397 title: "target annotated ClusterIp services return an endpoint with the specified CNAME", 1398 svcNamespace: "testing", 1399 svcName: "foo", 1400 svcType: v1.ServiceTypeClusterIP, 1401 annotations: map[string]string{ 1402 hostnameAnnotationKey: "foo.example.org.", 1403 targetAnnotationKey: "bar.example.org.", 1404 }, 1405 clusterIP: "1.2.3.4", 1406 expected: []*endpoint.Endpoint{ 1407 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org"}}, 1408 }, 1409 }, 1410 { 1411 title: "target annotated ClusterIp services return an endpoint with the specified AAAA", 1412 svcNamespace: "testing", 1413 svcName: "foo", 1414 svcType: v1.ServiceTypeClusterIP, 1415 annotations: map[string]string{ 1416 hostnameAnnotationKey: "foo.example.org.", 1417 targetAnnotationKey: "2001:DB8::1", 1418 }, 1419 clusterIP: "1.2.3.4", 1420 expected: []*endpoint.Endpoint{ 1421 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}}, 1422 }, 1423 }, 1424 { 1425 title: "multiple target annotated ClusterIp services return an endpoint with the specified CNAMES", 1426 svcNamespace: "testing", 1427 svcName: "foo", 1428 svcType: v1.ServiceTypeClusterIP, 1429 annotations: map[string]string{ 1430 hostnameAnnotationKey: "foo.example.org.", 1431 targetAnnotationKey: "bar.example.org.,baz.example.org.", 1432 }, 1433 clusterIP: "1.2.3.4", 1434 expected: []*endpoint.Endpoint{ 1435 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org", "baz.example.org"}}, 1436 }, 1437 }, 1438 { 1439 title: "multiple target annotated ClusterIp services return two endpoints with the specified CNAMES and AAAA", 1440 svcNamespace: "testing", 1441 svcName: "foo", 1442 svcType: v1.ServiceTypeClusterIP, 1443 annotations: map[string]string{ 1444 hostnameAnnotationKey: "foo.example.org.", 1445 targetAnnotationKey: "bar.example.org.,baz.example.org.,2001:DB8::1", 1446 }, 1447 clusterIP: "1.2.3.4", 1448 expected: []*endpoint.Endpoint{ 1449 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org", "baz.example.org"}}, 1450 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}}, 1451 }, 1452 }, 1453 { 1454 title: "hostname annotated ClusterIp services are ignored", 1455 svcNamespace: "testing", 1456 svcName: "foo", 1457 svcType: v1.ServiceTypeClusterIP, 1458 ignoreHostnameAnnotation: true, 1459 annotations: map[string]string{ 1460 hostnameAnnotationKey: "foo.example.org.", 1461 }, 1462 clusterIP: "1.2.3.4", 1463 expected: []*endpoint.Endpoint{}, 1464 }, 1465 { 1466 title: "hostname and target annotated ClusterIp services are ignored", 1467 svcNamespace: "testing", 1468 svcName: "foo", 1469 svcType: v1.ServiceTypeClusterIP, 1470 ignoreHostnameAnnotation: true, 1471 annotations: map[string]string{ 1472 hostnameAnnotationKey: "foo.example.org.", 1473 targetAnnotationKey: "bar.example.org.", 1474 }, 1475 clusterIP: "1.2.3.4", 1476 expected: []*endpoint.Endpoint{}, 1477 }, 1478 { 1479 title: "hostname and target annotated ClusterIp services return an endpoint with the specified CNAME", 1480 svcNamespace: "testing", 1481 svcName: "foo", 1482 svcType: v1.ServiceTypeClusterIP, 1483 annotations: map[string]string{ 1484 hostnameAnnotationKey: "foo.example.org.", 1485 targetAnnotationKey: "bar.example.org.", 1486 }, 1487 clusterIP: "1.2.3.4", 1488 expected: []*endpoint.Endpoint{ 1489 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org"}}, 1490 }, 1491 }, 1492 { 1493 title: "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP", 1494 svcNamespace: "testing", 1495 svcName: "foo", 1496 svcType: v1.ServiceTypeClusterIP, 1497 fqdnTemplate: "{{.Name}}.bar.example.com", 1498 clusterIP: "4.5.6.7", 1499 expected: []*endpoint.Endpoint{ 1500 {DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.5.6.7"}}, 1501 }, 1502 }, 1503 { 1504 title: "Headless services do not generate endpoints", 1505 svcNamespace: "testing", 1506 svcName: "foo", 1507 svcType: v1.ServiceTypeClusterIP, 1508 clusterIP: v1.ClusterIPNone, 1509 expected: []*endpoint.Endpoint{}, 1510 }, 1511 { 1512 title: "Headless services generate endpoints when target is specified", 1513 svcNamespace: "testing", 1514 svcName: "foo", 1515 svcType: v1.ServiceTypeClusterIP, 1516 annotations: map[string]string{ 1517 hostnameAnnotationKey: "foo.example.org.", 1518 targetAnnotationKey: "bar.example.org.", 1519 }, 1520 clusterIP: v1.ClusterIPNone, 1521 expected: []*endpoint.Endpoint{ 1522 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org"}}, 1523 }, 1524 }, 1525 { 1526 title: "ClusterIP service with matching label generates an endpoint", 1527 svcNamespace: "testing", 1528 svcName: "foo", 1529 svcType: v1.ServiceTypeClusterIP, 1530 fqdnTemplate: "{{.Name}}.bar.example.com", 1531 labels: map[string]string{"app": "web-internal"}, 1532 clusterIP: "4.5.6.7", 1533 expected: []*endpoint.Endpoint{ 1534 {DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.5.6.7"}}, 1535 }, 1536 labelSelector: "app=web-internal", 1537 }, 1538 { 1539 title: "ClusterIP service with matching label and target generates a CNAME endpoint", 1540 svcNamespace: "testing", 1541 svcName: "foo", 1542 svcType: v1.ServiceTypeClusterIP, 1543 fqdnTemplate: "{{.Name}}.bar.example.com", 1544 labels: map[string]string{"app": "web-internal"}, 1545 annotations: map[string]string{targetAnnotationKey: "bar.example.com."}, 1546 clusterIP: "4.5.6.7", 1547 expected: []*endpoint.Endpoint{ 1548 {DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.com"}}, 1549 }, 1550 labelSelector: "app=web-internal", 1551 }, 1552 { 1553 title: "ClusterIP service without matching label generates an endpoint", 1554 svcNamespace: "testing", 1555 svcName: "foo", 1556 svcType: v1.ServiceTypeClusterIP, 1557 fqdnTemplate: "{{.Name}}.bar.example.com", 1558 labels: map[string]string{"app": "web-internal"}, 1559 clusterIP: "4.5.6.7", 1560 expected: []*endpoint.Endpoint{}, 1561 labelSelector: "app=web-external", 1562 }, 1563 { 1564 title: "invalid hostname does not generate endpoints", 1565 svcNamespace: "testing", 1566 svcName: "foo", 1567 svcType: v1.ServiceTypeClusterIP, 1568 annotations: map[string]string{ 1569 hostnameAnnotationKey: "this-is-an-exceedingly-long-label-that-external-dns-should-reject.example.org.", 1570 }, 1571 clusterIP: "1.2.3.4", 1572 expected: []*endpoint.Endpoint{}, 1573 }, 1574 } { 1575 tc := tc 1576 t.Run(tc.title, func(t *testing.T) { 1577 t.Parallel() 1578 1579 // Create a Kubernetes testing client 1580 kubernetes := fake.NewSimpleClientset() 1581 1582 // Create a service to test against 1583 service := &v1.Service{ 1584 Spec: v1.ServiceSpec{ 1585 Type: tc.svcType, 1586 ClusterIP: tc.clusterIP, 1587 }, 1588 ObjectMeta: metav1.ObjectMeta{ 1589 Namespace: tc.svcNamespace, 1590 Name: tc.svcName, 1591 Labels: tc.labels, 1592 Annotations: tc.annotations, 1593 }, 1594 } 1595 1596 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 1597 require.NoError(t, err) 1598 1599 var labelSelector labels.Selector 1600 if tc.labelSelector != "" { 1601 labelSelector, err = labels.Parse(tc.labelSelector) 1602 require.NoError(t, err) 1603 } else { 1604 labelSelector = labels.Everything() 1605 } 1606 // Create our object under test and get the endpoints. 1607 client, _ := NewServiceSource( 1608 context.TODO(), 1609 kubernetes, 1610 tc.targetNamespace, 1611 tc.annotationFilter, 1612 tc.fqdnTemplate, 1613 false, 1614 tc.compatibility, 1615 true, 1616 false, 1617 false, 1618 []string{}, 1619 tc.ignoreHostnameAnnotation, 1620 labelSelector, 1621 false, 1622 ) 1623 require.NoError(t, err) 1624 1625 endpoints, err := client.Endpoints(context.Background()) 1626 if tc.expectError { 1627 require.Error(t, err) 1628 } else { 1629 require.NoError(t, err) 1630 } 1631 1632 // Validate returned endpoints against desired endpoints. 1633 validateEndpoints(t, endpoints, tc.expected) 1634 }) 1635 } 1636 } 1637 1638 // testNodePortServices tests that various services generate the correct endpoints. 1639 func TestServiceSourceNodePortServices(t *testing.T) { 1640 t.Parallel() 1641 1642 for _, tc := range []struct { 1643 title string 1644 targetNamespace string 1645 annotationFilter string 1646 svcNamespace string 1647 svcName string 1648 svcType v1.ServiceType 1649 svcTrafficPolicy v1.ServiceExternalTrafficPolicyType 1650 compatibility string 1651 fqdnTemplate string 1652 ignoreHostnameAnnotation bool 1653 labels map[string]string 1654 annotations map[string]string 1655 lbs []string 1656 expected []*endpoint.Endpoint 1657 expectError bool 1658 nodes []*v1.Node 1659 podNames []string 1660 nodeIndex []int 1661 phases []v1.PodPhase 1662 conditions []v1.PodCondition 1663 labelSelector labels.Selector 1664 deletionTimestamp []*metav1.Time 1665 }{ 1666 { 1667 title: "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", 1668 svcNamespace: "testing", 1669 svcName: "foo", 1670 svcType: v1.ServiceTypeNodePort, 1671 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 1672 annotations: map[string]string{ 1673 hostnameAnnotationKey: "foo.example.org.", 1674 }, 1675 expected: []*endpoint.Endpoint{ 1676 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 1677 {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, 1678 {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 1679 }, 1680 nodes: []*v1.Node{{ 1681 ObjectMeta: metav1.ObjectMeta{ 1682 Name: "node1", 1683 }, 1684 Status: v1.NodeStatus{ 1685 Addresses: []v1.NodeAddress{ 1686 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1687 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1688 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 1689 }, 1690 }, 1691 }, { 1692 ObjectMeta: metav1.ObjectMeta{ 1693 Name: "node2", 1694 }, 1695 Status: v1.NodeStatus{ 1696 Addresses: []v1.NodeAddress{ 1697 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1698 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1699 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 1700 }, 1701 }, 1702 }}, 1703 }, 1704 { 1705 title: "hostname annotated NodePort services are ignored", 1706 svcNamespace: "testing", 1707 svcName: "foo", 1708 svcType: v1.ServiceTypeNodePort, 1709 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 1710 ignoreHostnameAnnotation: true, 1711 annotations: map[string]string{ 1712 hostnameAnnotationKey: "foo.example.org.", 1713 }, 1714 nodes: []*v1.Node{{ 1715 ObjectMeta: metav1.ObjectMeta{ 1716 Name: "node1", 1717 }, 1718 Status: v1.NodeStatus{ 1719 Addresses: []v1.NodeAddress{ 1720 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1721 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1722 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 1723 }, 1724 }, 1725 }, { 1726 ObjectMeta: metav1.ObjectMeta{ 1727 Name: "node2", 1728 }, 1729 Status: v1.NodeStatus{ 1730 Addresses: []v1.NodeAddress{ 1731 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1732 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1733 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 1734 }, 1735 }, 1736 }}, 1737 expected: []*endpoint.Endpoint{}, 1738 }, 1739 { 1740 title: "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP", 1741 svcNamespace: "testing", 1742 svcName: "foo", 1743 svcType: v1.ServiceTypeNodePort, 1744 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 1745 fqdnTemplate: "{{.Name}}.bar.example.com", 1746 expected: []*endpoint.Endpoint{ 1747 {DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV}, 1748 {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, 1749 {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 1750 }, 1751 nodes: []*v1.Node{{ 1752 ObjectMeta: metav1.ObjectMeta{ 1753 Name: "node1", 1754 }, 1755 Status: v1.NodeStatus{ 1756 Addresses: []v1.NodeAddress{ 1757 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1758 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1759 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 1760 }, 1761 }, 1762 }, { 1763 ObjectMeta: metav1.ObjectMeta{ 1764 Name: "node2", 1765 }, 1766 Status: v1.NodeStatus{ 1767 Addresses: []v1.NodeAddress{ 1768 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1769 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1770 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 1771 }, 1772 }, 1773 }}, 1774 }, 1775 { 1776 title: "annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes", 1777 svcNamespace: "testing", 1778 svcName: "foo", 1779 svcType: v1.ServiceTypeNodePort, 1780 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 1781 annotations: map[string]string{ 1782 hostnameAnnotationKey: "foo.example.org.", 1783 }, 1784 expected: []*endpoint.Endpoint{ 1785 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 1786 {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, 1787 {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 1788 }, 1789 nodes: []*v1.Node{{ 1790 ObjectMeta: metav1.ObjectMeta{ 1791 Name: "node1", 1792 }, 1793 Status: v1.NodeStatus{ 1794 Addresses: []v1.NodeAddress{ 1795 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1796 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 1797 }, 1798 }, 1799 }, { 1800 ObjectMeta: metav1.ObjectMeta{ 1801 Name: "node2", 1802 }, 1803 Status: v1.NodeStatus{ 1804 Addresses: []v1.NodeAddress{ 1805 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1806 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 1807 }, 1808 }, 1809 }}, 1810 }, 1811 { 1812 title: "annotated NodePort services with ExternalTrafficPolicy=Local return an endpoint with IP addresses of the cluster's nodes where pods is running only", 1813 svcNamespace: "testing", 1814 svcName: "foo", 1815 svcType: v1.ServiceTypeNodePort, 1816 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, 1817 annotations: map[string]string{ 1818 hostnameAnnotationKey: "foo.example.org.", 1819 }, 1820 expected: []*endpoint.Endpoint{ 1821 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 1822 {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, 1823 {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 1824 }, 1825 nodes: []*v1.Node{{ 1826 ObjectMeta: metav1.ObjectMeta{ 1827 Name: "node1", 1828 }, 1829 Status: v1.NodeStatus{ 1830 Addresses: []v1.NodeAddress{ 1831 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1832 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1833 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 1834 }, 1835 }, 1836 }, { 1837 ObjectMeta: metav1.ObjectMeta{ 1838 Name: "node2", 1839 }, 1840 Status: v1.NodeStatus{ 1841 Addresses: []v1.NodeAddress{ 1842 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1843 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1844 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 1845 }, 1846 }, 1847 }}, 1848 podNames: []string{"pod-0"}, 1849 nodeIndex: []int{1}, 1850 phases: []v1.PodPhase{v1.PodRunning}, 1851 conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionFalse}}, 1852 deletionTimestamp: []*metav1.Time{{}}, 1853 }, 1854 { 1855 title: "annotated NodePort services with ExternalTrafficPolicy=Local and multiple pods on a single node return an endpoint with unique IP addresses of the cluster's nodes where pods is running only", 1856 svcNamespace: "testing", 1857 svcName: "foo", 1858 svcType: v1.ServiceTypeNodePort, 1859 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, 1860 labels: map[string]string{}, 1861 annotations: map[string]string{ 1862 hostnameAnnotationKey: "foo.example.org.", 1863 }, 1864 expected: []*endpoint.Endpoint{ 1865 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 1866 {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, 1867 {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 1868 }, 1869 nodes: []*v1.Node{{ 1870 ObjectMeta: metav1.ObjectMeta{ 1871 Name: "node1", 1872 }, 1873 Status: v1.NodeStatus{ 1874 Addresses: []v1.NodeAddress{ 1875 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1876 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1877 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 1878 }, 1879 }, 1880 }, { 1881 ObjectMeta: metav1.ObjectMeta{ 1882 Name: "node2", 1883 }, 1884 Status: v1.NodeStatus{ 1885 Addresses: []v1.NodeAddress{ 1886 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1887 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1888 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 1889 }, 1890 }, 1891 }}, 1892 podNames: []string{"pod-0", "pod-1"}, 1893 nodeIndex: []int{1, 1}, 1894 phases: []v1.PodPhase{v1.PodRunning, v1.PodRunning}, 1895 conditions: []v1.PodCondition{ 1896 {Type: v1.PodReady, Status: v1.ConditionFalse}, 1897 {Type: v1.PodReady, Status: v1.ConditionFalse}, 1898 }, 1899 deletionTimestamp: []*metav1.Time{{}, {}}, 1900 }, 1901 { 1902 title: "annotated NodePort services with ExternalTrafficPolicy=Local return pods in Ready & Running state", 1903 svcNamespace: "testing", 1904 svcName: "foo", 1905 svcType: v1.ServiceTypeNodePort, 1906 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, 1907 labels: map[string]string{}, 1908 annotations: map[string]string{ 1909 hostnameAnnotationKey: "foo.example.org.", 1910 }, 1911 expected: []*endpoint.Endpoint{ 1912 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 1913 {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA}, 1914 }, 1915 nodes: []*v1.Node{{ 1916 ObjectMeta: metav1.ObjectMeta{ 1917 Name: "node1", 1918 }, 1919 Status: v1.NodeStatus{ 1920 Addresses: []v1.NodeAddress{ 1921 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1922 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1923 }, 1924 }, 1925 }, { 1926 ObjectMeta: metav1.ObjectMeta{ 1927 Name: "node2", 1928 }, 1929 Status: v1.NodeStatus{ 1930 Addresses: []v1.NodeAddress{ 1931 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1932 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1933 }, 1934 }, 1935 }}, 1936 podNames: []string{"pod-0", "pod-1"}, 1937 nodeIndex: []int{0, 1}, 1938 phases: []v1.PodPhase{v1.PodRunning, v1.PodRunning}, 1939 conditions: []v1.PodCondition{ 1940 {Type: v1.PodReady, Status: v1.ConditionTrue}, 1941 {Type: v1.PodReady, Status: v1.ConditionFalse}, 1942 }, 1943 deletionTimestamp: []*metav1.Time{{}, {}}, 1944 }, 1945 { 1946 title: "annotated NodePort services with ExternalTrafficPolicy=Local return pods in Ready & Running state & not in Terminating", 1947 svcNamespace: "testing", 1948 svcName: "foo", 1949 svcType: v1.ServiceTypeNodePort, 1950 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, 1951 labels: map[string]string{}, 1952 annotations: map[string]string{ 1953 hostnameAnnotationKey: "foo.example.org.", 1954 }, 1955 expected: []*endpoint.Endpoint{ 1956 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 1957 {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA}, 1958 }, 1959 nodes: []*v1.Node{{ 1960 ObjectMeta: metav1.ObjectMeta{ 1961 Name: "node1", 1962 }, 1963 Status: v1.NodeStatus{ 1964 Addresses: []v1.NodeAddress{ 1965 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 1966 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 1967 }, 1968 }, 1969 }, { 1970 ObjectMeta: metav1.ObjectMeta{ 1971 Name: "node2", 1972 }, 1973 Status: v1.NodeStatus{ 1974 Addresses: []v1.NodeAddress{ 1975 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 1976 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 1977 }, 1978 }, 1979 }, { 1980 ObjectMeta: metav1.ObjectMeta{ 1981 Name: "node3", 1982 }, 1983 Status: v1.NodeStatus{ 1984 Addresses: []v1.NodeAddress{ 1985 {Type: v1.NodeExternalIP, Address: "54.10.11.3"}, 1986 {Type: v1.NodeInternalIP, Address: "10.0.1.3"}, 1987 }, 1988 }, 1989 }}, 1990 podNames: []string{"pod-0", "pod-1", "pod-2"}, 1991 nodeIndex: []int{0, 1, 2}, 1992 phases: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning}, 1993 conditions: []v1.PodCondition{ 1994 {Type: v1.PodReady, Status: v1.ConditionTrue}, 1995 {Type: v1.PodReady, Status: v1.ConditionFalse}, 1996 {Type: v1.PodReady, Status: v1.ConditionTrue}, 1997 }, 1998 deletionTimestamp: []*metav1.Time{nil, nil, {}}, 1999 }, 2000 { 2001 title: "access=private annotation NodePort services return an endpoint with private IP addresses of the cluster's nodes", 2002 svcNamespace: "testing", 2003 svcName: "foo", 2004 svcType: v1.ServiceTypeNodePort, 2005 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 2006 labels: map[string]string{}, 2007 annotations: map[string]string{ 2008 hostnameAnnotationKey: "foo.example.org.", 2009 accessAnnotationKey: "private", 2010 }, 2011 expected: []*endpoint.Endpoint{ 2012 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 2013 {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, 2014 {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 2015 }, 2016 nodes: []*v1.Node{{ 2017 ObjectMeta: metav1.ObjectMeta{ 2018 Name: "node1", 2019 }, 2020 Status: v1.NodeStatus{ 2021 Addresses: []v1.NodeAddress{ 2022 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 2023 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 2024 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 2025 }, 2026 }, 2027 }, { 2028 ObjectMeta: metav1.ObjectMeta{ 2029 Name: "node2", 2030 }, 2031 Status: v1.NodeStatus{ 2032 Addresses: []v1.NodeAddress{ 2033 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 2034 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 2035 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 2036 }, 2037 }, 2038 }}, 2039 }, 2040 { 2041 title: "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes", 2042 svcNamespace: "testing", 2043 svcName: "foo", 2044 svcType: v1.ServiceTypeNodePort, 2045 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 2046 labels: map[string]string{}, 2047 annotations: map[string]string{ 2048 hostnameAnnotationKey: "foo.example.org.", 2049 accessAnnotationKey: "public", 2050 }, 2051 expected: []*endpoint.Endpoint{ 2052 {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, 2053 {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, 2054 {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, 2055 }, 2056 nodes: []*v1.Node{{ 2057 ObjectMeta: metav1.ObjectMeta{ 2058 Name: "node1", 2059 }, 2060 Status: v1.NodeStatus{ 2061 Addresses: []v1.NodeAddress{ 2062 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 2063 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 2064 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 2065 }, 2066 }, 2067 }, { 2068 ObjectMeta: metav1.ObjectMeta{ 2069 Name: "node2", 2070 }, 2071 Status: v1.NodeStatus{ 2072 Addresses: []v1.NodeAddress{ 2073 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 2074 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 2075 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 2076 }, 2077 }, 2078 }}, 2079 }, 2080 { 2081 title: "node port services annotated DNS Controller annotations return an endpoint where all targets has the node role", 2082 svcNamespace: "testing", 2083 svcName: "foo", 2084 svcType: v1.ServiceTypeNodePort, 2085 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 2086 compatibility: "kops-dns-controller", 2087 labels: map[string]string{}, 2088 annotations: map[string]string{ 2089 kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", 2090 }, 2091 expected: []*endpoint.Endpoint{ 2092 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}}, 2093 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}}, 2094 {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}}, 2095 {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}}, 2096 }, 2097 nodes: []*v1.Node{{ 2098 ObjectMeta: metav1.ObjectMeta{ 2099 Name: "node1", 2100 Labels: map[string]string{ 2101 "node-role.kubernetes.io/node": "", 2102 }, 2103 }, 2104 Status: v1.NodeStatus{ 2105 Addresses: []v1.NodeAddress{ 2106 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 2107 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 2108 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 2109 }, 2110 }, 2111 }, { 2112 ObjectMeta: metav1.ObjectMeta{ 2113 Name: "node2", 2114 Labels: map[string]string{ 2115 "node-role.kubernetes.io/control-plane": "", 2116 }, 2117 }, 2118 Status: v1.NodeStatus{ 2119 Addresses: []v1.NodeAddress{ 2120 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 2121 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 2122 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 2123 }, 2124 }, 2125 }}, 2126 }, 2127 { 2128 title: "node port services annotated with internal DNS Controller annotations return an endpoint in compatibility mode", 2129 svcNamespace: "testing", 2130 svcName: "foo", 2131 svcType: v1.ServiceTypeNodePort, 2132 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 2133 compatibility: "kops-dns-controller", 2134 annotations: map[string]string{ 2135 kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", 2136 }, 2137 expected: []*endpoint.Endpoint{ 2138 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, 2139 {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, 2140 {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, 2141 {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, 2142 }, 2143 nodes: []*v1.Node{{ 2144 ObjectMeta: metav1.ObjectMeta{ 2145 Name: "node1", 2146 Labels: map[string]string{ 2147 "node-role.kubernetes.io/node": "", 2148 }, 2149 }, 2150 Status: v1.NodeStatus{ 2151 Addresses: []v1.NodeAddress{ 2152 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 2153 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 2154 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 2155 }, 2156 }, 2157 }, { 2158 ObjectMeta: metav1.ObjectMeta{ 2159 Name: "node2", 2160 Labels: map[string]string{ 2161 "node-role.kubernetes.io/node": "", 2162 }, 2163 }, 2164 Status: v1.NodeStatus{ 2165 Addresses: []v1.NodeAddress{ 2166 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 2167 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 2168 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 2169 }, 2170 }, 2171 }}, 2172 }, 2173 { 2174 title: "node port services annotated with external DNS Controller annotations return an endpoint in compatibility mode", 2175 svcNamespace: "testing", 2176 svcName: "foo", 2177 svcType: v1.ServiceTypeNodePort, 2178 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 2179 compatibility: "kops-dns-controller", 2180 annotations: map[string]string{ 2181 kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", 2182 }, 2183 expected: []*endpoint.Endpoint{ 2184 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, 2185 {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, 2186 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, 2187 {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, 2188 }, 2189 nodes: []*v1.Node{{ 2190 ObjectMeta: metav1.ObjectMeta{ 2191 Name: "node1", 2192 Labels: map[string]string{ 2193 "node-role.kubernetes.io/node": "", 2194 }, 2195 }, 2196 Status: v1.NodeStatus{ 2197 Addresses: []v1.NodeAddress{ 2198 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 2199 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 2200 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 2201 }, 2202 }, 2203 }, { 2204 ObjectMeta: metav1.ObjectMeta{ 2205 Name: "node2", 2206 Labels: map[string]string{ 2207 "node-role.kubernetes.io/node": "", 2208 }, 2209 }, 2210 Status: v1.NodeStatus{ 2211 Addresses: []v1.NodeAddress{ 2212 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 2213 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 2214 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 2215 }, 2216 }, 2217 }}, 2218 }, 2219 { 2220 title: "node port services annotated with both kops dns controller annotations return an empty set of addons", 2221 svcNamespace: "testing", 2222 svcName: "foo", 2223 svcType: v1.ServiceTypeNodePort, 2224 svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, 2225 compatibility: "kops-dns-controller", 2226 labels: map[string]string{}, 2227 annotations: map[string]string{ 2228 kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", 2229 kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", 2230 }, 2231 expected: []*endpoint.Endpoint{}, 2232 nodes: []*v1.Node{{ 2233 ObjectMeta: metav1.ObjectMeta{ 2234 Name: "node1", 2235 Labels: map[string]string{ 2236 "node-role.kubernetes.io/node": "", 2237 }, 2238 }, 2239 Status: v1.NodeStatus{ 2240 Addresses: []v1.NodeAddress{ 2241 {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, 2242 {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, 2243 {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, 2244 }, 2245 }, 2246 }, { 2247 ObjectMeta: metav1.ObjectMeta{ 2248 Name: "node2", 2249 Labels: map[string]string{ 2250 "node-role.kubernetes.io/node": "", 2251 }, 2252 }, 2253 Status: v1.NodeStatus{ 2254 Addresses: []v1.NodeAddress{ 2255 {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, 2256 {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, 2257 {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, 2258 }, 2259 }, 2260 }}, 2261 }, 2262 } { 2263 tc := tc 2264 t.Run(tc.title, func(t *testing.T) { 2265 t.Parallel() 2266 2267 // Create a Kubernetes testing client 2268 kubernetes := fake.NewSimpleClientset() 2269 2270 // Create the nodes 2271 for _, node := range tc.nodes { 2272 if _, err := kubernetes.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{}); err != nil { 2273 t.Fatal(err) 2274 } 2275 } 2276 2277 // Create pods 2278 for i, podname := range tc.podNames { 2279 pod := &v1.Pod{ 2280 Spec: v1.PodSpec{ 2281 Containers: []v1.Container{}, 2282 Hostname: podname, 2283 NodeName: tc.nodes[tc.nodeIndex[i]].Name, 2284 }, 2285 ObjectMeta: metav1.ObjectMeta{ 2286 Namespace: tc.svcNamespace, 2287 Name: podname, 2288 Labels: tc.labels, 2289 Annotations: tc.annotations, 2290 DeletionTimestamp: tc.deletionTimestamp[i], 2291 }, 2292 Status: v1.PodStatus{ 2293 Phase: tc.phases[i], 2294 Conditions: []v1.PodCondition{tc.conditions[i]}, 2295 }, 2296 } 2297 2298 _, err := kubernetes.CoreV1().Pods(tc.svcNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) 2299 require.NoError(t, err) 2300 } 2301 2302 // Create a service to test against 2303 service := &v1.Service{ 2304 Spec: v1.ServiceSpec{ 2305 Type: tc.svcType, 2306 ExternalTrafficPolicy: tc.svcTrafficPolicy, 2307 Ports: []v1.ServicePort{ 2308 { 2309 NodePort: 30192, 2310 }, 2311 }, 2312 }, 2313 ObjectMeta: metav1.ObjectMeta{ 2314 Namespace: tc.svcNamespace, 2315 Name: tc.svcName, 2316 Labels: tc.labels, 2317 Annotations: tc.annotations, 2318 }, 2319 } 2320 2321 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 2322 require.NoError(t, err) 2323 2324 // Create our object under test and get the endpoints. 2325 client, _ := NewServiceSource( 2326 context.TODO(), 2327 kubernetes, 2328 tc.targetNamespace, 2329 tc.annotationFilter, 2330 tc.fqdnTemplate, 2331 false, 2332 tc.compatibility, 2333 true, 2334 false, 2335 false, 2336 []string{}, 2337 tc.ignoreHostnameAnnotation, 2338 labels.Everything(), 2339 false, 2340 ) 2341 require.NoError(t, err) 2342 2343 endpoints, err := client.Endpoints(context.Background()) 2344 if tc.expectError { 2345 require.Error(t, err) 2346 } else { 2347 require.NoError(t, err) 2348 } 2349 2350 // Validate returned endpoints against desired endpoints. 2351 validateEndpoints(t, endpoints, tc.expected) 2352 }) 2353 } 2354 } 2355 2356 // TestHeadlessServices tests that headless services generate the correct endpoints. 2357 func TestHeadlessServices(t *testing.T) { 2358 t.Parallel() 2359 2360 for _, tc := range []struct { 2361 title string 2362 targetNamespace string 2363 svcNamespace string 2364 svcName string 2365 svcType v1.ServiceType 2366 compatibility string 2367 fqdnTemplate string 2368 ignoreHostnameAnnotation bool 2369 labels map[string]string 2370 svcAnnotations map[string]string 2371 podAnnotations map[string]string 2372 clusterIP string 2373 podIPs []string 2374 hostIPs []string 2375 selector map[string]string 2376 lbs []string 2377 podnames []string 2378 hostnames []string 2379 podsReady []bool 2380 publishNotReadyAddresses bool 2381 nodes []v1.Node 2382 expected []*endpoint.Endpoint 2383 expectError bool 2384 }{ 2385 { 2386 "annotated Headless services return IPv4 endpoints for each selected Pod", 2387 "", 2388 "testing", 2389 "foo", 2390 v1.ServiceTypeClusterIP, 2391 "", 2392 "", 2393 false, 2394 map[string]string{"component": "foo"}, 2395 map[string]string{ 2396 hostnameAnnotationKey: "service.example.org", 2397 }, 2398 map[string]string{}, 2399 v1.ClusterIPNone, 2400 []string{"1.1.1.1", "1.1.1.2"}, 2401 []string{"", ""}, 2402 map[string]string{ 2403 "component": "foo", 2404 }, 2405 []string{}, 2406 []string{"foo-0", "foo-1"}, 2407 []string{"foo-0", "foo-1"}, 2408 []bool{true, true}, 2409 false, 2410 []v1.Node{}, 2411 []*endpoint.Endpoint{ 2412 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 2413 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, 2414 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 2415 }, 2416 false, 2417 }, 2418 { 2419 "annotated Headless services return IPv6 endpoints for each selected Pod", 2420 "", 2421 "testing", 2422 "foo", 2423 v1.ServiceTypeClusterIP, 2424 "", 2425 "", 2426 false, 2427 map[string]string{"component": "foo"}, 2428 map[string]string{ 2429 hostnameAnnotationKey: "service.example.org", 2430 }, 2431 map[string]string{}, 2432 v1.ClusterIPNone, 2433 []string{"2001:db8::1", "2001:db8::2"}, 2434 []string{"", ""}, 2435 map[string]string{ 2436 "component": "foo", 2437 }, 2438 []string{}, 2439 []string{"foo-0", "foo-1"}, 2440 []string{"foo-0", "foo-1"}, 2441 []bool{true, true}, 2442 false, 2443 []v1.Node{}, 2444 []*endpoint.Endpoint{ 2445 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}}, 2446 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}}, 2447 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, 2448 }, 2449 false, 2450 }, 2451 { 2452 "hostname annotated Headless services are ignored", 2453 "", 2454 "testing", 2455 "foo", 2456 v1.ServiceTypeClusterIP, 2457 "", 2458 "", 2459 true, 2460 map[string]string{"component": "foo"}, 2461 map[string]string{ 2462 hostnameAnnotationKey: "service.example.org", 2463 }, 2464 map[string]string{}, 2465 v1.ClusterIPNone, 2466 []string{"1.1.1.1", "1.1.1.2"}, 2467 []string{"", ""}, 2468 map[string]string{ 2469 "component": "foo", 2470 }, 2471 []string{}, 2472 []string{"foo-0", "foo-1"}, 2473 []string{"foo-0", "foo-1"}, 2474 []bool{true, true}, 2475 false, 2476 []v1.Node{}, 2477 []*endpoint.Endpoint{}, 2478 false, 2479 }, 2480 { 2481 "annotated Headless services return IPv4 endpoints with TTL for each selected Pod", 2482 "", 2483 "testing", 2484 "foo", 2485 v1.ServiceTypeClusterIP, 2486 "", 2487 "", 2488 false, 2489 map[string]string{"component": "foo"}, 2490 map[string]string{ 2491 hostnameAnnotationKey: "service.example.org", 2492 ttlAnnotationKey: "1", 2493 }, 2494 map[string]string{}, 2495 v1.ClusterIPNone, 2496 []string{"1.1.1.1", "1.1.1.2"}, 2497 []string{"", ""}, 2498 map[string]string{ 2499 "component": "foo", 2500 }, 2501 []string{}, 2502 []string{"foo-0", "foo-1"}, 2503 []string{"foo-0", "foo-1"}, 2504 []bool{true, true}, 2505 false, 2506 []v1.Node{}, 2507 []*endpoint.Endpoint{ 2508 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, 2509 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, 2510 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, 2511 }, 2512 false, 2513 }, 2514 { 2515 "annotated Headless services return IPv6 endpoints with TTL for each selected Pod", 2516 "", 2517 "testing", 2518 "foo", 2519 v1.ServiceTypeClusterIP, 2520 "", 2521 "", 2522 false, 2523 map[string]string{"component": "foo"}, 2524 map[string]string{ 2525 hostnameAnnotationKey: "service.example.org", 2526 ttlAnnotationKey: "1", 2527 }, 2528 map[string]string{}, 2529 v1.ClusterIPNone, 2530 []string{"2001:db8::1", "2001:db8::2"}, 2531 []string{"", ""}, 2532 map[string]string{ 2533 "component": "foo", 2534 }, 2535 []string{}, 2536 []string{"foo-0", "foo-1"}, 2537 []string{"foo-0", "foo-1"}, 2538 []bool{true, true}, 2539 false, 2540 []v1.Node{}, 2541 []*endpoint.Endpoint{ 2542 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)}, 2543 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)}, 2544 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)}, 2545 }, 2546 false, 2547 }, 2548 { 2549 "annotated Headless services return endpoints for each selected Pod, which are in running state", 2550 "", 2551 "testing", 2552 "foo", 2553 v1.ServiceTypeClusterIP, 2554 "", 2555 "", 2556 false, 2557 map[string]string{"component": "foo"}, 2558 map[string]string{ 2559 hostnameAnnotationKey: "service.example.org", 2560 }, 2561 map[string]string{}, 2562 v1.ClusterIPNone, 2563 []string{"1.1.1.1", "1.1.1.2"}, 2564 []string{"", ""}, 2565 map[string]string{ 2566 "component": "foo", 2567 }, 2568 []string{}, 2569 []string{"foo-0", "foo-1"}, 2570 []string{"foo-0", "foo-1"}, 2571 []bool{true, false}, 2572 false, 2573 []v1.Node{}, 2574 []*endpoint.Endpoint{ 2575 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 2576 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 2577 }, 2578 false, 2579 }, 2580 { 2581 "annotated Headless services return endpoints for all Pod if publishNotReadyAddresses is set", 2582 "", 2583 "testing", 2584 "foo", 2585 v1.ServiceTypeClusterIP, 2586 "", 2587 "", 2588 false, 2589 map[string]string{"component": "foo"}, 2590 map[string]string{ 2591 hostnameAnnotationKey: "service.example.org", 2592 }, 2593 map[string]string{}, 2594 v1.ClusterIPNone, 2595 []string{"1.1.1.1", "1.1.1.2"}, 2596 []string{"", ""}, 2597 map[string]string{ 2598 "component": "foo", 2599 }, 2600 []string{}, 2601 []string{"foo-0", "foo-1"}, 2602 []string{"foo-0", "foo-1"}, 2603 []bool{true, false}, 2604 true, 2605 []v1.Node{}, 2606 []*endpoint.Endpoint{ 2607 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 2608 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, 2609 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 2610 }, 2611 false, 2612 }, 2613 { 2614 "annotated Headless services return endpoints for pods missing hostname", 2615 "", 2616 "testing", 2617 "foo", 2618 v1.ServiceTypeClusterIP, 2619 "", 2620 "", 2621 false, 2622 map[string]string{"component": "foo"}, 2623 map[string]string{ 2624 hostnameAnnotationKey: "service.example.org", 2625 }, 2626 map[string]string{}, 2627 v1.ClusterIPNone, 2628 []string{"1.1.1.1", "1.1.1.2"}, 2629 []string{"", ""}, 2630 map[string]string{ 2631 "component": "foo", 2632 }, 2633 []string{}, 2634 []string{"foo-0", "foo-1"}, 2635 []string{"", ""}, 2636 []bool{true, true}, 2637 false, 2638 []v1.Node{}, 2639 []*endpoint.Endpoint{ 2640 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 2641 }, 2642 false, 2643 }, 2644 { 2645 "annotated Headless services return only a unique set of IPv4 targets", 2646 "", 2647 "testing", 2648 "foo", 2649 v1.ServiceTypeClusterIP, 2650 "", 2651 "", 2652 false, 2653 map[string]string{"component": "foo"}, 2654 map[string]string{ 2655 hostnameAnnotationKey: "service.example.org", 2656 }, 2657 map[string]string{}, 2658 v1.ClusterIPNone, 2659 []string{"1.1.1.1", "1.1.1.1", "1.1.1.2"}, 2660 []string{"", "", ""}, 2661 map[string]string{ 2662 "component": "foo", 2663 }, 2664 []string{}, 2665 []string{"foo-0", "foo-1", "foo-3"}, 2666 []string{"", "", ""}, 2667 []bool{true, true, true}, 2668 false, 2669 []v1.Node{}, 2670 []*endpoint.Endpoint{ 2671 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 2672 }, 2673 false, 2674 }, 2675 { 2676 "annotated Headless services return only a unique set of IPv6 targets", 2677 "", 2678 "testing", 2679 "foo", 2680 v1.ServiceTypeClusterIP, 2681 "", 2682 "", 2683 false, 2684 map[string]string{"component": "foo"}, 2685 map[string]string{ 2686 hostnameAnnotationKey: "service.example.org", 2687 }, 2688 map[string]string{}, 2689 v1.ClusterIPNone, 2690 []string{"2001:db8::1", "2001:db8::1", "2001:db8::2"}, 2691 []string{"", "", ""}, 2692 map[string]string{ 2693 "component": "foo", 2694 }, 2695 []string{}, 2696 []string{"foo-0", "foo-1", "foo-3"}, 2697 []string{"", "", ""}, 2698 []bool{true, true, true}, 2699 false, 2700 []v1.Node{}, 2701 []*endpoint.Endpoint{ 2702 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, 2703 }, 2704 false, 2705 }, 2706 { 2707 "annotated Headless services return IPv4 targets from pod annotation", 2708 "", 2709 "testing", 2710 "foo", 2711 v1.ServiceTypeClusterIP, 2712 "", 2713 "", 2714 false, 2715 map[string]string{"component": "foo"}, 2716 map[string]string{ 2717 hostnameAnnotationKey: "service.example.org", 2718 }, 2719 map[string]string{ 2720 targetAnnotationKey: "1.2.3.4", 2721 }, 2722 v1.ClusterIPNone, 2723 []string{"1.1.1.1"}, 2724 []string{""}, 2725 map[string]string{ 2726 "component": "foo", 2727 }, 2728 []string{}, 2729 []string{"foo"}, 2730 []string{"", "", ""}, 2731 []bool{true, true, true}, 2732 false, 2733 []v1.Node{}, 2734 []*endpoint.Endpoint{ 2735 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 2736 }, 2737 false, 2738 }, 2739 { 2740 "annotated Headless services return IPv6 targets from pod annotation", 2741 "", 2742 "testing", 2743 "foo", 2744 v1.ServiceTypeClusterIP, 2745 "", 2746 "", 2747 false, 2748 map[string]string{"component": "foo"}, 2749 map[string]string{ 2750 hostnameAnnotationKey: "service.example.org", 2751 }, 2752 map[string]string{ 2753 targetAnnotationKey: "2001:db8::4", 2754 }, 2755 v1.ClusterIPNone, 2756 []string{"2001:db8::1"}, 2757 []string{""}, 2758 map[string]string{ 2759 "component": "foo", 2760 }, 2761 []string{}, 2762 []string{"foo"}, 2763 []string{"", "", ""}, 2764 []bool{true, true, true}, 2765 false, 2766 []v1.Node{}, 2767 []*endpoint.Endpoint{ 2768 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, 2769 }, 2770 false, 2771 }, 2772 { 2773 "annotated Headless services return IPv4 targets from node external IP if endpoints-type annotation is set", 2774 "", 2775 "testing", 2776 "foo", 2777 v1.ServiceTypeClusterIP, 2778 "", 2779 "", 2780 false, 2781 map[string]string{"component": "foo"}, 2782 map[string]string{ 2783 hostnameAnnotationKey: "service.example.org", 2784 endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP, 2785 }, 2786 map[string]string{}, 2787 v1.ClusterIPNone, 2788 []string{"1.1.1.1"}, 2789 []string{""}, 2790 map[string]string{ 2791 "component": "foo", 2792 }, 2793 []string{}, 2794 []string{"foo"}, 2795 []string{"", "", ""}, 2796 []bool{true, true, true}, 2797 false, 2798 []v1.Node{ 2799 { 2800 Status: v1.NodeStatus{ 2801 Addresses: []v1.NodeAddress{ 2802 { 2803 Type: v1.NodeExternalIP, 2804 Address: "1.2.3.4", 2805 }, 2806 }, 2807 }, 2808 }, 2809 }, 2810 []*endpoint.Endpoint{ 2811 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 2812 }, 2813 false, 2814 }, 2815 { 2816 "annotated Headless services return IPv6 targets from node external IP if endpoints-type annotation is set", 2817 "", 2818 "testing", 2819 "foo", 2820 v1.ServiceTypeClusterIP, 2821 "", 2822 "", 2823 false, 2824 map[string]string{"component": "foo"}, 2825 map[string]string{ 2826 hostnameAnnotationKey: "service.example.org", 2827 endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP, 2828 }, 2829 map[string]string{}, 2830 v1.ClusterIPNone, 2831 []string{"2001:db8::1"}, 2832 []string{""}, 2833 map[string]string{ 2834 "component": "foo", 2835 }, 2836 []string{}, 2837 []string{"foo"}, 2838 []string{"", "", ""}, 2839 []bool{true, true, true}, 2840 false, 2841 []v1.Node{ 2842 { 2843 Status: v1.NodeStatus{ 2844 Addresses: []v1.NodeAddress{ 2845 { 2846 Type: v1.NodeInternalIP, 2847 Address: "2001:db8::4", 2848 }, 2849 }, 2850 }, 2851 }, 2852 }, 2853 []*endpoint.Endpoint{ 2854 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, 2855 }, 2856 false, 2857 }, 2858 { 2859 "annotated Headless services return dual-stack targets from node external IP if endpoints-type annotation is set", 2860 "", 2861 "testing", 2862 "foo", 2863 v1.ServiceTypeClusterIP, 2864 "", 2865 "", 2866 false, 2867 map[string]string{"component": "foo"}, 2868 map[string]string{ 2869 hostnameAnnotationKey: "service.example.org", 2870 endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP, 2871 }, 2872 map[string]string{}, 2873 v1.ClusterIPNone, 2874 []string{"1.1.1.1"}, 2875 []string{""}, 2876 map[string]string{ 2877 "component": "foo", 2878 }, 2879 []string{}, 2880 []string{"foo"}, 2881 []string{"", "", ""}, 2882 []bool{true, true, true}, 2883 false, 2884 []v1.Node{ 2885 { 2886 Status: v1.NodeStatus{ 2887 Addresses: []v1.NodeAddress{ 2888 { 2889 Type: v1.NodeExternalIP, 2890 Address: "1.2.3.4", 2891 }, 2892 { 2893 Type: v1.NodeInternalIP, 2894 Address: "2001:db8::4", 2895 }, 2896 }, 2897 }, 2898 }, 2899 }, 2900 []*endpoint.Endpoint{ 2901 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 2902 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, 2903 }, 2904 false, 2905 }, 2906 { 2907 "annotated Headless services return IPv4 targets from hostIP if endpoints-type annotation is set", 2908 "", 2909 "testing", 2910 "foo", 2911 v1.ServiceTypeClusterIP, 2912 "", 2913 "", 2914 false, 2915 map[string]string{"component": "foo"}, 2916 map[string]string{ 2917 hostnameAnnotationKey: "service.example.org", 2918 endpointsTypeAnnotationKey: EndpointsTypeHostIP, 2919 }, 2920 map[string]string{}, 2921 v1.ClusterIPNone, 2922 []string{"1.1.1.1"}, 2923 []string{"1.2.3.4"}, 2924 map[string]string{ 2925 "component": "foo", 2926 }, 2927 []string{}, 2928 []string{"foo"}, 2929 []string{"", "", ""}, 2930 []bool{true, true, true}, 2931 false, 2932 []v1.Node{}, 2933 []*endpoint.Endpoint{ 2934 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, 2935 }, 2936 false, 2937 }, 2938 { 2939 "annotated Headless services return IPv6 targets from hostIP if endpoints-type annotation is set", 2940 "", 2941 "testing", 2942 "foo", 2943 v1.ServiceTypeClusterIP, 2944 "", 2945 "", 2946 false, 2947 map[string]string{"component": "foo"}, 2948 map[string]string{ 2949 hostnameAnnotationKey: "service.example.org", 2950 endpointsTypeAnnotationKey: EndpointsTypeHostIP, 2951 }, 2952 map[string]string{}, 2953 v1.ClusterIPNone, 2954 []string{"2001:db8::1"}, 2955 []string{"2001:db8::4"}, 2956 map[string]string{ 2957 "component": "foo", 2958 }, 2959 []string{}, 2960 []string{"foo"}, 2961 []string{"", "", ""}, 2962 []bool{true, true, true}, 2963 false, 2964 []v1.Node{}, 2965 []*endpoint.Endpoint{ 2966 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, 2967 }, 2968 false, 2969 }, 2970 } { 2971 tc := tc 2972 t.Run(tc.title, func(t *testing.T) { 2973 t.Parallel() 2974 2975 // Create a Kubernetes testing client 2976 kubernetes := fake.NewSimpleClientset() 2977 2978 service := &v1.Service{ 2979 Spec: v1.ServiceSpec{ 2980 Type: tc.svcType, 2981 ClusterIP: tc.clusterIP, 2982 Selector: tc.selector, 2983 PublishNotReadyAddresses: tc.publishNotReadyAddresses, 2984 }, 2985 ObjectMeta: metav1.ObjectMeta{ 2986 Namespace: tc.svcNamespace, 2987 Name: tc.svcName, 2988 Labels: tc.labels, 2989 Annotations: tc.svcAnnotations, 2990 }, 2991 Status: v1.ServiceStatus{}, 2992 } 2993 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 2994 require.NoError(t, err) 2995 2996 var addresses, notReadyAddresses []v1.EndpointAddress 2997 for i, podname := range tc.podnames { 2998 pod := &v1.Pod{ 2999 Spec: v1.PodSpec{ 3000 Containers: []v1.Container{}, 3001 Hostname: tc.hostnames[i], 3002 }, 3003 ObjectMeta: metav1.ObjectMeta{ 3004 Namespace: tc.svcNamespace, 3005 Name: podname, 3006 Labels: tc.labels, 3007 Annotations: tc.podAnnotations, 3008 }, 3009 Status: v1.PodStatus{ 3010 PodIP: tc.podIPs[i], 3011 HostIP: tc.hostIPs[i], 3012 }, 3013 } 3014 3015 _, err = kubernetes.CoreV1().Pods(tc.svcNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) 3016 require.NoError(t, err) 3017 3018 address := v1.EndpointAddress{ 3019 IP: tc.podIPs[i], 3020 TargetRef: &v1.ObjectReference{ 3021 APIVersion: "", 3022 Kind: "Pod", 3023 Name: podname, 3024 }, 3025 } 3026 if tc.podsReady[i] { 3027 addresses = append(addresses, address) 3028 } else { 3029 notReadyAddresses = append(notReadyAddresses, address) 3030 } 3031 } 3032 endpointsObject := &v1.Endpoints{ 3033 ObjectMeta: metav1.ObjectMeta{ 3034 Namespace: tc.svcNamespace, 3035 Name: tc.svcName, 3036 Labels: tc.labels, 3037 }, 3038 Subsets: []v1.EndpointSubset{ 3039 { 3040 Addresses: addresses, 3041 NotReadyAddresses: notReadyAddresses, 3042 }, 3043 }, 3044 } 3045 _, err = kubernetes.CoreV1().Endpoints(tc.svcNamespace).Create(context.Background(), endpointsObject, metav1.CreateOptions{}) 3046 require.NoError(t, err) 3047 for _, node := range tc.nodes { 3048 _, err = kubernetes.CoreV1().Nodes().Create(context.Background(), &node, metav1.CreateOptions{}) 3049 require.NoError(t, err) 3050 } 3051 3052 // Create our object under test and get the endpoints. 3053 client, _ := NewServiceSource( 3054 context.TODO(), 3055 kubernetes, 3056 tc.targetNamespace, 3057 "", 3058 tc.fqdnTemplate, 3059 false, 3060 tc.compatibility, 3061 true, 3062 false, 3063 false, 3064 []string{}, 3065 tc.ignoreHostnameAnnotation, 3066 labels.Everything(), 3067 false, 3068 ) 3069 require.NoError(t, err) 3070 3071 endpoints, err := client.Endpoints(context.Background()) 3072 if tc.expectError { 3073 require.Error(t, err) 3074 } else { 3075 require.NoError(t, err) 3076 } 3077 3078 // Validate returned endpoints against desired endpoints. 3079 validateEndpoints(t, endpoints, tc.expected) 3080 }) 3081 } 3082 } 3083 3084 // TestHeadlessServices tests that headless services generate the correct endpoints. 3085 func TestHeadlessServicesHostIP(t *testing.T) { 3086 t.Parallel() 3087 3088 for _, tc := range []struct { 3089 title string 3090 targetNamespace string 3091 svcNamespace string 3092 svcName string 3093 svcType v1.ServiceType 3094 compatibility string 3095 fqdnTemplate string 3096 ignoreHostnameAnnotation bool 3097 labels map[string]string 3098 annotations map[string]string 3099 clusterIP string 3100 hostIPs []string 3101 selector map[string]string 3102 lbs []string 3103 podnames []string 3104 hostnames []string 3105 podsReady []bool 3106 targetRefs []*v1.ObjectReference 3107 publishNotReadyAddresses bool 3108 expected []*endpoint.Endpoint 3109 expectError bool 3110 }{ 3111 { 3112 "annotated Headless services return IPv4 endpoints for each selected Pod", 3113 "", 3114 "testing", 3115 "foo", 3116 v1.ServiceTypeClusterIP, 3117 "", 3118 "", 3119 false, 3120 map[string]string{"component": "foo"}, 3121 map[string]string{ 3122 hostnameAnnotationKey: "service.example.org", 3123 }, 3124 v1.ClusterIPNone, 3125 []string{"1.1.1.1", "1.1.1.2"}, 3126 map[string]string{ 3127 "component": "foo", 3128 }, 3129 []string{}, 3130 []string{"foo-0", "foo-1"}, 3131 []string{"foo-0", "foo-1"}, 3132 []bool{true, true}, 3133 []*v1.ObjectReference{ 3134 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3135 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3136 }, 3137 false, 3138 []*endpoint.Endpoint{ 3139 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 3140 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, 3141 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 3142 }, 3143 false, 3144 }, 3145 { 3146 "annotated Headless services return IPv6 endpoints for each selected Pod", 3147 "", 3148 "testing", 3149 "foo", 3150 v1.ServiceTypeClusterIP, 3151 "", 3152 "", 3153 false, 3154 map[string]string{"component": "foo"}, 3155 map[string]string{ 3156 hostnameAnnotationKey: "service.example.org", 3157 }, 3158 v1.ClusterIPNone, 3159 []string{"2001:db8::1", "2001:db8::2"}, 3160 map[string]string{ 3161 "component": "foo", 3162 }, 3163 []string{}, 3164 []string{"foo-0", "foo-1"}, 3165 []string{"foo-0", "foo-1"}, 3166 []bool{true, true}, 3167 []*v1.ObjectReference{ 3168 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3169 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3170 }, 3171 false, 3172 []*endpoint.Endpoint{ 3173 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}}, 3174 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}}, 3175 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, 3176 }, 3177 false, 3178 }, 3179 { 3180 "hostname annotated Headless services are ignored", 3181 "", 3182 "testing", 3183 "foo", 3184 v1.ServiceTypeClusterIP, 3185 "", 3186 "", 3187 true, 3188 map[string]string{"component": "foo"}, 3189 map[string]string{ 3190 hostnameAnnotationKey: "service.example.org", 3191 }, 3192 v1.ClusterIPNone, 3193 []string{"1.1.1.1", "1.1.1.2"}, 3194 map[string]string{ 3195 "component": "foo", 3196 }, 3197 []string{}, 3198 []string{"foo-0", "foo-1"}, 3199 []string{"foo-0", "foo-1"}, 3200 []bool{true, true}, 3201 []*v1.ObjectReference{ 3202 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3203 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3204 }, 3205 false, 3206 []*endpoint.Endpoint{}, 3207 false, 3208 }, 3209 { 3210 "annotated Headless services return IPv4 endpoints with TTL for each selected Pod", 3211 "", 3212 "testing", 3213 "foo", 3214 v1.ServiceTypeClusterIP, 3215 "", 3216 "", 3217 false, 3218 map[string]string{"component": "foo"}, 3219 map[string]string{ 3220 hostnameAnnotationKey: "service.example.org", 3221 ttlAnnotationKey: "1", 3222 }, 3223 v1.ClusterIPNone, 3224 []string{"1.1.1.1", "1.1.1.2"}, 3225 map[string]string{ 3226 "component": "foo", 3227 }, 3228 []string{}, 3229 []string{"foo-0", "foo-1"}, 3230 []string{"foo-0", "foo-1"}, 3231 []bool{true, true}, 3232 []*v1.ObjectReference{ 3233 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3234 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3235 }, 3236 false, 3237 []*endpoint.Endpoint{ 3238 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, 3239 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, 3240 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, 3241 }, 3242 false, 3243 }, 3244 { 3245 "annotated Headless services return IPv6 endpoints with TTL for each selected Pod", 3246 "", 3247 "testing", 3248 "foo", 3249 v1.ServiceTypeClusterIP, 3250 "", 3251 "", 3252 false, 3253 map[string]string{"component": "foo"}, 3254 map[string]string{ 3255 hostnameAnnotationKey: "service.example.org", 3256 ttlAnnotationKey: "1", 3257 }, 3258 v1.ClusterIPNone, 3259 []string{"2001:db8::1", "2001:db8::2"}, 3260 map[string]string{ 3261 "component": "foo", 3262 }, 3263 []string{}, 3264 []string{"foo-0", "foo-1"}, 3265 []string{"foo-0", "foo-1"}, 3266 []bool{true, true}, 3267 []*v1.ObjectReference{ 3268 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3269 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3270 }, 3271 false, 3272 []*endpoint.Endpoint{ 3273 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)}, 3274 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)}, 3275 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)}, 3276 }, 3277 false, 3278 }, 3279 { 3280 "annotated Headless services return endpoints for each selected Pod, which are in running state", 3281 "", 3282 "testing", 3283 "foo", 3284 v1.ServiceTypeClusterIP, 3285 "", 3286 "", 3287 false, 3288 map[string]string{"component": "foo"}, 3289 map[string]string{ 3290 hostnameAnnotationKey: "service.example.org", 3291 }, 3292 v1.ClusterIPNone, 3293 []string{"1.1.1.1", "1.1.1.2"}, 3294 map[string]string{ 3295 "component": "foo", 3296 }, 3297 []string{}, 3298 []string{"foo-0", "foo-1"}, 3299 []string{"foo-0", "foo-1"}, 3300 []bool{true, false}, 3301 []*v1.ObjectReference{ 3302 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3303 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3304 }, 3305 false, 3306 []*endpoint.Endpoint{ 3307 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 3308 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 3309 }, 3310 false, 3311 }, 3312 { 3313 "annotated Headless services return endpoints for all Pod if publishNotReadyAddresses is set", 3314 "", 3315 "testing", 3316 "foo", 3317 v1.ServiceTypeClusterIP, 3318 "", 3319 "", 3320 false, 3321 map[string]string{"component": "foo"}, 3322 map[string]string{ 3323 hostnameAnnotationKey: "service.example.org", 3324 }, 3325 v1.ClusterIPNone, 3326 []string{"1.1.1.1", "1.1.1.2"}, 3327 map[string]string{ 3328 "component": "foo", 3329 }, 3330 []string{}, 3331 []string{"foo-0", "foo-1"}, 3332 []string{"foo-0", "foo-1"}, 3333 []bool{true, false}, 3334 []*v1.ObjectReference{ 3335 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3336 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3337 }, 3338 true, 3339 []*endpoint.Endpoint{ 3340 {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, 3341 {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, 3342 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 3343 }, 3344 false, 3345 }, 3346 { 3347 "annotated Headless services return IPv4 endpoints for pods missing hostname", 3348 "", 3349 "testing", 3350 "foo", 3351 v1.ServiceTypeClusterIP, 3352 "", 3353 "", 3354 false, 3355 map[string]string{"component": "foo"}, 3356 map[string]string{ 3357 hostnameAnnotationKey: "service.example.org", 3358 }, 3359 v1.ClusterIPNone, 3360 []string{"1.1.1.1", "1.1.1.2"}, 3361 map[string]string{ 3362 "component": "foo", 3363 }, 3364 []string{}, 3365 []string{"foo-0", "foo-1"}, 3366 []string{"", ""}, 3367 []bool{true, true}, 3368 []*v1.ObjectReference{ 3369 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3370 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3371 }, 3372 false, 3373 []*endpoint.Endpoint{ 3374 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, 3375 }, 3376 false, 3377 }, 3378 { 3379 "annotated Headless services return IPv6 endpoints for pods missing hostname", 3380 "", 3381 "testing", 3382 "foo", 3383 v1.ServiceTypeClusterIP, 3384 "", 3385 "", 3386 false, 3387 map[string]string{"component": "foo"}, 3388 map[string]string{ 3389 hostnameAnnotationKey: "service.example.org", 3390 }, 3391 v1.ClusterIPNone, 3392 []string{"2001:db8::1", "2001:db8::2"}, 3393 map[string]string{ 3394 "component": "foo", 3395 }, 3396 []string{}, 3397 []string{"foo-0", "foo-1"}, 3398 []string{"", ""}, 3399 []bool{true, true}, 3400 []*v1.ObjectReference{ 3401 {APIVersion: "", Kind: "Pod", Name: "foo-0"}, 3402 {APIVersion: "", Kind: "Pod", Name: "foo-1"}, 3403 }, 3404 false, 3405 []*endpoint.Endpoint{ 3406 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, 3407 }, 3408 false, 3409 }, 3410 { 3411 "annotated Headless services without a targetRef has no endpoints", 3412 "", 3413 "testing", 3414 "foo", 3415 v1.ServiceTypeClusterIP, 3416 "", 3417 "", 3418 false, 3419 map[string]string{"component": "foo"}, 3420 map[string]string{ 3421 hostnameAnnotationKey: "service.example.org", 3422 }, 3423 v1.ClusterIPNone, 3424 []string{"1.1.1.1"}, 3425 map[string]string{ 3426 "component": "foo", 3427 }, 3428 []string{}, 3429 []string{"foo-0"}, 3430 []string{"foo-0"}, 3431 []bool{true, true}, 3432 []*v1.ObjectReference{nil}, 3433 false, 3434 []*endpoint.Endpoint{}, 3435 false, 3436 }, 3437 } { 3438 tc := tc 3439 t.Run(tc.title, func(t *testing.T) { 3440 t.Parallel() 3441 3442 // Create a Kubernetes testing client 3443 kubernetes := fake.NewSimpleClientset() 3444 3445 service := &v1.Service{ 3446 Spec: v1.ServiceSpec{ 3447 Type: tc.svcType, 3448 ClusterIP: tc.clusterIP, 3449 Selector: tc.selector, 3450 PublishNotReadyAddresses: tc.publishNotReadyAddresses, 3451 }, 3452 ObjectMeta: metav1.ObjectMeta{ 3453 Namespace: tc.svcNamespace, 3454 Name: tc.svcName, 3455 Labels: tc.labels, 3456 Annotations: tc.annotations, 3457 }, 3458 Status: v1.ServiceStatus{}, 3459 } 3460 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 3461 require.NoError(t, err) 3462 3463 var addresses []v1.EndpointAddress 3464 var notReadyAddresses []v1.EndpointAddress 3465 for i, podname := range tc.podnames { 3466 pod := &v1.Pod{ 3467 Spec: v1.PodSpec{ 3468 Containers: []v1.Container{}, 3469 Hostname: tc.hostnames[i], 3470 }, 3471 ObjectMeta: metav1.ObjectMeta{ 3472 Namespace: tc.svcNamespace, 3473 Name: podname, 3474 Labels: tc.labels, 3475 Annotations: tc.annotations, 3476 }, 3477 Status: v1.PodStatus{ 3478 HostIP: tc.hostIPs[i], 3479 }, 3480 } 3481 3482 _, err = kubernetes.CoreV1().Pods(tc.svcNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) 3483 require.NoError(t, err) 3484 3485 address := v1.EndpointAddress{ 3486 IP: "4.3.2.1", 3487 TargetRef: tc.targetRefs[i], 3488 } 3489 if tc.podsReady[i] { 3490 addresses = append(addresses, address) 3491 } else { 3492 notReadyAddresses = append(notReadyAddresses, address) 3493 } 3494 } 3495 endpointsObject := &v1.Endpoints{ 3496 ObjectMeta: metav1.ObjectMeta{ 3497 Namespace: tc.svcNamespace, 3498 Name: tc.svcName, 3499 Labels: tc.labels, 3500 }, 3501 Subsets: []v1.EndpointSubset{ 3502 { 3503 Addresses: addresses, 3504 NotReadyAddresses: notReadyAddresses, 3505 }, 3506 }, 3507 } 3508 _, err = kubernetes.CoreV1().Endpoints(tc.svcNamespace).Create(context.Background(), endpointsObject, metav1.CreateOptions{}) 3509 require.NoError(t, err) 3510 3511 // Create our object under test and get the endpoints. 3512 client, _ := NewServiceSource( 3513 context.TODO(), 3514 kubernetes, 3515 tc.targetNamespace, 3516 "", 3517 tc.fqdnTemplate, 3518 false, 3519 tc.compatibility, 3520 true, 3521 true, 3522 false, 3523 []string{}, 3524 tc.ignoreHostnameAnnotation, 3525 labels.Everything(), 3526 false, 3527 ) 3528 require.NoError(t, err) 3529 3530 endpoints, err := client.Endpoints(context.Background()) 3531 if tc.expectError { 3532 require.Error(t, err) 3533 } else { 3534 require.NoError(t, err) 3535 } 3536 3537 // Validate returned endpoints against desired endpoints. 3538 validateEndpoints(t, endpoints, tc.expected) 3539 }) 3540 } 3541 } 3542 3543 // TestExternalServices tests that external services generate the correct endpoints. 3544 func TestExternalServices(t *testing.T) { 3545 t.Parallel() 3546 3547 for _, tc := range []struct { 3548 title string 3549 targetNamespace string 3550 svcNamespace string 3551 svcName string 3552 svcType v1.ServiceType 3553 compatibility string 3554 fqdnTemplate string 3555 ignoreHostnameAnnotation bool 3556 labels map[string]string 3557 annotations map[string]string 3558 externalName string 3559 externalIPs []string 3560 expected []*endpoint.Endpoint 3561 expectError bool 3562 }{ 3563 { 3564 "external services return an A endpoint for the external name that is an IPv4 address", 3565 "", 3566 "testing", 3567 "foo", 3568 v1.ServiceTypeExternalName, 3569 "", 3570 "", 3571 false, 3572 map[string]string{"component": "foo"}, 3573 map[string]string{ 3574 hostnameAnnotationKey: "service.example.org", 3575 }, 3576 "111.111.111.111", 3577 []string{}, 3578 []*endpoint.Endpoint{ 3579 {DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA}, 3580 }, 3581 false, 3582 }, 3583 { 3584 "external services return an AAAA endpoint for the external name that is an IPv6 address", 3585 "", 3586 "testing", 3587 "foo", 3588 v1.ServiceTypeExternalName, 3589 "", 3590 "", 3591 false, 3592 map[string]string{"component": "foo"}, 3593 map[string]string{ 3594 hostnameAnnotationKey: "service.example.org", 3595 }, 3596 "2001:db8::111", 3597 []string{}, 3598 []*endpoint.Endpoint{ 3599 {DNSName: "service.example.org", Targets: endpoint.Targets{"2001:db8::111"}, RecordType: endpoint.RecordTypeAAAA}, 3600 }, 3601 false, 3602 }, 3603 { 3604 "external services return a CNAME endpoint for the external name that is a domain", 3605 "", 3606 "testing", 3607 "foo", 3608 v1.ServiceTypeExternalName, 3609 "", 3610 "", 3611 false, 3612 map[string]string{"component": "foo"}, 3613 map[string]string{ 3614 hostnameAnnotationKey: "service.example.org", 3615 }, 3616 "remote.example.com", 3617 []string{}, 3618 []*endpoint.Endpoint{ 3619 {DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME}, 3620 }, 3621 false, 3622 }, 3623 { 3624 "annotated ExternalName service with externalIPs returns a single endpoint with multiple targets", 3625 "", 3626 "testing", 3627 "foo", 3628 v1.ServiceTypeExternalName, 3629 "", 3630 "", 3631 false, 3632 map[string]string{"component": "foo"}, 3633 map[string]string{ 3634 hostnameAnnotationKey: "service.example.org", 3635 }, 3636 "service.example.org", 3637 []string{"10.2.3.4", "11.2.3.4"}, 3638 []*endpoint.Endpoint{ 3639 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}}, 3640 }, 3641 false, 3642 }, 3643 { 3644 "annotated ExternalName service with externalIPs of dualstack addresses returns 2 endpoints with multiple targets", 3645 "", 3646 "testing", 3647 "foo", 3648 v1.ServiceTypeExternalName, 3649 "", 3650 "", 3651 false, 3652 map[string]string{"component": "foo"}, 3653 map[string]string{ 3654 hostnameAnnotationKey: "service.example.org", 3655 }, 3656 "service.example.org", 3657 []string{"10.2.3.4", "11.2.3.4", "2001:db8::1", "2001:db8::2"}, 3658 []*endpoint.Endpoint{ 3659 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}}, 3660 {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, 3661 }, 3662 false, 3663 }, 3664 } { 3665 tc := tc 3666 t.Run(tc.title, func(t *testing.T) { 3667 t.Parallel() 3668 3669 // Create a Kubernetes testing client 3670 kubernetes := fake.NewSimpleClientset() 3671 3672 service := &v1.Service{ 3673 Spec: v1.ServiceSpec{ 3674 Type: tc.svcType, 3675 ExternalName: tc.externalName, 3676 ExternalIPs: tc.externalIPs, 3677 }, 3678 ObjectMeta: metav1.ObjectMeta{ 3679 Namespace: tc.svcNamespace, 3680 Name: tc.svcName, 3681 Labels: tc.labels, 3682 Annotations: tc.annotations, 3683 }, 3684 Status: v1.ServiceStatus{}, 3685 } 3686 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 3687 require.NoError(t, err) 3688 3689 // Create our object under test and get the endpoints. 3690 client, _ := NewServiceSource( 3691 context.TODO(), 3692 kubernetes, 3693 tc.targetNamespace, 3694 "", 3695 tc.fqdnTemplate, 3696 false, 3697 tc.compatibility, 3698 true, 3699 false, 3700 false, 3701 []string{}, 3702 tc.ignoreHostnameAnnotation, 3703 labels.Everything(), 3704 false, 3705 ) 3706 require.NoError(t, err) 3707 3708 endpoints, err := client.Endpoints(context.Background()) 3709 if tc.expectError { 3710 require.Error(t, err) 3711 } else { 3712 require.NoError(t, err) 3713 } 3714 3715 // Validate returned endpoints against desired endpoints. 3716 validateEndpoints(t, endpoints, tc.expected) 3717 }) 3718 } 3719 } 3720 3721 func BenchmarkServiceEndpoints(b *testing.B) { 3722 kubernetes := fake.NewSimpleClientset() 3723 3724 service := &v1.Service{ 3725 ObjectMeta: metav1.ObjectMeta{ 3726 Namespace: "testing", 3727 Name: "foo", 3728 Annotations: map[string]string{ 3729 hostnameAnnotationKey: "foo.example.org.", 3730 }, 3731 }, 3732 Status: v1.ServiceStatus{ 3733 LoadBalancer: v1.LoadBalancerStatus{ 3734 Ingress: []v1.LoadBalancerIngress{ 3735 {IP: "1.2.3.4"}, 3736 {IP: "8.8.8.8"}, 3737 }, 3738 }, 3739 }, 3740 } 3741 3742 _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) 3743 require.NoError(b, err) 3744 3745 client, err := NewServiceSource( 3746 context.TODO(), 3747 kubernetes, 3748 v1.NamespaceAll, 3749 "", 3750 "", 3751 false, 3752 "", 3753 false, 3754 false, 3755 false, 3756 []string{}, 3757 false, 3758 labels.Everything(), 3759 false, 3760 ) 3761 require.NoError(b, err) 3762 3763 for i := 0; i < b.N; i++ { 3764 _, err := client.Endpoints(context.Background()) 3765 require.NoError(b, err) 3766 } 3767 }