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

[release/v1.1] fix: don't lose timeout settings that originate from the route when t… #4450

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
105 changes: 52 additions & 53 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@
rt = t.buildRetry(policy)
}
if policy.Spec.Timeout != nil {
if to, err = t.buildTimeout(policy, nil); err != nil {
if to, err = t.buildTimeout(*policy, nil); err != nil {
err = perr.WithMessage(err, "Timeout")
errs = errors.Join(errs, err)
}
Expand Down Expand Up @@ -411,6 +411,11 @@

for _, http := range x.HTTP {
for _, r := range http.Routes {
// Some timeout setting originate from the route.
if localTo, err := t.buildTimeout(*policy, r.Traffic); err == nil {
to = localTo
}

// Apply if there is a match
if strings.HasPrefix(r.Name, prefix) {
r.Traffic = &ir.TrafficFeatures{
Expand All @@ -423,18 +428,12 @@
TCPKeepalive: ka,
Retry: rt,
BackendConnection: bc,
Timeout: to,
}

// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname)

// Some timeout setting originate from the route.
if policy.Spec.Timeout != nil {
if to, err = t.buildTimeout(policy, r); err == nil {
r.Traffic.Timeout = to
}
}

if policy.Spec.UseClientProtocol != nil {
r.UseClientProtocol = policy.Spec.UseClientProtocol
}
Expand Down Expand Up @@ -498,7 +497,7 @@
rt = t.buildRetry(policy)
}
if policy.Spec.Timeout != nil {
if ct, err = t.buildTimeout(policy, nil); err != nil {
if ct, err = t.buildTimeout(*policy, nil); err != nil {
err = perr.WithMessage(err, "Timeout")
errs = errors.Join(errs, err)
}
Expand Down Expand Up @@ -596,10 +595,8 @@
// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname)

if policy.Spec.Timeout != nil {
if ct, err = t.buildTimeout(policy, r); err == nil {
r.Traffic.Timeout = ct
}
if ct, err = t.buildTimeout(*policy, r.Traffic); err == nil {
r.Traffic.Timeout = ct
}

if policy.Spec.UseClientProtocol != nil {
Expand Down Expand Up @@ -1069,23 +1066,26 @@
return cb, nil
}

func (t *Translator) buildTimeout(policy *egv1a1.BackendTrafficPolicy, r *ir.HTTPRoute) (*ir.Timeout, error) {
func (t *Translator) buildTimeout(policy egv1a1.BackendTrafficPolicy, traffic *ir.TrafficFeatures) (*ir.Timeout, error) {
if policy.Spec.Timeout == nil {
if traffic != nil {
// Don't lose any existing timeout definitions.
return mergeTimeoutSettings(nil, traffic.Timeout), nil
}
return nil, nil
}
var (
tto *ir.TCPTimeout
hto *ir.HTTPTimeout
terr bool
errs error
to = &ir.Timeout{}
pto = policy.Spec.Timeout
)

pto := policy.Spec.Timeout

if pto.TCP != nil && pto.TCP.ConnectTimeout != nil {
d, err := time.ParseDuration(string(*pto.TCP.ConnectTimeout))
if err != nil {
terr = true
errs = errors.Join(errs, fmt.Errorf("invalid ConnectTimeout value %s", *pto.TCP.ConnectTimeout))
} else {
tto = &ir.TCPTimeout{
to.TCP = &ir.TCPTimeout{
ConnectTimeout: ptr.To(metav1.Duration{Duration: d}),
}
}
Expand All @@ -1098,7 +1098,6 @@
if pto.HTTP.ConnectionIdleTimeout != nil {
d, err := time.ParseDuration(string(*pto.HTTP.ConnectionIdleTimeout))
if err != nil {
terr = true
errs = errors.Join(errs, fmt.Errorf("invalid ConnectionIdleTimeout value %s", *pto.HTTP.ConnectionIdleTimeout))
} else {
cit = ptr.To(metav1.Duration{Duration: d})
Expand All @@ -1108,51 +1107,51 @@
if pto.HTTP.MaxConnectionDuration != nil {
d, err := time.ParseDuration(string(*pto.HTTP.MaxConnectionDuration))
if err != nil {
terr = true
errs = errors.Join(errs, fmt.Errorf("invalid MaxConnectionDuration value %s", *pto.HTTP.MaxConnectionDuration))
} else {
mcd = ptr.To(metav1.Duration{Duration: d})
}
}

hto = &ir.HTTPTimeout{
to.HTTP = &ir.HTTPTimeout{
ConnectionIdleTimeout: cit,
MaxConnectionDuration: mcd,
}
}

// http request timeout is translated during the gateway-api route resource translation
// merge route timeout setting with backendtrafficpolicy timeout settings
if terr {
if r != nil && r.Traffic != nil && r.Traffic.Timeout != nil {
return r.Traffic.Timeout.DeepCopy(), errs
}
} else {
// http request timeout is translated during the gateway-api route resource translation
// merge route timeout setting with backendtrafficpolicy timeout settings
if r != nil &&
r.Traffic != nil &&
r.Traffic.Timeout != nil &&
r.Traffic.Timeout.HTTP != nil &&
r.Traffic.Timeout.HTTP.RequestTimeout != nil {
if hto == nil {
hto = &ir.HTTPTimeout{
RequestTimeout: r.Traffic.Timeout.HTTP.RequestTimeout,
}
} else {
hto.RequestTimeout = r.Traffic.Timeout.HTTP.RequestTimeout
}
}

if hto != nil || tto != nil {
return &ir.Timeout{
TCP: tto,
HTTP: hto,
}, nil
}
// merge route timeout setting with backendtrafficpolicy timeout settings.
// Merging is done after the clustersettings definitions are translated so that
// clustersettings will override previous settings.
if traffic != nil {
to = mergeTimeoutSettings(to, traffic.Timeout)
}
return to, errs
}

return nil, errs
// merge secondary into main if both are not nil, otherwise return the
// one that is not nil. If both are nil, returns nil
func mergeTimeoutSettings(main, secondary *ir.Timeout) *ir.Timeout {
switch {
case main == nil && secondary == nil:
return nil
case main == nil:
return secondary.DeepCopy()
case secondary == nil:
return main
default: // Neither main nor secondary are nil here
if secondary.HTTP != nil {
setIfNil(&main.HTTP, &ir.HTTPTimeout{})
setIfNil(&main.HTTP.RequestTimeout, secondary.HTTP.RequestTimeout)
setIfNil(&main.HTTP.ConnectionIdleTimeout, secondary.HTTP.ConnectionIdleTimeout)
setIfNil(&main.HTTP.MaxConnectionDuration, secondary.HTTP.MaxConnectionDuration)
}
if secondary.TCP != nil {
setIfNil(&main.TCP, &ir.TCPTimeout{})
setIfNil(&main.TCP.ConnectTimeout, secondary.TCP.ConnectTimeout)

Check warning on line 1151 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L1150-L1151

Added lines #L1150 - L1151 were not covered by tests
}
return main
}
}

func int64ToUint32(in int64) (uint32, bool) {
Expand Down
7 changes: 7 additions & 0 deletions internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,3 +580,10 @@ func getPolicyTargetRefs[T client.Object](policy egv1a1.PolicyTargetReferences,

return ret
}

// Sets *target to value if and only if *target is nil
func setIfNil[T any](target **T, value *T) {
if *target == nil {
*target = value
}
}
15 changes: 4 additions & 11 deletions internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,9 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
return routeRoutes, nil
}

func processTimeout(irRoute *ir.HTTPRoute, rule gwapiv1.HTTPRouteRule) {
func processRouteTimeout(irRoute *ir.HTTPRoute, rule gwapiv1.HTTPRouteRule) {
if rule.Timeouts != nil {
var rto *ir.Timeout

// Timeout is translated from multiple resources and may already be partially set
if irRoute.Traffic != nil && irRoute.Traffic.Timeout != nil {
rto = irRoute.Traffic.Timeout.DeepCopy()
} else {
rto = &ir.Timeout{}
}
rto := &ir.Timeout{}

if rule.Timeouts.Request != nil {
d, err := time.ParseDuration(string(*rule.Timeouts.Request))
Expand Down Expand Up @@ -309,7 +302,7 @@ func (t *Translator) processHTTPRouteRule(httpRoute *HTTPRouteContext, ruleIdx i
irRoute := &ir.HTTPRoute{
Name: irRouteName(httpRoute, ruleIdx, -1),
}
processTimeout(irRoute, rule)
processRouteTimeout(irRoute, rule)
applyHTTPFiltersContextToIRRoute(httpFiltersContext, irRoute)
ruleRoutes = append(ruleRoutes, irRoute)
}
Expand All @@ -321,7 +314,7 @@ func (t *Translator) processHTTPRouteRule(httpRoute *HTTPRouteContext, ruleIdx i
irRoute := &ir.HTTPRoute{
Name: irRouteName(httpRoute, ruleIdx, matchIdx),
}
processTimeout(irRoute, rule)
processRouteTimeout(irRoute, rule)

if match.Path != nil {
switch PathMatchTypeDerefOr(match.Path.Type, gwapiv1.PathMatchPathPrefix) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- gateway.envoyproxy.io
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- matches:
- path:
value: "/"
backendRefs:
- name: service-1
port: 8080
timeouts:
request: 130s
backendTrafficPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: default
name: policy-for-http-route-1
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: httproute-1
useClientProtocol: true

Loading
Loading