From c231e1c0af0a42d0a49ef31d7834338a2962e749 Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Wed, 21 Oct 2026 14:30:31 +0800 Subject: [PATCH] fix: preserve fractional upstream timeouts (backport #2894) --- api/adc/types.go | 6 +- internal/adc/translator/apisixroute.go | 6 +- internal/adc/translator/apisixupstream.go | 6 +- internal/adc/translator/ingress.go | 6 +- internal/adc/translator/policies.go | 7 +- internal/adc/translator/timeouts_test.go | 84 +++++++++++++++++++++++ test/e2e/ingress/annotations.go | 6 +- 7 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 internal/adc/translator/timeouts_test.go diff --git a/api/adc/types.go b/api/adc/types.go index bae1a373..26a28f9d 100644 --- a/api/adc/types.go +++ b/api/adc/types.go @@ -168,9 +168,9 @@ type Route struct { } type Timeout struct { - Connect int `json:"connect"` - Read int `json:"read"` - Send int `json:"send"` + Connect float64 `json:"connect"` + Read float64 `json:"read"` + Send float64 `json:"send"` } // +k8s:deepcopy-gen=true diff --git a/internal/adc/translator/apisixroute.go b/internal/adc/translator/apisixroute.go index 85ba2d29..038544d8 100644 --- a/internal/adc/translator/apisixroute.go +++ b/internal/adc/translator/apisixroute.go @@ -89,9 +89,9 @@ func (t *Translator) buildTimeout(rule apiv2.ApisixRouteHTTP) *adc.Timeout { } defaultTimeout := metav1.Duration{Duration: apiv2.DefaultUpstreamTimeout} return &adc.Timeout{ - Connect: cmp.Or(int(rule.Timeout.Connect.Seconds()), int(defaultTimeout.Seconds())), - Read: cmp.Or(int(rule.Timeout.Read.Seconds()), int(defaultTimeout.Seconds())), - Send: cmp.Or(int(rule.Timeout.Send.Seconds()), int(defaultTimeout.Seconds())), + Connect: cmp.Or(rule.Timeout.Connect.Seconds(), defaultTimeout.Seconds()), + Read: cmp.Or(rule.Timeout.Read.Seconds(), defaultTimeout.Seconds()), + Send: cmp.Or(rule.Timeout.Send.Seconds(), defaultTimeout.Seconds()), } } diff --git a/internal/adc/translator/apisixupstream.go b/internal/adc/translator/apisixupstream.go index 53b1b006..a552f1c7 100644 --- a/internal/adc/translator/apisixupstream.go +++ b/internal/adc/translator/apisixupstream.go @@ -164,9 +164,9 @@ func translateApisixUpstreamRetriesAndTimeout(config *apiv2.ApisixUpstreamConfig sendTimeout := cmp.Or(timeout.Send.Duration, apiv2.DefaultUpstreamTimeout) ups.Timeout = &adc.Timeout{ - Connect: int(connTimeout.Seconds()), - Read: int(readTimeout.Seconds()), - Send: int(sendTimeout.Seconds()), + Connect: connTimeout.Seconds(), + Read: readTimeout.Seconds(), + Send: sendTimeout.Seconds(), } return nil diff --git a/internal/adc/translator/ingress.go b/internal/adc/translator/ingress.go index f3e121fe..0c0d90e8 100644 --- a/internal/adc/translator/ingress.go +++ b/internal/adc/translator/ingress.go @@ -207,9 +207,9 @@ func (t *Translator) resolveIngressUpstream( } if upConfig.TimeoutConnect > 0 || upConfig.TimeoutRead > 0 || upConfig.TimeoutSend > 0 { upstream.Timeout = &adctypes.Timeout{ - Connect: cmp.Or(upConfig.TimeoutConnect, 60), - Read: cmp.Or(upConfig.TimeoutRead, 60), - Send: cmp.Or(upConfig.TimeoutSend, 60), + Connect: float64(cmp.Or(upConfig.TimeoutConnect, 60)), + Read: float64(cmp.Or(upConfig.TimeoutRead, 60)), + Send: float64(cmp.Or(upConfig.TimeoutSend, 60)), } } } diff --git a/internal/adc/translator/policies.go b/internal/adc/translator/policies.go index dd05c77a..5b8672ce 100644 --- a/internal/adc/translator/policies.go +++ b/internal/adc/translator/policies.go @@ -18,6 +18,7 @@ package translator import ( + "cmp" "fmt" corev1 "k8s.io/api/core/v1" @@ -123,9 +124,9 @@ func (t *Translator) attachBackendTrafficPolicyToUpstream(policy *v1alpha1.Backe } if policy.Spec.Timeout != nil { upstream.Timeout = &adctypes.Timeout{ - Connect: int(policy.Spec.Timeout.Connect.Seconds()), - Read: int(policy.Spec.Timeout.Read.Seconds()), - Send: int(policy.Spec.Timeout.Send.Seconds()), + Connect: cmp.Or(policy.Spec.Timeout.Connect.Duration, apiv2.DefaultUpstreamTimeout).Seconds(), + Read: cmp.Or(policy.Spec.Timeout.Read.Duration, apiv2.DefaultUpstreamTimeout).Seconds(), + Send: cmp.Or(policy.Spec.Timeout.Send.Duration, apiv2.DefaultUpstreamTimeout).Seconds(), } } if policy.Spec.LoadBalancer != nil { diff --git a/internal/adc/translator/timeouts_test.go b/internal/adc/translator/timeouts_test.go new file mode 100644 index 00000000..0292fca3 --- /dev/null +++ b/internal/adc/translator/timeouts_test.go @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package translator + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/apache/apisix-ingress-controller/api/adc" + "github.com/apache/apisix-ingress-controller/api/v1alpha1" + apiv2 "github.com/apache/apisix-ingress-controller/api/v2" +) + +func TestTranslateApisixUpstreamRetriesAndTimeoutPreservesSubsecondValues(t *testing.T) { + config := &apiv2.ApisixUpstreamConfig{ + Timeout: &apiv2.UpstreamTimeout{ + Connect: metav1.Duration{Duration: 500 * time.Millisecond}, + Read: metav1.Duration{Duration: 1500 * time.Millisecond}, + Send: metav1.Duration{Duration: 2500 * time.Millisecond}, + }, + } + upstream := adc.NewDefaultUpstream() + + err := translateApisixUpstreamRetriesAndTimeout(config, upstream) + + require.NoError(t, err) + assert.Equal(t, 0.5, upstream.Timeout.Connect) + assert.Equal(t, 1.5, upstream.Timeout.Read) + assert.Equal(t, 2.5, upstream.Timeout.Send) +} + +func TestBuildTimeoutPreservesSubsecondValues(t *testing.T) { + rule := apiv2.ApisixRouteHTTP{ + Timeout: &apiv2.UpstreamTimeout{ + Connect: metav1.Duration{Duration: 500 * time.Millisecond}, + Read: metav1.Duration{Duration: 1500 * time.Millisecond}, + Send: metav1.Duration{Duration: 2500 * time.Millisecond}, + }, + } + + timeout := (&Translator{}).buildTimeout(rule) + + assert.Equal(t, 0.5, timeout.Connect) + assert.Equal(t, 1.5, timeout.Read) + assert.Equal(t, 2.5, timeout.Send) +} + +func TestAttachBackendTrafficPolicyToUpstreamPreservesSubsecondValues(t *testing.T) { + policy := &v1alpha1.BackendTrafficPolicy{ + Spec: v1alpha1.BackendTrafficPolicySpec{ + Timeout: &v1alpha1.Timeout{ + Connect: metav1.Duration{}, + Read: metav1.Duration{Duration: 1500 * time.Millisecond}, + Send: metav1.Duration{Duration: 2500 * time.Millisecond}, + }, + }, + } + upstream := &adc.Upstream{} + + (&Translator{}).attachBackendTrafficPolicyToUpstream(policy, upstream) + + assert.Equal(t, 60.0, upstream.Timeout.Connect) + assert.Equal(t, 1.5, upstream.Timeout.Read) + assert.Equal(t, 2.5, upstream.Timeout.Send) +} diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go index 622d0cb8..9fe5de79 100644 --- a/test/e2e/ingress/annotations.go +++ b/test/e2e/ingress/annotations.go @@ -210,9 +210,9 @@ spec: Expect(err).NotTo(HaveOccurred(), "listing Upstream") Expect(upstreams).To(HaveLen(1), "checking Upstream length") Expect(upstreams[0].Timeout).ToNot(BeNil(), "checking Upstream timeout") - Expect(upstreams[0].Timeout.Read).To(Equal(2), "checking Upstream read timeout") - Expect(upstreams[0].Timeout.Send).To(Equal(3), "checking Upstream send timeout") - Expect(upstreams[0].Timeout.Connect).To(Equal(4), "checking Upstream connect timeout") + Expect(upstreams[0].Timeout.Read).To(Equal(2.0), "checking Upstream read timeout") + Expect(upstreams[0].Timeout.Send).To(Equal(3.0), "checking Upstream send timeout") + Expect(upstreams[0].Timeout.Connect).To(Equal(4.0), "checking Upstream connect timeout") }) It("cors annotations", func() {