sigs.k8s.io/external-dns@v0.14.1/source/gateway_httproute_test.go (about) 1 /* 2 Copyright 2021 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 "github.com/stretchr/testify/require" 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/labels" 27 kubefake "k8s.io/client-go/kubernetes/fake" 28 "sigs.k8s.io/external-dns/endpoint" 29 v1 "sigs.k8s.io/gateway-api/apis/v1" 30 gatewayfake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake" 31 ) 32 33 func mustGetLabelSelector(s string) labels.Selector { 34 v, err := getLabelSelector(s) 35 if err != nil { 36 panic(err) 37 } 38 return v 39 } 40 41 func gatewayStatus(ips ...string) v1.GatewayStatus { 42 typ := v1.IPAddressType 43 addrs := make([]v1.GatewayStatusAddress, len(ips)) 44 for i, ip := range ips { 45 addrs[i] = v1.GatewayStatusAddress{Type: &typ, Value: ip} 46 } 47 return v1.GatewayStatus{Addresses: addrs} 48 } 49 50 func httpRouteStatus(refs ...v1.ParentReference) v1.HTTPRouteStatus { 51 return v1.HTTPRouteStatus{RouteStatus: gwRouteStatus(refs...)} 52 } 53 54 func gwRouteStatus(refs ...v1.ParentReference) v1.RouteStatus { 55 var v v1.RouteStatus 56 for _, ref := range refs { 57 v.Parents = append(v.Parents, v1.RouteParentStatus{ 58 ParentRef: ref, 59 Conditions: []metav1.Condition{ 60 { 61 Type: string(v1.RouteConditionAccepted), 62 Status: metav1.ConditionTrue, 63 }, 64 }, 65 }) 66 } 67 return v 68 } 69 70 func gwParentRef(namespace, name string, options ...gwParentRefOption) v1.ParentReference { 71 group := v1.Group("gateway.networking.k8s.io") 72 kind := v1.Kind("Gateway") 73 ref := v1.ParentReference{ 74 Group: &group, 75 Kind: &kind, 76 Name: v1.ObjectName(name), 77 Namespace: (*v1.Namespace)(&namespace), 78 } 79 for _, opt := range options { 80 opt(&ref) 81 } 82 return ref 83 } 84 85 type gwParentRefOption func(*v1.ParentReference) 86 87 func withSectionName(name v1.SectionName) gwParentRefOption { 88 return func(ref *v1.ParentReference) { ref.SectionName = &name } 89 } 90 91 func withPortNumber(port v1.PortNumber) gwParentRefOption { 92 return func(ref *v1.ParentReference) { ref.Port = &port } 93 } 94 95 func newTestEndpoint(dnsName, recordType string, targets ...string) *endpoint.Endpoint { 96 return newTestEndpointWithTTL(dnsName, recordType, 0, targets...) 97 } 98 99 func newTestEndpointWithTTL(dnsName, recordType string, ttl int64, targets ...string) *endpoint.Endpoint { 100 return &endpoint.Endpoint{ 101 DNSName: dnsName, 102 Targets: append([]string(nil), targets...), // clone targets 103 RecordType: recordType, 104 RecordTTL: endpoint.TTL(ttl), 105 } 106 } 107 108 func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { 109 t.Parallel() 110 111 fromAll := v1.NamespacesFromAll 112 fromSame := v1.NamespacesFromSame 113 fromSelector := v1.NamespacesFromSelector 114 allowAllNamespaces := &v1.AllowedRoutes{ 115 Namespaces: &v1.RouteNamespaces{ 116 From: &fromAll, 117 }, 118 } 119 objectMeta := func(namespace, name string) metav1.ObjectMeta { 120 return metav1.ObjectMeta{ 121 Name: name, 122 Namespace: namespace, 123 } 124 } 125 namespaces := func(names ...string) []*corev1.Namespace { 126 v := make([]*corev1.Namespace, len(names)) 127 for i, name := range names { 128 v[i] = &corev1.Namespace{ObjectMeta: objectMeta("", name)} 129 } 130 return v 131 } 132 hostnames := func(names ...v1.Hostname) []v1.Hostname { return names } 133 134 tests := []struct { 135 title string 136 config Config 137 namespaces []*corev1.Namespace 138 gateways []*v1.Gateway 139 routes []*v1.HTTPRoute 140 endpoints []*endpoint.Endpoint 141 }{ 142 { 143 title: "GatewayNamespace", 144 config: Config{ 145 GatewayNamespace: "gateway-namespace", 146 }, 147 namespaces: namespaces("gateway-namespace", "not-gateway-namespace", "route-namespace"), 148 gateways: []*v1.Gateway{ 149 { 150 ObjectMeta: objectMeta("gateway-namespace", "test"), 151 Spec: v1.GatewaySpec{ 152 Listeners: []v1.Listener{{ 153 Protocol: v1.HTTPProtocolType, 154 AllowedRoutes: allowAllNamespaces, 155 }}, 156 }, 157 Status: gatewayStatus("1.2.3.4"), 158 }, 159 { 160 ObjectMeta: objectMeta("not-gateway-namespace", "test"), 161 Spec: v1.GatewaySpec{ 162 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 163 }, 164 Status: gatewayStatus("2.3.4.5"), 165 }, 166 }, 167 routes: []*v1.HTTPRoute{{ 168 ObjectMeta: objectMeta("route-namespace", "test"), 169 Spec: v1.HTTPRouteSpec{ 170 Hostnames: hostnames("test.example.internal"), 171 }, 172 Status: httpRouteStatus( // The route is attached to both gateways. 173 gwParentRef("gateway-namespace", "test"), 174 gwParentRef("not-gateway-namespace", "test"), 175 ), 176 }}, 177 endpoints: []*endpoint.Endpoint{ 178 newTestEndpoint("test.example.internal", "A", "1.2.3.4"), 179 }, 180 }, 181 { 182 title: "RouteNamespace", 183 config: Config{ 184 Namespace: "route-namespace", 185 }, 186 namespaces: namespaces("gateway-namespace", "route-namespace", "not-route-namespace"), 187 gateways: []*v1.Gateway{{ 188 ObjectMeta: objectMeta("gateway-namespace", "test"), 189 Spec: v1.GatewaySpec{ 190 Listeners: []v1.Listener{{ 191 Protocol: v1.HTTPProtocolType, 192 AllowedRoutes: allowAllNamespaces, 193 }}, 194 }, 195 Status: gatewayStatus("1.2.3.4"), 196 }}, 197 routes: []*v1.HTTPRoute{ 198 { 199 ObjectMeta: objectMeta("route-namespace", "test"), 200 Spec: v1.HTTPRouteSpec{ 201 Hostnames: hostnames("route-namespace.example.internal"), 202 }, 203 Status: httpRouteStatus(gwParentRef("gateway-namespace", "test")), 204 }, 205 { 206 ObjectMeta: objectMeta("not-route-namespace", "test"), 207 Spec: v1.HTTPRouteSpec{ 208 Hostnames: hostnames("not-route-namespace.example.internal"), 209 }, 210 Status: httpRouteStatus(gwParentRef("gateway-namespace", "test")), 211 }, 212 }, 213 endpoints: []*endpoint.Endpoint{ 214 newTestEndpoint("route-namespace.example.internal", "A", "1.2.3.4"), 215 }, 216 }, 217 { 218 title: "GatewayLabelFilter", 219 config: Config{ 220 GatewayLabelFilter: "foo=bar", 221 }, 222 namespaces: namespaces("default"), 223 gateways: []*v1.Gateway{ 224 { 225 ObjectMeta: metav1.ObjectMeta{ 226 Name: "labels-match", 227 Namespace: "default", 228 Labels: map[string]string{"foo": "bar"}, 229 }, 230 Spec: v1.GatewaySpec{ 231 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 232 }, 233 Status: gatewayStatus("1.2.3.4"), 234 }, 235 { 236 ObjectMeta: metav1.ObjectMeta{ 237 Name: "labels-dont-match", 238 Namespace: "default", 239 Labels: map[string]string{"foo": "qux"}, 240 }, 241 Spec: v1.GatewaySpec{ 242 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 243 }, 244 Status: gatewayStatus("2.3.4.5"), 245 }, 246 }, 247 routes: []*v1.HTTPRoute{{ 248 ObjectMeta: objectMeta("default", "test"), 249 Spec: v1.HTTPRouteSpec{ 250 Hostnames: hostnames("test.example.internal"), 251 }, 252 Status: httpRouteStatus( // The route is attached to both gateways. 253 gwParentRef("default", "labels-match"), 254 gwParentRef("default", "labels-dont-match"), 255 ), 256 }}, 257 endpoints: []*endpoint.Endpoint{ 258 newTestEndpoint("test.example.internal", "A", "1.2.3.4"), 259 }, 260 }, 261 { 262 title: "RouteLabelFilter", 263 config: Config{ 264 LabelFilter: mustGetLabelSelector("foo=bar"), 265 }, 266 namespaces: namespaces("default"), 267 gateways: []*v1.Gateway{{ 268 ObjectMeta: objectMeta("default", "test"), 269 Spec: v1.GatewaySpec{ 270 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 271 }, 272 Status: gatewayStatus("1.2.3.4"), 273 }}, 274 routes: []*v1.HTTPRoute{ 275 { 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: "labels-match", 278 Namespace: "default", 279 Labels: map[string]string{"foo": "bar"}, 280 }, 281 Spec: v1.HTTPRouteSpec{ 282 Hostnames: hostnames("labels-match.example.internal"), 283 }, 284 Status: httpRouteStatus(gwParentRef("default", "test")), 285 }, 286 { 287 ObjectMeta: metav1.ObjectMeta{ 288 Name: "labels-dont-match", 289 Namespace: "default", 290 Labels: map[string]string{"foo": "qux"}, 291 }, 292 Spec: v1.HTTPRouteSpec{ 293 Hostnames: hostnames("labels-dont-match.example.internal"), 294 }, 295 Status: httpRouteStatus(gwParentRef("default", "test")), 296 }, 297 }, 298 endpoints: []*endpoint.Endpoint{ 299 newTestEndpoint("labels-match.example.internal", "A", "1.2.3.4"), 300 }, 301 }, 302 { 303 title: "RouteAnnotationFilter", 304 config: Config{ 305 AnnotationFilter: "foo=bar", 306 }, 307 namespaces: namespaces("default"), 308 gateways: []*v1.Gateway{{ 309 ObjectMeta: objectMeta("default", "test"), 310 Spec: v1.GatewaySpec{ 311 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 312 }, 313 Status: gatewayStatus("1.2.3.4"), 314 }}, 315 routes: []*v1.HTTPRoute{ 316 { 317 ObjectMeta: metav1.ObjectMeta{ 318 Name: "annotations-match", 319 Namespace: "default", 320 Annotations: map[string]string{"foo": "bar"}, 321 }, 322 Spec: v1.HTTPRouteSpec{ 323 Hostnames: hostnames("annotations-match.example.internal"), 324 }, 325 Status: httpRouteStatus(gwParentRef("default", "test")), 326 }, 327 { 328 ObjectMeta: metav1.ObjectMeta{ 329 Name: "annotations-dont-match", 330 Namespace: "default", 331 Annotations: map[string]string{"foo": "qux"}, 332 }, 333 Spec: v1.HTTPRouteSpec{ 334 Hostnames: hostnames("annotations-dont-match.example.internal"), 335 }, 336 Status: httpRouteStatus(gwParentRef("default", "test")), 337 }, 338 }, 339 endpoints: []*endpoint.Endpoint{ 340 newTestEndpoint("annotations-match.example.internal", "A", "1.2.3.4"), 341 }, 342 }, 343 { 344 title: "SkipControllerAnnotation", 345 config: Config{}, 346 namespaces: namespaces("default"), 347 gateways: []*v1.Gateway{{ 348 ObjectMeta: objectMeta("default", "test"), 349 Spec: v1.GatewaySpec{ 350 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 351 }, 352 Status: gatewayStatus("1.2.3.4"), 353 }}, 354 routes: []*v1.HTTPRoute{{ 355 ObjectMeta: metav1.ObjectMeta{ 356 Name: "api", 357 Namespace: "default", 358 Annotations: map[string]string{ 359 controllerAnnotationKey: "something-else", 360 }, 361 }, 362 Spec: v1.HTTPRouteSpec{ 363 Hostnames: hostnames("api.example.internal"), 364 }, 365 Status: httpRouteStatus(gwParentRef("default", "test")), 366 }}, 367 endpoints: nil, 368 }, 369 { 370 title: "MultipleGateways", 371 config: Config{}, 372 namespaces: namespaces("default"), 373 gateways: []*v1.Gateway{ 374 { 375 ObjectMeta: objectMeta("default", "one"), 376 Spec: v1.GatewaySpec{ 377 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 378 }, 379 Status: gatewayStatus("1.2.3.4"), 380 }, 381 { 382 ObjectMeta: objectMeta("default", "two"), 383 Spec: v1.GatewaySpec{ 384 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 385 }, 386 Status: gatewayStatus("2.3.4.5"), 387 }, 388 }, 389 routes: []*v1.HTTPRoute{{ 390 ObjectMeta: objectMeta("default", "test"), 391 Spec: v1.HTTPRouteSpec{ 392 Hostnames: hostnames("test.example.internal"), 393 }, 394 Status: httpRouteStatus( 395 gwParentRef("default", "one"), 396 gwParentRef("default", "two"), 397 ), 398 }}, 399 endpoints: []*endpoint.Endpoint{ 400 newTestEndpoint("test.example.internal", "A", "1.2.3.4", "2.3.4.5"), 401 }, 402 }, 403 { 404 title: "MultipleListeners", 405 config: Config{}, 406 namespaces: namespaces("default"), 407 gateways: []*v1.Gateway{{ 408 ObjectMeta: objectMeta("default", "one"), 409 Spec: v1.GatewaySpec{ 410 Listeners: []v1.Listener{ 411 { 412 Name: "foo", 413 Protocol: v1.HTTPProtocolType, 414 Hostname: hostnamePtr("foo.example.internal"), 415 }, 416 { 417 Name: "bar", 418 Protocol: v1.HTTPProtocolType, 419 Hostname: hostnamePtr("bar.example.internal"), 420 }, 421 }, 422 }, 423 Status: gatewayStatus("1.2.3.4"), 424 }}, 425 routes: []*v1.HTTPRoute{{ 426 ObjectMeta: objectMeta("default", "test"), 427 Spec: v1.HTTPRouteSpec{ 428 Hostnames: hostnames("*.example.internal"), 429 }, 430 Status: httpRouteStatus( 431 gwParentRef("default", "one"), 432 ), 433 }}, 434 endpoints: []*endpoint.Endpoint{ 435 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 436 newTestEndpoint("bar.example.internal", "A", "1.2.3.4"), 437 }, 438 }, 439 { 440 title: "SectionNameMatch", 441 config: Config{}, 442 namespaces: namespaces("default"), 443 gateways: []*v1.Gateway{{ 444 ObjectMeta: objectMeta("default", "test"), 445 Spec: v1.GatewaySpec{ 446 Listeners: []v1.Listener{ 447 { 448 Name: "foo", 449 Protocol: v1.HTTPProtocolType, 450 Hostname: hostnamePtr("foo.example.internal"), 451 }, 452 { 453 Name: "bar", 454 Protocol: v1.HTTPProtocolType, 455 Hostname: hostnamePtr("bar.example.internal"), 456 }, 457 }, 458 }, 459 Status: gatewayStatus("1.2.3.4"), 460 }}, 461 routes: []*v1.HTTPRoute{{ 462 ObjectMeta: objectMeta("default", "test"), 463 Spec: v1.HTTPRouteSpec{ 464 Hostnames: hostnames("*.example.internal"), 465 }, 466 Status: httpRouteStatus( 467 gwParentRef("default", "test", withSectionName("foo")), 468 ), 469 }}, 470 endpoints: []*endpoint.Endpoint{ 471 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 472 }, 473 }, 474 { 475 // EXPERIMENTAL: https://gateway-api.sigs.k8s.io/geps/gep-957/ 476 title: "PortNumberMatch", 477 config: Config{}, 478 namespaces: namespaces("default"), 479 gateways: []*v1.Gateway{{ 480 ObjectMeta: objectMeta("default", "test"), 481 Spec: v1.GatewaySpec{ 482 Listeners: []v1.Listener{ 483 { 484 Name: "foo", 485 Protocol: v1.HTTPProtocolType, 486 Hostname: hostnamePtr("foo.example.internal"), 487 Port: 80, 488 }, 489 { 490 Name: "bar", 491 Protocol: v1.HTTPProtocolType, 492 Hostname: hostnamePtr("bar.example.internal"), 493 Port: 80, 494 }, 495 { 496 Name: "qux", 497 Protocol: v1.HTTPProtocolType, 498 Hostname: hostnamePtr("qux.example.internal"), 499 Port: 8080, 500 }, 501 }, 502 }, 503 Status: gatewayStatus("1.2.3.4"), 504 }}, 505 routes: []*v1.HTTPRoute{{ 506 ObjectMeta: objectMeta("default", "test"), 507 Spec: v1.HTTPRouteSpec{ 508 Hostnames: hostnames("*.example.internal"), 509 }, 510 Status: httpRouteStatus( 511 gwParentRef("default", "test", withPortNumber(80)), 512 ), 513 }}, 514 endpoints: []*endpoint.Endpoint{ 515 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 516 newTestEndpoint("bar.example.internal", "A", "1.2.3.4"), 517 }, 518 }, 519 { 520 title: "WildcardInGateway", 521 config: Config{}, 522 namespaces: namespaces("default"), 523 gateways: []*v1.Gateway{{ 524 ObjectMeta: objectMeta("default", "test"), 525 Spec: v1.GatewaySpec{ 526 Listeners: []v1.Listener{{ 527 Protocol: v1.HTTPProtocolType, 528 Hostname: hostnamePtr("*.example.internal"), 529 }}, 530 }, 531 Status: gatewayStatus("1.2.3.4"), 532 }}, 533 routes: []*v1.HTTPRoute{{ 534 ObjectMeta: objectMeta("default", "no-hostname"), 535 Spec: v1.HTTPRouteSpec{ 536 Hostnames: []v1.Hostname{ 537 "foo.example.internal", 538 }, 539 }, 540 Status: httpRouteStatus(gwParentRef("default", "test")), 541 }}, 542 endpoints: []*endpoint.Endpoint{ 543 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 544 }, 545 }, 546 { 547 title: "WildcardInRoute", 548 config: Config{}, 549 namespaces: namespaces("default"), 550 gateways: []*v1.Gateway{{ 551 ObjectMeta: objectMeta("default", "test"), 552 Spec: v1.GatewaySpec{ 553 Listeners: []v1.Listener{{ 554 Protocol: v1.HTTPProtocolType, 555 Hostname: hostnamePtr("foo.example.internal"), 556 }}, 557 }, 558 Status: gatewayStatus("1.2.3.4"), 559 }}, 560 routes: []*v1.HTTPRoute{{ 561 ObjectMeta: objectMeta("default", "no-hostname"), 562 Spec: v1.HTTPRouteSpec{ 563 Hostnames: []v1.Hostname{ 564 "*.example.internal", 565 }, 566 }, 567 Status: httpRouteStatus(gwParentRef("default", "test")), 568 }}, 569 endpoints: []*endpoint.Endpoint{ 570 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 571 }, 572 }, 573 { 574 title: "WildcardInRouteAndGateway", 575 config: Config{}, 576 namespaces: namespaces("default"), 577 gateways: []*v1.Gateway{{ 578 ObjectMeta: objectMeta("default", "test"), 579 Spec: v1.GatewaySpec{ 580 Listeners: []v1.Listener{{ 581 Protocol: v1.HTTPProtocolType, 582 Hostname: hostnamePtr("*.example.internal"), 583 }}, 584 }, 585 Status: gatewayStatus("1.2.3.4"), 586 }}, 587 routes: []*v1.HTTPRoute{{ 588 ObjectMeta: objectMeta("default", "no-hostname"), 589 Spec: v1.HTTPRouteSpec{ 590 Hostnames: []v1.Hostname{ 591 "*.example.internal", 592 }, 593 }, 594 Status: httpRouteStatus(gwParentRef("default", "test")), 595 }}, 596 endpoints: []*endpoint.Endpoint{ 597 newTestEndpoint("*.example.internal", "A", "1.2.3.4"), 598 }, 599 }, 600 { 601 title: "NoRouteHostname", 602 config: Config{}, 603 namespaces: namespaces("default"), 604 gateways: []*v1.Gateway{{ 605 ObjectMeta: objectMeta("default", "test"), 606 Spec: v1.GatewaySpec{ 607 Listeners: []v1.Listener{{ 608 Protocol: v1.HTTPProtocolType, 609 Hostname: hostnamePtr("foo.example.internal"), 610 }}, 611 }, 612 Status: gatewayStatus("1.2.3.4"), 613 }}, 614 routes: []*v1.HTTPRoute{{ 615 ObjectMeta: objectMeta("default", "no-hostname"), 616 Spec: v1.HTTPRouteSpec{ 617 Hostnames: nil, 618 }, 619 Status: httpRouteStatus(gwParentRef("default", "test")), 620 }}, 621 endpoints: []*endpoint.Endpoint{ 622 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 623 }, 624 }, 625 { 626 title: "NoGateways", 627 config: Config{}, 628 namespaces: namespaces("default"), 629 gateways: nil, 630 routes: []*v1.HTTPRoute{{ 631 ObjectMeta: objectMeta("default", "test"), 632 Spec: v1.HTTPRouteSpec{ 633 Hostnames: hostnames("example.internal"), 634 }, 635 Status: httpRouteStatus(), 636 }}, 637 endpoints: nil, 638 }, 639 { 640 title: "NoHostnames", 641 config: Config{}, 642 namespaces: namespaces("default"), 643 gateways: []*v1.Gateway{{ 644 ObjectMeta: objectMeta("default", "test"), 645 Spec: v1.GatewaySpec{ 646 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 647 }, 648 Status: gatewayStatus("1.2.3.4"), 649 }}, 650 routes: []*v1.HTTPRoute{{ 651 ObjectMeta: objectMeta("default", "no-hostname"), 652 Spec: v1.HTTPRouteSpec{ 653 Hostnames: nil, 654 }, 655 Status: httpRouteStatus(gwParentRef("default", "test")), 656 }}, 657 endpoints: nil, 658 }, 659 { 660 title: "HostnameAnnotation", 661 config: Config{}, 662 namespaces: namespaces("default"), 663 gateways: []*v1.Gateway{{ 664 ObjectMeta: objectMeta("default", "test"), 665 Spec: v1.GatewaySpec{ 666 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 667 }, 668 Status: gatewayStatus("1.2.3.4"), 669 }}, 670 routes: []*v1.HTTPRoute{ 671 { 672 ObjectMeta: metav1.ObjectMeta{ 673 Name: "without-hostame", 674 Namespace: "default", 675 Annotations: map[string]string{ 676 hostnameAnnotationKey: "annotation.without-hostname.internal", 677 }, 678 }, 679 Spec: v1.HTTPRouteSpec{ 680 Hostnames: nil, 681 }, 682 Status: httpRouteStatus(gwParentRef("default", "test")), 683 }, 684 { 685 ObjectMeta: metav1.ObjectMeta{ 686 Name: "with-hostame", 687 Namespace: "default", 688 Annotations: map[string]string{ 689 hostnameAnnotationKey: "annotation.with-hostname.internal", 690 }, 691 }, 692 Spec: v1.HTTPRouteSpec{ 693 Hostnames: hostnames("with-hostname.internal"), 694 }, 695 Status: httpRouteStatus(gwParentRef("default", "test")), 696 }, 697 }, 698 endpoints: []*endpoint.Endpoint{ 699 newTestEndpoint("annotation.without-hostname.internal", "A", "1.2.3.4"), 700 newTestEndpoint("annotation.with-hostname.internal", "A", "1.2.3.4"), 701 newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"), 702 }, 703 }, 704 { 705 title: "IgnoreHostnameAnnotation", 706 config: Config{ 707 IgnoreHostnameAnnotation: true, 708 }, 709 namespaces: namespaces("default"), 710 gateways: []*v1.Gateway{{ 711 ObjectMeta: objectMeta("default", "test"), 712 Spec: v1.GatewaySpec{ 713 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 714 }, 715 Status: gatewayStatus("1.2.3.4"), 716 }}, 717 routes: []*v1.HTTPRoute{{ 718 ObjectMeta: metav1.ObjectMeta{ 719 Name: "with-hostame", 720 Namespace: "default", 721 Annotations: map[string]string{ 722 hostnameAnnotationKey: "annotation.with-hostname.internal", 723 }, 724 }, 725 Spec: v1.HTTPRouteSpec{ 726 Hostnames: hostnames("with-hostname.internal"), 727 }, 728 Status: httpRouteStatus(gwParentRef("default", "test")), 729 }}, 730 endpoints: []*endpoint.Endpoint{ 731 newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"), 732 }, 733 }, 734 { 735 title: "FQDNTemplate", 736 config: Config{ 737 FQDNTemplate: "{{.Name}}.zero.internal, {{.Name}}.one.internal. , {{.Name}}.two.internal ", 738 }, 739 namespaces: namespaces("default"), 740 gateways: []*v1.Gateway{{ 741 ObjectMeta: objectMeta("default", "test"), 742 Spec: v1.GatewaySpec{ 743 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 744 }, 745 Status: gatewayStatus("1.2.3.4"), 746 }}, 747 routes: []*v1.HTTPRoute{ 748 { 749 ObjectMeta: objectMeta("default", "fqdn-with-hostnames"), 750 Spec: v1.HTTPRouteSpec{ 751 Hostnames: hostnames("fqdn-with-hostnames.internal"), 752 }, 753 Status: httpRouteStatus(gwParentRef("default", "test")), 754 }, 755 { 756 ObjectMeta: objectMeta("default", "fqdn-without-hostnames"), 757 Spec: v1.HTTPRouteSpec{ 758 Hostnames: nil, 759 }, 760 Status: httpRouteStatus(gwParentRef("default", "test")), 761 }, 762 }, 763 endpoints: []*endpoint.Endpoint{ 764 newTestEndpoint("fqdn-without-hostnames.zero.internal", "A", "1.2.3.4"), 765 newTestEndpoint("fqdn-without-hostnames.one.internal", "A", "1.2.3.4"), 766 newTestEndpoint("fqdn-without-hostnames.two.internal", "A", "1.2.3.4"), 767 newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"), 768 }, 769 }, 770 { 771 title: "CombineFQDN", 772 config: Config{ 773 FQDNTemplate: "combine-{{.Name}}.internal", 774 CombineFQDNAndAnnotation: true, 775 }, 776 namespaces: namespaces("default"), 777 gateways: []*v1.Gateway{{ 778 ObjectMeta: objectMeta("default", "test"), 779 Spec: v1.GatewaySpec{ 780 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 781 }, 782 Status: gatewayStatus("1.2.3.4"), 783 }}, 784 routes: []*v1.HTTPRoute{{ 785 ObjectMeta: objectMeta("default", "fqdn-with-hostnames"), 786 Spec: v1.HTTPRouteSpec{ 787 Hostnames: hostnames("fqdn-with-hostnames.internal"), 788 }, 789 Status: httpRouteStatus(gwParentRef("default", "test")), 790 }}, 791 endpoints: []*endpoint.Endpoint{ 792 newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"), 793 newTestEndpoint("combine-fqdn-with-hostnames.internal", "A", "1.2.3.4"), 794 }, 795 }, 796 { 797 title: "TTL", 798 config: Config{}, 799 namespaces: namespaces("default"), 800 gateways: []*v1.Gateway{{ 801 ObjectMeta: objectMeta("default", "test"), 802 Spec: v1.GatewaySpec{ 803 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 804 }, 805 Status: gatewayStatus("1.2.3.4"), 806 }}, 807 routes: []*v1.HTTPRoute{ 808 { 809 ObjectMeta: metav1.ObjectMeta{ 810 Name: "valid-ttl", 811 Namespace: "default", 812 Annotations: map[string]string{ttlAnnotationKey: "15s"}, 813 }, 814 Spec: v1.HTTPRouteSpec{ 815 Hostnames: hostnames("valid-ttl.internal"), 816 }, 817 Status: httpRouteStatus(gwParentRef("default", "test")), 818 }, 819 { 820 ObjectMeta: metav1.ObjectMeta{ 821 Name: "invalid-ttl", 822 Namespace: "default", 823 Annotations: map[string]string{ttlAnnotationKey: "abc"}, 824 }, 825 Spec: v1.HTTPRouteSpec{ 826 Hostnames: hostnames("invalid-ttl.internal"), 827 }, 828 Status: httpRouteStatus(gwParentRef("default", "test")), 829 }, 830 }, 831 endpoints: []*endpoint.Endpoint{ 832 newTestEndpoint("invalid-ttl.internal", "A", "1.2.3.4"), 833 newTestEndpointWithTTL("valid-ttl.internal", "A", 15, "1.2.3.4"), 834 }, 835 }, 836 { 837 title: "ProviderAnnotations", 838 config: Config{}, 839 namespaces: namespaces("default"), 840 gateways: []*v1.Gateway{{ 841 ObjectMeta: objectMeta("default", "test"), 842 Spec: v1.GatewaySpec{ 843 Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, 844 }, 845 Status: gatewayStatus("1.2.3.4"), 846 }}, 847 routes: []*v1.HTTPRoute{{ 848 ObjectMeta: metav1.ObjectMeta{ 849 Name: "provider-annotations", 850 Namespace: "default", 851 Annotations: map[string]string{ 852 SetIdentifierKey: "test-set-identifier", 853 aliasAnnotationKey: "true", 854 }, 855 }, 856 Spec: v1.HTTPRouteSpec{ 857 Hostnames: hostnames("provider-annotations.com"), 858 }, 859 Status: httpRouteStatus(gwParentRef("default", "test")), 860 }}, 861 endpoints: []*endpoint.Endpoint{ 862 newTestEndpoint("provider-annotations.com", "A", "1.2.3.4"). 863 WithProviderSpecific("alias", "true"). 864 WithSetIdentifier("test-set-identifier"), 865 }, 866 }, 867 { 868 title: "DifferentHostnameDifferentGateway", 869 config: Config{}, 870 namespaces: namespaces("default"), 871 gateways: []*v1.Gateway{ 872 { 873 ObjectMeta: objectMeta("default", "one"), 874 Spec: v1.GatewaySpec{ 875 Listeners: []v1.Listener{{ 876 Hostname: hostnamePtr("*.one.internal"), 877 Protocol: v1.HTTPProtocolType, 878 }}, 879 }, 880 Status: gatewayStatus("1.2.3.4"), 881 }, 882 { 883 ObjectMeta: objectMeta("default", "two"), 884 Spec: v1.GatewaySpec{ 885 Listeners: []v1.Listener{{ 886 Hostname: hostnamePtr("*.two.internal"), 887 Protocol: v1.HTTPProtocolType, 888 }}, 889 }, 890 Status: gatewayStatus("2.3.4.5"), 891 }, 892 }, 893 routes: []*v1.HTTPRoute{{ 894 ObjectMeta: objectMeta("default", "test"), 895 Spec: v1.HTTPRouteSpec{ 896 Hostnames: hostnames("test.one.internal", "test.two.internal"), 897 }, 898 Status: httpRouteStatus( 899 gwParentRef("default", "one"), 900 gwParentRef("default", "two"), 901 ), 902 }}, 903 endpoints: []*endpoint.Endpoint{ 904 newTestEndpoint("test.one.internal", "A", "1.2.3.4"), 905 newTestEndpoint("test.two.internal", "A", "2.3.4.5"), 906 }, 907 }, 908 { 909 title: "AllowedRoutesSameNamespace", 910 config: Config{}, 911 namespaces: namespaces("same-namespace", "other-namespace"), 912 gateways: []*v1.Gateway{{ 913 ObjectMeta: objectMeta("same-namespace", "test"), 914 Spec: v1.GatewaySpec{ 915 Listeners: []v1.Listener{{ 916 Protocol: v1.HTTPProtocolType, 917 AllowedRoutes: &v1.AllowedRoutes{ 918 Namespaces: &v1.RouteNamespaces{ 919 From: &fromSame, 920 }, 921 }, 922 }}, 923 }, 924 Status: gatewayStatus("1.2.3.4"), 925 }}, 926 routes: []*v1.HTTPRoute{ 927 { 928 ObjectMeta: objectMeta("same-namespace", "test"), 929 Spec: v1.HTTPRouteSpec{ 930 Hostnames: hostnames("same-namespace.example.internal"), 931 }, 932 Status: httpRouteStatus(gwParentRef("same-namespace", "test")), 933 }, 934 { 935 ObjectMeta: objectMeta("other-namespace", "test"), 936 Spec: v1.HTTPRouteSpec{ 937 Hostnames: hostnames("other-namespace.example.internal"), 938 }, 939 Status: httpRouteStatus(gwParentRef("same-namespace", "test")), 940 }, 941 }, 942 endpoints: []*endpoint.Endpoint{ 943 newTestEndpoint("same-namespace.example.internal", "A", "1.2.3.4"), 944 }, 945 }, 946 { 947 title: "AllowedRoutesNamespaceSelector", 948 config: Config{}, 949 namespaces: []*corev1.Namespace{ 950 { 951 ObjectMeta: metav1.ObjectMeta{ 952 Name: "default", 953 }, 954 }, 955 { 956 ObjectMeta: metav1.ObjectMeta{ 957 Name: "foo", 958 Labels: map[string]string{"team": "foo"}, 959 }, 960 }, 961 { 962 ObjectMeta: metav1.ObjectMeta{ 963 Name: "bar", 964 Labels: map[string]string{"team": "bar"}, 965 }, 966 }, 967 }, 968 gateways: []*v1.Gateway{{ 969 ObjectMeta: objectMeta("default", "test"), 970 Spec: v1.GatewaySpec{ 971 Listeners: []v1.Listener{{ 972 Protocol: v1.HTTPProtocolType, 973 AllowedRoutes: &v1.AllowedRoutes{ 974 Namespaces: &v1.RouteNamespaces{ 975 From: &fromSelector, 976 Selector: &metav1.LabelSelector{ 977 MatchLabels: map[string]string{"team": "foo"}, 978 }, 979 }, 980 }, 981 }}, 982 }, 983 Status: gatewayStatus("1.2.3.4"), 984 }}, 985 routes: []*v1.HTTPRoute{ 986 { 987 ObjectMeta: objectMeta("foo", "test"), 988 Spec: v1.HTTPRouteSpec{ 989 Hostnames: hostnames("foo.example.internal"), 990 }, 991 Status: httpRouteStatus(gwParentRef("default", "test")), 992 }, 993 { 994 ObjectMeta: objectMeta("bar", "test"), 995 Spec: v1.HTTPRouteSpec{ 996 Hostnames: hostnames("bar.example.internal"), 997 }, 998 Status: httpRouteStatus(gwParentRef("default", "test")), 999 }, 1000 }, 1001 endpoints: []*endpoint.Endpoint{ 1002 newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), 1003 }, 1004 }, 1005 { 1006 title: "MissingNamespace", 1007 config: Config{}, 1008 namespaces: nil, 1009 gateways: []*v1.Gateway{{ 1010 ObjectMeta: objectMeta("default", "test"), 1011 Spec: v1.GatewaySpec{ 1012 Listeners: []v1.Listener{{ 1013 Protocol: v1.HTTPProtocolType, 1014 AllowedRoutes: &v1.AllowedRoutes{ 1015 Namespaces: &v1.RouteNamespaces{ 1016 // Namespace selector triggers namespace lookup. 1017 From: &fromSelector, 1018 Selector: &metav1.LabelSelector{ 1019 MatchLabels: map[string]string{"foo": "bar"}, 1020 }, 1021 }, 1022 }, 1023 }}, 1024 }, 1025 Status: gatewayStatus("1.2.3.4"), 1026 }}, 1027 routes: []*v1.HTTPRoute{{ 1028 ObjectMeta: objectMeta("default", "test"), 1029 Spec: v1.HTTPRouteSpec{ 1030 Hostnames: hostnames("example.internal"), 1031 }, 1032 Status: httpRouteStatus(gwParentRef("default", "test")), 1033 }}, 1034 endpoints: nil, 1035 }, 1036 { 1037 title: "AnnotationOverride", 1038 config: Config{ 1039 GatewayNamespace: "gateway-namespace", 1040 }, 1041 namespaces: namespaces("gateway-namespace", "route-namespace"), 1042 gateways: []*v1.Gateway{ 1043 { 1044 ObjectMeta: metav1.ObjectMeta{ 1045 Name: "overriden-gateway", 1046 Namespace: "gateway-namespace", 1047 Annotations: map[string]string{ 1048 targetAnnotationKey: "4.3.2.1", 1049 }, 1050 }, 1051 Spec: v1.GatewaySpec{ 1052 Listeners: []v1.Listener{{ 1053 Protocol: v1.HTTPProtocolType, 1054 AllowedRoutes: allowAllNamespaces, 1055 }}, 1056 }, 1057 Status: gatewayStatus("1.2.3.4"), 1058 }, 1059 }, 1060 routes: []*v1.HTTPRoute{{ 1061 ObjectMeta: objectMeta("route-namespace", "test"), 1062 Spec: v1.HTTPRouteSpec{ 1063 Hostnames: hostnames("test.example.internal"), 1064 }, 1065 Status: httpRouteStatus( // The route is attached to both gateways. 1066 gwParentRef("gateway-namespace", "overriden-gateway"), 1067 ), 1068 }}, 1069 endpoints: []*endpoint.Endpoint{ 1070 newTestEndpoint("test.example.internal", "A", "4.3.2.1"), 1071 }, 1072 }, 1073 { 1074 title: "MutlipleGatewaysOneAnnotationOverride", 1075 config: Config{ 1076 GatewayNamespace: "gateway-namespace", 1077 }, 1078 namespaces: namespaces("gateway-namespace", "route-namespace"), 1079 gateways: []*v1.Gateway{ 1080 { 1081 ObjectMeta: metav1.ObjectMeta{ 1082 Name: "overriden-gateway", 1083 Namespace: "gateway-namespace", 1084 Annotations: map[string]string{ 1085 targetAnnotationKey: "4.3.2.1", 1086 }, 1087 }, 1088 Spec: v1.GatewaySpec{ 1089 Listeners: []v1.Listener{{ 1090 Protocol: v1.HTTPProtocolType, 1091 AllowedRoutes: allowAllNamespaces, 1092 }}, 1093 }, 1094 Status: gatewayStatus("1.2.3.4"), 1095 }, 1096 { 1097 ObjectMeta: objectMeta("gateway-namespace", "test"), 1098 Spec: v1.GatewaySpec{ 1099 Listeners: []v1.Listener{{ 1100 Protocol: v1.HTTPProtocolType, 1101 AllowedRoutes: allowAllNamespaces, 1102 }}, 1103 }, 1104 Status: gatewayStatus("2.3.4.5"), 1105 }, 1106 }, 1107 routes: []*v1.HTTPRoute{{ 1108 ObjectMeta: objectMeta("route-namespace", "test"), 1109 Spec: v1.HTTPRouteSpec{ 1110 Hostnames: hostnames("test.example.internal"), 1111 }, 1112 Status: httpRouteStatus( // The route is attached to both gateways. 1113 gwParentRef("gateway-namespace", "overriden-gateway"), 1114 gwParentRef("gateway-namespace", "test"), 1115 ), 1116 }}, 1117 endpoints: []*endpoint.Endpoint{ 1118 newTestEndpoint("test.example.internal", "A", "4.3.2.1", "2.3.4.5"), 1119 }, 1120 }, 1121 } 1122 for _, tt := range tests { 1123 tt := tt 1124 t.Run(tt.title, func(t *testing.T) { 1125 t.Parallel() 1126 1127 ctx := context.Background() 1128 gwClient := gatewayfake.NewSimpleClientset() 1129 for _, gw := range tt.gateways { 1130 _, err := gwClient.GatewayV1().Gateways(gw.Namespace).Create(ctx, gw, metav1.CreateOptions{}) 1131 require.NoError(t, err, "failed to create Gateway") 1132 1133 } 1134 for _, rt := range tt.routes { 1135 _, err := gwClient.GatewayV1().HTTPRoutes(rt.Namespace).Create(ctx, rt, metav1.CreateOptions{}) 1136 require.NoError(t, err, "failed to create HTTPRoute") 1137 } 1138 kubeClient := kubefake.NewSimpleClientset() 1139 for _, ns := range tt.namespaces { 1140 _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) 1141 require.NoError(t, err, "failed to create Namespace") 1142 } 1143 1144 clients := new(MockClientGenerator) 1145 clients.On("GatewayClient").Return(gwClient, nil) 1146 clients.On("KubeClient").Return(kubeClient, nil) 1147 1148 src, err := NewGatewayHTTPRouteSource(clients, &tt.config) 1149 require.NoError(t, err, "failed to create Gateway HTTPRoute Source") 1150 1151 endpoints, err := src.Endpoints(ctx) 1152 require.NoError(t, err, "failed to get Endpoints") 1153 validateEndpoints(t, endpoints, tt.endpoints) 1154 }) 1155 } 1156 } 1157 1158 func hostnamePtr(val v1.Hostname) *v1.Hostname { return &val }