/
traffic.go
141 lines (112 loc) · 3.64 KB
/
traffic.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
134
135
136
137
138
139
140
141
/*
Package traffic implements a predicate to control the matching
probability for a given route by setting its weight.
The probability for matching a route is defined by the mandatory first
parameter, that must be a decimal number between 0.0 and 1.0 (both
inclusive).
The optional second argument is used to specify the cookie name for
the traffic group, in case you want to use stickiness. Stickiness
allows all subsequent requests from the same client to match the same
route. Stickiness of traffic is supported by the optional third
parameter, indicating whether the request being matched belongs to the
traffic group of the current route. If yes, the predicate matches
ignoring the chance argument.
You always have to specify one argument, if you do not need stickiness,
and three arguments, if your service requires stickiness.
Predicates cannot modify the response, so the responsibility of
setting the traffic group cookie remains to either a filter or the
backend service.
The below example, shows a possible eskip document used for green-blue
deployments of APIS, which usually don't require stickiness:
// hit by 10% percent chance
v2:
Traffic(.1) ->
"https://api-test-green";
// hit by remaining chance
v1:
"https://api-test-blue";
The below example, shows a possible eskip document with two,
independent traffic controlled route sets, which uses session stickiness:
// hit by 5% percent chance
cartTest:
Traffic(.05, "cart-test", "test") && Path("/cart") ->
responseCookie("cart-test", "test") ->
"https://cart-test";
// hit by remaining chance
cart:
Path("/cart") ->
responseCookie("cart-test", "default") ->
"https://cart";
// hit by 15% percent chance
catalogTestA:
Traffic(.15, "catalog-test", "A") ->
responseCookie("catalog-test", "A") ->
"https://catalog-test-a";
// hit by 30% percent chance
catalogTestB:
Traffic(.3, "catalog-test", "B") ->
responseCookie("catalog-test", "B") ->
"https://catalog-test-b";
// hit by remaining chance
catalog:
* ->
responseCookie("catalog-test", "default") ->
"https://catalog";
*/
package traffic
import (
"math/rand"
"net/http"
"github.com/zalando/skipper/predicates"
"github.com/zalando/skipper/routing"
)
const (
// The eskip name of the predicate.
PredicateName = "Traffic"
)
type spec struct{}
type predicate struct {
chance float64
trafficGroup string
trafficGroupCookie string
}
// Creates a new traffic control predicate specification.
func New() routing.PredicateSpec { return &spec{} }
func (s *spec) Name() string { return PredicateName }
func (s *spec) Create(args []interface{}) (routing.Predicate, error) {
if !(len(args) == 1 || len(args) == 3) {
return nil, predicates.ErrInvalidPredicateParameters
}
p := &predicate{}
if c, ok := args[0].(float64); ok && 0.0 <= c && c <= 1.0 {
p.chance = c
} else {
return nil, predicates.ErrInvalidPredicateParameters
}
if len(args) == 3 {
if tgc, ok := args[1].(string); ok {
p.trafficGroupCookie = tgc
} else {
return nil, predicates.ErrInvalidPredicateParameters
}
if tg, ok := args[2].(string); ok {
p.trafficGroup = tg
} else {
return nil, predicates.ErrInvalidPredicateParameters
}
}
return p, nil
}
func (p *predicate) takeChance() bool {
return rand.Float64() < p.chance // #nosec
}
func (p *predicate) Match(r *http.Request) bool {
if p.trafficGroup == "" {
return p.takeChance()
}
if c, err := r.Cookie(p.trafficGroupCookie); err == nil {
return c.Value == p.trafficGroup
} else {
return p.takeChance()
}
}