istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/common/traffic.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 common 19 20 import ( 21 "fmt" 22 23 "istio.io/istio/pkg/test" 24 "istio.io/istio/pkg/test/framework" 25 "istio.io/istio/pkg/test/framework/components/echo" 26 "istio.io/istio/pkg/test/framework/components/echo/check" 27 "istio.io/istio/pkg/test/framework/components/echo/common/deployment" 28 "istio.io/istio/pkg/test/framework/components/echo/echotest" 29 "istio.io/istio/pkg/test/framework/components/echo/match" 30 "istio.io/istio/pkg/test/framework/components/istio" 31 "istio.io/istio/pkg/test/framework/components/istio/ingress" 32 "istio.io/istio/pkg/test/framework/resource" 33 "istio.io/istio/pkg/test/util/tmpl" 34 "istio.io/istio/pkg/test/util/yml" 35 ) 36 37 type TrafficCall struct { 38 name string 39 call func(t test.Failer, options echo.CallOptions) echo.CallResult 40 opts echo.CallOptions 41 } 42 43 type skip struct { 44 skip bool 45 reason string 46 } 47 48 type TrafficTestCase struct { 49 name string 50 // config can optionally be templated using the params src, dst (each are []echo.Instance) 51 config string 52 53 // Multiple calls. Cannot be used with call/opts 54 children []TrafficCall 55 56 // Single call. Cannot be used with children or workloadAgnostic tests. 57 call func(t test.Failer, options echo.CallOptions) echo.CallResult 58 // opts specifies the echo call options. When using RunForApps, the To will be set dynamically. 59 opts echo.CallOptions 60 // setupOpts allows modifying options based on sources/destinations 61 setupOpts func(src echo.Caller, opts *echo.CallOptions) 62 // check is used to build validators dynamically when using RunForApps based on the active/src dest pair 63 check func(src echo.Caller, opts *echo.CallOptions) echo.Checker 64 checkForN func(src echo.Caller, dst echo.Services, opts *echo.CallOptions) echo.Checker 65 66 // setting cases to skipped is better than not adding them - gives visibility to what needs to be fixed 67 skip skip 68 69 // workloadAgnostic is a temporary setting to trigger using RunForApps 70 // TODO remove this and force everything to be workoad agnostic 71 workloadAgnostic bool 72 73 // toN causes the test to be run for N destinations. The call will be made from instances of the first deployment 74 // in each subset in each cluster. See echotes.T's RunToN for more details. 75 toN int 76 // viaIngress makes the ingress gateway the caller for tests 77 viaIngress bool 78 // sourceMatchers allows adding additional filtering for workload agnostic cases to test using fewer clients 79 sourceMatchers []match.Matcher 80 // targetMatchers allows adding additional filtering for workload agnostic cases to test using fewer targets 81 targetMatchers []match.Matcher 82 // comboFilters allows conditionally filtering based on pairs of apps 83 comboFilters []echotest.CombinationFilter 84 // vars given to the config template 85 templateVars func(src echo.Callers, dest echo.Instances) map[string]any 86 87 // minIstioVersion allows conditionally skipping based on required version 88 minIstioVersion string 89 90 // If set, a datapath with no L7 proxies can run this test 91 RequiresL4 bool 92 // If set, will apply config to all clusters, not just the local one 93 globalConfig bool 94 } 95 96 func (c TrafficTestCase) RunForApps(t framework.TestContext, apps echo.Instances, namespace string) { 97 if c.skip.skip { 98 t.Skip(c.skip.reason) 99 } 100 if c.minIstioVersion != "" { 101 skipMV := !t.Settings().Revisions.AtLeast(resource.IstioVersion(c.minIstioVersion)) 102 if skipMV { 103 t.SkipNow() 104 } 105 } 106 if c.opts.To != nil { 107 t.Fatal("TrafficTestCase.RunForApps: opts.To must not be specified") 108 } 109 if c.call != nil { 110 t.Fatal("TrafficTestCase.RunForApps: call must not be specified") 111 } 112 // just check if any of the required fields are set 113 optsSpecified := c.opts.Port.Name != "" || c.opts.Port.Protocol != "" || c.opts.Scheme != "" 114 if optsSpecified && len(c.children) > 0 { 115 t.Fatal("TrafficTestCase: must not specify both opts and children") 116 } 117 if !optsSpecified && len(c.children) == 0 { 118 t.Fatal("TrafficTestCase: must specify either opts or children") 119 } 120 121 if !c.RequiresL4 { 122 c.comboFilters = append(c.comboFilters, echotest.HasL7) 123 } 124 125 job := func(t framework.TestContext) { 126 echoT := echotest.New(t, apps). 127 SetupForServicePair(func(t framework.TestContext, src echo.Callers, dsts echo.Services) error { 128 tmplData := map[string]any{ 129 // tests that use simple Run only need the first 130 "dst": dsts[0], 131 "dstSvc": dsts[0][0].Config().Service, 132 // tests that use RunForN need all destination deployments 133 "dsts": dsts, 134 "dstSvcs": dsts.NamespacedNames().Names(), 135 } 136 if len(src) > 0 { 137 tmplData["src"] = src 138 if src, ok := src[0].(echo.Instance); ok { 139 tmplData["srcSvc"] = src.Config().Service 140 } 141 } 142 if c.templateVars != nil { 143 for k, v := range c.templateVars(src, dsts[0]) { 144 tmplData[k] = v 145 } 146 } 147 cfg := yml.MustApplyNamespace(t, tmpl.MustEvaluate(c.config, tmplData), namespace) 148 // we only apply to config clusters 149 scope := t.ConfigIstio() 150 if c.globalConfig { 151 scope = t.ConfigKube() 152 } 153 return scope.YAML("", cfg).Apply() 154 }). 155 FromMatch(match.And(c.sourceMatchers...)). 156 // TODO mainly testing proxyless features as a client for now 157 ToMatch(match.And(append(c.targetMatchers, match.NotProxylessGRPC)...)). 158 WithDefaultFilters(1, c.toN). 159 ConditionallyTo(c.comboFilters...) 160 161 doTest := func(t framework.TestContext, from echo.Caller, to echo.Services) { 162 buildOpts := func(options echo.CallOptions) echo.CallOptions { 163 opts := options 164 opts.To = to[0] 165 if c.check != nil { 166 opts.Check = c.check(from, &opts) 167 } 168 if c.checkForN != nil { 169 opts.Check = c.checkForN(from, to, &opts) 170 } 171 if c.setupOpts != nil { 172 c.setupOpts(from, &opts) 173 } 174 // If unset, assume they want to just check the request succeeds 175 if opts.Check == nil { 176 opts.Check = check.OK() 177 } 178 return opts 179 } 180 if optsSpecified { 181 from.CallOrFail(t, buildOpts(c.opts)) 182 } 183 for _, child := range c.children { 184 t.NewSubTest(child.name).Run(func(t framework.TestContext) { 185 from.CallOrFail(t, buildOpts(child.opts)) 186 }) 187 } 188 } 189 190 if c.toN > 0 { 191 echoT.RunToN(c.toN, func(t framework.TestContext, src echo.Instance, dsts echo.Services) { 192 doTest(t, src, dsts) 193 }) 194 } else if c.viaIngress { 195 echoT.RunViaIngress(func(t framework.TestContext, from ingress.Instance, to echo.Target) { 196 doTest(t, from, echo.Services{to.Instances()}) 197 }) 198 } else { 199 echoT.Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 200 doTest(t, from, echo.Services{to.Instances()}) 201 }) 202 } 203 } 204 205 if c.name != "" { 206 t.NewSubTest(c.name).Run(job) 207 } else { 208 job(t) 209 } 210 } 211 212 func (c TrafficTestCase) Run(t framework.TestContext, namespace string) { 213 job := func(t framework.TestContext) { 214 if c.skip.skip { 215 t.Skip(c.skip.reason) 216 } 217 if c.minIstioVersion != "" { 218 skipMV := !t.Settings().Revisions.AtLeast(resource.IstioVersion(c.minIstioVersion)) 219 if skipMV { 220 t.SkipNow() 221 } 222 } 223 // we only apply to config clusters 224 if len(c.config) > 0 { 225 tmplData := map[string]any{} 226 if c.templateVars != nil { 227 // we don't have echo instances so just pass nil 228 for k, v := range c.templateVars(nil, nil) { 229 tmplData[k] = v 230 } 231 } 232 cfg := yml.MustApplyNamespace(t, tmpl.MustEvaluate(c.config, tmplData), namespace) 233 scope := t.ConfigIstio() 234 if c.globalConfig { 235 scope = t.ConfigKube() 236 } 237 scope.YAML("", cfg).ApplyOrFail(t) 238 } 239 240 if c.call != nil && len(c.children) > 0 { 241 t.Fatal("TrafficTestCase: must not specify both call and children") 242 } 243 244 if c.call != nil { 245 c.call(t, c.opts) 246 } 247 248 for _, child := range c.children { 249 t.NewSubTest(child.name).Run(func(t framework.TestContext) { 250 child.call(t, child.opts) 251 }) 252 } 253 } 254 if c.name != "" { 255 t.NewSubTest(c.name).Run(job) 256 } else { 257 job(t) 258 } 259 } 260 261 func skipAmbient(t framework.TestContext, reason string) skip { 262 return skip{skip: t.Settings().Ambient, reason: reason} 263 } 264 265 func RunAllTrafficTests(t framework.TestContext, i istio.Instance, apps deployment.SingleNamespaceView) { 266 RunCase := func(name string, f func(t TrafficContext)) { 267 t.NewSubTest(name).Run(func(t framework.TestContext) { 268 f(TrafficContext{TestContext: t, Apps: apps, Istio: i}) 269 }) 270 } 271 RunSkipAmbient := func(name string, f func(t TrafficContext), reason string) { 272 t.NewSubTest(name).Run(func(t framework.TestContext) { 273 if t.Settings().Ambient { 274 t.Skipf("ambient skipped: %v", reason) 275 } else { 276 f(TrafficContext{TestContext: t, Apps: apps, Istio: i}) 277 } 278 }) 279 } 280 RunCase("jwt-claim-route", jwtClaimRoute) 281 RunCase("virtualservice", virtualServiceCases) 282 RunCase("sniffing", protocolSniffingCases) 283 RunCase("selfcall", selfCallsCases) 284 RunSkipAmbient("serverfirst", serverFirstTestCases, "Expected success cases time out") 285 RunCase("gateway", gatewayCases) 286 RunCase("autopassthrough", autoPassthroughCases) 287 RunSkipAmbient("loop", trafficLoopCases, "does not error (waypoint -> waypoint)") 288 RunSkipAmbient("tls-origination", tlsOriginationCases, "not workload agnostic") 289 RunSkipAmbient("instanceip", instanceIPTests, "not supported") 290 RunCase("services", serviceCases) 291 RunSkipAmbient("externalname", externalNameCases, "Relies on X-Forwarded-Client-Cert in checker") 292 RunSkipAmbient("host", hostCases, "Relies on X-Forwarded-Client-Cert in checker") 293 RunSkipAmbient("envoyfilter", envoyFilterCases, "not supported") 294 RunCase("consistent-hash", consistentHashCases) 295 RunCase("use-client-protocol", useClientProtocolCases) 296 RunCase("destinationrule", destinationRuleCases) 297 RunCase("vm", VMTestCases(apps.VM)) 298 RunSkipAmbient("dns", DNSTestCases, "https://github.com/istio/istio/issues/48614") 299 RunCase("externalservice", TestExternalService) 300 } 301 302 func ExpectString(got, expected, help string) error { 303 if got != expected { 304 return fmt.Errorf("got unexpected %v: got %q, wanted %q", help, got, expected) 305 } 306 return nil 307 } 308 309 func AlmostEquals(a, b, precision int) bool { 310 upper := a + precision 311 lower := a - precision 312 if b < lower || b > upper { 313 return false 314 } 315 return true 316 } 317 318 type TrafficContext struct { 319 framework.TestContext 320 Apps deployment.SingleNamespaceView 321 Istio istio.Instance 322 323 // sourceFilters defines default filters for all cases 324 sourceMatchers []match.Matcher 325 // targetFilters defines default filters for all cases 326 targetMatchers []match.Matcher 327 // comboFilters defines default filters for all cases 328 comboFilters []echotest.CombinationFilter 329 } 330 331 func (t *TrafficContext) SetDefaultSourceMatchers(f ...match.Matcher) { 332 t.sourceMatchers = f 333 } 334 335 func (t *TrafficContext) SetDefaultTargetMatchers(f ...match.Matcher) { 336 t.targetMatchers = f 337 } 338 339 func (t *TrafficContext) SetDefaultComboFilter(f ...echotest.CombinationFilter) { 340 t.comboFilters = f 341 } 342 343 func (t TrafficContext) RunTraffic(tt TrafficTestCase) { 344 if tt.sourceMatchers == nil { 345 tt.sourceMatchers = t.sourceMatchers 346 } 347 if tt.targetMatchers == nil { 348 tt.targetMatchers = t.targetMatchers 349 } 350 if tt.comboFilters == nil { 351 tt.comboFilters = t.comboFilters 352 } 353 if tt.workloadAgnostic { 354 tt.RunForApps(t, t.Apps.All.Instances(), t.Apps.Namespace.Name()) 355 } else { 356 tt.Run(t, t.Apps.Namespace.Name()) 357 } 358 }