generated from knative-extensions/sample-controller
/
percentage.go
133 lines (116 loc) · 4.05 KB
/
percentage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ingress
import (
"context"
"errors"
"math"
"testing"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/util/intstr"
"knative.dev/networking/pkg/apis/networking"
"knative.dev/networking/pkg/apis/networking/v1alpha1"
"knative.dev/networking/test"
)
// TestPercentage verifies that an Ingress splitting over multiple backends respects
// the given percentage distribution.
func TestPercentage(t *testing.T) {
t.Parallel()
ctx, clients := context.Background(), test.Setup(t)
// Use a post-split injected header to establish which split we are sending traffic to.
const headerName = "Foo-Bar-Baz"
backends := make([]v1alpha1.IngressBackendSplit, 0, 10)
weights := make(map[string]float64, len(backends))
// Double the percentage of the split each iteration until it would overflow, and then
// give the last route the remainder.
percent, total := 1, 0
for i := 0; i < 10; i++ {
name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1)
backends = append(backends, v1alpha1.IngressBackendSplit{
IngressBackend: v1alpha1.IngressBackend{
ServiceName: name,
ServiceNamespace: test.ServingNamespace,
ServicePort: intstr.FromInt(port),
},
// Append different headers to each split, which lets us identify
// which backend we hit.
AppendHeaders: map[string]string{
headerName: name,
},
Percent: percent,
})
weights[name] = float64(percent)
total += percent
percent *= 2
// Cap the final non-zero bucket so that we total 100%
// After that, this will zero out remaining buckets.
if total+percent > 100 {
percent = 100 - total
}
}
// Create a simple Ingress over the 10 Services.
name := test.ObjectNameForTest(t)
_, client, _ := CreateIngressReady(ctx, t, clients, v1alpha1.IngressSpec{
Rules: []v1alpha1.IngressRule{{
Hosts: []string{name + "." + test.NetworkingFlags.ServiceDomain},
Visibility: v1alpha1.IngressVisibilityExternalIP,
HTTP: &v1alpha1.HTTPIngressRuleValue{
Paths: []v1alpha1.HTTPIngressPath{{
Splits: backends,
}},
},
}},
})
// Create a large enough population of requests that we can reasonably assess how
// well the Ingress respected the percentage split.
seen := make(map[string]float64, len(backends))
const (
// The total number of requests to make (as a float to avoid conversions in later computations).
totalRequests = 1000.0
// The increment to make for each request, so that the values of seen reflect the
// percentage of the total number of requests we are making.
increment = 100.0 / totalRequests
// Allow the Ingress to be within 10% of the configured value.
margin = 10.0
)
var g errgroup.Group
g.SetLimit(8)
resultCh := make(chan string, totalRequests)
for i := 0.0; i < totalRequests; i++ {
g.Go(func() error {
ri := RuntimeRequest(ctx, t, client, "http://"+name+"."+test.NetworkingFlags.ServiceDomain)
if ri == nil {
return errors.New("failed to request")
}
resultCh <- ri.Request.Headers.Get(headerName)
return nil
})
}
if err := g.Wait(); err != nil {
t.Error("Error while sending requests:", err)
}
close(resultCh)
for r := range resultCh {
seen[r] += increment
}
for name, want := range weights {
got := seen[name]
switch {
case want == 0.0 && got > 0.0:
// For 0% targets, we have tighter requirements.
t.Errorf("Target %q received traffic, wanted none (0%% target).", name)
case math.Abs(got-want) > margin:
t.Errorf("Target %q received %f%%, wanted %f +/- %f", name, got, want, margin)
}
}
}