istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/gateway_test.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package pilot 19 20 import ( 21 "context" 22 "fmt" 23 "testing" 24 "time" 25 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 k8sv1 "sigs.k8s.io/gateway-api/apis/v1" 29 30 "istio.io/istio/pilot/pkg/model/kstatus" 31 "istio.io/istio/pkg/config/constants" 32 "istio.io/istio/pkg/config/protocol" 33 "istio.io/istio/pkg/http/headers" 34 "istio.io/istio/pkg/test/echo/common/scheme" 35 "istio.io/istio/pkg/test/framework" 36 "istio.io/istio/pkg/test/framework/components/crd" 37 "istio.io/istio/pkg/test/framework/components/echo" 38 "istio.io/istio/pkg/test/framework/components/echo/check" 39 "istio.io/istio/pkg/test/framework/components/istio" 40 testKube "istio.io/istio/pkg/test/kube" 41 "istio.io/istio/pkg/test/util/assert" 42 "istio.io/istio/pkg/test/util/retry" 43 ingressutil "istio.io/istio/tests/integration/security/sds_ingress/util" 44 ) 45 46 func TestGateway(t *testing.T) { 47 framework. 48 NewTest(t). 49 Run(func(t framework.TestContext) { 50 crd.DeployGatewayAPIOrSkip(t) 51 52 t.NewSubTest("unmanaged").Run(UnmanagedGatewayTest) 53 t.NewSubTest("managed").Run(ManagedGatewayTest) 54 t.NewSubTest("managed-owner").Run(ManagedOwnerGatewayTest) 55 t.NewSubTest("status").Run(StatusGatewayTest) 56 t.NewSubTest("managed-short-name").Run(ManagedGatewayShortNameTest) 57 }) 58 } 59 60 func ManagedOwnerGatewayTest(t framework.TestContext) { 61 image := fmt.Sprintf("%s/app:%s", t.Settings().Image.Hub, t.Settings().Image.Tag) 62 t.ConfigIstio().YAML(apps.Namespace.Name(), fmt.Sprintf(` 63 apiVersion: v1 64 kind: Service 65 metadata: 66 name: managed-owner-istio 67 spec: 68 ports: 69 - appProtocol: http 70 name: default 71 port: 80 72 selector: 73 gateway.networking.k8s.io/gateway-name: managed-owner 74 --- 75 apiVersion: apps/v1 76 kind: Deployment 77 metadata: 78 name: managed-owner-istio 79 spec: 80 selector: 81 matchLabels: 82 gateway.networking.k8s.io/gateway-name: managed-owner 83 replicas: 1 84 template: 85 metadata: 86 labels: 87 gateway.networking.k8s.io/gateway-name: managed-owner 88 istio.io/gateway-name: managed-owner 89 spec: 90 containers: 91 - name: fake 92 image: %s 93 `, image)).ApplyOrFail(t) 94 cls := t.Clusters().Kube().Default() 95 fetchFn := testKube.NewSinglePodFetch(cls, apps.Namespace.Name(), "gateway.networking.k8s.io/gateway-name=managed-owner") 96 if _, err := testKube.WaitUntilPodsAreReady(fetchFn); err != nil { 97 t.Fatal(err) 98 } 99 100 t.ConfigIstio().YAML(apps.Namespace.Name(), ` 101 apiVersion: gateway.networking.k8s.io/v1beta1 102 kind: Gateway 103 metadata: 104 name: managed-owner 105 spec: 106 gatewayClassName: istio 107 listeners: 108 - name: default 109 hostname: "*.example.com" 110 port: 80 111 protocol: HTTP 112 `).ApplyOrFail(t) 113 114 // Make sure Gateway becomes programmed.. 115 client := t.Clusters().Kube().Default().GatewayAPI().GatewayV1beta1().Gateways(apps.Namespace.Name()) 116 check := func() error { 117 gw, _ := client.Get(context.Background(), "managed-owner", metav1.GetOptions{}) 118 if gw == nil { 119 return fmt.Errorf("failed to find gateway") 120 } 121 cond := kstatus.GetCondition(gw.Status.Conditions, string(k8sv1.GatewayConditionProgrammed)) 122 if cond.Status != metav1.ConditionTrue { 123 return fmt.Errorf("failed to find programmed condition: %+v", cond) 124 } 125 if cond.ObservedGeneration != gw.Generation { 126 return fmt.Errorf("stale GWC generation: %+v", cond) 127 } 128 return nil 129 } 130 retry.UntilSuccessOrFail(t, check) 131 132 // Make sure we did not overwrite our deployment or service 133 dep, err := t.Clusters().Kube().Default().Kube().AppsV1().Deployments(apps.Namespace.Name()). 134 Get(context.Background(), "managed-owner-istio", metav1.GetOptions{}) 135 assert.NoError(t, err) 136 assert.Equal(t, dep.Labels[constants.ManagedGatewayLabel], "") 137 assert.Equal(t, dep.Spec.Template.Spec.Containers[0].Image, image) 138 139 svc, err := t.Clusters().Kube().Default().Kube().CoreV1().Services(apps.Namespace.Name()). 140 Get(context.Background(), "managed-owner-istio", metav1.GetOptions{}) 141 assert.NoError(t, err) 142 assert.Equal(t, svc.Labels[constants.ManagedGatewayLabel], "") 143 assert.Equal(t, svc.Spec.Type, corev1.ServiceTypeClusterIP) 144 } 145 146 func ManagedGatewayTest(t framework.TestContext) { 147 t.ConfigIstio().YAML(apps.Namespace.Name(), `apiVersion: gateway.networking.k8s.io/v1beta1 148 kind: Gateway 149 metadata: 150 name: gateway 151 spec: 152 gatewayClassName: istio 153 listeners: 154 - name: default 155 hostname: "*.example.com" 156 port: 80 157 protocol: HTTP 158 --- 159 apiVersion: gateway.networking.k8s.io/v1beta1 160 kind: HTTPRoute 161 metadata: 162 name: http-1 163 spec: 164 parentRefs: 165 - name: gateway 166 hostnames: ["bar.example.com"] 167 rules: 168 - backendRefs: 169 - name: b 170 port: 80 171 --- 172 apiVersion: gateway.networking.k8s.io/v1beta1 173 kind: HTTPRoute 174 metadata: 175 name: http-2 176 spec: 177 parentRefs: 178 - name: gateway 179 hostnames: ["foo.example.com"] 180 rules: 181 - backendRefs: 182 - name: d 183 port: 80 184 `).ApplyOrFail(t) 185 testCases := []struct { 186 check echo.Checker 187 from echo.Instances 188 host string 189 }{ 190 { 191 check: check.OK(), 192 from: apps.B, 193 host: "bar.example.com", 194 }, 195 { 196 check: check.NotOK(), 197 from: apps.B, 198 host: "bar", 199 }, 200 } 201 if t.Settings().EnableDualStack { 202 additionalTestCases := []struct { 203 check echo.Checker 204 from echo.Instances 205 host string 206 }{ 207 // apps.D hosts a dual-stack service, 208 // apps.E hosts an ipv6 only service and 209 // apps.B hosts an ipv4 only service 210 { 211 check: check.OK(), 212 from: apps.D, 213 host: "bar.example.com", 214 }, 215 { 216 check: check.OK(), 217 from: apps.E, 218 host: "bar.example.com", 219 }, 220 { 221 check: check.OK(), 222 from: apps.E, 223 host: "foo.example.com", 224 }, 225 { 226 check: check.OK(), 227 from: apps.D, 228 host: "foo.example.com", 229 }, 230 { 231 check: check.OK(), 232 from: apps.B, 233 host: "foo.example.com", 234 }, 235 } 236 testCases = append(testCases, additionalTestCases...) 237 } 238 for _, tc := range testCases { 239 t.NewSubTest(fmt.Sprintf("gateway-connectivity-from-%s", tc.from[0].NamespacedName())).Run(func(t framework.TestContext) { 240 tc.from[0].CallOrFail(t, echo.CallOptions{ 241 Port: echo.Port{ 242 Protocol: protocol.HTTP, 243 ServicePort: 80, 244 }, 245 Scheme: scheme.HTTP, 246 HTTP: echo.HTTP{ 247 Headers: headers.New().WithHost(tc.host).Build(), 248 }, 249 Address: fmt.Sprintf("gateway-istio.%s.svc.cluster.local", apps.Namespace.Name()), 250 Check: tc.check, 251 }) 252 }) 253 } 254 } 255 256 func ManagedGatewayShortNameTest(t framework.TestContext) { 257 t.ConfigIstio().YAML(apps.Namespace.Name(), `apiVersion: gateway.networking.k8s.io/v1beta1 258 kind: Gateway 259 metadata: 260 name: gateway 261 spec: 262 gatewayClassName: istio 263 listeners: 264 - name: default 265 hostname: "bar" 266 port: 80 267 protocol: HTTP 268 --- 269 apiVersion: gateway.networking.k8s.io/v1beta1 270 kind: HTTPRoute 271 metadata: 272 name: http 273 spec: 274 parentRefs: 275 - name: gateway 276 rules: 277 - backendRefs: 278 - name: b 279 port: 80 280 `).ApplyOrFail(t) 281 apps.B[0].CallOrFail(t, echo.CallOptions{ 282 Port: echo.Port{ServicePort: 80}, 283 Scheme: scheme.HTTP, 284 HTTP: echo.HTTP{ 285 Headers: headers.New().WithHost("bar").Build(), 286 }, 287 Address: fmt.Sprintf("gateway-istio.%s.svc.cluster.local", apps.Namespace.Name()), 288 Check: check.OK(), 289 Retry: echo.Retry{ 290 Options: []retry.Option{retry.Timeout(time.Minute)}, 291 }, 292 }) 293 apps.B[0].CallOrFail(t, echo.CallOptions{ 294 Port: echo.Port{ServicePort: 80}, 295 Scheme: scheme.HTTP, 296 HTTP: echo.HTTP{ 297 Headers: headers.New().WithHost("bar.example.com").Build(), 298 }, 299 Address: fmt.Sprintf("gateway-istio.%s.svc.cluster.local", apps.Namespace.Name()), 300 Check: check.NotOK(), 301 Retry: echo.Retry{ 302 Options: []retry.Option{retry.Timeout(time.Minute)}, 303 }, 304 }) 305 } 306 307 func UnmanagedGatewayTest(t framework.TestContext) { 308 ingressutil.CreateIngressKubeSecret(t, "test-gateway-cert-same", ingressutil.TLS, ingressutil.IngressCredentialA, 309 false, t.Clusters().Configs()...) 310 ingressutil.CreateIngressKubeSecretInNamespace(t, "test-gateway-cert-cross", ingressutil.TLS, ingressutil.IngressCredentialB, 311 false, apps.Namespace.Name(), t.Clusters().Configs()...) 312 313 t.ConfigIstio(). 314 YAML("", ` 315 apiVersion: gateway.networking.k8s.io/v1beta1 316 kind: GatewayClass 317 metadata: 318 name: custom-istio 319 spec: 320 controllerName: istio.io/gateway-controller 321 `). 322 YAML("", fmt.Sprintf(` 323 apiVersion: gateway.networking.k8s.io/v1beta1 324 kind: Gateway 325 metadata: 326 name: gateway 327 namespace: istio-system 328 spec: 329 addresses: 330 - value: istio-ingressgateway 331 type: Hostname 332 gatewayClassName: custom-istio 333 listeners: 334 - name: http 335 hostname: "*.domain.example" 336 port: 80 337 protocol: HTTP 338 allowedRoutes: 339 namespaces: 340 from: All 341 - name: tcp 342 port: 31400 343 protocol: TCP 344 allowedRoutes: 345 namespaces: 346 from: All 347 - name: tls-cross 348 hostname: cross-namespace.domain.example 349 port: 443 350 protocol: HTTPS 351 allowedRoutes: 352 namespaces: 353 from: All 354 tls: 355 mode: Terminate 356 certificateRefs: 357 - kind: Secret 358 name: test-gateway-cert-cross 359 namespace: "%s" 360 - name: tls-same 361 hostname: same-namespace.domain.example 362 port: 443 363 protocol: HTTPS 364 allowedRoutes: 365 namespaces: 366 from: All 367 tls: 368 mode: Terminate 369 certificateRefs: 370 - kind: Secret 371 name: test-gateway-cert-same 372 `, apps.Namespace.Name())). 373 YAML(apps.Namespace.Name(), ` 374 apiVersion: gateway.networking.k8s.io/v1beta1 375 kind: HTTPRoute 376 metadata: 377 name: http 378 spec: 379 hostnames: ["my.domain.example"] 380 parentRefs: 381 - name: gateway 382 namespace: istio-system 383 rules: 384 - matches: 385 - path: 386 type: PathPrefix 387 value: /get/ 388 backendRefs: 389 - name: b 390 port: 80 391 --- 392 apiVersion: gateway.networking.k8s.io/v1alpha2 393 kind: TCPRoute 394 metadata: 395 name: tcp 396 spec: 397 parentRefs: 398 - name: gateway 399 namespace: istio-system 400 rules: 401 - backendRefs: 402 - name: b 403 port: 80 404 --- 405 apiVersion: gateway.networking.k8s.io/v1beta1 406 kind: HTTPRoute 407 metadata: 408 name: b 409 spec: 410 parentRefs: 411 - group: "" 412 kind: Service 413 name: b 414 - name: gateway 415 namespace: istio-system 416 hostnames: ["b"] 417 rules: 418 - matches: 419 - path: 420 type: PathPrefix 421 value: /path 422 filters: 423 - type: RequestHeaderModifier 424 requestHeaderModifier: 425 add: 426 - name: my-added-header 427 value: added-value 428 backendRefs: 429 - name: b 430 port: 80 431 --- 432 apiVersion: gateway.networking.k8s.io/v1alpha2 433 kind: GRPCRoute 434 metadata: 435 name: grpc 436 spec: 437 parentRefs: 438 - group: "" 439 kind: Service 440 name: c 441 - name: gateway 442 namespace: istio-system 443 rules: 444 - matches: 445 - method: 446 method: Echo 447 filters: 448 - type: RequestHeaderModifier 449 requestHeaderModifier: 450 add: 451 - name: my-added-header 452 value: added-grpc-value 453 backendRefs: 454 - name: c 455 port: 7070 456 --- 457 apiVersion: gateway.networking.k8s.io/v1beta1 458 kind: HTTPRoute 459 metadata: 460 name: tls-same 461 spec: 462 parentRefs: 463 - name: gateway 464 sectionName: tls-same 465 namespace: istio-system 466 rules: 467 - backendRefs: 468 - name: b 469 port: 80 470 --- 471 apiVersion: gateway.networking.k8s.io/v1beta1 472 kind: HTTPRoute 473 metadata: 474 name: tls-cross 475 spec: 476 parentRefs: 477 - name: gateway 478 sectionName: tls-cross 479 namespace: istio-system 480 rules: 481 - backendRefs: 482 - name: b 483 port: 80 484 `).YAML(apps.Namespace.Name(), fmt.Sprintf(` 485 apiVersion: gateway.networking.k8s.io/v1beta1 486 kind: ReferenceGrant 487 metadata: 488 name: allow-gateways-to-ref-secrets 489 namespace: "%s" 490 spec: 491 from: 492 - group: gateway.networking.k8s.io 493 kind: Gateway 494 namespace: istio-system 495 to: 496 - group: "" 497 kind: Secret 498 `, apps.Namespace.Name())). 499 ApplyOrFail(t) 500 for _, ingr := range istio.IngressesOrFail(t, t) { 501 t.NewSubTest(ingr.Cluster().StableName()).Run(func(t framework.TestContext) { 502 t.NewSubTest("http").Run(func(t framework.TestContext) { 503 paths := []string{"/get", "/get/", "/get/prefix"} 504 for _, path := range paths { 505 _ = ingr.CallOrFail(t, echo.CallOptions{ 506 Port: echo.Port{ 507 Protocol: protocol.HTTP, 508 }, 509 HTTP: echo.HTTP{ 510 Path: path, 511 Headers: headers.New().WithHost("my.domain.example").Build(), 512 }, 513 Check: check.OK(), 514 }) 515 } 516 }) 517 t.NewSubTest("tcp").Run(func(t framework.TestContext) { 518 _ = ingr.CallOrFail(t, echo.CallOptions{ 519 Port: echo.Port{ 520 Protocol: protocol.HTTP, 521 ServicePort: 31400, 522 }, 523 HTTP: echo.HTTP{ 524 Path: "/", 525 Headers: headers.New().WithHost("my.domain.example").Build(), 526 }, 527 Check: check.OK(), 528 }) 529 }) 530 t.NewSubTest("mesh").Run(func(t framework.TestContext) { 531 _ = apps.A[0].CallOrFail(t, echo.CallOptions{ 532 To: apps.B, 533 Count: 1, 534 Port: echo.Port{ 535 Name: "http", 536 }, 537 HTTP: echo.HTTP{ 538 Path: "/path", 539 }, 540 Check: check.And( 541 check.OK(), 542 check.RequestHeader("My-Added-Header", "added-value")), 543 }) 544 }) 545 t.NewSubTest("mesh-grpc").Run(func(t framework.TestContext) { 546 _ = apps.A[0].CallOrFail(t, echo.CallOptions{ 547 To: apps.C, 548 Count: 1, 549 Port: echo.Port{ 550 Name: "grpc", 551 }, 552 Check: check.And( 553 check.OK(), 554 check.RequestHeader("My-Added-Header", "added-grpc-value")), 555 }) 556 }) 557 t.NewSubTest("status").Run(func(t framework.TestContext) { 558 retry.UntilSuccessOrFail(t, func() error { 559 gwc, err := t.Clusters().Kube().Default().GatewayAPI().GatewayV1beta1().GatewayClasses().Get(context.Background(), "istio", metav1.GetOptions{}) 560 if err != nil { 561 return err 562 } 563 if s := kstatus.GetCondition(gwc.Status.Conditions, string(k8sv1.GatewayClassConditionStatusAccepted)).Status; s != metav1.ConditionTrue { 564 return fmt.Errorf("expected status %q, got %q", metav1.ConditionTrue, s) 565 } 566 return nil 567 }) 568 }) 569 t.NewSubTest("tls-same").Run(func(t framework.TestContext) { 570 _ = ingr.CallOrFail(t, echo.CallOptions{ 571 Port: echo.Port{ 572 Protocol: protocol.HTTPS, 573 ServicePort: 443, 574 }, 575 HTTP: echo.HTTP{ 576 Path: "/", 577 Headers: headers.New().WithHost("same-namespace.domain.example").Build(), 578 }, 579 Check: check.OK(), 580 }) 581 }) 582 t.NewSubTest("tls-cross").Run(func(t framework.TestContext) { 583 _ = ingr.CallOrFail(t, echo.CallOptions{ 584 Port: echo.Port{ 585 Protocol: protocol.HTTPS, 586 ServicePort: 443, 587 }, 588 HTTP: echo.HTTP{ 589 Path: "/", 590 Headers: headers.New().WithHost("cross-namespace.domain.example").Build(), 591 }, 592 Check: check.OK(), 593 }) 594 }) 595 }) 596 } 597 } 598 599 func StatusGatewayTest(t framework.TestContext) { 600 client := t.Clusters().Kube().Default().GatewayAPI().GatewayV1beta1().GatewayClasses() 601 602 check := func() error { 603 gwc, _ := client.Get(context.Background(), "istio", metav1.GetOptions{}) 604 if gwc == nil { 605 return fmt.Errorf("failed to find GatewayClass istio") 606 } 607 cond := kstatus.GetCondition(gwc.Status.Conditions, string(k8sv1.GatewayClassConditionStatusAccepted)) 608 if cond.Status != metav1.ConditionTrue { 609 return fmt.Errorf("failed to find accepted condition: %+v", cond) 610 } 611 if cond.ObservedGeneration != gwc.Generation { 612 return fmt.Errorf("stale GWC generation: %+v", cond) 613 } 614 return nil 615 } 616 retry.UntilSuccessOrFail(t, check) 617 618 // Wipe out the status 619 gwc, _ := client.Get(context.Background(), "istio", metav1.GetOptions{}) 620 gwc.Status.Conditions = nil 621 client.Update(context.Background(), gwc, metav1.UpdateOptions{}) 622 // It should be added back 623 retry.UntilSuccessOrFail(t, check) 624 } 625 626 // Verify that the envoy readiness probes are reachable at 627 // https://GatewaySvcIP:15021/healthz/ready . This is being explicitly done 628 // to make sure, in dual-stack scenarios both v4 and v6 probes are reachable. 629 func TestGatewayReadinessProbes(t *testing.T) { 630 // nolint: staticcheck 631 framework.NewTest(t). 632 RequiresSingleCluster(). 633 RequiresLocalControlPlane(). 634 Run(func(t framework.TestContext) { 635 c := t.Clusters().Default() 636 var svc *corev1.Service 637 svc, _, err := testKube.WaitUntilServiceEndpointsAreReady(c.Kube(), "istio-system", "istio-ingressgateway") 638 if err != nil { 639 t.Fatalf("error getting ingress gateway svc ips: %v", err) 640 } 641 for _, ip := range svc.Spec.ClusterIPs { 642 t.NewSubTest("gateway-readiness-probe-" + ip).Run(func(t framework.TestContext) { 643 apps.External.All[0].CallOrFail(t, echo.CallOptions{ 644 Address: ip, 645 Port: echo.Port{ServicePort: 15021}, 646 Scheme: scheme.HTTP, 647 HTTP: echo.HTTP{ 648 Path: "/healthz/ready", 649 }, 650 Check: check.And( 651 check.Status(200), 652 ), 653 }) 654 }) 655 } 656 }) 657 } 658 659 // Verify that the envoy metrics endpoints are reachable at 660 // https://GatewayPodIP:15090/stats/prometheus . This is being explicitly done 661 // to make sure, in dual-stack scenarios both v4 and v6 probes are reachable. 662 func TestGatewayMetricsEndpoints(t *testing.T) { 663 // nolint: staticcheck 664 framework.NewTest(t). 665 RequiresSingleCluster(). 666 RequiresLocalControlPlane(). 667 Run(func(t framework.TestContext) { 668 c := t.Clusters().Default() 669 podIPs, err := i.PodIPsFor(c, i.Settings().SystemNamespace, "app=istio-ingressgateway") 670 if err != nil { 671 t.Fatalf("error getting ingress gateway pod ips: %v", err) 672 } 673 for _, ip := range podIPs { 674 t.NewSubTest("gateway-metrics-endpoints-" + ip.IP).Run(func(t framework.TestContext) { 675 apps.External.All[0].CallOrFail(t, echo.CallOptions{ 676 Address: ip.IP, 677 Port: echo.Port{ServicePort: 15090}, 678 Scheme: scheme.HTTP, 679 HTTP: echo.HTTP{ 680 Path: "/stats/prometheus", 681 }, 682 Check: check.And( 683 check.Status(200), 684 ), 685 }) 686 }) 687 } 688 }) 689 }