Skip to content

Commit 2a3078b

Browse files
committed
POC HTTPRoute Timeouts
* update HTTPRoute interfaces according to latest proposal in GEP-1742 * include Request and BackendRequest in HTTPRouteTimeouts * add validation for HTTPRouteTimeouts * add unit tests in httproute_test.go * relates to kubernetes-sigs#1997
1 parent b692c69 commit 2a3078b

File tree

5 files changed

+249
-0
lines changed

5 files changed

+249
-0
lines changed

apis/v1beta1/httproute_types.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,56 @@ type HTTPRouteRule struct {
244244
// +optional
245245
// +kubebuilder:validation:MaxItems=16
246246
BackendRefs []HTTPBackendRef `json:"backendRefs,omitempty"`
247+
248+
// Timeouts defines the timeouts that can be configured for an HTTP request.
249+
//
250+
// Support: Extended
251+
//
252+
// +optional
253+
// <gateway:experimental>
254+
Timeouts *HTTPRouteTimeouts `json:"timeouts,omitempty"`
255+
}
256+
257+
// HTTPRouteTimeouts defines timeouts that can be configured for an HTTPRoute.
258+
// Timeout values are formatted like 1h/1m/1s/1ms as parsed by Golang time.ParseDuration
259+
// and MUST BE >= 1ms or 0 to disable (no timeout).
260+
type HTTPRouteTimeouts struct {
261+
// Request specifies the duration for processing an HTTP client request after which the
262+
// gateway will time out if unable to send a response.
263+
//
264+
// For example, setting the `rules.timeouts.request` field to the value `10s` in an
265+
// `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds
266+
// to complete.
267+
//
268+
// This timeout is intended to cover as close to the whole request-response transaction
269+
// as possible although an implementation MAY choose to start the timeout after the entire
270+
// request stream has been received instead of immediately after the transaction is
271+
// initiated by the client.
272+
//
273+
// When this field is unspecified, request timeout behavior is implementation-dependent.
274+
//
275+
// Support: Extended
276+
//
277+
// +optional
278+
// +kubebuilder:validation:Format=duration
279+
Request *metav1.Duration `json:"request,omitempty"`
280+
281+
// BackendRequest specifies a timeout for an individual request from the gateway
282+
// to a backend service. This covers the time from when the request first starts being
283+
// sent from the gateway to when the full response has been received from the backend.
284+
//
285+
// An entire client HTTP transaction with a gateway, covered by the Request timeout,
286+
// may result in more than one call from the gateway to the destination backend service,
287+
// for example, if automatic retries are supported.
288+
//
289+
// Because the Request timeout encompasses the BackendRequest timeout,
290+
// the value of BackendRequest defaults to and must be <= the value of Request timeout.
291+
//
292+
// Support: Extended
293+
//
294+
// +optional
295+
// +kubebuilder:validation:Format=duration
296+
BackendRequest *metav1.Duration `json:"backendRequest,omitempty"`
247297
}
248298

249299
// PathMatchType specifies the semantics of how HTTP paths should be compared.

apis/v1beta1/validation/httproute.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"net/http"
2222
"regexp"
2323
"strings"
24+
"time"
2425

2526
"k8s.io/apimachinery/pkg/util/validation/field"
2627

@@ -40,6 +41,9 @@ var (
4041

4142
// All valid path characters per RFC-3986
4243
validPathCharacters = "^(?:[A-Za-z0-9\\/\\-._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$"
44+
45+
// Minimum value of timeout in milliseconds
46+
timeoutMinValue = time.Duration(time.Millisecond * 1)
4347
)
4448

4549
// ValidateHTTPRoute validates HTTPRoute according to the Gateway API specification.
@@ -71,6 +75,10 @@ func ValidateHTTPRouteSpec(spec *gatewayv1b1.HTTPRouteSpec, path *field.Path) fi
7175
errs = append(errs, validateHTTPQueryParamMatches(m.QueryParams, matchPath.Child("queryParams"))...)
7276
}
7377
}
78+
79+
if rule.Timeouts != nil {
80+
errs = append(errs, validateHTTPRouteTimeouts(rule.Timeouts, path.Child("rules").Child("timeouts"))...)
81+
}
7482
}
7583
errs = append(errs, validateHTTPRouteBackendServicePorts(spec.Rules, path.Child("rules"))...)
7684
errs = append(errs, ValidateParentRefs(spec.ParentRefs, path.Child("spec"))...)
@@ -334,6 +342,31 @@ func validateHTTPHeaderModifier(filter gatewayv1b1.HTTPHeaderFilter, path *field
334342
return errs
335343
}
336344

