istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/ingress_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  	"net"
    24  	"net/http"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  	"time"
    29  
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  
    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/env"
    36  	"istio.io/istio/pkg/test/framework"
    37  	kubecluster "istio.io/istio/pkg/test/framework/components/cluster/kube"
    38  	"istio.io/istio/pkg/test/framework/components/echo"
    39  	"istio.io/istio/pkg/test/framework/components/echo/check"
    40  	"istio.io/istio/pkg/test/framework/components/environment/kube"
    41  	"istio.io/istio/pkg/test/framework/components/istio"
    42  	"istio.io/istio/pkg/test/framework/components/namespace"
    43  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    44  	"istio.io/istio/pkg/test/helm"
    45  	kubetest "istio.io/istio/pkg/test/kube"
    46  	"istio.io/istio/pkg/test/util/retry"
    47  	helmtest "istio.io/istio/tests/integration/helm"
    48  	ingressutil "istio.io/istio/tests/integration/security/sds_ingress/util"
    49  )
    50  
    51  func skipIfIngressClassUnsupported(t framework.TestContext) {
    52  	if !t.Clusters().Default().MinKubeVersion(18) {
    53  		t.Skip("IngressClass not supported")
    54  	}
    55  }
    56  
    57  // TestIngress tests that we can route using standard Kubernetes Ingress objects.
    58  func TestIngress(t *testing.T) {
    59  	framework.
    60  		NewTest(t).
    61  		Run(func(t framework.TestContext) {
    62  			skipIfIngressClassUnsupported(t)
    63  			// Set up secret contain some TLS certs for *.example.com
    64  			// we will define one for foo.example.com and one for bar.example.com, to ensure both can co-exist
    65  			ingressutil.CreateIngressKubeSecret(t, "k8s-ingress-secret-foo", ingressutil.TLS, ingressutil.IngressCredentialA, false, t.Clusters().Kube()...)
    66  			ingressutil.CreateIngressKubeSecret(t, "k8s-ingress-secret-bar", ingressutil.TLS, ingressutil.IngressCredentialB, false, t.Clusters().Kube()...)
    67  
    68  			ingressClassConfig := `
    69  apiVersion: networking.k8s.io/v1
    70  kind: IngressClass
    71  metadata:
    72    name: istio-test
    73  spec:
    74    controller: istio.io/ingress-controller`
    75  
    76  			ingressConfigTemplate := `
    77  apiVersion: networking.k8s.io/v1
    78  kind: Ingress
    79  metadata:
    80    name: %s
    81  spec:
    82    ingressClassName: %s
    83    tls:
    84    - hosts: ["foo.example.com"]
    85      secretName: k8s-ingress-secret-foo
    86    - hosts: ["bar.example.com"]
    87      secretName: k8s-ingress-secret-bar
    88    rules:
    89    - http:
    90        paths:
    91        - backend:
    92            service:
    93              name: b
    94              port:
    95                name: http
    96          path: %s/namedport
    97          pathType: ImplementationSpecific
    98        - backend:
    99            service:
   100              name: b
   101              port:
   102                number: 80
   103          path: %s
   104          pathType: ImplementationSpecific
   105        - backend:
   106            service:
   107              name: b
   108              port:
   109                number: 80
   110          path: %s
   111          pathType: Prefix
   112  `
   113  
   114  			successChecker := check.And(check.OK(), check.ReachedClusters(t.AllClusters(), apps.B.Clusters()))
   115  			failureChecker := check.Status(http.StatusNotFound)
   116  			count := 2 * t.Clusters().Len()
   117  
   118  			// TODO check all clusters were hit
   119  			cases := []struct {
   120  				name       string
   121  				path       string
   122  				prefixPath string
   123  				call       echo.CallOptions
   124  			}{
   125  				{
   126  					// Basic HTTP call
   127  					name: "http",
   128  					call: echo.CallOptions{
   129  						Port: echo.Port{
   130  							Protocol: protocol.HTTP,
   131  						},
   132  						HTTP: echo.HTTP{
   133  							Path:    "/test",
   134  							Headers: headers.New().WithHost("server").Build(),
   135  						},
   136  						Check: successChecker,
   137  						Count: count,
   138  					},
   139  					path:       "/test",
   140  					prefixPath: "/prefix",
   141  				},
   142  				{
   143  					// Prefix /prefix/should MATCHES prefix/should/match
   144  					name: "http-prefix-matches-subpath",
   145  					call: echo.CallOptions{
   146  						Port: echo.Port{
   147  							Protocol: protocol.HTTP,
   148  						},
   149  						HTTP: echo.HTTP{
   150  							Path:    "/prefix/should/match",
   151  							Headers: headers.New().WithHost("server").Build(),
   152  						},
   153  						Check: successChecker,
   154  						Count: count,
   155  					},
   156  					path:       "/test",
   157  					prefixPath: "/prefix/should",
   158  				},
   159  				{
   160  					// Prefix /prefix/test/ should match path /prefix/test
   161  					name: "http-prefix-matches-without-trailing-backslash",
   162  					call: echo.CallOptions{
   163  						Port: echo.Port{
   164  							Protocol: protocol.HTTP,
   165  						},
   166  						HTTP: echo.HTTP{
   167  							Path:    "/prefix/test",
   168  							Headers: headers.New().WithHost("server").Build(),
   169  						},
   170  						Check: successChecker,
   171  						Count: count,
   172  					},
   173  					path:       "/test",
   174  					prefixPath: "/prefix/test/",
   175  				},
   176  				{
   177  					// Prefix /prefix/test should match /prefix/test/
   178  					name: "http-prefix-matches-trailing-blackslash",
   179  					call: echo.CallOptions{
   180  						Port: echo.Port{
   181  							Protocol: protocol.HTTP,
   182  						},
   183  						HTTP: echo.HTTP{
   184  							Path:    "/prefix/test/",
   185  							Headers: headers.New().WithHost("server").Build(),
   186  						},
   187  						Check: successChecker,
   188  						Count: count,
   189  					},
   190  					path:       "/test",
   191  					prefixPath: "/prefix/test",
   192  				},
   193  				{
   194  					// Prefix /prefix/test should NOT match /prefix/testrandom
   195  					name: "http-prefix-should-not-match-path-continuation",
   196  					call: echo.CallOptions{
   197  						Port: echo.Port{
   198  							Protocol: protocol.HTTP,
   199  						},
   200  						HTTP: echo.HTTP{
   201  							Path:    "/prefix/testrandom/",
   202  							Headers: headers.New().WithHost("server").Build(),
   203  						},
   204  						Check: failureChecker,
   205  						Count: count,
   206  					},
   207  					path:       "/test",
   208  					prefixPath: "/prefix/test",
   209  				},
   210  				{
   211  					// Prefix / should match any path
   212  					name: "http-root-prefix-should-match-random-path",
   213  					call: echo.CallOptions{
   214  						Port: echo.Port{
   215  							Protocol: protocol.HTTP,
   216  						},
   217  						HTTP: echo.HTTP{
   218  							Path:    "/testrandom",
   219  							Headers: headers.New().WithHost("server").Build(),
   220  						},
   221  						Check: successChecker,
   222  						Count: count,
   223  					},
   224  					path:       "/test",
   225  					prefixPath: "/",
   226  				},
   227  				{
   228  					// Basic HTTPS call for foo. CaCert matches the secret
   229  					name: "https-foo",
   230  					call: echo.CallOptions{
   231  						Port: echo.Port{
   232  							Protocol: protocol.HTTPS,
   233  						},
   234  						HTTP: echo.HTTP{
   235  							Path:    "/test",
   236  							Headers: headers.New().WithHost("foo.example.com").Build(),
   237  						},
   238  						TLS: echo.TLS{
   239  							CaCert: ingressutil.IngressCredentialA.CaCert,
   240  						},
   241  						Check: successChecker,
   242  						Count: count,
   243  					},
   244  					path:       "/test",
   245  					prefixPath: "/prefix",
   246  				},
   247  				{
   248  					// Basic HTTPS call for bar. CaCert matches the secret
   249  					name: "https-bar",
   250  					call: echo.CallOptions{
   251  						Port: echo.Port{
   252  							Protocol: protocol.HTTPS,
   253  						},
   254  						HTTP: echo.HTTP{
   255  							Path:    "/test",
   256  							Headers: headers.New().WithHost("bar.example.com").Build(),
   257  						},
   258  						TLS: echo.TLS{
   259  							CaCert: ingressutil.IngressCredentialB.CaCert,
   260  						},
   261  						Check: successChecker,
   262  						Count: count,
   263  					},
   264  					path:       "/test",
   265  					prefixPath: "/prefix",
   266  				},
   267  				{
   268  					// HTTPS call for bar with namedport route. CaCert matches the secret
   269  					name: "https-namedport",
   270  					call: echo.CallOptions{
   271  						Port: echo.Port{
   272  							Protocol: protocol.HTTPS,
   273  						},
   274  						HTTP: echo.HTTP{
   275  							Path:    "/test/namedport",
   276  							Headers: headers.New().WithHost("bar.example.com").Build(),
   277  						},
   278  						TLS: echo.TLS{
   279  							CaCert: ingressutil.IngressCredentialB.CaCert,
   280  						},
   281  						Check: successChecker,
   282  						Count: count,
   283  					},
   284  					path:       "/test",
   285  					prefixPath: "/prefix",
   286  				},
   287  			}
   288  
   289  			for _, ingr := range istio.IngressesOrFail(t, t) {
   290  				ingr := ingr
   291  				t.NewSubTestf("from %s", ingr.Cluster().StableName()).Run(func(t framework.TestContext) {
   292  					for _, c := range cases {
   293  						c := c
   294  						t.NewSubTest(c.name).Run(func(t framework.TestContext) {
   295  							if err := t.ConfigIstio().YAML(apps.Namespace.Name(), ingressClassConfig,
   296  								fmt.Sprintf(ingressConfigTemplate, "ingress", "istio-test", c.path, c.path, c.prefixPath)).
   297  								Apply(); err != nil {
   298  								t.Fatal(err)
   299  							}
   300  							c.call.Retry.Options = []retry.Option{
   301  								retry.Delay(500 * time.Millisecond),
   302  								retry.Timeout(time.Minute * 2),
   303  							}
   304  							ingr.CallOrFail(t, c.call)
   305  						})
   306  					}
   307  				})
   308  			}
   309  
   310  			defaultIngress := istio.DefaultIngressOrFail(t, t)
   311  			t.NewSubTest("status").Run(func(t framework.TestContext) {
   312  				if !t.Environment().(*kube.Environment).Settings().LoadBalancerSupported {
   313  					t.Skip("ingress status not supported without load balancer")
   314  				}
   315  				if err := t.ConfigIstio().YAML(apps.Namespace.Name(), ingressClassConfig,
   316  					fmt.Sprintf(ingressConfigTemplate, "ingress", "istio-test", "/test", "/test", "/test")).
   317  					Apply(); err != nil {
   318  					t.Fatal(err)
   319  				}
   320  
   321  				hosts, _ := defaultIngress.HTTPAddresses()
   322  				for _, host := range hosts {
   323  					hostIsIP := net.ParseIP(host).String() != "<nil>"
   324  					ingressHostFound := false
   325  					actualHosts := []string{}
   326  					retry.UntilSuccessOrFail(t, func() error {
   327  						ing, err := t.Clusters().Default().Kube().NetworkingV1().Ingresses(apps.Namespace.Name()).Get(context.Background(), "ingress", metav1.GetOptions{})
   328  						if err != nil {
   329  							return err
   330  						}
   331  						if len(ing.Status.LoadBalancer.Ingress) < 1 {
   332  							return fmt.Errorf("unexpected ingress status, ingress is empty")
   333  						}
   334  						for _, ingress := range ing.Status.LoadBalancer.Ingress {
   335  							got := ingress.Hostname
   336  							if hostIsIP {
   337  								got = ingress.IP
   338  							}
   339  							actualHosts = append(actualHosts, got)
   340  							if got == host {
   341  								ingressHostFound = true
   342  								break
   343  							}
   344  						}
   345  						if !ingressHostFound {
   346  							return fmt.Errorf("unexpected ingress status, got %+v want %v", actualHosts, host)
   347  						}
   348  						return nil
   349  					}, retry.Timeout(time.Second*90))
   350  				}
   351  			})
   352  
   353  			// setup another ingress pointing to a different route; the ingress will have an ingress class that should be targeted at first
   354  			const updateIngressName = "update-test-ingress"
   355  			if err := t.ConfigIstio().YAML(apps.Namespace.Name(), ingressClassConfig,
   356  				fmt.Sprintf(ingressConfigTemplate, updateIngressName, "istio-test", "/update-test", "/update-test", "/update-test")).
   357  				Apply(); err != nil {
   358  				t.Fatal(err)
   359  			}
   360  			// these cases make sure that when new Ingress configs are applied our controller picks up on them
   361  			// and updates the accessible ingress-gateway routes accordingly
   362  			ingressUpdateCases := []struct {
   363  				name         string
   364  				ingressClass string
   365  				path         string
   366  				call         echo.CallOptions
   367  			}{
   368  				// Ensure we get a 200 initially
   369  				{
   370  					name:         "initial state",
   371  					ingressClass: "istio-test",
   372  					path:         "/update-test",
   373  					call: echo.CallOptions{
   374  						Port: echo.Port{
   375  							Protocol: protocol.HTTP,
   376  						},
   377  						HTTP: echo.HTTP{
   378  							Path:    "/update-test",
   379  							Headers: headers.New().WithHost("server").Build(),
   380  						},
   381  						Check: check.OK(),
   382  					},
   383  				},
   384  				{
   385  					name:         "update-class-not-istio",
   386  					ingressClass: "not-istio",
   387  					path:         "/update-test",
   388  					call: echo.CallOptions{
   389  						Port: echo.Port{
   390  							Protocol: protocol.HTTP,
   391  						},
   392  						HTTP: echo.HTTP{
   393  							Path:    "/update-test",
   394  							Headers: headers.New().WithHost("server").Build(),
   395  						},
   396  						Check: func(result echo.CallResult, err error) error {
   397  							if err != nil {
   398  								return nil
   399  							}
   400  
   401  							return check.Status(http.StatusNotFound).Check(result, nil)
   402  						},
   403  					},
   404  				},
   405  				{
   406  					name:         "update-class-istio",
   407  					ingressClass: "istio-test",
   408  					path:         "/update-test",
   409  					call: echo.CallOptions{
   410  						Port: echo.Port{
   411  							Protocol: protocol.HTTP,
   412  						},
   413  						HTTP: echo.HTTP{
   414  							Path:    "/update-test",
   415  							Headers: headers.New().WithHost("server").Build(),
   416  						},
   417  						Check: check.OK(),
   418  					},
   419  				},
   420  				{
   421  					name:         "update-path",
   422  					ingressClass: "istio-test",
   423  					path:         "/updated",
   424  					call: echo.CallOptions{
   425  						Port: echo.Port{
   426  							Protocol: protocol.HTTP,
   427  						},
   428  						HTTP: echo.HTTP{
   429  							Path:    "/updated",
   430  							Headers: headers.New().WithHost("server").Build(),
   431  						},
   432  						Check: check.OK(),
   433  					},
   434  				},
   435  			}
   436  
   437  			for _, c := range ingressUpdateCases {
   438  				c := c
   439  				updatedIngress := fmt.Sprintf(ingressConfigTemplate, updateIngressName, c.ingressClass, c.path, c.path, c.path)
   440  				t.ConfigIstio().YAML(apps.Namespace.Name(), updatedIngress).ApplyOrFail(t)
   441  				t.NewSubTest(c.name).Run(func(t framework.TestContext) {
   442  					c.call.Retry.Options = []retry.Option{retry.Timeout(time.Minute)}
   443  					defaultIngress.CallOrFail(t, c.call)
   444  				})
   445  			}
   446  		})
   447  }
   448  
   449  // TestCustomGateway deploys a simple gateway deployment, that is fully injected, and verifies it can startup and send traffic
   450  func TestCustomGateway(t *testing.T) {
   451  	framework.
   452  		NewTest(t).
   453  		Run(func(t framework.TestContext) {
   454  			inject := false
   455  			if t.Settings().Compatibility {
   456  				inject = true
   457  			}
   458  			injectLabel := `sidecar.istio.io/inject: "true"`
   459  			if t.Settings().Revisions.Default() != "" {
   460  				injectLabel = fmt.Sprintf(`istio.io/rev: "%v"`, t.Settings().Revisions.Default())
   461  			}
   462  
   463  			templateParams := map[string]string{
   464  				"imagePullSecret": t.Settings().Image.PullSecretNameOrFail(t),
   465  				"injectLabel":     injectLabel,
   466  				"host":            apps.A.Config().ClusterLocalFQDN(),
   467  				"imagePullPolicy": t.Settings().Image.PullPolicy,
   468  			}
   469  
   470  			t.NewSubTest("minimal").Run(func(t framework.TestContext) {
   471  				gatewayNs := namespace.NewOrFail(t, t, namespace.Config{Prefix: "custom-gateway-minimal", Inject: inject})
   472  				_ = t.ConfigIstio().Eval(gatewayNs.Name(), templateParams, `apiVersion: v1
   473  kind: Service
   474  metadata:
   475    name: custom-gateway
   476    labels:
   477      istio: custom
   478  spec:
   479    ports:
   480    - port: 80
   481      targetPort: 8080
   482      name: http
   483    selector:
   484      istio: custom
   485  ---
   486  apiVersion: apps/v1
   487  kind: Deployment
   488  metadata:
   489    name: custom-gateway
   490  spec:
   491    selector:
   492      matchLabels:
   493        istio: custom
   494    template:
   495      metadata:
   496        annotations:
   497          inject.istio.io/templates: gateway
   498        labels:
   499          istio: custom
   500          {{ .injectLabel }}
   501      spec:
   502        {{- if ne .imagePullSecret "" }}
   503        imagePullSecrets:
   504        - name: {{ .imagePullSecret }}
   505        {{- end }}
   506        containers:
   507        - name: istio-proxy
   508          image: auto
   509          imagePullPolicy: {{ .imagePullPolicy }}
   510  ---
   511  apiVersion: networking.istio.io/v1alpha3
   512  kind: Gateway
   513  metadata:
   514    name: app
   515  spec:
   516    selector:
   517      istio: custom
   518    servers:
   519    - port:
   520        number: 80
   521        name: http
   522        protocol: HTTP
   523      hosts:
   524      - "*"
   525  ---
   526  apiVersion: networking.istio.io/v1alpha3
   527  kind: VirtualService
   528  metadata:
   529    name: app
   530  spec:
   531    hosts:
   532    - "*"
   533    gateways:
   534    - app
   535    http:
   536    - route:
   537      - destination:
   538          host: {{ .host }}
   539          port:
   540            number: 80
   541  `).Apply(apply.NoCleanup)
   542  				cs := t.Clusters().Default().(*kubecluster.Cluster)
   543  				retry.UntilSuccessOrFail(t, func() error {
   544  					_, err := kubetest.CheckPodsAreReady(kubetest.NewPodFetch(cs, gatewayNs.Name(), "istio=custom"))
   545  					return err
   546  				}, retry.Timeout(time.Minute*2))
   547  				apps.B[0].CallOrFail(t, echo.CallOptions{
   548  					Port:    echo.Port{ServicePort: 80},
   549  					Scheme:  scheme.HTTP,
   550  					Address: fmt.Sprintf("custom-gateway.%s.svc.cluster.local", gatewayNs.Name()),
   551  					Check:   check.OK(),
   552  				})
   553  			})
   554  			// TODO we could add istioctl as well, but the framework adds a bunch of stuff beyond just `istioctl install`
   555  			// that mess with certs, multicluster, etc
   556  			t.NewSubTest("helm").Run(func(t framework.TestContext) {
   557  				gatewayNs := namespace.NewOrFail(t, t, namespace.Config{Prefix: "custom-gateway-helm", Inject: inject})
   558  				d := filepath.Join(t.TempDir(), "gateway-values.yaml")
   559  				rev := ""
   560  				if t.Settings().Revisions.Default() != "" {
   561  					rev = t.Settings().Revisions.Default()
   562  				}
   563  				os.WriteFile(d, []byte(fmt.Sprintf(`
   564  revision: %v
   565  gateways:
   566    istio-ingressgateway:
   567      name: custom-gateway-helm
   568      injectionTemplate: gateway
   569      type: ClusterIP # LoadBalancer is slow and not necessary for this tests
   570      autoscaleMax: 1
   571      resources:
   572        requests:
   573          cpu: 10m
   574          memory: 40Mi
   575      labels:
   576        istio: custom-gateway-helm
   577  `, rev)), 0o644)
   578  				cs := t.Clusters().Default().(*kubecluster.Cluster)
   579  				h := helm.New(cs.Filename())
   580  				// Install ingress gateway chart
   581  				if err := h.InstallChart("ingress", filepath.Join(env.IstioSrc, "manifests/charts/gateways/istio-ingress"), gatewayNs.Name(),
   582  					d, helmtest.Timeout); err != nil {
   583  					t.Fatal(err)
   584  				}
   585  				retry.UntilSuccessOrFail(t, func() error {
   586  					_, err := kubetest.CheckPodsAreReady(kubetest.NewPodFetch(cs, gatewayNs.Name(), "istio=custom-gateway-helm"))
   587  					return err
   588  				}, retry.Timeout(time.Minute*2), retry.Delay(time.Millisecond*500))
   589  				_ = t.ConfigIstio().YAML(gatewayNs.Name(), fmt.Sprintf(`apiVersion: networking.istio.io/v1alpha3
   590  kind: Gateway
   591  metadata:
   592    name: app
   593  spec:
   594    selector:
   595      istio: custom-gateway-helm
   596    servers:
   597    - port:
   598        number: 80
   599        name: http
   600        protocol: HTTP
   601      hosts:
   602      - "*"
   603  ---
   604  apiVersion: networking.istio.io/v1alpha3
   605  kind: VirtualService
   606  metadata:
   607    name: app
   608  spec:
   609    hosts:
   610    - "*"
   611    gateways:
   612    - app
   613    http:
   614    - route:
   615      - destination:
   616          host: %s
   617          port:
   618            number: 80
   619  `, apps.A.Config().ClusterLocalFQDN())).Apply(apply.NoCleanup)
   620  				apps.B[0].CallOrFail(t, echo.CallOptions{
   621  					Port:    echo.Port{ServicePort: 80},
   622  					Scheme:  scheme.HTTP,
   623  					Address: fmt.Sprintf("custom-gateway-helm.%s.svc.cluster.local", gatewayNs.Name()),
   624  					Check:   check.OK(),
   625  				})
   626  			})
   627  			t.NewSubTest("helm-simple").Run(func(t framework.TestContext) {
   628  				gatewayNs := namespace.NewOrFail(t, t, namespace.Config{Prefix: "custom-gateway-helm", Inject: inject})
   629  				d := filepath.Join(t.TempDir(), "gateway-values.yaml")
   630  				rev := ""
   631  				if t.Settings().Revisions.Default() != "" {
   632  					rev = t.Settings().Revisions.Default()
   633  				}
   634  				os.WriteFile(d, []byte(fmt.Sprintf(`
   635  revision: %q
   636  service:
   637    type: ClusterIP # LoadBalancer is slow and not necessary for this tests
   638  autoscaling:
   639    enabled: false
   640  resources:
   641    requests:
   642      cpu: 10m
   643      memory: 40Mi
   644  `, rev)), 0o644)
   645  				cs := t.Clusters().Default().(*kubecluster.Cluster)
   646  				h := helm.New(cs.Filename())
   647  				// Install ingress gateway chart
   648  				if err := h.InstallChart("helm-simple", filepath.Join(env.IstioSrc, "manifests/charts/gateway"), gatewayNs.Name(),
   649  					d, helmtest.Timeout); err != nil {
   650  					t.Fatal(err)
   651  				}
   652  				retry.UntilSuccessOrFail(t, func() error {
   653  					_, err := kubetest.CheckPodsAreReady(kubetest.NewPodFetch(cs, gatewayNs.Name(), "istio=helm-simple"))
   654  					return err
   655  				}, retry.Timeout(time.Minute*2), retry.Delay(time.Millisecond*500))
   656  				_ = t.ConfigIstio().YAML(gatewayNs.Name(), fmt.Sprintf(`apiVersion: networking.istio.io/v1alpha3
   657  kind: Gateway
   658  metadata:
   659    name: app
   660  spec:
   661    selector:
   662      istio: helm-simple
   663    servers:
   664    - port:
   665        number: 80
   666        name: http
   667        protocol: HTTP
   668      hosts:
   669      - "*"
   670  ---
   671  apiVersion: networking.istio.io/v1alpha3
   672  kind: VirtualService
   673  metadata:
   674    name: app
   675  spec:
   676    hosts:
   677    - "*"
   678    gateways:
   679    - app
   680    http:
   681    - route:
   682      - destination:
   683          host: %s
   684          port:
   685            number: 80
   686  `, apps.A.Config().ClusterLocalFQDN())).Apply(apply.NoCleanup)
   687  				apps.B[0].CallOrFail(t, echo.CallOptions{
   688  					Port:    echo.Port{ServicePort: 80},
   689  					Scheme:  scheme.HTTP,
   690  					Address: fmt.Sprintf("helm-simple.%s.svc.cluster.local", gatewayNs.Name()),
   691  					Check:   check.OK(),
   692  				})
   693  			})
   694  		})
   695  }