1// Copyright 2019 Istio Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package pilot 16 17import ( 18 "fmt" 19 "math" 20 "strings" 21 "testing" 22 "time" 23 24 "istio.io/istio/pkg/test/util/retry" 25 26 "istio.io/istio/pkg/test/framework/resource/environment" 27 28 "istio.io/istio/pkg/config/protocol" 29 "istio.io/istio/pkg/test/framework" 30 "istio.io/istio/pkg/test/framework/components/echo" 31 "istio.io/istio/pkg/test/framework/components/echo/echoboot" 32 "istio.io/istio/pkg/test/framework/components/namespace" 33 "istio.io/istio/pkg/test/util/file" 34 "istio.io/istio/pkg/test/util/tmpl" 35) 36 37// Virtual service topology 38// 39// a 40// |-------| 41// | Host0 | 42// |-------| 43// | 44// | 45// | 46// ------------------------- 47// |weight1 |weight2 |weight3 48// |b |c |d 49// |-------| |-------| |-------| 50// | Host0 | | Host1 | | Host2 | 51// |-------| |-------| |-------| 52// 53// 54 55const ( 56 // Error threshold. For example, we expect 25% traffic, traffic distribution within [15%, 35%] is accepted. 57 errorThreshold = 10.0 58) 59 60type VirtualServiceConfig struct { 61 Name string 62 Host0 string 63 Host1 string 64 Host2 string 65 Namespace string 66 Weight0 int32 67 Weight1 int32 68 Weight2 int32 69} 70 71func TestTrafficShifting(t *testing.T) { 72 // Traffic distribution 73 weights := map[string][]int32{ 74 "20-80": {20, 80}, 75 "50-50": {50, 50}, 76 "33-33-34": {33, 33, 34}, 77 } 78 79 framework. 80 NewTest(t). 81 RequiresEnvironment(environment.Kube). 82 Run(func(ctx framework.TestContext) { 83 ns := namespace.NewOrFail(t, ctx, namespace.Config{ 84 Prefix: "traffic-shifting", 85 Inject: true, 86 }) 87 88 var instances [4]echo.Instance 89 echoboot.NewBuilderOrFail(t, ctx). 90 With(&instances[0], echoConfig(ns, "a")). 91 With(&instances[1], echoConfig(ns, "b")). 92 With(&instances[2], echoConfig(ns, "c")). 93 With(&instances[3], echoConfig(ns, "d")). 94 BuildOrFail(t) 95 96 hosts := []string{"b", "c", "d"} 97 98 for k, v := range weights { 99 t.Run(k, func(t *testing.T) { 100 v = append(v, make([]int32, 3-len(v))...) 101 102 vsc := VirtualServiceConfig{ 103 "traffic-shifting-rule", 104 hosts[0], 105 hosts[1], 106 hosts[2], 107 ns.Name(), 108 v[0], 109 v[1], 110 v[2], 111 } 112 113 deployment := tmpl.EvaluateOrFail(t, file.AsStringOrFail(t, "testdata/traffic-shifting.yaml"), vsc) 114 g.ApplyConfigOrFail(t, ns, deployment) 115 116 sendTraffic(t, 100, instances[0], instances[1], hosts, v, errorThreshold) 117 }) 118 } 119 }) 120} 121 122func echoConfig(ns namespace.Instance, name string) echo.Config { 123 return echo.Config{ 124 Service: name, 125 Namespace: ns, 126 Ports: []echo.Port{ 127 { 128 Name: "http", 129 Protocol: protocol.HTTP, 130 // We use a port > 1024 to not require root 131 InstancePort: 8090, 132 }, 133 }, 134 Subsets: []echo.SubsetConfig{{}}, 135 Galley: g, 136 Pilot: p, 137 } 138} 139 140func sendTraffic(t *testing.T, batchSize int, from, to echo.Instance, hosts []string, weight []int32, errorThreshold float64) { 141 t.Helper() 142 // Send `batchSize` requests and ensure they are distributed as expected. 143 retry.UntilSuccessOrFail(t, func() error { 144 resp, err := from.Call(echo.CallOptions{ 145 Target: to, 146 PortName: "http", 147 Count: batchSize, 148 }) 149 if err != nil { 150 return fmt.Errorf("error during call: %v", err) 151 } 152 var totalRequests int 153 hitCount := map[string]int{} 154 for _, r := range resp { 155 for _, h := range hosts { 156 if strings.HasPrefix(r.Hostname, h+"-") { 157 hitCount[h]++ 158 totalRequests++ 159 break 160 } 161 } 162 } 163 164 for i, v := range hosts { 165 percentOfTrafficToHost := float64(hitCount[v]) * 100.0 / float64(totalRequests) 166 deltaFromExpected := math.Abs(float64(weight[i]) - percentOfTrafficToHost) 167 if errorThreshold-deltaFromExpected < 0 { 168 return fmt.Errorf("unexpected traffic weight for host %v. Expected %d%%, got %g%% (thresold: %g%%)", 169 v, weight[i], percentOfTrafficToHost, errorThreshold) 170 } 171 t.Logf("Got expected traffic weight for host %v. Expected %d%%, got %g%% (thresold: %g%%)", 172 v, weight[i], percentOfTrafficToHost, errorThreshold) 173 } 174 return nil 175 }, retry.Delay(time.Second)) 176} 177