345+
func validateHTTPRouteTimeouts(timeouts *gatewayv1b1.HTTPRouteTimeouts, path *field.Path) field.ErrorList {
346+
var errs field.ErrorList
347+
errorString := fmt.Sprintf("timeout value must be greater than or equal to %d ms", timeoutMinValue)
348+
if timeouts.BackendRequest != nil {
349+
backendTimeout := timeouts.BackendRequest.Duration
350+
if backendTimeout < timeoutMinValue && backendTimeout != 0 {
351+
errs = append(errs, field.Invalid(path.Child("backendRequest"), timeouts.BackendRequest.Duration, errorString))
352+
}
353+
if timeouts.Request != nil {
354+
timeout := timeouts.Request.Duration
355+
if backendTimeout > timeout && timeout != 0 {
356+
errs = append(errs, field.Invalid(path.Child("backendRequest"), timeouts.BackendRequest.Duration, "backendRequest timeout cannot be longer than request timeout"))
357+
}
358+
}
359+
}
360+
if timeouts.Request != nil {
361+
timeout := timeouts.Request.Duration
362+
if timeout < timeoutMinValue && timeout != 0 {
363+
errs = append(errs, field.Invalid(path.Child("request"), timeouts.Request.Duration, errorString))
364+
}
365+
}
366+
367+
return errs
368+
}
369+
337370
func hasExactlyOnePrefixMatch(matches []gatewayv1b1.HTTPRouteMatch) bool {
338371
if len(matches) != 1 || matches[0].Path == nil {
339372
return false

apis/v1beta1/validation/httproute_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ package validation
1818

1919
import (
2020
"testing"
21+
"time"
2122

2223
"github.com/stretchr/testify/assert"
2324
"github.com/stretchr/testify/require"
2425
"k8s.io/apimachinery/pkg/util/validation/field"
2526

27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628
gatewayv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
2729
)
2830

@@ -1140,3 +1142,65 @@ func TestValidateHTTPRouteTypeMatchesField(t *testing.T) {
11401142
})
11411143
}
11421144
}
1145+
1146+
func TestValidateHTTPTimeouts(t *testing.T) {
1147+
tests := []struct {
1148+
name string
1149+
rules []gatewayv1b1.HTTPRouteRule
1150+
errCount int
1151+
}{{
1152+
name: "valid httpRoute Rules timeouts",
1153+
errCount: 0,
1154+
rules: []gatewayv1b1.HTTPRouteRule{{
1155+
Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1156+
Request: &metav1.Duration{
1157+
Duration: time.Duration(time.Millisecond * 1),
1158+
},
1159+
},
1160+
},
1161+
}}, {
1162+
name: "valid httpRoute Rules timeout set to 0 (disabled)",
1163+
errCount: 0,
1164+
rules: []gatewayv1b1.HTTPRouteRule{{
1165+
Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1166+
Request: &metav1.Duration{
1167+
Duration: time.Duration(0),
1168+
},
1169+
},
1170+
},
1171+
}}, {
1172+
name: "invalid httpRoute Rules timeout",
1173+
errCount: 1,
1174+
rules: []gatewayv1b1.HTTPRouteRule{{
1175+
Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1176+
Request: &metav1.Duration{
1177+
Duration: time.Duration(time.Microsecond * 500),
1178+
},
1179+
},
1180+
},
1181+
}}, {
1182+
name: "invalid httpRoute Rules backendRequest timeout cannot be longer than request timeout",
1183+
errCount: 1,
1184+
rules: []gatewayv1b1.HTTPRouteRule{{
1185+
Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1186+
Request: &metav1.Duration{
1187+
Duration: time.Duration(time.Millisecond * 1),
1188+
},
1189+
BackendRequest: &metav1.Duration{
1190+
Duration: time.Duration(time.Millisecond * 2),
1191+
},
1192+
},
1193+
},
1194+
}},
1195+
}
1196+
1197+
for _, tc := range tests {
1198+
t.Run(tc.name, func(t *testing.T) {
1199+
route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{Rules: tc.rules}}
1200+
errs := ValidateHTTPRoute(&route)
1201+
if len(errs) != tc.errCount {
1202+
t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
1203+
}
1204+
})
1205+
}
1206+
}

apis/v1beta1/zz_generated.deepcopy.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)