Skip to content

Commit

Permalink
Gateway API: support request timeout (#5997)
Browse files Browse the repository at this point in the history
Adds support for HTTPRoute request timeouts.

Closes #5921.

Signed-off-by: gang.liu <gang.liu@daocloud.io>
Signed-off-by: Steve Kriss <stephen.kriss@gmail.com>
Co-authored-by: gang.liu <gang.liu@daocloud.io>
  • Loading branch information
skriss and izturn authored Dec 6, 2023
1 parent 9d71639 commit f42a569
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 66 deletions.
3 changes: 3 additions & 0 deletions changelogs/unreleased/5997-izturn-minor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Gateway API: support HTTPRoute request timeouts

Contour now enables end-users to specify request timeouts by setting the [HTTPRouteRule.Timeouts.Request](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteTimeouts) parameter. Note that `BackendRequest` is not yet implemented because without Gateway API support for retries, it's functionally equivalent to `Request`.
89 changes: 88 additions & 1 deletion internal/dag/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4158,7 +4158,59 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
),
},
// END

"HTTPRoute rule with request timeout": {
gatewayclass: validClass,
gateway: gatewayHTTPAllNamespaces,
objs: []any{
kuardService,
makeHTTPRoute("5s", ""),
},
want: listeners(
&Listener{
Name: "http-80",
VirtualHosts: virtualhosts(
virtualhost("test.projectcontour.io",
&Route{
PathMatchCondition: prefixString("/"),
Clusters: clustersWeight(service(kuardService)),
TimeoutPolicy: RouteTimeoutPolicy{
ResponseTimeout: timeout.DurationSetting(5 * time.Second),
},
},
),
),
},
),
},
"HTTPRoute rule with request and backendRequest timeout": {
gatewayclass: validClass,
gateway: gatewayHTTPAllNamespaces,
objs: []any{
kuardService,
makeHTTPRoute("5s", "5s"),
},
want: listeners(),
},

"HTTPRoute rule with backendRequest timeout only": {
gatewayclass: validClass,
gateway: gatewayHTTPAllNamespaces,
objs: []any{
kuardService,
makeHTTPRoute("", "5s"),
},
want: listeners(),
},
"HTTPRoute rule with invalid request timeout": {
gatewayclass: validClass,
gateway: gatewayHTTPAllNamespaces,
objs: []any{
kuardService,
makeHTTPRoute("invalid", ""),
},
want: listeners(),
},

"different weights for multiple forwardTos": {
gatewayclass: validClass,
Expand Down Expand Up @@ -16273,3 +16325,38 @@ func withMirror(r *Route, mirrors []*Service, weight int64) *Route {
}
return r
}

func makeHTTPRouteTimeouts(request, backendRequest string) *gatewayapi_v1.HTTPRouteTimeouts {
httpRouteTimeouts := &gatewayapi_v1.HTTPRouteTimeouts{}

if request != "" {
httpRouteTimeouts.Request = ref.To(gatewayapi_v1.Duration(request))
}
if backendRequest != "" {
httpRouteTimeouts.BackendRequest = ref.To(gatewayapi_v1.Duration(backendRequest))
}

return httpRouteTimeouts
}

func makeHTTPRoute(request, backendRequest string) *gatewayapi_v1beta1.HTTPRoute {
return &gatewayapi_v1beta1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1beta1.HTTPRouteSpec{
CommonRouteSpec: gatewayapi_v1beta1.CommonRouteSpec{
ParentRefs: []gatewayapi_v1beta1.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")},
},
Hostnames: []gatewayapi_v1beta1.Hostname{
"test.projectcontour.io",
},
Rules: []gatewayapi_v1beta1.HTTPRouteRule{{
Matches: gatewayapi.HTTPRouteMatch(gatewayapi_v1.PathMatchPathPrefix, "/"),
BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1),
Timeouts: makeHTTPRouteTimeouts(request, backendRequest),
}},
},
}
}
73 changes: 57 additions & 16 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package dag

import (
"errors"
"fmt"
"net/http"
"regexp"
Expand All @@ -24,6 +25,7 @@ import (
"github.com/projectcontour/contour/internal/k8s"
"github.com/projectcontour/contour/internal/ref"
"github.com/projectcontour/contour/internal/status"
"github.com/projectcontour/contour/internal/timeout"

"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -1103,6 +1105,32 @@ func (p *GatewayAPIProcessor) resolveRouteRefs(route any, routeAccessor *status.
}
}

