sigs.k8s.io/external-dns@v0.14.1/source/contour_httpproxy_test.go (about) 1 /* 2 Copyright 2020 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 "testing" 22 23 fakeDynamic "k8s.io/client-go/dynamic/fake" 24 25 "github.com/pkg/errors" 26 projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 "github.com/stretchr/testify/suite" 30 v1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 33 "k8s.io/apimachinery/pkg/runtime" 34 "sigs.k8s.io/external-dns/endpoint" 35 ) 36 37 // This is a compile-time validation that httpProxySource is a Source. 38 var _ Source = &httpProxySource{} 39 40 type HTTPProxySuite struct { 41 suite.Suite 42 source Source 43 httpProxy *projectcontour.HTTPProxy 44 } 45 46 func newDynamicKubernetesClient() (*fakeDynamic.FakeDynamicClient, *runtime.Scheme) { 47 s := runtime.NewScheme() 48 _ = projectcontour.AddToScheme(s) 49 return fakeDynamic.NewSimpleDynamicClient(s), s 50 } 51 52 type fakeLoadBalancerService struct { 53 ips []string 54 hostnames []string 55 namespace string 56 name string 57 } 58 59 func (ig fakeLoadBalancerService) Service() *v1.Service { 60 svc := &v1.Service{ 61 ObjectMeta: metav1.ObjectMeta{ 62 Namespace: ig.namespace, 63 Name: ig.name, 64 }, 65 Status: v1.ServiceStatus{ 66 LoadBalancer: v1.LoadBalancerStatus{ 67 Ingress: []v1.LoadBalancerIngress{}, 68 }, 69 }, 70 } 71 72 for _, ip := range ig.ips { 73 svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, v1.LoadBalancerIngress{ 74 IP: ip, 75 }) 76 } 77 for _, hostname := range ig.hostnames { 78 svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, v1.LoadBalancerIngress{ 79 Hostname: hostname, 80 }) 81 } 82 83 return svc 84 } 85 86 func (suite *HTTPProxySuite) SetupTest() { 87 fakeDynamicClient, s := newDynamicKubernetesClient() 88 var err error 89 90 suite.source, err = NewContourHTTPProxySource( 91 context.TODO(), 92 fakeDynamicClient, 93 "default", 94 "", 95 "{{.Name}}", 96 false, 97 false, 98 ) 99 suite.NoError(err, "should initialize httpproxy source") 100 101 suite.httpProxy = (fakeHTTPProxy{ 102 name: "foo-httpproxy-with-targets", 103 namespace: "default", 104 host: "example.com", 105 }).HTTPProxy() 106 107 // Convert to unstructured 108 unstructuredHTTPProxy, err := convertHTTPProxyToUnstructured(suite.httpProxy, s) 109 if err != nil { 110 suite.Error(err) 111 } 112 113 _, err = fakeDynamicClient.Resource(projectcontour.HTTPProxyGVR).Namespace(suite.httpProxy.Namespace).Create(context.Background(), unstructuredHTTPProxy, metav1.CreateOptions{}) 114 suite.NoError(err, "should succeed") 115 } 116 117 func (suite *HTTPProxySuite) TestResourceLabelIsSet() { 118 endpoints, _ := suite.source.Endpoints(context.Background()) 119 for _, ep := range endpoints { 120 suite.Equal("httpproxy/default/foo-httpproxy-with-targets", ep.Labels[endpoint.ResourceLabelKey], "should set correct resource label") 121 } 122 } 123 124 func convertHTTPProxyToUnstructured(hp *projectcontour.HTTPProxy, s *runtime.Scheme) (*unstructured.Unstructured, error) { 125 unstructuredHTTPProxy := &unstructured.Unstructured{} 126 if err := s.Convert(hp, unstructuredHTTPProxy, context.Background()); err != nil { 127 return nil, err 128 } 129 return unstructuredHTTPProxy, nil 130 } 131 132 func TestHTTPProxy(t *testing.T) { 133 t.Parallel() 134 135 suite.Run(t, new(HTTPProxySuite)) 136 t.Run("endpointsFromHTTPProxy", testEndpointsFromHTTPProxy) 137 t.Run("Endpoints", testHTTPProxyEndpoints) 138 } 139 140 func TestNewContourHTTPProxySource(t *testing.T) { 141 t.Parallel() 142 143 for _, ti := range []struct { 144 title string 145 annotationFilter string 146 fqdnTemplate string 147 combineFQDNAndAnnotation bool 148 expectError bool 149 }{ 150 { 151 title: "invalid template", 152 expectError: true, 153 fqdnTemplate: "{{.Name", 154 }, 155 { 156 title: "valid empty template", 157 expectError: false, 158 }, 159 { 160 title: "valid template", 161 expectError: false, 162 fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com", 163 }, 164 { 165 title: "valid template", 166 expectError: false, 167 fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com, {{.Name}}-{{.Namespace}}.ext-dna.test.com", 168 }, 169 { 170 title: "valid template", 171 expectError: false, 172 fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com, {{.Name}}-{{.Namespace}}.ext-dna.test.com", 173 combineFQDNAndAnnotation: true, 174 }, 175 { 176 title: "non-empty annotation filter label", 177 expectError: false, 178 annotationFilter: "contour.heptio.com/ingress.class=contour", 179 }, 180 } { 181 ti := ti 182 t.Run(ti.title, func(t *testing.T) { 183 t.Parallel() 184 185 fakeDynamicClient, _ := newDynamicKubernetesClient() 186 187 _, err := NewContourHTTPProxySource( 188 context.TODO(), 189 fakeDynamicClient, 190 "", 191 ti.annotationFilter, 192 ti.fqdnTemplate, 193 ti.combineFQDNAndAnnotation, 194 false, 195 ) 196 if ti.expectError { 197 assert.Error(t, err) 198 } else { 199 assert.NoError(t, err) 200 } 201 }) 202 } 203 } 204 205 func testEndpointsFromHTTPProxy(t *testing.T) { 206 t.Parallel() 207 208 for _, ti := range []struct { 209 title string 210 httpProxy fakeHTTPProxy 211 expected []*endpoint.Endpoint 212 }{ 213 { 214 title: "one rule.host one lb.hostname", 215 httpProxy: fakeHTTPProxy{ 216 host: "foo.bar", // Kubernetes requires removal of trailing dot 217 loadBalancer: fakeLoadBalancerService{ 218 hostnames: []string{"lb.com"}, // Kubernetes omits the trailing dot 219 }, 220 }, 221 expected: []*endpoint.Endpoint{ 222 { 223 DNSName: "foo.bar", 224 RecordType: endpoint.RecordTypeCNAME, 225 Targets: endpoint.Targets{"lb.com"}, 226 }, 227 }, 228 }, 229 { 230 title: "one rule.host one lb.IP", 231 httpProxy: fakeHTTPProxy{ 232 host: "foo.bar", 233 loadBalancer: fakeLoadBalancerService{ 234 ips: []string{"8.8.8.8"}, 235 }, 236 }, 237 expected: []*endpoint.Endpoint{ 238 { 239 DNSName: "foo.bar", 240 RecordType: endpoint.RecordTypeA, 241 Targets: endpoint.Targets{"8.8.8.8"}, 242 }, 243 }, 244 }, 245 { 246 title: "one rule.host two lb.IP and two lb.Hostname", 247 httpProxy: fakeHTTPProxy{ 248 host: "foo.bar", 249 loadBalancer: fakeLoadBalancerService{ 250 ips: []string{"8.8.8.8", "127.0.0.1"}, 251 hostnames: []string{"elb.com", "alb.com"}, 252 }, 253 }, 254 expected: []*endpoint.Endpoint{ 255 { 256 DNSName: "foo.bar", 257 RecordType: endpoint.RecordTypeA, 258 Targets: endpoint.Targets{"8.8.8.8", "127.0.0.1"}, 259 }, 260 { 261 DNSName: "foo.bar", 262 RecordType: endpoint.RecordTypeCNAME, 263 Targets: endpoint.Targets{"elb.com", "alb.com"}, 264 }, 265 }, 266 }, 267 { 268 title: "no rule.host", 269 httpProxy: fakeHTTPProxy{}, 270 expected: []*endpoint.Endpoint{}, 271 }, 272 { 273 title: "no targets", 274 httpProxy: fakeHTTPProxy{}, 275 expected: []*endpoint.Endpoint{}, 276 }, 277 { 278 title: "delegate httpproxy", 279 httpProxy: fakeHTTPProxy{ 280 delegate: true, 281 }, 282 expected: []*endpoint.Endpoint{}, 283 }, 284 } { 285 ti := ti 286 t.Run(ti.title, func(t *testing.T) { 287 t.Parallel() 288 289 if source, err := newTestHTTPProxySource(); err != nil { 290 require.NoError(t, err) 291 } else if endpoints, err := source.endpointsFromHTTPProxy(ti.httpProxy.HTTPProxy()); err != nil { 292 require.NoError(t, err) 293 } else { 294 validateEndpoints(t, endpoints, ti.expected) 295 } 296 }) 297 } 298 } 299 300 func testHTTPProxyEndpoints(t *testing.T) { 301 t.Parallel() 302 303 namespace := "testing" 304 for _, ti := range []struct { 305 title string 306 targetNamespace string 307 annotationFilter string 308 loadBalancer fakeLoadBalancerService 309 httpProxyItems []fakeHTTPProxy 310 expected []*endpoint.Endpoint 311 expectError bool 312 fqdnTemplate string 313 combineFQDNAndAnnotation bool 314 ignoreHostnameAnnotation bool 315 }{ 316 { 317 title: "no httpproxy", 318 targetNamespace: "", 319 }, 320 { 321 title: "two simple httpproxys", 322 targetNamespace: "", 323 loadBalancer: fakeLoadBalancerService{ 324 ips: []string{"8.8.8.8"}, 325 hostnames: []string{"lb.com"}, 326 }, 327 httpProxyItems: []fakeHTTPProxy{ 328 { 329 name: "fake1", 330 namespace: namespace, 331 host: "example.org", 332 }, 333 { 334 name: "fake2", 335 namespace: namespace, 336 host: "new.org", 337 }, 338 }, 339 expected: []*endpoint.Endpoint{ 340 { 341 DNSName: "example.org", 342 RecordType: endpoint.RecordTypeA, 343 Targets: endpoint.Targets{"8.8.8.8"}, 344 }, 345 { 346 DNSName: "example.org", 347 RecordType: endpoint.RecordTypeCNAME, 348 Targets: endpoint.Targets{"lb.com"}, 349 }, 350 { 351 DNSName: "new.org", 352 RecordType: endpoint.RecordTypeA, 353 Targets: endpoint.Targets{"8.8.8.8"}, 354 }, 355 { 356 DNSName: "new.org", 357 RecordType: endpoint.RecordTypeCNAME, 358 Targets: endpoint.Targets{"lb.com"}, 359 }, 360 }, 361 }, 362 { 363 title: "two simple httpproxys on different namespaces", 364 targetNamespace: "", 365 loadBalancer: fakeLoadBalancerService{ 366 ips: []string{"8.8.8.8"}, 367 hostnames: []string{"lb.com"}, 368 }, 369 httpProxyItems: []fakeHTTPProxy{ 370 { 371 name: "fake1", 372 namespace: "testing1", 373 host: "example.org", 374 }, 375 { 376 name: "fake2", 377 namespace: "testing2", 378 host: "new.org", 379 }, 380 }, 381 expected: []*endpoint.Endpoint{ 382 { 383 DNSName: "example.org", 384 RecordType: endpoint.RecordTypeA, 385 Targets: endpoint.Targets{"8.8.8.8"}, 386 }, 387 { 388 DNSName: "example.org", 389 RecordType: endpoint.RecordTypeCNAME, 390 Targets: endpoint.Targets{"lb.com"}, 391 }, 392 { 393 DNSName: "new.org", 394 RecordType: endpoint.RecordTypeA, 395 Targets: endpoint.Targets{"8.8.8.8"}, 396 }, 397 { 398 DNSName: "new.org", 399 RecordType: endpoint.RecordTypeCNAME, 400 Targets: endpoint.Targets{"lb.com"}, 401 }, 402 }, 403 }, 404 { 405 title: "two simple httpproxys on different namespaces and a target namespace", 406 targetNamespace: "testing1", 407 loadBalancer: fakeLoadBalancerService{ 408 ips: []string{"8.8.8.8"}, 409 hostnames: []string{"lb.com"}, 410 }, 411 httpProxyItems: []fakeHTTPProxy{ 412 { 413 name: "fake1", 414 namespace: "testing1", 415 host: "example.org", 416 }, 417 { 418 name: "fake2", 419 namespace: "testing2", 420 host: "new.org", 421 }, 422 }, 423 expected: []*endpoint.Endpoint{ 424 { 425 DNSName: "example.org", 426 RecordType: endpoint.RecordTypeA, 427 Targets: endpoint.Targets{"8.8.8.8"}, 428 }, 429 { 430 DNSName: "example.org", 431 RecordType: endpoint.RecordTypeCNAME, 432 Targets: endpoint.Targets{"lb.com"}, 433 }, 434 }, 435 }, 436 { 437 title: "valid matching annotation filter expression", 438 targetNamespace: "", 439 annotationFilter: "contour.heptio.com/ingress.class in (alb, contour)", 440 loadBalancer: fakeLoadBalancerService{ 441 ips: []string{"8.8.8.8"}, 442 }, 443 httpProxyItems: []fakeHTTPProxy{ 444 { 445 name: "fake1", 446 namespace: namespace, 447 annotations: map[string]string{ 448 "contour.heptio.com/ingress.class": "contour", 449 }, 450 host: "example.org", 451 }, 452 }, 453 expected: []*endpoint.Endpoint{ 454 { 455 DNSName: "example.org", 456 RecordType: endpoint.RecordTypeA, 457 Targets: endpoint.Targets{"8.8.8.8"}, 458 }, 459 }, 460 }, 461 { 462 title: "valid non-matching annotation filter expression", 463 targetNamespace: "", 464 annotationFilter: "contour.heptio.com/ingress.class in (alb, contour)", 465 loadBalancer: fakeLoadBalancerService{ 466 ips: []string{"8.8.8.8"}, 467 }, 468 httpProxyItems: []fakeHTTPProxy{ 469 { 470 name: "fake1", 471 namespace: namespace, 472 annotations: map[string]string{ 473 "contour.heptio.com/ingress.class": "tectonic", 474 }, 475 host: "example.org", 476 }, 477 }, 478 expected: []*endpoint.Endpoint{}, 479 }, 480 { 481 title: "invalid annotation filter expression", 482 targetNamespace: "", 483 annotationFilter: "contour.heptio.com/ingress.name in (a b)", 484 loadBalancer: fakeLoadBalancerService{ 485 ips: []string{"8.8.8.8"}, 486 }, 487 httpProxyItems: []fakeHTTPProxy{ 488 { 489 name: "fake1", 490 namespace: namespace, 491 annotations: map[string]string{ 492 "contour.heptio.com/ingress.class": "alb", 493 }, 494 host: "example.org", 495 }, 496 }, 497 expected: []*endpoint.Endpoint{}, 498 expectError: true, 499 }, 500 { 501 title: "valid matching annotation filter label", 502 targetNamespace: "", 503 annotationFilter: "contour.heptio.com/ingress.class=contour", 504 loadBalancer: fakeLoadBalancerService{ 505 ips: []string{"8.8.8.8"}, 506 }, 507 httpProxyItems: []fakeHTTPProxy{ 508 { 509 name: "fake1", 510 namespace: namespace, 511 annotations: map[string]string{ 512 "contour.heptio.com/ingress.class": "contour", 513 }, 514 host: "example.org", 515 }, 516 }, 517 expected: []*endpoint.Endpoint{ 518 { 519 DNSName: "example.org", 520 RecordType: endpoint.RecordTypeA, 521 Targets: endpoint.Targets{"8.8.8.8"}, 522 }, 523 }, 524 }, 525 { 526 title: "valid non-matching annotation filter label", 527 targetNamespace: "", 528 annotationFilter: "contour.heptio.com/ingress.class=contour", 529 loadBalancer: fakeLoadBalancerService{ 530 ips: []string{"8.8.8.8"}, 531 }, 532 httpProxyItems: []fakeHTTPProxy{ 533 { 534 name: "fake1", 535 namespace: namespace, 536 annotations: map[string]string{ 537 "contour.heptio.com/ingress.class": "alb", 538 }, 539 host: "example.org", 540 }, 541 }, 542 expected: []*endpoint.Endpoint{}, 543 }, 544 { 545 title: "our controller type is dns-controller", 546 targetNamespace: "", 547 loadBalancer: fakeLoadBalancerService{ 548 ips: []string{"8.8.8.8"}, 549 }, 550 httpProxyItems: []fakeHTTPProxy{ 551 { 552 name: "fake1", 553 namespace: namespace, 554 annotations: map[string]string{ 555 controllerAnnotationKey: controllerAnnotationValue, 556 }, 557 host: "example.org", 558 }, 559 }, 560 expected: []*endpoint.Endpoint{ 561 { 562 DNSName: "example.org", 563 RecordType: endpoint.RecordTypeA, 564 Targets: endpoint.Targets{"8.8.8.8"}, 565 }, 566 }, 567 }, 568 { 569 title: "different controller types are ignored", 570 targetNamespace: "", 571 loadBalancer: fakeLoadBalancerService{ 572 ips: []string{"8.8.8.8"}, 573 }, 574 httpProxyItems: []fakeHTTPProxy{ 575 { 576 name: "fake1", 577 namespace: namespace, 578 annotations: map[string]string{ 579 controllerAnnotationKey: "some-other-tool", 580 }, 581 host: "example.org", 582 }, 583 }, 584 expected: []*endpoint.Endpoint{}, 585 }, 586 { 587 title: "template for httpproxy if host is missing", 588 targetNamespace: "", 589 loadBalancer: fakeLoadBalancerService{ 590 ips: []string{"8.8.8.8"}, 591 hostnames: []string{"elb.com"}, 592 }, 593 httpProxyItems: []fakeHTTPProxy{ 594 { 595 name: "fake1", 596 namespace: namespace, 597 annotations: map[string]string{ 598 controllerAnnotationKey: controllerAnnotationValue, 599 }, 600 host: "", 601 }, 602 }, 603 expected: []*endpoint.Endpoint{ 604 { 605 DNSName: "fake1.ext-dns.test.com", 606 RecordType: endpoint.RecordTypeA, 607 Targets: endpoint.Targets{"8.8.8.8"}, 608 }, 609 { 610 DNSName: "fake1.ext-dns.test.com", 611 RecordType: endpoint.RecordTypeCNAME, 612 Targets: endpoint.Targets{"elb.com"}, 613 }, 614 }, 615 fqdnTemplate: "{{.Name}}.ext-dns.test.com", 616 }, 617 { 618 title: "another controller annotation skipped even with template", 619 targetNamespace: "", 620 loadBalancer: fakeLoadBalancerService{ 621 ips: []string{"8.8.8.8"}, 622 }, 623 httpProxyItems: []fakeHTTPProxy{ 624 { 625 name: "fake1", 626 namespace: namespace, 627 annotations: map[string]string{ 628 controllerAnnotationKey: "other-controller", 629 }, 630 host: "", 631 }, 632 }, 633 expected: []*endpoint.Endpoint{}, 634 fqdnTemplate: "{{.Name}}.ext-dns.test.com", 635 }, 636 { 637 title: "multiple FQDN template hostnames", 638 targetNamespace: "", 639 loadBalancer: fakeLoadBalancerService{ 640 ips: []string{"8.8.8.8"}, 641 }, 642 httpProxyItems: []fakeHTTPProxy{ 643 { 644 name: "fake1", 645 namespace: namespace, 646 annotations: map[string]string{}, 647 host: "", 648 }, 649 }, 650 expected: []*endpoint.Endpoint{ 651 { 652 DNSName: "fake1.ext-dns.test.com", 653 Targets: endpoint.Targets{"8.8.8.8"}, 654 RecordType: endpoint.RecordTypeA, 655 }, 656 { 657 DNSName: "fake1.ext-dna.test.com", 658 Targets: endpoint.Targets{"8.8.8.8"}, 659 RecordType: endpoint.RecordTypeA, 660 }, 661 }, 662 fqdnTemplate: "{{.Name}}.ext-dns.test.com, {{.Name}}.ext-dna.test.com", 663 }, 664 { 665 title: "multiple FQDN template hostnames", 666 targetNamespace: "", 667 loadBalancer: fakeLoadBalancerService{ 668 ips: []string{"8.8.8.8"}, 669 }, 670 httpProxyItems: []fakeHTTPProxy{ 671 { 672 name: "fake1", 673 namespace: namespace, 674 annotations: map[string]string{}, 675 host: "", 676 }, 677 { 678 name: "fake2", 679 namespace: namespace, 680 annotations: map[string]string{ 681 targetAnnotationKey: "httpproxy-target.com", 682 }, 683 host: "example.org", 684 }, 685 }, 686 expected: []*endpoint.Endpoint{ 687 { 688 DNSName: "fake1.ext-dns.test.com", 689 Targets: endpoint.Targets{"8.8.8.8"}, 690 RecordType: endpoint.RecordTypeA, 691 }, 692 { 693 DNSName: "fake1.ext-dna.test.com", 694 Targets: endpoint.Targets{"8.8.8.8"}, 695 RecordType: endpoint.RecordTypeA, 696 }, 697 { 698 DNSName: "example.org", 699 Targets: endpoint.Targets{"httpproxy-target.com"}, 700 RecordType: endpoint.RecordTypeCNAME, 701 }, 702 { 703 DNSName: "fake2.ext-dns.test.com", 704 Targets: endpoint.Targets{"httpproxy-target.com"}, 705 RecordType: endpoint.RecordTypeCNAME, 706 }, 707 { 708 DNSName: "fake2.ext-dna.test.com", 709 Targets: endpoint.Targets{"httpproxy-target.com"}, 710 RecordType: endpoint.RecordTypeCNAME, 711 }, 712 }, 713 fqdnTemplate: "{{.Name}}.ext-dns.test.com, {{.Name}}.ext-dna.test.com", 714 combineFQDNAndAnnotation: true, 715 }, 716 { 717 title: "httpproxy rules with annotation", 718 targetNamespace: "", 719 loadBalancer: fakeLoadBalancerService{ 720 ips: []string{"8.8.8.8"}, 721 }, 722 httpProxyItems: []fakeHTTPProxy{ 723 { 724 name: "fake1", 725 namespace: namespace, 726 annotations: map[string]string{ 727 targetAnnotationKey: "httpproxy-target.com", 728 }, 729 host: "example.org", 730 }, 731 { 732 name: "fake2", 733 namespace: namespace, 734 annotations: map[string]string{ 735 targetAnnotationKey: "httpproxy-target.com", 736 }, 737 host: "example2.org", 738 }, 739 { 740 name: "fake3", 741 namespace: namespace, 742 annotations: map[string]string{ 743 targetAnnotationKey: "1.2.3.4", 744 }, 745 host: "example3.org", 746 }, 747 }, 748 expected: []*endpoint.Endpoint{ 749 { 750 DNSName: "example.org", 751 Targets: endpoint.Targets{"httpproxy-target.com"}, 752 RecordType: endpoint.RecordTypeCNAME, 753 }, 754 { 755 DNSName: "example2.org", 756 Targets: endpoint.Targets{"httpproxy-target.com"}, 757 RecordType: endpoint.RecordTypeCNAME, 758 }, 759 { 760 DNSName: "example3.org", 761 Targets: endpoint.Targets{"1.2.3.4"}, 762 RecordType: endpoint.RecordTypeA, 763 }, 764 }, 765 }, 766 { 767 title: "httpproxy rules with hostname annotation", 768 targetNamespace: "", 769 loadBalancer: fakeLoadBalancerService{ 770 ips: []string{"1.2.3.4"}, 771 }, 772 httpProxyItems: []fakeHTTPProxy{ 773 { 774 name: "fake1", 775 namespace: namespace, 776 annotations: map[string]string{ 777 hostnameAnnotationKey: "dns-through-hostname.com", 778 }, 779 host: "example.org", 780 }, 781 }, 782 expected: []*endpoint.Endpoint{ 783 { 784 DNSName: "example.org", 785 Targets: endpoint.Targets{"1.2.3.4"}, 786 RecordType: endpoint.RecordTypeA, 787 }, 788 { 789 DNSName: "dns-through-hostname.com", 790 Targets: endpoint.Targets{"1.2.3.4"}, 791 RecordType: endpoint.RecordTypeA, 792 }, 793 }, 794 }, 795 { 796 title: "httpproxy rules with hostname annotation having multiple hostnames", 797 targetNamespace: "", 798 loadBalancer: fakeLoadBalancerService{ 799 ips: []string{"1.2.3.4"}, 800 }, 801 httpProxyItems: []fakeHTTPProxy{ 802 { 803 name: "fake1", 804 namespace: namespace, 805 annotations: map[string]string{ 806 hostnameAnnotationKey: "dns-through-hostname.com, another-dns-through-hostname.com", 807 }, 808 host: "example.org", 809 }, 810 }, 811 expected: []*endpoint.Endpoint{ 812 { 813 DNSName: "example.org", 814 Targets: endpoint.Targets{"1.2.3.4"}, 815 RecordType: endpoint.RecordTypeA, 816 }, 817 { 818 DNSName: "dns-through-hostname.com", 819 Targets: endpoint.Targets{"1.2.3.4"}, 820 RecordType: endpoint.RecordTypeA, 821 }, 822 { 823 DNSName: "another-dns-through-hostname.com", 824 Targets: endpoint.Targets{"1.2.3.4"}, 825 RecordType: endpoint.RecordTypeA, 826 }, 827 }, 828 }, 829 { 830 title: "httpproxy rules with hostname and target annotation", 831 targetNamespace: "", 832 loadBalancer: fakeLoadBalancerService{ 833 ips: []string{}, 834 }, 835 httpProxyItems: []fakeHTTPProxy{ 836 { 837 name: "fake1", 838 namespace: namespace, 839 annotations: map[string]string{ 840 hostnameAnnotationKey: "dns-through-hostname.com", 841 targetAnnotationKey: "httpproxy-target.com", 842 }, 843 host: "example.org", 844 }, 845 }, 846 expected: []*endpoint.Endpoint{ 847 { 848 DNSName: "example.org", 849 Targets: endpoint.Targets{"httpproxy-target.com"}, 850 RecordType: endpoint.RecordTypeCNAME, 851 }, 852 { 853 DNSName: "dns-through-hostname.com", 854 Targets: endpoint.Targets{"httpproxy-target.com"}, 855 RecordType: endpoint.RecordTypeCNAME, 856 }, 857 }, 858 }, 859 { 860 title: "httpproxy rules with annotation and custom TTL", 861 targetNamespace: "", 862 loadBalancer: fakeLoadBalancerService{ 863 ips: []string{"8.8.8.8"}, 864 }, 865 httpProxyItems: []fakeHTTPProxy{ 866 { 867 name: "fake1", 868 namespace: namespace, 869 annotations: map[string]string{ 870 targetAnnotationKey: "httpproxy-target.com", 871 ttlAnnotationKey: "6", 872 }, 873 host: "example.org", 874 }, 875 { 876 name: "fake2", 877 namespace: namespace, 878 annotations: map[string]string{ 879 targetAnnotationKey: "httpproxy-target.com", 880 ttlAnnotationKey: "1", 881 }, 882 host: "example2.org", 883 }, 884 { 885 name: "fake3", 886 namespace: namespace, 887 annotations: map[string]string{ 888 targetAnnotationKey: "httpproxy-target.com", 889 ttlAnnotationKey: "10s", 890 }, 891 host: "example3.org", 892 }, 893 }, 894 expected: []*endpoint.Endpoint{ 895 { 896 DNSName: "example.org", 897 RecordType: endpoint.RecordTypeCNAME, 898 Targets: endpoint.Targets{"httpproxy-target.com"}, 899 RecordTTL: endpoint.TTL(6), 900 }, 901 { 902 DNSName: "example2.org", 903 RecordType: endpoint.RecordTypeCNAME, 904 Targets: endpoint.Targets{"httpproxy-target.com"}, 905 RecordTTL: endpoint.TTL(1), 906 }, 907 { 908 DNSName: "example3.org", 909 RecordType: endpoint.RecordTypeCNAME, 910 Targets: endpoint.Targets{"httpproxy-target.com"}, 911 RecordTTL: endpoint.TTL(10), 912 }, 913 }, 914 }, 915 { 916 title: "template for httpproxy with annotation", 917 targetNamespace: "", 918 loadBalancer: fakeLoadBalancerService{ 919 ips: []string{}, 920 hostnames: []string{}, 921 }, 922 httpProxyItems: []fakeHTTPProxy{ 923 { 924 name: "fake1", 925 namespace: namespace, 926 annotations: map[string]string{ 927 targetAnnotationKey: "httpproxy-target.com", 928 }, 929 host: "", 930 }, 931 { 932 name: "fake2", 933 namespace: namespace, 934 annotations: map[string]string{ 935 targetAnnotationKey: "httpproxy-target.com", 936 }, 937 host: "", 938 }, 939 { 940 name: "fake3", 941 namespace: namespace, 942 annotations: map[string]string{ 943 targetAnnotationKey: "1.2.3.4", 944 }, 945 host: "", 946 }, 947 }, 948 expected: []*endpoint.Endpoint{ 949 { 950 DNSName: "fake1.ext-dns.test.com", 951 Targets: endpoint.Targets{"httpproxy-target.com"}, 952 RecordType: endpoint.RecordTypeCNAME, 953 }, 954 { 955 DNSName: "fake2.ext-dns.test.com", 956 Targets: endpoint.Targets{"httpproxy-target.com"}, 957 RecordType: endpoint.RecordTypeCNAME, 958 }, 959 { 960 DNSName: "fake3.ext-dns.test.com", 961 Targets: endpoint.Targets{"1.2.3.4"}, 962 RecordType: endpoint.RecordTypeA, 963 }, 964 }, 965 fqdnTemplate: "{{.Name}}.ext-dns.test.com", 966 }, 967 { 968 title: "httpproxy with empty annotation", 969 targetNamespace: "", 970 loadBalancer: fakeLoadBalancerService{ 971 ips: []string{}, 972 hostnames: []string{}, 973 }, 974 httpProxyItems: []fakeHTTPProxy{ 975 { 976 name: "fake1", 977 namespace: namespace, 978 annotations: map[string]string{ 979 targetAnnotationKey: "", 980 }, 981 host: "", 982 }, 983 }, 984 expected: []*endpoint.Endpoint{}, 985 fqdnTemplate: "{{.Name}}.ext-dns.test.com", 986 }, 987 { 988 title: "ignore hostname annotations", 989 targetNamespace: "", 990 loadBalancer: fakeLoadBalancerService{ 991 ips: []string{"8.8.8.8"}, 992 hostnames: []string{"lb.com"}, 993 }, 994 httpProxyItems: []fakeHTTPProxy{ 995 { 996 name: "fake1", 997 namespace: namespace, 998 annotations: map[string]string{ 999 hostnameAnnotationKey: "ignore.me", 1000 }, 1001 host: "example.org", 1002 }, 1003 { 1004 name: "fake2", 1005 namespace: namespace, 1006 annotations: map[string]string{ 1007 hostnameAnnotationKey: "ignore.me.too", 1008 }, 1009 host: "new.org", 1010 }, 1011 }, 1012 expected: []*endpoint.Endpoint{ 1013 { 1014 DNSName: "example.org", 1015 RecordType: endpoint.RecordTypeA, 1016 Targets: endpoint.Targets{"8.8.8.8"}, 1017 }, 1018 { 1019 DNSName: "example.org", 1020 RecordType: endpoint.RecordTypeCNAME, 1021 Targets: endpoint.Targets{"lb.com"}, 1022 }, 1023 { 1024 DNSName: "new.org", 1025 RecordType: endpoint.RecordTypeA, 1026 Targets: endpoint.Targets{"8.8.8.8"}, 1027 }, 1028 { 1029 DNSName: "new.org", 1030 RecordType: endpoint.RecordTypeCNAME, 1031 Targets: endpoint.Targets{"lb.com"}, 1032 }, 1033 }, 1034 ignoreHostnameAnnotation: true, 1035 }, 1036 } { 1037 ti := ti 1038 t.Run(ti.title, func(t *testing.T) { 1039 t.Parallel() 1040 1041 httpProxies := make([]*projectcontour.HTTPProxy, 0) 1042 for _, item := range ti.httpProxyItems { 1043 item.loadBalancer = ti.loadBalancer 1044 httpProxies = append(httpProxies, item.HTTPProxy()) 1045 } 1046 1047 fakeDynamicClient, scheme := newDynamicKubernetesClient() 1048 for _, httpProxy := range httpProxies { 1049 converted, err := convertHTTPProxyToUnstructured(httpProxy, scheme) 1050 require.NoError(t, err) 1051 _, err = fakeDynamicClient.Resource(projectcontour.HTTPProxyGVR).Namespace(httpProxy.Namespace).Create(context.Background(), converted, metav1.CreateOptions{}) 1052 require.NoError(t, err) 1053 } 1054 1055 httpProxySource, err := NewContourHTTPProxySource( 1056 context.TODO(), 1057 fakeDynamicClient, 1058 ti.targetNamespace, 1059 ti.annotationFilter, 1060 ti.fqdnTemplate, 1061 ti.combineFQDNAndAnnotation, 1062 ti.ignoreHostnameAnnotation, 1063 ) 1064 require.NoError(t, err) 1065 1066 res, err := httpProxySource.Endpoints(context.Background()) 1067 if ti.expectError { 1068 assert.Error(t, err) 1069 } else { 1070 assert.NoError(t, err) 1071 } 1072 1073 validateEndpoints(t, res, ti.expected) 1074 }) 1075 } 1076 } 1077 1078 // httpproxy specific helper functions 1079 func newTestHTTPProxySource() (*httpProxySource, error) { 1080 fakeDynamicClient, _ := newDynamicKubernetesClient() 1081 1082 src, err := NewContourHTTPProxySource( 1083 context.TODO(), 1084 fakeDynamicClient, 1085 "default", 1086 "", 1087 "{{.Name}}", 1088 false, 1089 false, 1090 ) 1091 if err != nil { 1092 return nil, err 1093 } 1094 1095 irsrc, ok := src.(*httpProxySource) 1096 if !ok { 1097 return nil, errors.New("underlying source type was not httpproxy") 1098 } 1099 1100 return irsrc, nil 1101 } 1102 1103 type fakeHTTPProxy struct { 1104 namespace string 1105 name string 1106 annotations map[string]string 1107 1108 host string 1109 delegate bool 1110 loadBalancer fakeLoadBalancerService 1111 } 1112 1113 func (ir fakeHTTPProxy) HTTPProxy() *projectcontour.HTTPProxy { 1114 var spec projectcontour.HTTPProxySpec 1115 if ir.delegate { 1116 spec = projectcontour.HTTPProxySpec{} 1117 } else { 1118 spec = projectcontour.HTTPProxySpec{ 1119 VirtualHost: &projectcontour.VirtualHost{ 1120 Fqdn: ir.host, 1121 }, 1122 } 1123 } 1124 1125 lb := v1.LoadBalancerStatus{ 1126 Ingress: []v1.LoadBalancerIngress{}, 1127 } 1128 1129 for _, ip := range ir.loadBalancer.ips { 1130 lb.Ingress = append(lb.Ingress, v1.LoadBalancerIngress{ 1131 IP: ip, 1132 }) 1133 } 1134 for _, hostname := range ir.loadBalancer.hostnames { 1135 lb.Ingress = append(lb.Ingress, v1.LoadBalancerIngress{ 1136 Hostname: hostname, 1137 }) 1138 } 1139 1140 httpProxy := &projectcontour.HTTPProxy{ 1141 ObjectMeta: metav1.ObjectMeta{ 1142 Namespace: ir.namespace, 1143 Name: ir.name, 1144 Annotations: ir.annotations, 1145 }, 1146 Spec: spec, 1147 Status: projectcontour.HTTPProxyStatus{ 1148 LoadBalancer: lb, 1149 }, 1150 } 1151 1152 return httpProxy 1153 }