From 4d0cff6f286956a2752176e68a4d1a2c04487d27 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Tue, 21 Apr 2026 13:07:48 -0700 Subject: [PATCH 1/3] internal/envoy: always set NumRetries so numRetries=-1 actually disables retries HTTPProxy documents `numRetries: -1` as "disable retries". The DAG translator in internal/dag/policy.go already maps the API value -1 to the DAG value 0 (with DAG value 1 standing for the default one-retry behaviour). internal/envoy/v3/route.retryPolicy, however, only forwarded NumRetries to Envoy when the DAG value was > 0. When a user set numRetries: -1, the DAG value 0 silently disappeared from the Envoy RetryPolicy, and Envoy's documented default of 1 kicked in -- giving exactly one retry instead of none (#5944). Always set NumRetries (including 0) so the Envoy config matches what the API promised. Fixes #5944. Signed-off-by: SAY-5 --- internal/envoy/v3/route.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/envoy/v3/route.go b/internal/envoy/v3/route.go index f35a19f600e..df082fd0547 100644 --- a/internal/envoy/v3/route.go +++ b/internal/envoy/v3/route.go @@ -547,9 +547,14 @@ func retryPolicy(r *dag.Route) *envoy_config_route_v3.RetryPolicy { RetryOn: r.RetryPolicy.RetryOn, RetriableStatusCodes: r.RetryPolicy.RetriableStatusCodes, } - if r.RetryPolicy.NumRetries > 0 { - rp.NumRetries = wrapperspb.UInt32(r.RetryPolicy.NumRetries) - } + // HTTPProxy documents numRetries: -1 as "disable retries". The DAG + // translates -1 to 0 (internal/dag/policy.go), so the DAG value 0 + // here uniquely means "user asked for no retries". Previously we + // only set NumRetries when > 0, and Envoy then defaulted to 1 -- + // giving exactly one retry instead of none (projectcontour#5944). + // Always set NumRetries (including 0) so Envoy respects the + // documented contract. + rp.NumRetries = wrapperspb.UInt32(r.RetryPolicy.NumRetries) rp.PerTryTimeout = envoy.Timeout(r.RetryPolicy.PerTryTimeout) return rp From 7f23167d821f747dbb66774cf3033229699c540f Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Wed, 6 May 2026 14:37:24 -0700 Subject: [PATCH 2/3] test: align retry policy helpers with always-set NumRetries Signed-off-by: SAY-5 --- internal/featuretests/v3/envoy.go | 6 ++---- internal/xdscache/v3/route_test.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/featuretests/v3/envoy.go b/internal/featuretests/v3/envoy.go index 04544a93e68..5cccfd4ef5e 100644 --- a/internal/featuretests/v3/envoy.go +++ b/internal/featuretests/v3/envoy.go @@ -313,10 +313,8 @@ func withPrefixRewrite(route *envoy_config_route_v3.Route_Route, replacement str func withRetryPolicy(route *envoy_config_route_v3.Route_Route, retryOn string, numRetries uint32, perTryTimeout time.Duration) *envoy_config_route_v3.Route_Route { route.Route.RetryPolicy = &envoy_config_route_v3.RetryPolicy{ - RetryOn: retryOn, - } - if numRetries > 0 { - route.Route.RetryPolicy.NumRetries = wrapperspb.UInt32(numRetries) + RetryOn: retryOn, + NumRetries: wrapperspb.UInt32(numRetries), } if perTryTimeout > 0 { route.Route.RetryPolicy.PerTryTimeout = durationpb.New(perTryTimeout) diff --git a/internal/xdscache/v3/route_test.go b/internal/xdscache/v3/route_test.go index 4f01441bbdd..364cd09917c 100644 --- a/internal/xdscache/v3/route_test.go +++ b/internal/xdscache/v3/route_test.go @@ -3896,10 +3896,8 @@ func routetimeout(cluster string, timeout time.Duration) *envoy_config_route_v3. func routeretry(cluster, retryOn string, numRetries uint32, perTryTimeout time.Duration) *envoy_config_route_v3.Route_Route { r := routecluster(cluster) r.Route.RetryPolicy = &envoy_config_route_v3.RetryPolicy{ - RetryOn: retryOn, - } - if numRetries > 0 { - r.Route.RetryPolicy.NumRetries = wrapperspb.UInt32(numRetries) + RetryOn: retryOn, + NumRetries: wrapperspb.UInt32(numRetries), } if perTryTimeout > 0 { r.Route.RetryPolicy.PerTryTimeout = durationpb.New(perTryTimeout) From 861e959e8ccdb73b04ccaee627a4c6c9728cf8c9 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Thu, 7 May 2026 12:54:14 -0700 Subject: [PATCH 3/3] Add changelog entry for #7525 Signed-off-by: SAY-5 --- changelogs/unreleased/7525-SAY-5-small.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/unreleased/7525-SAY-5-small.md diff --git a/changelogs/unreleased/7525-SAY-5-small.md b/changelogs/unreleased/7525-SAY-5-small.md new file mode 100644 index 00000000000..3aa3299a5ad --- /dev/null +++ b/changelogs/unreleased/7525-SAY-5-small.md @@ -0,0 +1 @@ +Fix RetryPolicy numRetries=0 being silently coerced to 1 by populating num_retries explicitly when set, so HTTPProxy retry policies that explicitly request zero retries are honored.