Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose HTTP timeout on AIServiceBackend #384

Merged
merged 7 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/v1alpha1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ type AIGatewayRouteRule struct {
// +optional
// +kubebuilder:validation:MaxItems=128
Matches []AIGatewayRouteRuleMatch `json:"matches,omitempty"`

// Timeouts defines the timeouts that can be configured for an HTTP request.
//
// +optional
Timeouts *gwapiv1.HTTPRouteTimeouts `json:"timeouts,omitempty"`
}

// AIGatewayRouteRuleBackendRef is a reference to a AIServiceBackend with a weight.
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 70 additions & 5 deletions internal/controller/ai_gateway_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"fmt"
"path"
"time"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/go-logr/logr"
Expand Down Expand Up @@ -44,6 +45,9 @@ const (
//
// secret with backendSecurityPolicy auth instead of mounting new secret files to the external proc.
mountedExtProcSecretPath = "/etc/backend_security_policy" // #nosec G101

defaultRequestTimeout = "60s"
defaultBackendRequestTimeout = "60s"
)

// AIGatewayRouteController implements [reconcile.TypedReconciler].
Expand Down Expand Up @@ -391,14 +395,28 @@ func (c *AIGatewayRouteController) updateExtProcConfigMap(ctx context.Context, a
// newHTTPRoute updates the HTTPRoute with the new AIGatewayRoute.
func (c *AIGatewayRouteController) newHTTPRoute(ctx context.Context, dst *gwapiv1.HTTPRoute, aiGatewayRoute *aigv1a1.AIGatewayRoute) error {
var backends []*aigv1a1.AIServiceBackend
dedup := make(map[string]struct{})
// for two usages:
// 1. Deduplicate BackendRefs
// 2. Store timeout value
dedup := make(map[string]*gwapiv1.HTTPRouteTimeouts)
for _, rule := range aiGatewayRoute.Spec.Rules {
for _, br := range rule.BackendRefs {
key := fmt.Sprintf("%s.%s", br.Name, aiGatewayRoute.Namespace)
if _, ok := dedup[key]; ok {
if timeout, ok := dedup[key]; ok {
// use large one if two timeout value for same backend
if rule.Timeouts != nil {
t, err := combineTimeouts(timeout, rule.Timeouts)
if err != nil {
return err
}
dedup[key] = t
}
continue
}
dedup[key] = struct{}{}
if rule.Timeouts == nil {
dedup[key] = defaultTimeout()
}
dedup[key] = rule.Timeouts
backend, err := c.backend(ctx, aiGatewayRoute.Namespace, br.Name)
if err != nil {
return fmt.Errorf("AIServiceBackend %s not found", key)
Expand Down Expand Up @@ -427,7 +445,8 @@ func (c *AIGatewayRouteController) newHTTPRoute(ctx context.Context, dst *gwapiv
Matches: []gwapiv1.HTTPRouteMatch{
{Headers: []gwapiv1.HTTPHeaderMatch{{Name: selectedBackendHeaderKey, Value: key}}},
},
Filters: rewriteFilters,
Filters: rewriteFilters,
Timeouts: dedup[key],
}
rules[i] = rule
}
Expand All @@ -441,7 +460,8 @@ func (c *AIGatewayRouteController) newHTTPRoute(ctx context.Context, dst *gwapiv
BackendRefs: []gwapiv1.HTTPBackendRef{
{BackendRef: gwapiv1.BackendRef{BackendObjectReference: backends[0].Spec.BackendRef}},
},
Filters: rewriteFilters,
Filters: rewriteFilters,
Timeouts: defaultTimeout(),
})
}

Expand Down Expand Up @@ -675,3 +695,48 @@ func backendSecurityPolicyVolumeName(ruleIndex, backendRefIndex int, name string
func backendSecurityMountPath(backendSecurityPolicyKey string) string {
return fmt.Sprintf("%s/%s", mountedExtProcSecretPath, backendSecurityPolicyKey)
}

func defaultTimeout() *gwapiv1.HTTPRouteTimeouts {
var (
requestTimeout = gwapiv1.Duration(defaultRequestTimeout)
backendRequestTimeout = gwapiv1.Duration(defaultBackendRequestTimeout)
)
return &gwapiv1.HTTPRouteTimeouts{
Request: &requestTimeout,
BackendRequest: &backendRequestTimeout,
}
}

// compareDurations returns larger Request timeout and BackendRequest timeout.
func combineTimeouts(a, b *gwapiv1.HTTPRouteTimeouts) (*gwapiv1.HTTPRouteTimeouts, error) {
requestTimeout, err := combineDurations(a.Request, b.Request)
if err != nil {
return nil, err
}

backenRequestTimeout, err := combineDurations(a.BackendRequest, b.BackendRequest)
if err != nil {
return nil, err
}

return &gwapiv1.HTTPRouteTimeouts{Request: requestTimeout, BackendRequest: backenRequestTimeout}, nil
}

// combineDurations returns larger one.
func combineDurations(a, b *gwapiv1.Duration) (*gwapiv1.Duration, error) {
d1, err := time.ParseDuration(string(*a))
if err != nil {
return nil, err
}

d2, err := time.ParseDuration(string(*b))
if err != nil {
return nil, err
}

if d1 > d2 {
return a, nil
}

return b, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,66 @@ spec:
type: object
maxItems: 128
type: array
timeouts:
description: Timeouts defines the timeouts that can be configured
for an HTTP request.
properties:
backendRequest:
description: |-
BackendRequest specifies a timeout for an individual request from the gateway
to a backend. This covers the time from when the request first starts being
sent from the gateway to when the full response has been received from the backend.

Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
completely. Implementations that cannot completely disable the timeout MUST
instead interpret the zero duration as the longest possible value to which
the timeout can be set.

An entire client HTTP transaction with a gateway, covered by the Request timeout,
may result in more than one call from the gateway to the destination backend,
for example, if automatic retries are supported.

The value of BackendRequest must be a Gateway API Duration string as defined by
GEP-2257. When this field is unspecified, its behavior is implementation-specific;
when specified, the value of BackendRequest must be no more than the value of the
Request timeout (since the Request timeout encompasses the BackendRequest timeout).

Support: Extended
pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$
type: string
request:
description: |-
Request specifies the maximum duration for a gateway to respond to an HTTP request.
If the gateway has not been able to respond before this deadline is met, the gateway
MUST return a timeout error.

For example, setting the `rules.timeouts.request` field to the value `10s` in an
`HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds
to complete.

Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
completely. Implementations that cannot completely disable the timeout MUST
instead interpret the zero duration as the longest possible value to which
the timeout can be set.

This timeout is intended to cover as close to the whole request-response transaction
as possible although an implementation MAY choose to start the timeout after the entire
request stream has been received instead of immediately after the transaction is
initiated by the client.

The value of Request is a Gateway API Duration string as defined by GEP-2257. When this
field is unspecified, request timeout behavior is implementation-specific.

Support: Extended
pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$
type: string
type: object
x-kubernetes-validations:
- message: backendRequest timeout cannot be longer than request
timeout
rule: '!(has(self.request) && has(self.backendRequest) &&
duration(self.request) != duration(''0s'') && duration(self.backendRequest)
> duration(self.request))'
type: object
maxItems: 128
type: array
Expand Down
5 changes: 5 additions & 0 deletions site/docs/api/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ AIGatewayRouteRule is a rule that defines the routing behavior of the AIGatewayR
type="[AIGatewayRouteRuleMatch](#aigatewayrouterulematch) array"
required="false"
description="Matches is the list of AIGatewayRouteMatch that this rule will match the traffic to.<br />This is a subset of the HTTPRouteMatch in the Gateway API. See for the details:<br />https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.HTTPRouteMatch"
/><ApiField
name="timeouts"
type="[HTTPRouteTimeouts](#httproutetimeouts)"
required="false"
description="Timeouts defines the timeouts that can be configured for an HTTP request."
/>


Expand Down
Loading