This repository has been archived by the owner on Jun 18, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 231
/
router.go
350 lines (283 loc) · 9.91 KB
/
router.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
package v1
import (
"strconv"
"strings"
"github.com/knative/pkg/apis/istio/v1alpha3"
"github.com/rancher/mapper/convert"
"github.com/rancher/wrangler/pkg/genericcondition"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Router is a top level resource to create L7 routing to different services. It will create VirtualService, ServiceEntry and DestinationRules
type Router struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RouterSpec `json:"spec,omitempty"`
Status RouterStatus `json:"status,omitempty"`
}
type RouterSpec struct {
// An ordered list of route rules for HTTP traffic. The first rule matching an incoming request is used.
Routes []RouteSpec `json:"routes,omitempty"`
}
type RouterStatus struct {
// The list of publicedomains pointing to the router
PublicDomains []string `json:"publicDomains,omitempty"`
// The endpoint to access the router
Endpoints []string `json:"endpoints,omitempty"`
// Represents the latest available observations of a PublicDomain's current state.
Conditions []genericcondition.GenericCondition `json:"conditions,omitempty"`
}
type RouteSpec struct {
//Match conditions to be satisfied for the rule to be activated. All conditions inside a single match block have AND semantics, while the list of match blocks have OR semantics.
// The rule is matched if any one of the match blocks succeed.
Matches []Match `json:"matches,omitempty"`
// A http rule can either redirect or forward (default) traffic. The forwarding target can be one of several versions of a service (see glossary in beginning of document).
// Weights associated with the service version determine the proportion of traffic it receives.
To []WeightedDestination `json:"to,omitempty"`
// A http rule can either redirect or forward (default) traffic. If traffic passthrough option is specified in the rule, route/redirect will be ignored.
// The redirect primitive can be used to send a HTTP 301 redirect to a different URI or Authority.
Redirect *Redirect `json:"redirect,omitempty"`
// Rewrite HTTP URIs and Authority headers. Rewrite cannot be used with Redirect primitive. Rewrite will be performed before forwarding.
Rewrite *Rewrite `json:"rewrite,omitempty"`
//Header manipulation rules
Headers *v1alpha3.HeaderOperations `json:"headers,omitempty"`
RouteTraffic
}
type RouteTraffic struct {
// Fault injection policy to apply on HTTP traffic at the client side. Note that timeouts or retries will not be enabled when faults are enabled on the client side.
Fault *Fault `json:"fault,omitempty"`
// Mirror HTTP traffic to a another destination in addition to forwarding the requests to the intended destination.
// Mirrored traffic is on a best effort basis where the sidecar/gateway will not wait for the mirrored cluster to respond before returning the response from the original destination.
// Statistics will be generated for the mirrored destination.
Mirror *Destination `json:"mirror,omitempty"`
// Timeout for HTTP requests.
TimeoutMillis *int `json:"timeoutMillis,omitempty"`
// Retry policy for HTTP requests.
Retry *Retry `json:"retry,omitempty"`
}
type Retry struct {
// REQUIRED. Number of retries for a given request. The interval between retries will be determined automatically (25ms+).
// Actual number of retries attempted depends on the httpReqTimeout.
Attempts int `json:"attempts,omitempty"`
// Timeout per retry attempt for a given request. Units: milliseconds
TimeoutMillis int `json:"timeoutMillis,omitempty"`
}
type WeightedDestination struct {
Destination
// Weight for the Destination
Weight int `json:"weight,omitempty"`
}
type Destination struct {
// Destination Service
Service string `json:"service,omitempty"`
// Destination Namespace
Namespace string `json:"namespace,omitempty"`
// Destination Revision
Revision string `json:"revision,omitempty"`
// Destination Port
Port *uint32 `json:"port,omitempty"`
}
type ServiceSource struct {
Service string `json:"service,omitempty"`
Stack string `json:"stack,omitempty"`
Revision string `json:"revision,omitempty"`
}
func (s ServiceSource) String() string {
return Destination{
Namespace: s.Stack,
Service: s.Service,
Revision: s.Revision,
}.String()
}
func (d Destination) String() string {
result := strings.Builder{}
if d.Namespace != "" {
result.WriteString(d.Namespace)
result.WriteString("/")
}
result.WriteString(d.Service)
if d.Revision != "" && d.Revision != "latest" {
result.WriteString(":")
result.WriteString(d.Revision)
}
if d.Port != nil && *d.Port > 0 {
result.WriteString(",port=")
result.WriteString(strconv.FormatInt(int64(*d.Port), 10))
}
return result.String()
}
func (w WeightedDestination) String() string {
str := w.Destination.String()
if w.Weight <= 0 {
return str
}
return str + ",weight=" + strconv.FormatInt(int64(w.Weight), 10)
}
type Fault struct {
// Percentage of requests on which the delay will be injected.
Percentage int `json:"percentage,omitempty" norman:"min=0,max=100"`
// REQUIRED. Add a fixed delay before forwarding the request. Units: milliseconds
DelayMillis int `json:"delayMillis,omitempty"`
// Abort Http request attempts and return error codes back to downstream service, giving the impression that the upstream service is faulty.
Abort Abort `json:"abort,omitempty"`
}
type Abort struct {
// REQUIRED. HTTP status code to use to abort the Http request.
HTTPStatus int `json:"httpStatus,omitempty"`
}
type Match struct {
//URI to match values are case-sensitive and formatted as follows:
//
// exact: "value" for exact string match
//
// prefix: "value" for prefix-based match
//
// regex: "value" for ECMAscript style regex-based match
Path *StringMatch `json:"path,omitempty"`
// URI Scheme values are case-sensitive and formatted as follows:
//
// exact: "value" for exact string match
//
// prefix: "value" for prefix-based match
//
// regex: "value" for ECMAscript style regex-based match
Scheme *StringMatch `json:"scheme,omitempty"`
// HTTP Method values are case-sensitive and formatted as follows:
//
// exact: "value" for exact string match
//
// prefix: "value" for prefix-based match
//
// regex: "value" for ECMAscript style regex-based match
Method *StringMatch `json:"method,omitempty"`
// The header keys must be lowercase and use hyphen as the separator, e.g. x-request-id.
//
// Header values are case-sensitive and formatted as follows:
//
// exact: "value" for exact string match
//
// prefix: "value" for prefix-based match
//
// regex: "value" for ECMAscript style regex-based match
//
// Note: The keys uri, scheme, method, and authority will be ignored.
Headers map[string]StringMatch `json:"headers,omitempty"`
// Cookies must be lowercase and use hyphen as the separator, e.g. x-request-id.
//
// Header values are case-sensitive and formatted as follows:
//
// exact: "value" for exact string match
//
// prefix: "value" for prefix-based match
//
// regex: "value" for ECMAscript style regex-based match
//
// Note: The keys uri, scheme, method, and authority will be ignored.
Cookies map[string]StringMatch `json:"cookies,omitempty"`
// Specifies the ports on the host that is being addressed. Many services only expose a single port or label ports with the protocols they support, in these cases it is not required to explicitly select the port.
Port *int `json:"port,omitempty"`
From *ServiceSource `json:"from,omitempty"`
}
func (m Match) MaybeString() interface{} {
path := m.Path.String()
scheme := m.Scheme.String()
method := m.Scheme.String()
authority := m.Scheme.String()
from := m.From.String()
if containsComma(authority, from, method, path, scheme) ||
containsCommaInMaps(m.Cookies, m.Headers) {
v, _ := convert.EncodeToMap(m)
return v
}
prefixData := strings.Builder{}
addPrefixedMap(&prefixData, "cookie", m.Cookies)
addPrefixedMap(&prefixData, "header", m.Headers)
matchData := strings.Builder{}
if method != "" {
matchData.WriteString(method)
matchData.WriteString(" ")
}
if scheme != "" {
matchData.WriteString(scheme)
matchData.WriteString("://")
}
matchData.WriteString(authority)
if m.Port != nil && *m.Port != 0 {
matchData.WriteString(":")
matchData.WriteString(strconv.Itoa(*m.Port))
}
if len(path) > 0 && path[0] != '/' {
matchData.WriteString("/")
}
matchData.WriteString(path)
if matchData.Len() == 0 {
return prefixData.String()
} else if prefixData.Len() == 0 {
return matchData.String()
}
prefixData.WriteString(",")
prefixData.Write([]byte(matchData.String()))
return prefixData.String()
}
type Redirect struct {
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
}
type Rewrite struct {
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
}
type StringMatch struct {
Exact string `json:"exact,omitempty"`
Prefix string `json:"prefix,omitempty"`
Regexp string `json:"regexp,omitempty"`
}
func (s StringMatch) String() string {
switch {
case s.Exact != "":
return s.Exact
case s.Prefix != "":
return s.Prefix + "*"
case s.Regexp != "":
return "regex(" + s.Regexp + ")"
default:
return ""
}
}
func containsComma(strs ...string) bool {
for _, str := range strs {
if strings.ContainsRune(str, ',') {
return true
}
}
return false
}
func containsCommaInMaps(maps ...map[string]StringMatch) bool {
for _, m := range maps {
for k, v := range m {
if strings.ContainsRune(k, ',') {
return true
}
if strings.ContainsRune(v.String(), ',') {
return true
}
}
}
return false
}
func addPrefixedMap(buf *strings.Builder, prefix string, data map[string]StringMatch) {
for k, v := range data {
if buf.Len() > 0 {
buf.WriteString(",")
}
buf.WriteString(prefix)
buf.WriteString("=")
buf.WriteString(k)
str := v.String()
if str != "" {
buf.WriteString("=")
buf.WriteString(str)
}
}
}