github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/httproute_reconcile_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package gateway_api 5 6 import ( 7 "context" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 corev1 "k8s.io/api/core/v1" 13 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/types" 16 ctrl "sigs.k8s.io/controller-runtime" 17 "sigs.k8s.io/controller-runtime/pkg/client" 18 "sigs.k8s.io/controller-runtime/pkg/client/fake" 19 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 20 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 21 mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1" 22 mcsapicontrollers "sigs.k8s.io/mcs-api/pkg/controllers" 23 24 "github.com/cilium/cilium/operator/pkg/model" 25 ) 26 27 var ( 28 httpRFFinalizer = "batch.gateway.io/finalizer" 29 30 crdsFixture = []client.Object{ 31 // Minimal ServiceImport CRD for existence checking 32 &apiextensionsv1.CustomResourceDefinition{ 33 ObjectMeta: metav1.ObjectMeta{ 34 Name: "serviceimports.multicluster.x-k8s.io", 35 }, 36 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 37 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ 38 Name: "v1alpha1", 39 }}, 40 }, 41 }, 42 } 43 44 httpRouteServiceImportFixture = []client.Object{ 45 // Service for valid HTTPRoute 46 &mcsapiv1alpha1.ServiceImport{ 47 ObjectMeta: metav1.ObjectMeta{ 48 Name: "dummy-backend", 49 Namespace: "default", 50 Annotations: map[string]string{ 51 mcsapicontrollers.DerivedServiceAnnotation: "dummy-backend", 52 }, 53 }, 54 }, 55 56 // Service in another namespace 57 &mcsapiv1alpha1.ServiceImport{ 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: "dummy-backend", 60 Namespace: "another-namespace", 61 Annotations: map[string]string{ 62 mcsapicontrollers.DerivedServiceAnnotation: "dummy-backend", 63 }, 64 }, 65 }, 66 67 // Service for reference grant in another namespace 68 &mcsapiv1alpha1.ServiceImport{ 69 ObjectMeta: metav1.ObjectMeta{ 70 Name: "dummy-backend-grant", 71 Namespace: "another-namespace", 72 Annotations: map[string]string{ 73 mcsapicontrollers.DerivedServiceAnnotation: "dummy-backend-grant", 74 }, 75 }, 76 }, 77 78 // ServiceImport with Service that doesn't exists 79 &mcsapiv1alpha1.ServiceImport{ 80 ObjectMeta: metav1.ObjectMeta{ 81 Name: "dummy-backend-no-svc", 82 Namespace: "default", 83 Annotations: map[string]string{ 84 mcsapicontrollers.DerivedServiceAnnotation: "nonexistent-derived-svc", 85 }, 86 }, 87 }, 88 &mcsapiv1alpha1.ServiceImport{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Name: "dummy-backend-no-svc-annotation", 91 Namespace: "default", 92 }, 93 }, 94 } 95 96 httpRouteFixture = []client.Object{ 97 // GatewayClass 98 &gatewayv1.GatewayClass{ 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: "cilium", 101 }, 102 Spec: gatewayv1.GatewayClassSpec{ 103 ControllerName: "io.cilium/gateway-controller", 104 }, 105 }, 106 107 // Gateway for valid HTTPRoute 108 &gatewayv1.Gateway{ 109 ObjectMeta: metav1.ObjectMeta{ 110 Name: "dummy-gateway", 111 Namespace: "default", 112 }, 113 Spec: gatewayv1.GatewaySpec{ 114 GatewayClassName: "cilium", 115 Listeners: []gatewayv1.Listener{ 116 { 117 Name: "http", 118 Port: 80, 119 Hostname: model.AddressOf[gatewayv1.Hostname]("*.cilium.io"), 120 }, 121 }, 122 }, 123 Status: gatewayv1.GatewayStatus{}, 124 }, 125 126 // Gateway for valid HTTPRoute with hostnames 127 &gatewayv1.Gateway{ 128 ObjectMeta: metav1.ObjectMeta{ 129 Name: "dummy-gateway-hostnames", 130 Namespace: "default", 131 }, 132 Spec: gatewayv1.GatewaySpec{ 133 GatewayClassName: "cilium", 134 Listeners: []gatewayv1.Listener{ 135 { 136 Name: "http", 137 Port: 80, 138 Hostname: model.AddressOf[gatewayv1.Hostname]("bar.foo.com"), 139 }, 140 }, 141 }, 142 Status: gatewayv1.GatewayStatus{}, 143 }, 144 145 // Gateway in another namespace 146 &gatewayv1.Gateway{ 147 ObjectMeta: metav1.ObjectMeta{ 148 Name: "dummy-gateway", 149 Namespace: "another-namespace", 150 }, 151 Spec: gatewayv1.GatewaySpec{ 152 GatewayClassName: "cilium", 153 Listeners: []gatewayv1.Listener{ 154 { 155 Name: "http", 156 Port: 80, 157 AllowedRoutes: &gatewayv1.AllowedRoutes{ 158 Namespaces: &gatewayv1.RouteNamespaces{ 159 From: model.AddressOf(gatewayv1.NamespacesFromSame), 160 }, 161 }, 162 }, 163 }, 164 }, 165 Status: gatewayv1.GatewayStatus{}, 166 }, 167 168 // Gateway in default namespace 169 &gatewayv1.Gateway{ 170 ObjectMeta: metav1.ObjectMeta{ 171 Name: "dummy-gateway-two-listeners", 172 Namespace: "default", 173 }, 174 Spec: gatewayv1.GatewaySpec{ 175 GatewayClassName: "cilium", 176 Listeners: []gatewayv1.Listener{ 177 { 178 Name: "http", 179 Port: 80, 180 AllowedRoutes: &gatewayv1.AllowedRoutes{ 181 Namespaces: &gatewayv1.RouteNamespaces{ 182 From: model.AddressOf(gatewayv1.NamespacesFromSame), 183 }, 184 }, 185 }, 186 { 187 Name: "https", 188 Port: 443, 189 AllowedRoutes: &gatewayv1.AllowedRoutes{ 190 Namespaces: &gatewayv1.RouteNamespaces{ 191 From: model.AddressOf(gatewayv1.NamespacesFromAll), 192 }, 193 }, 194 }, 195 }, 196 }, 197 Status: gatewayv1.GatewayStatus{}, 198 }, 199 // Service for valid HTTPRoute 200 &corev1.Service{ 201 ObjectMeta: metav1.ObjectMeta{ 202 Name: "dummy-backend", 203 Namespace: "default", 204 }, 205 }, 206 207 // Service in another namespace 208 &corev1.Service{ 209 ObjectMeta: metav1.ObjectMeta{ 210 Name: "dummy-backend", 211 Namespace: "another-namespace", 212 }, 213 }, 214 215 // Service for reference grant in another namespace 216 &corev1.Service{ 217 ObjectMeta: metav1.ObjectMeta{ 218 Name: "dummy-backend-grant", 219 Namespace: "another-namespace", 220 }, 221 }, 222 223 // Deleting HTTPRoute 224 &gatewayv1.HTTPRoute{ 225 ObjectMeta: metav1.ObjectMeta{ 226 Name: "deleting-http-route", 227 Namespace: "default", 228 Finalizers: []string{httpRFFinalizer}, 229 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 230 }, 231 Spec: gatewayv1.HTTPRouteSpec{}, 232 }, 233 234 // Valid HTTPRoute 235 &gatewayv1.HTTPRoute{ 236 ObjectMeta: metav1.ObjectMeta{ 237 Name: "valid-http-route-service", 238 Namespace: "default", 239 }, 240 Spec: gatewayv1.HTTPRouteSpec{ 241 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 242 ParentRefs: []gatewayv1.ParentReference{ 243 { 244 Name: "dummy-gateway", 245 }, 246 }, 247 }, 248 Rules: []gatewayv1.HTTPRouteRule{ 249 { 250 BackendRefs: []gatewayv1.HTTPBackendRef{ 251 { 252 BackendRef: gatewayv1.BackendRef{ 253 BackendObjectReference: gatewayv1.BackendObjectReference{ 254 Name: "dummy-backend", 255 Port: model.AddressOf[gatewayv1.PortNumber](8080), 256 }, 257 }, 258 }, 259 }, 260 }, 261 }, 262 }, 263 }, 264 265 // Valid HTTPRoute with Hostnames 266 &gatewayv1.HTTPRoute{ 267 ObjectMeta: metav1.ObjectMeta{ 268 Name: "valid-http-route-hostname-service", 269 Namespace: "default", 270 }, 271 Spec: gatewayv1.HTTPRouteSpec{ 272 Hostnames: []gatewayv1.Hostname{ 273 "bar.foo.com", 274 }, 275 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 276 ParentRefs: []gatewayv1.ParentReference{ 277 { 278 Name: "dummy-gateway-hostnames", 279 }, 280 }, 281 }, 282 Rules: []gatewayv1.HTTPRouteRule{ 283 { 284 BackendRefs: []gatewayv1.HTTPBackendRef{ 285 { 286 BackendRef: gatewayv1.BackendRef{ 287 BackendObjectReference: gatewayv1.BackendObjectReference{ 288 Name: "dummy-backend", 289 Port: model.AddressOf[gatewayv1.PortNumber](8080), 290 }, 291 }, 292 }, 293 }, 294 }, 295 }, 296 }, 297 }, 298 299 // Valid HTTPRoute with Hostnames 300 &gatewayv1.HTTPRoute{ 301 ObjectMeta: metav1.ObjectMeta{ 302 Name: "valid-http-route-hostname-serviceimport", 303 Namespace: "default", 304 }, 305 Spec: gatewayv1.HTTPRouteSpec{ 306 Hostnames: []gatewayv1.Hostname{ 307 "bar.foo.com", 308 }, 309 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 310 ParentRefs: []gatewayv1.ParentReference{ 311 { 312 Name: "dummy-gateway-hostnames", 313 }, 314 }, 315 }, 316 Rules: []gatewayv1.HTTPRouteRule{ 317 { 318 BackendRefs: []gatewayv1.HTTPBackendRef{ 319 { 320 BackendRef: gatewayv1.BackendRef{ 321 BackendObjectReference: gatewayv1.BackendObjectReference{ 322 Name: "dummy-backend", 323 Port: model.AddressOf[gatewayv1.PortNumber](8080), 324 }, 325 }, 326 }, 327 { 328 BackendRef: gatewayv1.BackendRef{ 329 BackendObjectReference: gatewayv1.BackendObjectReference{ 330 Group: GroupPtr(mcsapiv1alpha1.GroupName), 331 Kind: KindPtr("ServiceImport"), 332 Name: "dummy-backend", 333 Port: model.AddressOf[gatewayv1.PortNumber](8080), 334 }, 335 }, 336 }, 337 }, 338 }, 339 }, 340 }, 341 }, 342 343 &gatewayv1.HTTPRoute{ 344 ObjectMeta: metav1.ObjectMeta{ 345 Name: "valid-http-route-serviceimport", 346 Namespace: "default", 347 }, 348 Spec: gatewayv1.HTTPRouteSpec{ 349 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 350 ParentRefs: []gatewayv1.ParentReference{ 351 { 352 Name: "dummy-gateway", 353 }, 354 }, 355 }, 356 Rules: []gatewayv1.HTTPRouteRule{ 357 { 358 BackendRefs: []gatewayv1.HTTPBackendRef{ 359 { 360 BackendRef: gatewayv1.BackendRef{ 361 BackendObjectReference: gatewayv1.BackendObjectReference{ 362 Name: "dummy-backend", 363 Port: model.AddressOf[gatewayv1.PortNumber](8080), 364 }, 365 }, 366 }, 367 { 368 BackendRef: gatewayv1.BackendRef{ 369 BackendObjectReference: gatewayv1.BackendObjectReference{ 370 Group: GroupPtr(mcsapiv1alpha1.GroupName), 371 Kind: KindPtr("ServiceImport"), 372 Name: "dummy-backend", 373 Port: model.AddressOf[gatewayv1.PortNumber](8080), 374 }, 375 }, 376 }, 377 }, 378 }, 379 }, 380 }, 381 }, 382 383 // HTTPRoute with nonexistent backend 384 &gatewayv1.HTTPRoute{ 385 ObjectMeta: metav1.ObjectMeta{ 386 Name: "http-route-with-nonexistent-svc", 387 Namespace: "default", 388 }, 389 Spec: gatewayv1.HTTPRouteSpec{ 390 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 391 ParentRefs: []gatewayv1.ParentReference{ 392 { 393 Name: "dummy-gateway", 394 }, 395 }, 396 }, 397 Rules: []gatewayv1.HTTPRouteRule{ 398 { 399 BackendRefs: []gatewayv1.HTTPBackendRef{ 400 { 401 BackendRef: gatewayv1.BackendRef{ 402 BackendObjectReference: gatewayv1.BackendObjectReference{ 403 Name: "nonexistent-backend", 404 Port: model.AddressOf[gatewayv1.PortNumber](8080), 405 }, 406 }, 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 &gatewayv1.HTTPRoute{ 414 ObjectMeta: metav1.ObjectMeta{ 415 Name: "http-route-with-nonexistent-svcimport", 416 Namespace: "default", 417 }, 418 Spec: gatewayv1.HTTPRouteSpec{ 419 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 420 ParentRefs: []gatewayv1.ParentReference{ 421 { 422 Name: "dummy-gateway", 423 }, 424 }, 425 }, 426 Rules: []gatewayv1.HTTPRouteRule{ 427 { 428 BackendRefs: []gatewayv1.HTTPBackendRef{ 429 { 430 BackendRef: gatewayv1.BackendRef{ 431 BackendObjectReference: gatewayv1.BackendObjectReference{ 432 Group: GroupPtr(mcsapiv1alpha1.GroupName), 433 Kind: KindPtr("ServiceImport"), 434 Name: "nonexistent-backend", 435 Port: model.AddressOf[gatewayv1.PortNumber](8080), 436 }, 437 }, 438 }, 439 }, 440 }, 441 }, 442 }, 443 }, 444 &gatewayv1.HTTPRoute{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Name: "http-route-with-nonexistent-svcimport-svc", 447 Namespace: "default", 448 }, 449 Spec: gatewayv1.HTTPRouteSpec{ 450 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 451 ParentRefs: []gatewayv1.ParentReference{ 452 { 453 Name: "dummy-gateway", 454 }, 455 }, 456 }, 457 Rules: []gatewayv1.HTTPRouteRule{ 458 { 459 BackendRefs: []gatewayv1.HTTPBackendRef{ 460 { 461 BackendRef: gatewayv1.BackendRef{ 462 BackendObjectReference: gatewayv1.BackendObjectReference{ 463 Group: GroupPtr(mcsapiv1alpha1.GroupName), 464 Kind: KindPtr("ServiceImport"), 465 Name: "dummy-backend-no-svc", 466 Port: model.AddressOf[gatewayv1.PortNumber](8080), 467 }, 468 }, 469 }, 470 }, 471 }, 472 }, 473 }, 474 }, 475 &gatewayv1.HTTPRoute{ 476 ObjectMeta: metav1.ObjectMeta{ 477 Name: "http-route-with-nonexistent-svcimport-svc-annotation", 478 Namespace: "default", 479 }, 480 Spec: gatewayv1.HTTPRouteSpec{ 481 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 482 ParentRefs: []gatewayv1.ParentReference{ 483 { 484 Name: "dummy-gateway", 485 }, 486 }, 487 }, 488 Rules: []gatewayv1.HTTPRouteRule{ 489 { 490 BackendRefs: []gatewayv1.HTTPBackendRef{ 491 { 492 BackendRef: gatewayv1.BackendRef{ 493 BackendObjectReference: gatewayv1.BackendObjectReference{ 494 Group: GroupPtr(mcsapiv1alpha1.GroupName), 495 Kind: KindPtr("ServiceImport"), 496 Name: "dummy-backend-no-svc-annotation", 497 Port: model.AddressOf[gatewayv1.PortNumber](8080), 498 }, 499 }, 500 }, 501 }, 502 }, 503 }, 504 }, 505 }, 506 507 // HTTPRoute with cross namespace backend 508 &gatewayv1.HTTPRoute{ 509 ObjectMeta: metav1.ObjectMeta{ 510 Name: "http-route-with-cross-namespace-service", 511 Namespace: "default", 512 }, 513 Spec: gatewayv1.HTTPRouteSpec{ 514 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 515 ParentRefs: []gatewayv1.ParentReference{ 516 { 517 Name: "dummy-gateway", 518 }, 519 }, 520 }, 521 Rules: []gatewayv1.HTTPRouteRule{ 522 { 523 BackendRefs: []gatewayv1.HTTPBackendRef{ 524 { 525 BackendRef: gatewayv1.BackendRef{ 526 BackendObjectReference: gatewayv1.BackendObjectReference{ 527 Name: "dummy-backend", 528 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 529 }, 530 }, 531 }, 532 }, 533 }, 534 }, 535 }, 536 }, 537 &gatewayv1.HTTPRoute{ 538 ObjectMeta: metav1.ObjectMeta{ 539 Name: "http-route-with-cross-namespace-serviceimport", 540 Namespace: "default", 541 }, 542 Spec: gatewayv1.HTTPRouteSpec{ 543 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 544 ParentRefs: []gatewayv1.ParentReference{ 545 { 546 Name: "dummy-gateway", 547 }, 548 }, 549 }, 550 Rules: []gatewayv1.HTTPRouteRule{ 551 { 552 BackendRefs: []gatewayv1.HTTPBackendRef{ 553 { 554 BackendRef: gatewayv1.BackendRef{ 555 BackendObjectReference: gatewayv1.BackendObjectReference{ 556 Group: GroupPtr(mcsapiv1alpha1.GroupName), 557 Kind: KindPtr("ServiceImport"), 558 Name: "dummy-backend", 559 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 560 Port: model.AddressOf[gatewayv1.PortNumber](8080), 561 }, 562 }, 563 }, 564 }, 565 }, 566 }, 567 }, 568 }, 569 // HTTPRoute with cross namespace listener 570 &gatewayv1.HTTPRoute{ 571 ObjectMeta: metav1.ObjectMeta{ 572 Name: "http-route-with-cross-namespace-listener", 573 Namespace: "another-namespace", 574 }, 575 Spec: gatewayv1.HTTPRouteSpec{ 576 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 577 ParentRefs: []gatewayv1.ParentReference{ 578 { 579 Name: "dummy-gateway-two-listeners", 580 Namespace: model.AddressOf[gatewayv1.Namespace]("default"), 581 }, 582 }, 583 }, 584 Rules: []gatewayv1.HTTPRouteRule{ 585 { 586 BackendRefs: []gatewayv1.HTTPBackendRef{ 587 { 588 BackendRef: gatewayv1.BackendRef{ 589 BackendObjectReference: gatewayv1.BackendObjectReference{ 590 Name: "dummy-backend", 591 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 592 Port: model.AddressOf(gatewayv1.PortNumber(8080)), 593 }, 594 }, 595 }, 596 }, 597 }, 598 }, 599 }, 600 }, 601 // HTTPRoute with hostnames and cross namespace listener 602 &gatewayv1.HTTPRoute{ 603 ObjectMeta: metav1.ObjectMeta{ 604 Name: "http-route-with-hostnames-and-cross-namespace-listener", 605 Namespace: "another-namespace", 606 }, 607 Spec: gatewayv1.HTTPRouteSpec{ 608 Hostnames: []gatewayv1.Hostname{ 609 "bar.foo.com", 610 }, 611 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 612 ParentRefs: []gatewayv1.ParentReference{ 613 { 614 Name: "dummy-gateway-two-listeners", 615 Namespace: model.AddressOf[gatewayv1.Namespace]("default"), 616 }, 617 }, 618 }, 619 Rules: []gatewayv1.HTTPRouteRule{ 620 { 621 BackendRefs: []gatewayv1.HTTPBackendRef{ 622 { 623 BackendRef: gatewayv1.BackendRef{ 624 BackendObjectReference: gatewayv1.BackendObjectReference{ 625 Name: "dummy-backend", 626 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 627 Port: model.AddressOf(gatewayv1.PortNumber(8080)), 628 }, 629 }, 630 }, 631 }, 632 }, 633 }, 634 }, 635 }, 636 // HTTPRoute with cross namespace backend 637 &gatewayv1.HTTPRoute{ 638 ObjectMeta: metav1.ObjectMeta{ 639 Name: "http-route-with-cross-namespace-backend-with-grant", 640 Namespace: "default", 641 }, 642 Spec: gatewayv1.HTTPRouteSpec{ 643 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 644 ParentRefs: []gatewayv1.ParentReference{ 645 { 646 Name: "dummy-gateway", 647 }, 648 }, 649 }, 650 Rules: []gatewayv1.HTTPRouteRule{ 651 { 652 BackendRefs: []gatewayv1.HTTPBackendRef{ 653 { 654 BackendRef: gatewayv1.BackendRef{ 655 BackendObjectReference: gatewayv1.BackendObjectReference{ 656 Name: "dummy-backend-grant", 657 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 658 Port: model.AddressOf[gatewayv1.PortNumber](8080), 659 }, 660 }, 661 }, 662 { 663 BackendRef: gatewayv1.BackendRef{ 664 BackendObjectReference: gatewayv1.BackendObjectReference{ 665 Group: GroupPtr(mcsapiv1alpha1.GroupName), 666 Kind: KindPtr("ServiceImport"), 667 Name: "dummy-backend-grant", 668 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 669 Port: model.AddressOf[gatewayv1.PortNumber](8080), 670 }, 671 }, 672 }, 673 }, 674 }, 675 }, 676 }, 677 }, 678 679 // ReferenceGrant to allow "http-route-with-cross-namespace-backend-with-grant 680 &gatewayv1beta1.ReferenceGrant{ 681 ObjectMeta: metav1.ObjectMeta{ 682 Name: "allow-service-from-default", 683 Namespace: "another-namespace", 684 }, 685 Spec: gatewayv1beta1.ReferenceGrantSpec{ 686 From: []gatewayv1beta1.ReferenceGrantFrom{ 687 { 688 Group: "gateway.networking.k8s.io", 689 Kind: "HTTPRoute", 690 Namespace: "default", 691 }, 692 }, 693 To: []gatewayv1beta1.ReferenceGrantTo{ 694 { 695 Group: "", 696 Kind: "Service", 697 Name: ObjectNamePtr("dummy-backend-grant"), 698 }, 699 }, 700 }, 701 }, 702 &gatewayv1beta1.ReferenceGrant{ 703 ObjectMeta: metav1.ObjectMeta{ 704 Name: "allow-service-import-from-default", 705 Namespace: "another-namespace", 706 }, 707 Spec: gatewayv1beta1.ReferenceGrantSpec{ 708 From: []gatewayv1beta1.ReferenceGrantFrom{ 709 { 710 Group: "gateway.networking.k8s.io", 711 Kind: "HTTPRoute", 712 Namespace: "default", 713 }, 714 }, 715 To: []gatewayv1beta1.ReferenceGrantTo{ 716 { 717 Group: mcsapiv1alpha1.GroupName, 718 Kind: "ServiceImport", 719 Name: ObjectNamePtr("dummy-backend-grant"), 720 }, 721 }, 722 }, 723 }, 724 725 // HTTPRoute with unsupported backend 726 &gatewayv1.HTTPRoute{ 727 ObjectMeta: metav1.ObjectMeta{ 728 Name: "http-route-with-unsupported-backend", 729 Namespace: "default", 730 }, 731 Spec: gatewayv1.HTTPRouteSpec{ 732 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 733 ParentRefs: []gatewayv1.ParentReference{ 734 { 735 Name: "dummy-gateway", 736 }, 737 }, 738 }, 739 Rules: []gatewayv1.HTTPRouteRule{ 740 { 741 BackendRefs: []gatewayv1.HTTPBackendRef{ 742 { 743 BackendRef: gatewayv1.BackendRef{ 744 BackendObjectReference: gatewayv1.BackendObjectReference{ 745 Name: "unsupported-backend", 746 Group: GroupPtr("unsupported-group"), 747 Kind: KindPtr("UnsupportedKind"), 748 }, 749 }, 750 }, 751 }, 752 }, 753 }, 754 }, 755 }, 756 // HTTPRoute missing port for Service and ServiceImport 757 &gatewayv1.HTTPRoute{ 758 ObjectMeta: metav1.ObjectMeta{ 759 Name: "http-route-missing-port-for-backend-service", 760 Namespace: "default", 761 }, 762 Spec: gatewayv1.HTTPRouteSpec{ 763 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 764 ParentRefs: []gatewayv1.ParentReference{ 765 { 766 Name: "dummy-gateway", 767 }, 768 }, 769 }, 770 Rules: []gatewayv1.HTTPRouteRule{ 771 { 772 BackendRefs: []gatewayv1.HTTPBackendRef{ 773 { 774 BackendRef: gatewayv1.BackendRef{ 775 BackendObjectReference: gatewayv1.BackendObjectReference{ 776 Name: "missing-port-service-backend", 777 Group: GroupPtr(""), 778 Kind: KindPtr("Service"), 779 }, 780 }, 781 }, 782 }, 783 }, 784 }, 785 }, 786 }, 787 &gatewayv1.HTTPRoute{ 788 ObjectMeta: metav1.ObjectMeta{ 789 Name: "http-route-missing-port-for-backend-serviceimport", 790 Namespace: "default", 791 }, 792 Spec: gatewayv1.HTTPRouteSpec{ 793 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 794 ParentRefs: []gatewayv1.ParentReference{ 795 { 796 Name: "dummy-gateway", 797 }, 798 }, 799 }, 800 Rules: []gatewayv1.HTTPRouteRule{ 801 { 802 BackendRefs: []gatewayv1.HTTPBackendRef{ 803 { 804 BackendRef: gatewayv1.BackendRef{ 805 BackendObjectReference: gatewayv1.BackendObjectReference{ 806 Group: GroupPtr(mcsapiv1alpha1.GroupName), 807 Kind: KindPtr("ServiceImport"), 808 Name: "missing-port-service-backend", 809 }, 810 }, 811 }, 812 }, 813 }, 814 }, 815 }, 816 }, 817 818 // HTTPRoute with non-existent gateway 819 &gatewayv1.HTTPRoute{ 820 ObjectMeta: metav1.ObjectMeta{ 821 Name: "http-route-with-nonexistent-gateway", 822 Namespace: "default", 823 }, 824 Spec: gatewayv1.HTTPRouteSpec{ 825 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 826 ParentRefs: []gatewayv1.ParentReference{ 827 { 828 Name: "non-existent-gateway", 829 }, 830 }, 831 }, 832 Rules: []gatewayv1.HTTPRouteRule{ 833 { 834 BackendRefs: []gatewayv1.HTTPBackendRef{ 835 { 836 BackendRef: gatewayv1.BackendRef{ 837 BackendObjectReference: gatewayv1.BackendObjectReference{ 838 Name: "dummy-backend", 839 Port: model.AddressOf[gatewayv1.PortNumber](8080), 840 }, 841 }, 842 }, 843 }, 844 }, 845 }, 846 }, 847 }, 848 849 // HTTPRoute with valid but not allowed gateway 850 &gatewayv1.HTTPRoute{ 851 ObjectMeta: metav1.ObjectMeta{ 852 Name: "http-route-with-not-allowed-gateway", 853 Namespace: "default", 854 }, 855 Spec: gatewayv1.HTTPRouteSpec{ 856 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 857 ParentRefs: []gatewayv1.ParentReference{ 858 { 859 Name: "dummy-gateway", 860 Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"), 861 }, 862 }, 863 }, 864 Rules: []gatewayv1.HTTPRouteRule{ 865 { 866 BackendRefs: []gatewayv1.HTTPBackendRef{ 867 { 868 BackendRef: gatewayv1.BackendRef{ 869 BackendObjectReference: gatewayv1.BackendObjectReference{ 870 Name: "dummy-backend", 871 Port: model.AddressOf[gatewayv1.PortNumber](8080), 872 }, 873 }, 874 }, 875 }, 876 }, 877 }, 878 }, 879 }, 880 881 // HTTPRoute with non-matching hostname with gateway listener 882 &gatewayv1.HTTPRoute{ 883 ObjectMeta: metav1.ObjectMeta{ 884 Name: "http-route-with-non-matching-hostname", 885 Namespace: "default", 886 }, 887 Spec: gatewayv1.HTTPRouteSpec{ 888 CommonRouteSpec: gatewayv1.CommonRouteSpec{ 889 ParentRefs: []gatewayv1.ParentReference{ 890 { 891 Name: "dummy-gateway", 892 }, 893 }, 894 }, 895 Hostnames: []gatewayv1.Hostname{ 896 "non-matching-hostname", 897 }, 898 Rules: []gatewayv1.HTTPRouteRule{ 899 { 900 BackendRefs: []gatewayv1.HTTPBackendRef{ 901 { 902 BackendRef: gatewayv1.BackendRef{ 903 BackendObjectReference: gatewayv1.BackendObjectReference{ 904 Name: "dummy-backend", 905 Port: model.AddressOf[gatewayv1.PortNumber](8080), 906 }, 907 }, 908 }, 909 }, 910 }, 911 }, 912 }, 913 }, 914 } 915 ) 916 917 func Test_httpRouteReconciler_Reconcile(t *testing.T) { 918 scheme := testScheme() 919 mcsapiv1alpha1.AddToScheme(scheme) 920 921 c := fake.NewClientBuilder(). 922 WithScheme(scheme). 923 WithObjects(crdsFixture...). 924 WithObjects(httpRouteFixture...). 925 WithObjects(httpRouteServiceImportFixture...). 926 WithStatusSubresource(&gatewayv1.HTTPRoute{}). 927 Build() 928 929 r := &httpRouteReconciler{Client: c} 930 931 t.Run("no http route", func(t *testing.T) { 932 result, err := r.Reconcile(context.Background(), ctrl.Request{ 933 NamespacedName: types.NamespacedName{ 934 Name: "non-existing-http-route", 935 Namespace: "default", 936 }, 937 }) 938 require.NoError(t, err) 939 require.Equal(t, ctrl.Result{}, result) 940 }) 941 942 t.Run("http route exists but being deleted", func(t *testing.T) { 943 result, err := r.Reconcile(context.Background(), ctrl.Request{ 944 NamespacedName: types.NamespacedName{ 945 Name: "deleting-http-route", 946 Namespace: "default", 947 }, 948 }) 949 950 require.NoError(t, err, "Error reconciling httpRoute") 951 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 952 }) 953 954 t.Run("valid http route", func(t *testing.T) { 955 for _, name := range []string{"service", "serviceimport"} { 956 key := types.NamespacedName{ 957 Name: "valid-http-route-" + name, 958 Namespace: "default", 959 } 960 result, err := r.Reconcile(context.Background(), ctrl.Request{ 961 NamespacedName: key, 962 }) 963 964 require.NoError(t, err, "Error reconciling httpRoute") 965 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 966 967 route := &gatewayv1.HTTPRoute{} 968 err = c.Get(context.Background(), key, route) 969 970 require.NoError(t, err) 971 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 972 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 973 974 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 975 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 976 977 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 978 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 979 } 980 }) 981 982 t.Run("valid http route and hostname matches", func(t *testing.T) { 983 for _, name := range []string{"service", "serviceimport"} { 984 key := types.NamespacedName{ 985 Name: "valid-http-route-hostname-" + name, 986 Namespace: "default", 987 } 988 result, err := r.Reconcile(context.Background(), ctrl.Request{ 989 NamespacedName: key, 990 }) 991 992 require.NoError(t, err, "Error reconciling httpRoute") 993 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 994 995 route := &gatewayv1.HTTPRoute{} 996 err = c.Get(context.Background(), key, route) 997 998 require.NoError(t, err) 999 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1000 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1001 1002 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1003 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1004 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Reason) 1005 require.Equal(t, "Accepted HTTPRoute", route.Status.RouteStatus.Parents[0].Conditions[0].Message) 1006 1007 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1008 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1009 } 1010 }) 1011 1012 t.Run("valid http route and hostname matches cross-namespace", func(t *testing.T) { 1013 key := types.NamespacedName{ 1014 Name: "http-route-with-hostnames-and-cross-namespace-listener", 1015 Namespace: "another-namespace", 1016 } 1017 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1018 NamespacedName: key, 1019 }) 1020 1021 require.NoError(t, err, "Error reconciling httpRoute") 1022 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1023 1024 route := &gatewayv1.HTTPRoute{} 1025 err = c.Get(context.Background(), key, route) 1026 1027 require.NoError(t, err) 1028 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1029 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1030 1031 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1032 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1033 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Reason) 1034 require.Equal(t, "Accepted HTTPRoute", route.Status.RouteStatus.Parents[0].Conditions[0].Message) 1035 1036 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1037 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1038 }) 1039 1040 t.Run("http route with nonexistent backend", func(t *testing.T) { 1041 for _, name := range []string{"svc", "svcimport", "svcimport-svc", "svcimport-svc-annotation"} { 1042 key := types.NamespacedName{ 1043 Name: "http-route-with-nonexistent-" + name, 1044 Namespace: "default", 1045 } 1046 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1047 NamespacedName: key, 1048 }) 1049 1050 require.NoError(t, err, "Error reconciling httpRoute") 1051 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1052 1053 route := &gatewayv1.HTTPRoute{} 1054 err = c.Get(context.Background(), key, route) 1055 1056 require.NoError(t, err) 1057 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1058 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1059 1060 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1061 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1062 1063 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1064 require.Equal(t, "BackendNotFound", route.Status.RouteStatus.Parents[0].Conditions[1].Reason) 1065 } 1066 }) 1067 1068 t.Run("http route with nonexistent gateway", func(t *testing.T) { 1069 key := types.NamespacedName{ 1070 Name: "http-route-with-nonexistent-gateway", 1071 Namespace: "default", 1072 } 1073 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1074 NamespacedName: key, 1075 }) 1076 1077 require.NoError(t, err, "Error reconciling httpRoute") 1078 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1079 1080 route := &gatewayv1.HTTPRoute{} 1081 err = c.Get(context.Background(), key, route) 1082 1083 require.NoError(t, err) 1084 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1085 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1086 1087 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1088 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1089 require.Equal(t, "InvalidHTTPRoute", route.Status.RouteStatus.Parents[0].Conditions[0].Reason) 1090 1091 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1092 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1093 }) 1094 1095 t.Run("http route with valid but not allowed gateway", func(t *testing.T) { 1096 key := types.NamespacedName{ 1097 Name: "http-route-with-not-allowed-gateway", 1098 Namespace: "default", 1099 } 1100 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1101 NamespacedName: key, 1102 }) 1103 1104 require.NoError(t, err, "Error reconciling httpRoute") 1105 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1106 1107 route := &gatewayv1.HTTPRoute{} 1108 err = c.Get(context.Background(), key, route) 1109 1110 require.NoError(t, err) 1111 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1112 1113 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1114 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1115 require.Equal(t, "NotAllowedByListeners", route.Status.RouteStatus.Parents[0].Conditions[0].Reason) 1116 require.Equal(t, "HTTPRoute is not allowed to attach to this Gateway due to namespace restrictions", route.Status.RouteStatus.Parents[0].Conditions[0].Message) 1117 1118 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1119 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1120 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1121 }) 1122 1123 t.Run("http route with non-matching hostname", func(t *testing.T) { 1124 key := types.NamespacedName{ 1125 Name: "http-route-with-non-matching-hostname", 1126 Namespace: "default", 1127 } 1128 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1129 NamespacedName: key, 1130 }) 1131 1132 require.NoError(t, err, "Error reconciling httpRoute") 1133 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1134 1135 route := &gatewayv1.HTTPRoute{} 1136 err = c.Get(context.Background(), key, route) 1137 1138 require.NoError(t, err) 1139 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1140 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1141 1142 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1143 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1144 require.Equal(t, "NoMatchingListenerHostname", route.Status.RouteStatus.Parents[0].Conditions[0].Reason) 1145 require.Equal(t, "No matching listener hostname", route.Status.RouteStatus.Parents[0].Conditions[0].Message) 1146 1147 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1148 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1149 }) 1150 1151 t.Run("http route with cross namespace backend", func(t *testing.T) { 1152 for _, name := range []string{"service", "serviceimport"} { 1153 key := types.NamespacedName{ 1154 Name: "http-route-with-cross-namespace-" + name, 1155 Namespace: "default", 1156 } 1157 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1158 NamespacedName: key, 1159 }) 1160 1161 require.NoError(t, err, "Error reconciling httpRoute") 1162 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1163 1164 route := &gatewayv1.HTTPRoute{} 1165 err = c.Get(context.Background(), key, route) 1166 1167 require.NoError(t, err) 1168 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1169 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1170 1171 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1172 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1173 1174 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1175 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1176 require.Equal(t, "RefNotPermitted", route.Status.RouteStatus.Parents[0].Conditions[1].Reason) 1177 require.Equal(t, "Cross namespace references are not allowed", route.Status.RouteStatus.Parents[0].Conditions[1].Message) 1178 } 1179 }) 1180 1181 t.Run("http route with cross namespace listener", func(t *testing.T) { 1182 key := types.NamespacedName{ 1183 Name: "http-route-with-cross-namespace-listener", 1184 Namespace: "another-namespace", 1185 } 1186 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1187 NamespacedName: key, 1188 }) 1189 1190 require.NoError(t, err, "Error reconciling httpRoute") 1191 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1192 1193 route := &gatewayv1.HTTPRoute{} 1194 err = c.Get(context.Background(), key, route) 1195 1196 require.NoError(t, err) 1197 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1198 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1199 1200 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1201 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1202 1203 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1204 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1205 }) 1206 1207 t.Run("http route with cross namespace backend with reference grant", func(t *testing.T) { 1208 key := types.NamespacedName{ 1209 Name: "http-route-with-cross-namespace-backend-with-grant", 1210 Namespace: "default", 1211 } 1212 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1213 NamespacedName: key, 1214 }) 1215 1216 require.NoError(t, err, "Error reconciling httpRoute") 1217 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1218 1219 route := &gatewayv1.HTTPRoute{} 1220 err = c.Get(context.Background(), key, route) 1221 1222 require.NoError(t, err) 1223 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1224 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1225 1226 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1227 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1228 1229 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1230 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1231 }) 1232 1233 t.Run("http route with un-supported backend", func(t *testing.T) { 1234 key := types.NamespacedName{ 1235 Name: "http-route-with-unsupported-backend", 1236 Namespace: "default", 1237 } 1238 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1239 NamespacedName: key, 1240 }) 1241 1242 require.NoError(t, err, "Error reconciling httpRoute") 1243 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1244 1245 route := &gatewayv1.HTTPRoute{} 1246 err = c.Get(context.Background(), key, route) 1247 1248 require.NoError(t, err) 1249 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1250 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1251 1252 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1253 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1254 1255 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1256 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1257 require.Equal(t, "InvalidKind", route.Status.RouteStatus.Parents[0].Conditions[1].Reason) 1258 require.Equal(t, "Unsupported backend kind UnsupportedKind", route.Status.RouteStatus.Parents[0].Conditions[1].Message) 1259 }) 1260 1261 t.Run("http route missing port of Service backend", func(t *testing.T) { 1262 for _, name := range []string{"service", "serviceimport"} { 1263 key := types.NamespacedName{ 1264 Name: "http-route-missing-port-for-backend-" + name, 1265 Namespace: "default", 1266 } 1267 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1268 NamespacedName: key, 1269 }) 1270 1271 require.NoError(t, err, "Error reconciling httpRoute") 1272 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1273 1274 route := &gatewayv1.HTTPRoute{} 1275 err = c.Get(context.Background(), key, route) 1276 1277 require.NoError(t, err) 1278 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1279 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1280 1281 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1282 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1283 1284 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1285 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1286 require.Equal(t, "InvalidKind", route.Status.RouteStatus.Parents[0].Conditions[1].Reason) 1287 require.Equal(t, "Must have port for backend object reference", route.Status.RouteStatus.Parents[0].Conditions[1].Message) 1288 } 1289 }) 1290 } 1291 1292 func Test_httpRouteReconciler_Reconcile_NoServiceImportCRD(t *testing.T) { 1293 c := fake.NewClientBuilder(). 1294 WithScheme(testScheme()). 1295 WithObjects(httpRouteFixture...). 1296 WithStatusSubresource(&gatewayv1.HTTPRoute{}). 1297 Build() 1298 1299 r := &httpRouteReconciler{Client: c} 1300 1301 t.Run("valid http route with Service", func(t *testing.T) { 1302 key := types.NamespacedName{ 1303 Name: "valid-http-route-service", 1304 Namespace: "default", 1305 } 1306 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1307 NamespacedName: key, 1308 }) 1309 1310 require.NoError(t, err, "Error reconciling httpRoute") 1311 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1312 1313 route := &gatewayv1.HTTPRoute{} 1314 err = c.Get(context.Background(), key, route) 1315 1316 require.NoError(t, err) 1317 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1318 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1319 1320 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1321 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1322 1323 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1324 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1325 }) 1326 1327 t.Run("valid http route with ServiceImport", func(t *testing.T) { 1328 key := types.NamespacedName{ 1329 Name: "valid-http-route-serviceimport", 1330 Namespace: "default", 1331 } 1332 result, err := r.Reconcile(context.Background(), ctrl.Request{ 1333 NamespacedName: key, 1334 }) 1335 1336 require.NoError(t, err, "Error reconciling httpRoute") 1337 require.Equal(t, ctrl.Result{}, result, "Result should be empty") 1338 1339 route := &gatewayv1.HTTPRoute{} 1340 err = c.Get(context.Background(), key, route) 1341 1342 require.NoError(t, err) 1343 require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent") 1344 require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2) 1345 1346 require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type) 1347 require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status) 1348 1349 require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type) 1350 require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status) 1351 require.Equal(t, "BackendNotFound", route.Status.RouteStatus.Parents[0].Conditions[1].Reason) 1352 require.Equal(t, "Attempt to reference a ServiceImport backend while "+ 1353 "the corresponding CRD is not installed, "+ 1354 "please restart the cilium-operator if the CRD is already installed", 1355 route.Status.RouteStatus.Parents[0].Conditions[1].Message) 1356 }) 1357 }