istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/workloadentry_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  	"fmt"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"istio.io/istio/pkg/test/echo/common/scheme"
    27  	"istio.io/istio/pkg/test/framework"
    28  	"istio.io/istio/pkg/test/framework/components/crd"
    29  	"istio.io/istio/pkg/test/framework/components/echo"
    30  	"istio.io/istio/pkg/test/framework/components/echo/check"
    31  	"istio.io/istio/pkg/test/framework/components/echo/common/ports"
    32  	"istio.io/istio/pkg/test/framework/components/istio"
    33  	"istio.io/istio/pkg/test/util/retry"
    34  )
    35  
    36  // TestWorkloadEntryGateway covers a model of multi-network where we rely on writing WorkloadEntry
    37  // resources inside each config cluster rather than doing cross-cluster discovery via remote secret.
    38  // Each case tests a different way of using local resources to reach remote destination(s).
    39  func TestWorkloadEntryGateway(t *testing.T) {
    40  	// nolint: staticcheck
    41  	framework.NewTest(t).
    42  		RequiresMinClusters(2).
    43  		Run(func(t framework.TestContext) {
    44  			crd.DeployGatewayAPIOrSkip(t)
    45  			i := istio.GetOrFail(t, t)
    46  			type gwAddr struct {
    47  				ip   string
    48  				port int
    49  			}
    50  			gatewayAddresses := map[string]gwAddr{}
    51  			for _, cluster := range t.Clusters() {
    52  				if _, ok := gatewayAddresses[cluster.NetworkName()]; ok {
    53  					continue
    54  				}
    55  				ips, ports := i.EastWestGatewayFor(cluster).AddressesForPort(15443)
    56  				if ips != nil {
    57  					gatewayAddresses[cluster.NetworkName()] = gwAddr{ips[0], ports[0]}
    58  				}
    59  			}
    60  			if len(t.Clusters().Networks()) != len(gatewayAddresses) {
    61  				t.Skip("must have an east-west for each network")
    62  			}
    63  
    64  			// we have an imaginary network for each network called {name}-manual-discovery
    65  			gwTmpl := `
    66  ---
    67  apiVersion: gateway.networking.k8s.io/v1beta1
    68  kind: Gateway
    69  metadata:
    70    name: remote-gateway-manual-discovery-%s
    71    labels:
    72      topology.istio.io/network: "%s-manual-discovery"
    73  spec:
    74    gatewayClassName: istio-remote
    75    addresses:
    76    - value: %q
    77    listeners:
    78    - name: cross-network
    79      port: 15443
    80      protocol: TLS
    81      tls:
    82        mode: Passthrough
    83        options:
    84          gateway.istio.io/listener-protocol: auto-passthrough
    85  `
    86  			// a serviceentry that only includes cluster-local endpoints (avoid automatic cross-cluster discovery)
    87  			seTmpl := `
    88  ---
    89  apiVersion: networking.istio.io/v1beta1
    90  kind: ServiceEntry
    91  metadata:
    92    name: serviceentry.mesh.global
    93  spec:
    94    addresses:
    95    - 240.240.240.240
    96    hosts: 
    97    - serviceentry.mesh.global
    98    ports:
    99    - number: 80
   100      targetPort: 18080
   101      name: http
   102      protocol: HTTP
   103    resolution: STATIC
   104    location: MESH_INTERNAL
   105    workloadSelector:
   106      labels:
   107        app: b
   108        topology.istio.io/cluster: %s
   109  `
   110  
   111  			exposeServices := `
   112  apiVersion: networking.istio.io/v1alpha3
   113  kind: Gateway
   114  metadata:
   115    name: cross-network-gateway
   116  spec:
   117    selector:
   118      istio: eastwestgateway
   119    servers:
   120    - port:
   121        number: 15443
   122        name: tls
   123        protocol: TLS
   124      tls:
   125        mode: AUTO_PASSTHROUGH
   126      hosts:
   127      - "serviceentry.mesh.global"
   128        `
   129  
   130  			// expose the ServiceEntry and create the "manual discovery" gateways in all clusters
   131  			cfg := t.ConfigIstio().YAML(i.Settings().SystemNamespace, exposeServices)
   132  			for _, network := range t.Clusters().Networks() {
   133  				cfg.
   134  					YAML(i.Settings().SystemNamespace, fmt.Sprintf(gwTmpl, network, network, gatewayAddresses[network].ip))
   135  			}
   136  			cfg.ApplyOrFail(t)
   137  
   138  			// create a unique SE per cluster
   139  			for _, c := range t.Clusters().Configs() {
   140  				t.ConfigKube(c).YAML(apps.Namespace.Name(), fmt.Sprintf(seTmpl, c.Name())).ApplyOrFail(t)
   141  			}
   142  
   143  			weTmpl := `
   144  apiVersion: networking.istio.io/v1alpha3
   145  kind: WorkloadEntry
   146  metadata:
   147    name: se-cross-network-{{.testName}}
   148    labels:
   149      app: b
   150      security.istio.io/tlsMode: istio
   151      # TODO this should be implicit, but for some reason it isn't for WorkloadEntry
   152      topology.istio.io/cluster: {{.clusterName}}
   153  spec:
   154    address: "{{.address}}"
   155    network: "{{.network}}"
   156  {{- if gt .targetPort 0 }}
   157    ports:
   158      http: {{ .targetPort }}
   159  {{- end }}
   160  `
   161  
   162  			testCases := []struct {
   163  				name            string
   164  				addressFunc     func(nw string) string
   165  				targetPortFunc  func(nw string) int
   166  				networkNameFunc func(nw string) string
   167  			}{
   168  				{
   169  					name:            "with gateway address",
   170  					addressFunc:     func(nw string) string { return gatewayAddresses[nw].ip },
   171  					targetPortFunc:  func(nw string) int { return gatewayAddresses[nw].port },
   172  					networkNameFunc: func(nw string) string { return "" },
   173  				},
   174  				{
   175  					name:            "empty address and network",
   176  					addressFunc:     func(nw string) string { return "" },
   177  					targetPortFunc:  func(nw string) int { return 0 },
   178  					networkNameFunc: func(nw string) string { return nw },
   179  				},
   180  				{
   181  					name:            "locally registered gateway",
   182  					addressFunc:     func(nw string) string { return "" },
   183  					targetPortFunc:  func(nw string) int { return 0 },
   184  					networkNameFunc: func(nw string) string { return nw + "-manual-discovery" },
   185  				},
   186  			}
   187  
   188  			for _, tc := range testCases {
   189  				tc := tc
   190  				t.NewSubTest(tc.name).Run(func(t framework.TestContext) {
   191  					for network, networkClusters := range t.Clusters().ByNetwork() {
   192  						weClusters := t.Clusters().Configs(networkClusters...)
   193  						for _, weCluster := range weClusters {
   194  							t.ConfigKube(weCluster).Eval(apps.Namespace.Name(), map[string]interface{}{
   195  								// used so this WE doesn't get cross-cluster discovered
   196  								"clusterName": weCluster.Name(),
   197  								"testName":    strings.ReplaceAll(tc.name, " ", "-"),
   198  								"network":     tc.networkNameFunc(network),
   199  								"address":     tc.addressFunc(network),
   200  								"targetPort":  tc.targetPortFunc(network),
   201  							}, weTmpl).ApplyOrFail(t)
   202  						}
   203  					}
   204  
   205  					for _, src := range apps.A.Instances() {
   206  						src := src
   207  						// TODO possibly can run parallel
   208  						t.NewSubTestf("from %s", src.Clusters().Default().Name()).Run(func(t framework.TestContext) {
   209  							src.CallOrFail(t, echo.CallOptions{
   210  								// check that we lb to all the networks (we won't reach non-config clusters because of the topology.istio.io/cluster selector)
   211  								// that selector helps us verify that we reached the endpoints due to the WorkloadEntry and not regular multicluster service discovery
   212  								Check:                   check.ReachedClusters(t.Clusters(), apps.A.Clusters().Configs()),
   213  								Address:                 "serviceentry.mesh.global",
   214  								Port:                    ports.HTTP,
   215  								Scheme:                  scheme.HTTP,
   216  								NewConnectionPerRequest: true,
   217  								Retry:                   echo.Retry{Options: []retry.Option{multiclusterRetryDelay, retry.Timeout(time.Minute)}},
   218  								Count:                   10,
   219  							})
   220  						})
   221  					}
   222  				})
   223  			}
   224  		})
   225  }