24
24
package kmesh
25
25
26
26
import (
27
+ "bytes"
27
28
"context"
28
29
"fmt"
29
30
"testing"
30
31
"time"
31
32
33
+ "github.com/hashicorp/go-multierror"
34
+ "istio.io/istio/pkg/test"
32
35
"istio.io/istio/pkg/test/framework"
33
36
"istio.io/istio/pkg/test/framework/components/echo"
34
- "istio.io/istio/pkg/test/framework/components/echo/util/traffic "
37
+ "istio.io/istio/pkg/test/framework/components/echo/check "
35
38
kubetest "istio.io/istio/pkg/test/kube"
36
39
"istio.io/istio/pkg/test/util/retry"
37
40
appsv1 "k8s.io/api/apps/v1"
@@ -54,13 +57,13 @@ func TestKmeshRestart(t *testing.T) {
54
57
Retry : echo.Retry {NoRetry : true },
55
58
}
56
59
57
- g := traffic . NewGenerator (t , traffic. Config {
60
+ g := NewGenerator (t , Config {
58
61
Source : src ,
59
62
Options : options ,
60
63
Interval : 5 * time .Millisecond ,
61
64
}).Start ()
62
65
63
- for i := 0 ; i < 10 ; i ++ {
66
+ for i := 0 ; i < 30 ; i ++ {
64
67
restartKmesh (t )
65
68
}
66
69
@@ -107,3 +110,163 @@ func restartKmesh(t framework.TestContext) {
107
110
func daemonsetsetComplete (ds * appsv1.DaemonSet ) bool {
108
111
return ds .Status .UpdatedNumberScheduled == ds .Status .DesiredNumberScheduled && ds .Status .NumberReady == ds .Status .DesiredNumberScheduled && ds .Status .ObservedGeneration >= ds .Generation
109
112
}
113
+
114
+ const (
115
+ defaultInterval = 1 * time .Second
116
+ defaultTimeout = 15 * time .Second
117
+ )
118
+
119
+ // Config for a traffic Generator.
120
+ type Config struct {
121
+ // Source of the traffic.
122
+ Source echo.Caller
123
+
124
+ // Options for generating traffic from the Source to the target.
125
+ Options echo.CallOptions
126
+
127
+ // Interval between successive call operations. If not set, defaults to 1 second.
128
+ Interval time.Duration
129
+
130
+ // Maximum time to wait for traffic to complete after stopping. If not set, defaults to 15 seconds.
131
+ StopTimeout time.Duration
132
+ }
133
+
134
+ // Generator of traffic between echo instances. Every time interval
135
+ // (as defined by Config.Interval), a grpc request is sent to the source pod,
136
+ // causing it to send a request to the destination echo server. Results are
137
+ // captured for each request for later processing.
138
+ type Generator interface {
139
+ // Start sending traffic.
140
+ Start () Generator
141
+
142
+ // Stop sending traffic and wait for any in-flight requests to complete.
143
+ // Returns the Result
144
+ Stop () Result
145
+ }
146
+
147
+ // NewGenerator returns a new Generator with the given configuration.
148
+ func NewGenerator (t test.Failer , cfg Config ) Generator {
149
+ fillInDefaults (& cfg )
150
+ return & generator {
151
+ Config : cfg ,
152
+ t : t ,
153
+ stop : make (chan struct {}),
154
+ stopped : make (chan struct {}),
155
+ }
156
+ }
157
+
158
+ var _ Generator = & generator {}
159
+
160
+ type generator struct {
161
+ Config
162
+ t test.Failer
163
+ result Result
164
+ stop chan struct {}
165
+ stopped chan struct {}
166
+ }
167
+
168
+ func (g * generator ) Start () Generator {
169
+ go func () {
170
+ t := time .NewTimer (g .Interval )
171
+ for {
172
+ select {
173
+ case <- g .stop :
174
+ t .Stop ()
175
+ close (g .stopped )
176
+ return
177
+ case <- t .C :
178
+ result := g .Source .CallOrFail (g .t , g .Options )
179
+ for _ , res := range result .Responses {
180
+ if res .Code != "200" {
181
+ g .t .Fatal ("failed" )
182
+ }
183
+ }
184
+ g .result .add (result , nil )
185
+ t .Reset (g .Interval )
186
+ }
187
+ }
188
+ }()
189
+ return g
190
+ }
191
+
192
+ func (g * generator ) Stop () Result {
193
+ // Trigger the generator to stop.
194
+ close (g .stop )
195
+
196
+ // Wait for the generator to exit.
197
+ t := time .NewTimer (g .StopTimeout )
198
+ select {
199
+ case <- g .stopped :
200
+ t .Stop ()
201
+ if g .result .TotalRequests == 0 {
202
+ g .t .Fatal ("no requests completed before stopping the traffic generator" )
203
+ }
204
+ return g .result
205
+ case <- t .C :
206
+ g .t .Fatal ("timed out waiting for result" )
207
+ }
208
+ // Can never happen, but the compiler doesn't know that Fatal terminates
209
+ return Result {}
210
+ }
211
+
212
+ func fillInDefaults (cfg * Config ) {
213
+ if cfg .Interval == 0 {
214
+ cfg .Interval = defaultInterval
215
+ }
216
+ if cfg .StopTimeout == 0 {
217
+ cfg .StopTimeout = defaultTimeout
218
+ }
219
+ if cfg .Options .Check == nil {
220
+ cfg .Options .Check = check .OK ()
221
+ }
222
+ }
223
+
224
+ // Result of a traffic generation operation.
225
+ type Result struct {
226
+ TotalRequests int
227
+ SuccessfulRequests int
228
+ Error error
229
+ }
230
+
231
+ func (r Result ) String () string {
232
+ buf := & bytes.Buffer {}
233
+
234
+ _ , _ = fmt .Fprintf (buf , "TotalRequests: %d\n " , r .TotalRequests )
235
+ _ , _ = fmt .Fprintf (buf , "SuccessfulRequests: %d\n " , r .SuccessfulRequests )
236
+ _ , _ = fmt .Fprintf (buf , "PercentSuccess: %f\n " , r .PercentSuccess ())
237
+ _ , _ = fmt .Fprintf (buf , "Errors: %v\n " , r .Error )
238
+
239
+ return buf .String ()
240
+ }
241
+
242
+ func (r * Result ) add (result echo.CallResult , err error ) {
243
+ count := result .Responses .Len ()
244
+ if count == 0 {
245
+ count = 1
246
+ }
247
+
248
+ r .TotalRequests += count
249
+ if err != nil {
250
+ r .Error = multierror .Append (r .Error , fmt .Errorf ("request %d: %v" , r .TotalRequests , err ))
251
+ } else {
252
+ r .SuccessfulRequests += count
253
+ }
254
+ }
255
+
256
+ func (r Result ) PercentSuccess () float64 {
257
+ return float64 (r .SuccessfulRequests ) / float64 (r .TotalRequests )
258
+ }
259
+
260
+ // CheckSuccessRate asserts that a minimum success threshold was met.
261
+ func (r Result ) CheckSuccessRate (t test.Failer , minimumPercent float64 ) {
262
+ t .Helper ()
263
+ if r .PercentSuccess () < minimumPercent {
264
+ t .Fatalf ("Minimum success threshold, %f, was not met. %d/%d (%f) requests failed: %v" ,
265
+ minimumPercent , r .SuccessfulRequests , r .TotalRequests , r .PercentSuccess (), r .Error )
266
+ }
267
+ if r .SuccessfulRequests == r .TotalRequests {
268
+ t .Logf ("traffic checker succeeded with all successful requests (%d/%d)" , r .SuccessfulRequests , r .TotalRequests )
269
+ } else {
270
+ t .Logf ("traffic checker met minimum threshold, with %d/%d successes, but encountered some failures: %v" , r .SuccessfulRequests , r .TotalRequests , r .Error )
271
+ }
272
+ }
0 commit comments