Skip to content

Commit e65cea9

Browse files
committed
Add fail-timeout and max-fails support to vs/vsr
1 parent 37a6700 commit e65cea9

10 files changed

+178
-62
lines changed

docs/virtualserver-and-virtualserverroute.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ The upstream defines a destination for the routing configuration. For example:
176176
name: tea
177177
service: tea-svc
178178
port: 80
179-
lb-method: "round_robin"
179+
lb-method: round_robin
180+
fail-timeout: 10s
181+
max-fails: 1
180182
```
181183

182184
| Field | Description | Type | Required |
@@ -185,7 +187,8 @@ lb-method: "round_robin"
185187
| `service` | The name of a [service](https://kubernetes.io/docs/concepts/services-networking/service/). The service must belong to the same namespace as the resource. If the service doesn't exist, NGINX will assume the service has zero endpoints and return a `502` response for requests for this upstream. | `string` | Yes |
186188
| `port` | The port of the service. If the service doesn't define that port, NGINX will assume the service has zero endpoints and return a `502` response for requests for this upstream. The port must fall into the range `1..65553`. | `uint16` | Yes |
187189
| `lb-method` | The load [balancing method](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#choosing-a-load-balancing-method). To use the round-robin method, specify `round_robin`. The default is specified in the `lb-method` ConfigMap key. | `string` | No |
188-
190+
| `fail-timeout` | The time during which the specified number of unsuccessful attempts to communicate with an upstream server should happen to consider the server unavailable. See the [fail_timeout](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#fail_timeout) parameter of the server directive. The default is set in the `fail-timeout` ConfigMap key. | `string` | No |
191+
| `max-fails` | The number of unsuccessful attempts to communicate with an upstream server that should happen in the duration set by the `fail-timeout` to consider the server unavailable. See the [max_fails](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#max_fails) parameter of the server directive. The default is set in the `max-fails` ConfgMap key. | `int` | No |
189192

190193
### Split
191194

internal/configs/annotations.go

+1-29
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package configs
22

33
import (
4-
"errors"
54
"fmt"
6-
"regexp"
75
"strconv"
86
"strings"
97

@@ -102,7 +100,7 @@ func parseAnnotations(ingEx *IngressEx, baseCfgParams *ConfigParams, isPlus bool
102100
}
103101

104102
if slowStart, exists := ingEx.Ingress.Annotations["nginx.com/slow-start"]; exists {
105-
if parsedSlowStart, err := parseSlowStart(slowStart); err != nil {
103+
if parsedSlowStart, err := ParseTime(slowStart); err != nil {
106104
glog.Errorf("Ingress %s/%s: Invalid value nginx.org/slow-start: got %q: %v", ingEx.Ingress.GetNamespace(), ingEx.Ingress.GetName(), slowStart, err)
107105
} else {
108106
if isPlus {
@@ -426,31 +424,6 @@ func mergeMasterAnnotationsIntoMinion(minionAnnotations map[string]string, maste
426424
}
427425
}
428426

429-
// http://nginx.org/en/docs/syntax.html
430-
var validTimeSuffixes = []string{
431-
"ms",
432-
"s",
433-
"m",
434-
"h",
435-
"d",
436-
"w",
437-
"M",
438-
"y",
439-
}
440-
441-
var durationEscaped = strings.Join(validTimeSuffixes, "|")
442-
var validNginxTime = regexp.MustCompile(`^([0-9]+([` + durationEscaped + `]?){0,1} *)+$`)
443-
444-
// parseSlowStart ensures that the slow_start value in the annotation is valid.
445-
func parseSlowStart(s string) (string, error) {
446-
s = strings.TrimSpace(s)
447-
448-
if validNginxTime.MatchString(s) {
449-
return s, nil
450-
}
451-
return "", errors.New("Invalid time string")
452-
}
453-
454427
func parsePort(value string) (int, error) {
455428
port, err := strconv.ParseInt(value, 10, 16)
456429
if err != nil {
@@ -504,4 +477,3 @@ func parseRewrites(service string) (serviceName string, rewrite string, err erro
504477

505478
return svcNameParts[1], rwPathParts[1], nil
506479
}
507-

internal/configs/annotations_test.go

-20
Original file line numberDiff line numberDiff line change
@@ -151,23 +151,3 @@ func TestMergeMasterAnnotationsIntoMinion(t *testing.T) {
151151
t.Errorf("mergeMasterAnnotationsIntoMinion returned %v, but expected %v", minionAnnotations, expectedMergedAnnotations)
152152
}
153153
}
154-
155-
func TestParseSlowStart(t *testing.T) {
156-
var testsWithValidInput = []string{"1", "1m10s", "11 11", "5m 30s", "1s", "100m", "5w", "15m", "11M", "3h", "100y", "600"}
157-
var invalidInput = []string{"ss", "rM", "m0m", "s1s", "-5s", "", "1L"}
158-
for _, test := range testsWithValidInput {
159-
result, err := parseSlowStart(test)
160-
if err != nil {
161-
t.Errorf("TestParseSlowStart(%q) returned an error for valid input", test)
162-
}
163-
if test != result {
164-
t.Errorf("TestParseSlowStart(%q) returned %q expected %q", test, result, test)
165-
}
166-
}
167-
for _, test := range invalidInput {
168-
result, err := parseSlowStart(test)
169-
if err == nil {
170-
t.Errorf("TestParseSlowStart(%q) didn't return error. Returned: %q", test, result)
171-
}
172-
}
173-
}

internal/configs/parsing_helpers.go

+27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package configs
22

33
import (
4+
"errors"
45
"fmt"
6+
"regexp"
57
"strconv"
68
"strings"
79

@@ -159,3 +161,28 @@ func validateHashLBMethod(method string) (string, error) {
159161

160162
return "", fmt.Errorf("Invalid load balancing method: %q", method)
161163
}
164+
165+
// http://nginx.org/en/docs/syntax.html
166+
var validTimeSuffixes = []string{
167+
"ms",
168+
"s",
169+
"m",
170+
"h",
171+
"d",
172+
"w",
173+
"M",
174+
"y",
175+
}
176+
177+
var durationEscaped = strings.Join(validTimeSuffixes, "|")
178+
var validNginxTime = regexp.MustCompile(`^([0-9]+([` + durationEscaped + `]?){0,1} *)+$`)
179+
180+
// ParseTime ensures that the string value in the annotation is a valid time.
181+
func ParseTime(s string) (string, error) {
182+
s = strings.TrimSpace(s)
183+
184+
if validNginxTime.MatchString(s) {
185+
return s, nil
186+
}
187+
return "", errors.New("Invalid time string")
188+
}

internal/configs/parsing_helpers_test.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"reflect"
55
"testing"
66

7-
"k8s.io/api/core/v1"
7+
v1 "k8s.io/api/core/v1"
88
"k8s.io/api/extensions/v1beta1"
99
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1010
)
@@ -362,3 +362,23 @@ func TestParseLBMethodForPlus(t *testing.T) {
362362
}
363363
}
364364
}
365+
366+
func TestParseTime(t *testing.T) {
367+
var testsWithValidInput = []string{"1", "1m10s", "11 11", "5m 30s", "1s", "100m", "5w", "15m", "11M", "3h", "100y", "600"}
368+
var invalidInput = []string{"ss", "rM", "m0m", "s1s", "-5s", "", "1L"}
369+
for _, test := range testsWithValidInput {
370+
result, err := ParseTime(test)
371+
if err != nil {
372+
t.Errorf("TestparseTime(%q) returned an error for valid input", test)
373+
}
374+
if test != result {
375+
t.Errorf("TestparseTime(%q) returned %q expected %q", test, result, test)
376+
}
377+
}
378+
for _, test := range invalidInput {
379+
result, err := ParseTime(test)
380+
if err == nil {
381+
t.Errorf("TestparseTime(%q) didn't return error. Returned: %q", test, result)
382+
}
383+
}
384+
}

internal/configs/virtualserver.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -201,17 +201,17 @@ func generateUpstream(upstreamName string, upstream conf_v1alpha1.Upstream, endp
201201
for _, e := range endpoints {
202202
s := version2.UpstreamServer{
203203
Address: e,
204-
MaxFails: cfgParams.MaxFails,
205-
FailTimeout: cfgParams.FailTimeout,
204+
MaxFails: generatePositiveIntFromPointer(upstream.MaxFails, cfgParams.MaxFails),
205+
FailTimeout: generateTime(upstream.FailTimeout, cfgParams.FailTimeout),
206206
}
207207
upsServers = append(upsServers, s)
208208
}
209209

210210
if !isPlus && len(upsServers) == 0 {
211211
s := version2.UpstreamServer{
212212
Address: nginx502Server,
213-
MaxFails: cfgParams.MaxFails,
214-
FailTimeout: cfgParams.FailTimeout,
213+
MaxFails: generatePositiveIntFromPointer(upstream.MaxFails, cfgParams.MaxFails),
214+
FailTimeout: generateTime(upstream.FailTimeout, cfgParams.FailTimeout),
215215
}
216216
upsServers = append(upsServers, s)
217217
}
@@ -235,6 +235,20 @@ func generateLBMethod(method string, defaultMethod string) string {
235235
return method
236236
}
237237

238+
func generateTime(time string, defaultTime string) string {
239+
if time == "" {
240+
return defaultTime
241+
}
242+
return time
243+
}
244+
245+
func generatePositiveIntFromPointer(n *int, defaultN int) int {
246+
if n == nil {
247+
return defaultN
248+
}
249+
return *n
250+
}
251+
238252
func generateLocation(path string, upstreamName string, cfgParams *ConfigParams) version2.Location {
239253
loc := version2.Location{
240254
Path: path,

pkg/apis/configuration/v1alpha1/types.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ type VirtualServerSpec struct {
2525

2626
// Upstream defines an upstream.
2727
type Upstream struct {
28-
Name string `json:"name"`
29-
Service string `json:"service"`
30-
Port uint16 `json:"port"`
31-
LBMethod string `json:"lb-method"`
28+
Name string `json:"name"`
29+
Service string `json:"service"`
30+
Port uint16 `json:"port"`
31+
LBMethod string `json:"lb-method"`
32+
FailTimeout string `json:"fail-timeout"`
33+
MaxFails *int `json:"max-fails"`
3234
}
3335

3436
// Route defines a route.

pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go

+11-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/configuration/validation/validation.go

+29
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,33 @@ func validateTLS(tls *v1alpha1.TLS, fieldPath *field.Path) field.ErrorList {
5757
return validateSecretName(tls.Secret, fieldPath.Child("secret"))
5858
}
5959

60+
func validatePositiveIntOrZero(n *int, fieldPath *field.Path) field.ErrorList {
61+
allErrs := field.ErrorList{}
62+
if n == nil {
63+
return allErrs
64+
}
65+
66+
if *n < 0 {
67+
return append(allErrs, field.Invalid(fieldPath, n, "must be positive or zero"))
68+
}
69+
70+
return allErrs
71+
}
72+
73+
func validateTime(time string, fieldPath *field.Path) field.ErrorList {
74+
allErrs := field.ErrorList{}
75+
76+
if time == "" {
77+
return allErrs
78+
}
79+
80+
if _, err := configs.ParseTime(time); err != nil {
81+
return append(allErrs, field.Invalid(fieldPath, time, err.Error()))
82+
}
83+
84+
return allErrs
85+
}
86+
6087
func validateUpstreamLBMethod(lBMethod string, fieldPath *field.Path, isPlus bool) field.ErrorList {
6188
allErrs := field.ErrorList{}
6289
if lBMethod == "" {
@@ -112,6 +139,8 @@ func validateUpstreams(upstreams []v1alpha1.Upstream, fieldPath *field.Path, isP
112139

113140
allErrs = append(allErrs, validateServiceName(u.Service, idxPath.Child("service"))...)
114141
allErrs = append(allErrs, validateUpstreamLBMethod(u.LBMethod, idxPath.Child("lb-method"), isPlus)...)
142+
allErrs = append(allErrs, validateTime(u.FailTimeout, idxPath.Child("fail-timeout"))...)
143+
allErrs = append(allErrs, validatePositiveIntOrZero(u.MaxFails, idxPath.Child("max-fails"))...)
115144

116145
for _, msg := range validation.IsValidPortNum(int(u.Port)) {
117146
allErrs = append(allErrs, field.Invalid(idxPath.Child("port"), u.Port, msg))

pkg/apis/configuration/validation/validation_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -1437,3 +1437,63 @@ func TestValidateUpstreamLBMethodFails(t *testing.T) {
14371437
}
14381438
}
14391439
}
1440+
1441+
func createPointerFromInt(n int) *int {
1442+
return &n
1443+
}
1444+
1445+
func TestValidatePositiveIntOrZero(t *testing.T) {
1446+
tests := []struct {
1447+
number *int
1448+
msg string
1449+
}{
1450+
{
1451+
number: nil,
1452+
msg: "valid (nil)",
1453+
},
1454+
{
1455+
number: createPointerFromInt(0),
1456+
msg: "valid (0)",
1457+
},
1458+
{
1459+
number: createPointerFromInt(1),
1460+
msg: "valid (1)",
1461+
},
1462+
}
1463+
1464+
for _, test := range tests {
1465+
allErrs := validatePositiveIntOrZero(test.number, field.NewPath("int-field"))
1466+
1467+
if len(allErrs) != 0 {
1468+
t.Errorf("validatePositiveInt returned errors for case: %v", test.msg)
1469+
}
1470+
}
1471+
}
1472+
1473+
func TestValidatePositiveIntOrZeroFails(t *testing.T) {
1474+
number := createPointerFromInt(-1)
1475+
allErrs := validatePositiveIntOrZero(number, field.NewPath("int-field"))
1476+
1477+
if len(allErrs) == 0 {
1478+
t.Error("validatePositiveInt returned no errors for case: invalid (-1)")
1479+
}
1480+
1481+
}
1482+
1483+
func TestValidateTime(t *testing.T) {
1484+
time := "1h 2s"
1485+
allErrs := validateTime(time, field.NewPath("time-field"))
1486+
1487+
if len(allErrs) != 0 {
1488+
t.Errorf("validateTime returned errors %v valid input %v", allErrs, time)
1489+
}
1490+
}
1491+
1492+
func TestValidateTimeFails(t *testing.T) {
1493+
time := "invalid"
1494+
allErrs := validateTime(time, field.NewPath("time-field"))
1495+
1496+
if len(allErrs) == 0 {
1497+
t.Errorf("validateTime returned no errors for invalid input %v", time)
1498+
}
1499+
}

0 commit comments

Comments
 (0)