Skip to content

Commit d4ca389

Browse files
Merge branch 'main' into auto-getn
2 parents c4421a1 + de2f8f3 commit d4ca389

13 files changed

+634
-19
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ require (
1515
github.com/docker/cli v28.5.1+incompatible
1616
github.com/docker/docker v28.3.3+incompatible
1717
github.com/dominikbraun/graph v0.23.0
18-
github.com/envoyproxy/go-control-plane v0.13.5-0.20250929230642-07d3df27ff4f
18+
github.com/envoyproxy/go-control-plane v0.13.5-0.20251022160057-de4316c523b7
1919
github.com/envoyproxy/go-control-plane/contrib v1.32.5-0.20250430092421-68a532e11403
20-
github.com/envoyproxy/go-control-plane/envoy v1.35.1-0.20250929230642-07d3df27ff4f
20+
github.com/envoyproxy/go-control-plane/envoy v1.35.1-0.20251022160057-de4316c523b7
2121
github.com/envoyproxy/go-control-plane/ratelimit v0.1.1-0.20250805143705-d51f8590a549
2222
github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7
2323
github.com/evanphx/json-patch v5.9.11+incompatible

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,12 @@ github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr
173173
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
174174
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
175175
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
176-
github.com/envoyproxy/go-control-plane v0.13.5-0.20250929230642-07d3df27ff4f h1:36vvJBe/wXWfD7qrTb1WnbPVPMxNFDfEygztH8wgebw=
177-
github.com/envoyproxy/go-control-plane v0.13.5-0.20250929230642-07d3df27ff4f/go.mod h1:PTY7yDlLxB4bW7rEOO7e79uTDr9yXzpuI1QGIDfxEzc=
176+
github.com/envoyproxy/go-control-plane v0.13.5-0.20251022160057-de4316c523b7 h1:JsOVlgacLOjgYWGb4V2E/d/c/o4+HEvYjr0+KnL4j4o=
177+
github.com/envoyproxy/go-control-plane v0.13.5-0.20251022160057-de4316c523b7/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
178178
github.com/envoyproxy/go-control-plane/contrib v1.32.5-0.20250430092421-68a532e11403 h1:5wPocL1bGYhA4TtKZwcdVI5fsXo1JatkbcxPBcFQswc=
179179
github.com/envoyproxy/go-control-plane/contrib v1.32.5-0.20250430092421-68a532e11403/go.mod h1:Xkwx/TGvEKRCL2mitdiuQWOD1ECvfM5krWWVo2vI2Zk=
180-
github.com/envoyproxy/go-control-plane/envoy v1.35.1-0.20250929230642-07d3df27ff4f h1:4efYrIQgVRwCmwCveby6ck+VpxqzibdOL1Out1rJqqc=
181-
github.com/envoyproxy/go-control-plane/envoy v1.35.1-0.20250929230642-07d3df27ff4f/go.mod h1:2LcmvJoXsDSrsGZIxGM0Gah9ykiwTn/kgjyQdnNH8Jc=
180+
github.com/envoyproxy/go-control-plane/envoy v1.35.1-0.20251022160057-de4316c523b7 h1:Q4zISHdb9brRNvzmQl8Bwvap7GAtGrQjHyyw9OYmkFk=
181+
github.com/envoyproxy/go-control-plane/envoy v1.35.1-0.20251022160057-de4316c523b7/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
182182
github.com/envoyproxy/go-control-plane/ratelimit v0.1.1-0.20250805143705-d51f8590a549 h1:5K0vH5H4dtCIO8+w/yq6vDaMcGn9RoPrHfmPAFAztwU=
183183
github.com/envoyproxy/go-control-plane/ratelimit v0.1.1-0.20250805143705-d51f8590a549/go.mod h1:KxtyvDAPIEkqUUvF9ooo5gSGVOtQ08wUTnQe5LsJC6c=
184184
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=

internal/message/watchutil.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,41 @@ func HandleSubscription[K comparable, V any](
9999
}
100100
for snapshot := range subscription {
101101
watchableDepth.With(meta.LabelValues()...).Record(float64(len(subscription)))
102-
for _, update := range snapshot.Updates {
102+
103+
for _, update := range coalesceUpdates(meta.Runner, snapshot.Updates) {
103104
handleWithCrashRecovery(handle, Update[K, V](update), meta, errChans)
104105
}
105106
}
106107
}
108+
109+
// coalesceUpdates merges multiple updates for the same key into a single update,
110+
// preserving the latest state for each key.
111+
// This helps reduce redundant processing and ensures that only the most recent update per key is handled.
112+
func coalesceUpdates[K comparable, V any](runner string, updates []watchable.Update[K, V]) []watchable.Update[K, V] {
113+
if len(updates) <= 1 {
114+
return updates
115+
}
116+
117+
seen := make(map[K]struct{}, len(updates))
118+
write := len(updates) - 1
119+
120+
for read := len(updates) - 1; read >= 0; read-- {
121+
update := updates[read]
122+
if _, ok := seen[update.Key]; ok {
123+
continue
124+
}
125+
seen[update.Key] = struct{}{}
126+
updates[write] = update
127+
write--
128+
}
129+
130+
result := updates[write+1:]
131+
if len(result) != len(updates) {
132+
logger.WithValues("runner", runner).Info(
133+
"coalesced updates",
134+
"count", len(result),
135+
"before", len(updates),
136+
)
137+
}
138+
return result
139+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright Envoy Gateway Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
// The full text of the Apache license is available in the LICENSE file at
4+
// the root of the repo.
5+
6+
package message
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
"github.com/telepresenceio/watchable"
13+
)
14+
15+
func TestCoalesceUpdates(t *testing.T) {
16+
t.Parallel()
17+
18+
tests := []struct {
19+
name string
20+
input []watchable.Update[string, int]
21+
expected []watchable.Update[string, int]
22+
}{
23+
{
24+
name: "empty input returns nil",
25+
input: []watchable.Update[string, int]{},
26+
expected: []watchable.Update[string, int]{},
27+
},
28+
{
29+
name: "simple updates without repeats",
30+
input: []watchable.Update[string, int]{
31+
{Key: "foo", Value: 1},
32+
{Key: "bar", Value: 2},
33+
{Key: "baz", Value: 3},
34+
},
35+
expected: []watchable.Update[string, int]{
36+
{Key: "foo", Value: 1},
37+
{Key: "bar", Value: 2},
38+
{Key: "baz", Value: 3},
39+
},
40+
},
41+
{
42+
name: "latest update per key wins",
43+
input: []watchable.Update[string, int]{
44+
{Key: "foo", Value: 1},
45+
{Key: "bar", Delete: true, Value: 2},
46+
{Key: "baz", Value: 3},
47+
{Key: "bar", Value: 4},
48+
{Key: "foo", Value: 5},
49+
{Key: "baz", Delete: true, Value: 6},
50+
{Key: "bar", Value: 7},
51+
},
52+
expected: []watchable.Update[string, int]{
53+
{Key: "foo", Value: 5},
54+
{Key: "baz", Delete: true, Value: 6},
55+
{Key: "bar", Value: 7},
56+
},
57+
},
58+
}
59+
60+
for _, tc := range tests {
61+
t.Run(tc.name, func(t *testing.T) {
62+
t.Parallel()
63+
64+
actual := coalesceUpdates("test-runner", tc.input)
65+
require.Equal(t, tc.expected, actual)
66+
})
67+
}
68+
}

internal/message/watchutil_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func TestHandleSubscriptionAlreadyInitialized(t *testing.T) {
8989
}
9090
},
9191
)
92-
assert.Equal(t, 2, storeCalls)
92+
assert.LessOrEqual(t, 2, storeCalls) // updates can be coalesced
9393
assert.Equal(t, 1, deleteCalls)
9494
}
9595

