-
Notifications
You must be signed in to change notification settings - Fork 596
ir: Fix Service updates not propagating through translation #12809
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the equality comparison logic for HTTP backend objects to properly include AppProtocol field comparisons. The key changes centralize backend equality checks and make the ResourceName field public for better API consistency.
Key Changes
- Refactored
HttpBackendOrDelegate.Equals()method to properly delegate equality checks to constituent types - Updated
BackendObjectIR.Equals()to includeAppProtocolcomparison - Made
BackendObjectIR.ResourceNamefield public and removed redundant accessor method - Added comprehensive test coverage for
AppProtocoldifferences in backend equality
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/pluginsdk/ir/backend.go | Made ResourceName public, removed accessor method, added AppProtocol to equality check |
| pkg/pluginsdk/ir/gw.go | Added HttpBackendOrDelegate.Equals() method and moved type definition |
| pkg/pluginsdk/ir/routes.go | Refactored to use new HttpBackendOrDelegate.Equals() method |
| pkg/pluginsdk/ir/routes_test.go | Added test coverage for AppProtocol differences in backends |
| pkg/pluginsdk/ir/model.go | Updated to use public ResourceName field |
| internal/kgateway/krtcollections/policy.go | Updated to use public ResourceName field |
| internal/kgateway/extensions2/plugins/inferenceextension/endpointpicker/collections.go | Updated to use public ResourceName field |
| internal/kgateway/extensions2/plugins/inferenceextension/endpointpicker/backends.go | Updated to use public ResourceName field |
| internal/kgateway/extensions2/plugins/backendtlspolicy/plugin.go | Removed explicit type parameter from kubeclient.Register call |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have a test where we can change the appProtocol or an equivalent field and verify that it triggers a translation update on the route
| } | ||
|
|
||
| func (h HttpBackendOrDelegate) Equals(other HttpBackendOrDelegate) bool { | ||
| if !h.AttachedPolicies.Equals(other.AttachedPolicies) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: order comparision in the order of fields declared in the struct
| return false | ||
| } | ||
| } else { | ||
| if !backend.Equals(backendsb[j]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were we intentionally restricting Backend comparison to specific fields before or was this an oversight?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the latter is very likely. If you go through the git history for this file, you'll reach the massive refactor PR that Yuval did during the early days of the donation that predated my time: 5d4e603#diff-5b7e43b2f30d5fce0ff29295ccad60d0882ce08abb34ba7e6f9464b942e02a6f. @lgadban @yuval-k if you can confirm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is intentional - there's no need to propagate changes like app protocol for routes - i believe it is a no-op in envoy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. i don't care about backend properties that don't end up on the envoy route object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timflannagan do you know if something about the app protcol impacts the envoy route?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem is that appProtocol, at least for websockets, does contribute to envoy config:
kgateway/internal/kgateway/translator/irtranslator/route.go
Lines 594 to 603 in e74aaa4
| if back := backend.Backend.BackendObject; back != nil && back.AppProtocol == ir.WebSocketAppProtocol { | |
| // add websocket upgrade if not already present | |
| if !slices.ContainsFunc(action.GetUpgradeConfigs(), func(uc *envoyroutev3.RouteAction_UpgradeConfig) bool { | |
| return uc.GetUpgradeType() == webSocketUpgradeType | |
| }) { | |
| action.UpgradeConfigs = append(action.GetUpgradeConfigs(), &envoyroutev3.RouteAction_UpgradeConfig{ | |
| UpgradeType: webSocketUpgradeType, | |
| }) | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW for the BackendObjectIR's Equals() not checking the AppProtocol field. I think we're all set due to
kgateway/pkg/pluginsdk/ir/iface.go
Lines 306 to 314 in 7a9e2af
| func versionEquals(a, b metav1.Object) bool { | |
| var versionEquals bool | |
| if a.GetGeneration() != 0 && b.GetGeneration() != 0 { | |
| versionEquals = a.GetGeneration() == b.GetGeneration() | |
| } else { | |
| versionEquals = a.GetResourceVersion() == b.GetResourceVersion() | |
| } | |
| return versionEquals && a.GetUID() == b.GetUID() | |
| } |
So the issue here is solely due to the lack of explicit backend.Backend equals checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, we are automatically add an upgrade to the route based on the backend app protocol. then yea the app protocol is effectivly a route policy in this case, so it should be in the equals
pkg/pluginsdk/ir/backend.go
Outdated
| } | ||
|
|
||
| return objEq && objVersionEq && objIrEq && polEq && nameEq && disableIstioAutoMTLSEq | ||
| return objEq && objVersionEq && objIrEq && polEq && nameEq && disableIstioAutoMTLSEq && c.AppProtocol == in.AppProtocol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we collapse L173-177 into L187 for consistency
|
Going to look into an IR-based linter in parallel. Throwing the WIP label depending on the level of effort for that. |
SG. I'll look into either a setup test || e2e test here in addition to the unit test added. |
This change fixes an issue where patching Kubernetes Services (e.g. changing appProtocol or ports) did not dynamically propagate to the xDS config, and required restarting the kgw container. The root cause was HttpRouteIR.rulesEqual() not comparing BackendObject fields when checking backend equality. It was only comparing ClusterName and Weight, missing changes to the underlying Service configuration like appProtocol. Implements an Equals() method for the HttpBackendOrDelegate type to fix this edge case and cleanup the overall implementation to be consistent with the rest of the codebase and avoid harcoding the conditional checks related to Equals-based collection checking. Fixes kgateway-dev#12576. Signed-off-by: timflannagan <[email protected]>
ba400db to
978d069
Compare
Description
This change fixes an issue where patching Kubernetes Services (e.g. changing appProtocol or ports) did not dynamically propagate to the xDS config, and required restarting the kgw container to get the desired functional behavior.
The root cause was the HttpRouteIR.rulesEqual() implementation didn't compare the BackendObject fields when checking backend equality. It was only comparing ClusterName and Weight, missing changes to the underlying Service configuration like appProtocol.
Implements an Equals() method for the HttpBackendOrDelegate type to fix this edge case and cleanup the overall implementation to be consistent with the rest of the codebase and avoid harcoding the conditional checks related to Equals-based collection checking.
Fixes #12576.
Change Type
/kind fix
Changelog
Additional Notes
In order to reproduce locally:
``yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: test-gateway
namespace: default
spec:
gatewayClassName: kgateway
listeners:
- name: http
protocol: HTTP
port: 8080
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: test-route
namespace: default
spec:
parentRefs:
- name: test-gateway
hostnames:
- "test.example.com"
rules:
- backendRefs:
- name: echo-svc
port: 80
apiVersion: v1
kind: Service
metadata:
name: echo-svc
namespace: default
spec:
selector:
app: echo-server
ports:
- protocol: TCP
port: 80
targetPort: 8080
# appProtocol: kubernetes.io/ws
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-server
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: echo-server
template:
metadata:
labels:
app: echo-server
spec:
containers:
- name: echo
image: jmalloc/echo-server:latest
ports:
- containerPort: 8080
k -n default port-forward deploy/test-gateway 8080:8080 &
curl -v -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" -H "Host: test.example.com" http://localhost:8080