func parseHTTPRouteTimeouts(httpRouteTimeouts *gatewayapi_v1.HTTPRouteTimeouts) (*RouteTimeoutPolicy, error) {
if httpRouteTimeouts == nil {
return nil, nil
}

// Since Gateway API doesn't yet support retries, this timeout setting
// is functionally equivalent to httpRouteTimeouts.Request, so we're
// not implementing it for now. Once retries are added to Gateway API,
// support for backend request timeouts can be added.
if httpRouteTimeouts.BackendRequest != nil {
return nil, errors.New("HTTPRoute.Spec.Rules.Timeouts.BackendRequest is not supported, use HTTPRoute.Spec.Rules.Timeouts.Request instead")
}
if httpRouteTimeouts.Request == nil {
return nil, nil
}

requestTimeout, err := timeout.Parse(string(*httpRouteTimeouts.Request))
if err != nil {
return nil, fmt.Errorf("invalid HTTPRoute.Spec.Rules.Timeouts.Request: %v", err)
}

return &RouteTimeoutPolicy{
ResponseTimeout: requestTimeout,
}, nil
}

func (p *GatewayAPIProcessor) computeHTTPRouteForListener(route *gatewayapi_v1beta1.HTTPRoute, routeAccessor *status.RouteParentStatusUpdate, listener *listenerInfo, hosts sets.Set[string]) bool {
var programmed bool
for ruleIndex, rule := range route.Spec.Rules {
Expand Down Expand Up @@ -1145,14 +1173,22 @@ func (p *GatewayAPIProcessor) computeHTTPRouteForListener(route *gatewayapi_v1be

// Process rule-level filters.
var (
requestHeaderPolicy *HeadersPolicy
responseHeaderPolicy *HeadersPolicy
err error
redirect *Redirect
urlRewriteHostname string
mirrorPolicies []*MirrorPolicy
requestHeaderPolicy *HeadersPolicy
responseHeaderPolicy *HeadersPolicy
pathRewritePolicy *PathRewritePolicy
urlRewriteHostname string
timeoutPolicy *RouteTimeoutPolicy
)

timeoutPolicy, err = parseHTTPRouteTimeouts(rule.Timeouts)
if err != nil {
routeAccessor.AddCondition(gatewayapi_v1beta1.RouteConditionAccepted, metav1.ConditionFalse, gatewayapi_v1beta1.RouteReasonUnsupportedValue, err.Error())
continue
}

// Per Gateway API docs: "Specifying the same filter multiple times is
// not supported unless explicitly indicated in the filter." For filters
// that can't be used multiple times within the same rule, Contour
Expand Down Expand Up @@ -1360,17 +1396,17 @@ func (p *GatewayAPIProcessor) computeHTTPRouteForListener(route *gatewayapi_v1be
}
routes = p.clusterRoutes(
matchconditions,
requestHeaderPolicy,
responseHeaderPolicy,
mirrorPolicies,
clusters,
totalWeight,
priority,
pathRewritePolicy,
KindHTTPRoute,
route.Namespace,
route.Name,
)
requestHeaderPolicy,
responseHeaderPolicy,
mirrorPolicies,
pathRewritePolicy,
timeoutPolicy)
}

// Add each route to the relevant vhost(s)/svhosts(s).
Expand Down Expand Up @@ -1505,16 +1541,17 @@ func (p *GatewayAPIProcessor) computeGRPCRouteForListener(route *gatewayapi_v1al
}
routes = p.clusterRoutes(
matchconditions,
requestHeaderPolicy,
responseHeaderPolicy,
mirrorPolicies,
clusters,
totalWeight,
priority,
nil,
KindGRPCRoute,
route.Namespace,
route.Name,
requestHeaderPolicy,
responseHeaderPolicy,
mirrorPolicies,
nil,
nil,
)

// Add each route to the relevant vhost(s)/svhosts(s).
Expand Down Expand Up @@ -2087,16 +2124,17 @@ func (p *GatewayAPIProcessor) grpcClusters(routeNamespace string, backendRefs []
// clusterRoutes builds a []*dag.Route for the supplied set of matchConditions, headerPolicies and backendRefs.
func (p *GatewayAPIProcessor) clusterRoutes(
matchConditions []*matchConditions,
requestHeaderPolicy *HeadersPolicy,
responseHeaderPolicy *HeadersPolicy,
mirrorPolicies []*MirrorPolicy,
clusters []*Cluster,
totalWeight uint32,
priority uint8,
pathRewritePolicy *PathRewritePolicy,
kind string,
namespace string,
name string,
requestHeaderPolicy *HeadersPolicy,
responseHeaderPolicy *HeadersPolicy,
mirrorPolicies []*MirrorPolicy,
pathRewritePolicy *PathRewritePolicy,
timeoutPolicy *RouteTimeoutPolicy,
) []*Route {

var routes []*Route
Expand All @@ -2121,6 +2159,9 @@ func (p *GatewayAPIProcessor) clusterRoutes(
Priority: priority,
PathRewritePolicy: pathRewritePolicy,
}
if timeoutPolicy != nil {
route.TimeoutPolicy = *timeoutPolicy
}

if p.SetSourceMetadataOnRoutes {
route.Kind = kind
Expand Down
Loading

0 comments on commit f42a569

Please sign in to comment.