@@ -268,7 +268,11 @@ func TestControllerResourceUpdate(t *testing.T) {
268268
m.GatewayAPIResources.Close()
269269
}
270270
})
271-
assert.Equal(t, tc.updates, updates)
271+
if tc.updates > 1 {
272+
assert.LessOrEqual(t, updates, tc.updates) // Updates can be coalesced
273+
} else {
274+
assert.Equal(t, 1, updates)
275+
}
272276
})
273277
}
274278
}

internal/xds/extensions/extensions.gen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import (
7676
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dns/v3"
7777
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3"
7878
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/redis/v3"
79+
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/reverse_connection/v3"
7980
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/common/async_files/v3"
8081
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/common/aws/v3"
8182
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3"
@@ -140,6 +141,7 @@ import (
140141
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/kill_request/v3"
141142
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
142143
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3"
144+
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/mcp/v3"
143145
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/oauth2/v3"
144146
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/on_demand/v3"
145147
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/original_src/v3"
@@ -224,6 +226,7 @@ import (
224226
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/custom_response/local_response_policy/v3"
225227
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/custom_response/redirect_policy/v3"
226228
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/early_header_mutation/header_mutation/v3"
229+
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/ext_proc/processing_request_modifiers/mapped_attribute_builder/v3"
227230
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/ext_proc/response_processors/save_processing_response/v3"
228231
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_formatters/preserve_case/v3"
229232
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_validators/envoy_default/v3"
@@ -253,6 +256,7 @@ import (
253256
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/environment_variable/v3"
254257
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/network/v3"
255258
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/ssl/v3"
259+
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/stats/v3"
256260
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/consistent_hashing/v3"
257261
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/ip/v3"
258262
_ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/metadata/v3"

internal/xds/translator/extauth.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,26 +134,32 @@ func extAuthConfig(extAuth *ir.ExtAuth) (*extauthv3.ExtAuthz, error) {
134134
timeout = durationpb.New(extAuth.Timeout.Duration)
135135
}
136136

137+
var rp *corev3.RetryPolicy
138+
// Set the retry policy if it exists.
139+
if extAuth.Traffic != nil && extAuth.Traffic.Retry != nil {
140+
var err error
141+
rp, err = buildNonRouteRetryPolicy(extAuth.Traffic.Retry)
142+
if err != nil {
143+
return nil, fmt.Errorf("build retry policy for http service: %w", err)
144+
}
145+
}
146+
137147
if extAuth.HTTP != nil {
148+
hs := httpService(extAuth.HTTP, timeout)
149+
hs.RetryPolicy = rp
150+
138151
config.Services = &extauthv3.ExtAuthz_HttpService{
139-
HttpService: httpService(extAuth.HTTP, timeout),
152+
HttpService: hs,
140153
}
141-
// Retry policy is not supported for HTTP service.
142154
} else if extAuth.GRPC != nil {
143155
service := &corev3.GrpcService{
144156
TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{
145157
EnvoyGrpc: grpcService(extAuth.GRPC),
146158
},
147159
Timeout: timeout,
148160
}
149-
// Set the retry policy if it exists.
150-
if extAuth.Traffic != nil && extAuth.Traffic.Retry != nil {
151-
rp, err := buildNonRouteRetryPolicy(extAuth.Traffic.Retry)
152-
if err != nil {
153-
return nil, fmt.Errorf("build retry policy for gRPC service: %w", err)
154-
}
155-
service.RetryPolicy = rp
156-
}
161+
service.RetryPolicy = rp
162+
157163
config.Services = &extauthv3.ExtAuthz_GrpcService{
158164
GrpcService: service,
159165
}

0 commit comments

Comments
 (0)