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 }