Description
A BackendTrafficPolicy using targetSelectors can become stale in status when the selector is changed from matching an existing route to matching no routes.
After the selector update:
- the policy
metadata.generation increments
- status still shows the old
Accepted=True condition
observedGeneration remains stale
- no condition indicates that the selector now matches zero resources
This makes the policy appear attached/valid even after it no longer selects any targets.
Environment
- Envoy Gateway repo:
main
- Reproduced at commit:
69b1dde32fdb44cca7eb3ee880064e36f90492f4
Reproducer
- Install Envoy Gateway from current
main
- Apply the quickstart example
- Label the quickstart
HTTPRoute
- Create a
BackendTrafficPolicy using targetSelectors that matches that route
- Update the same policy so the selector matches no routes
Route setup
Label the quickstart route:
kubectl -n default label httproute backend app=selector-demo --overwrite
Initial policy that matches
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: quickstart-btp-selector-flip
namespace: default
spec:
targetSelectors:
- group: gateway.networking.k8s.io
kind: HTTPRoute
matchLabels:
app: selector-demo
retry:
numRetries: 3
perRetry:
backOff:
baseInterval: 100ms
maxInterval: 1s
timeout: 250ms
retryOn:
triggers:
- 5xx
- gateway-error
- connect-failure
Observed status after create:
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg
namespace: default
conditions:
- type: Accepted
status: "True"
reason: Accepted
message: Policy has been accepted.
observedGeneration: 1
Updated policy that matches nothing
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: quickstart-btp-selector-flip
namespace: default
spec:
targetSelectors:
- group: gateway.networking.k8s.io
kind: HTTPRoute
matchLabels:
app: selector-demo-no-match
retry:
numRetries: 3
perRetry:
backOff:
baseInterval: 100ms
maxInterval: 1s
timeout: 250ms
retryOn:
triggers:
- 5xx
- gateway-error
- connect-failure
The route labels remain:
kubectl -n default get httproute backend --show-labels
# NAME HOSTNAMES AGE LABELS
# backend ["www.example.com"] ... app=selector-demo
So the updated selector no longer matches any route.
Observed behavior
After the update:
metadata.generation increments from 1 to 2
- status still shows:
Accepted=True
observedGeneration=1
- no condition indicates:
- zero selected resources
- detachment / unattached policy
- selector resolution result for the new generation
Example:
metadata:
generation: 2
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg
namespace: default
conditions:
- type: Accepted
status: "True"
reason: Accepted
message: Policy has been accepted.
observedGeneration: 1
Expected behavior
When a selector-based policy changes from matching resources to matching none, I’d expect status to reflect the new state, for example by:
- updating
observedGeneration to the latest generation
- surfacing a condition that zero resources are selected / attached
- or otherwise making it clear that the policy is no longer effectively attached
Why this matters
This is misleading operationally:
- the initial status looks correct when the selector matches
- after the update, the policy still appears accepted and attached
- but it no longer selects any resources
- stale
observedGeneration makes it unclear whether reconciliation completed
This makes selector-based policy debugging harder and can hide unintended detachments.
Description
A
BackendTrafficPolicyusingtargetSelectorscan become stale in status when the selector is changed from matching an existing route to matching no routes.After the selector update:
metadata.generationincrementsAccepted=TrueconditionobservedGenerationremains staleThis makes the policy appear attached/valid even after it no longer selects any targets.
Environment
main69b1dde32fdb44cca7eb3ee880064e36f90492f4Reproducer
mainHTTPRouteBackendTrafficPolicyusingtargetSelectorsthat matches that routeRoute setup
Label the quickstart route:
Initial policy that matches
Observed status after create:
Updated policy that matches nothing
The route labels remain:
So the updated selector no longer matches any route.
Observed behavior
After the update:
metadata.generationincrements from1to2Accepted=TrueobservedGeneration=1Example:
Expected behavior
When a selector-based policy changes from matching resources to matching none, I’d expect status to reflect the new state, for example by:
observedGenerationto the latest generationWhy this matters
This is misleading operationally:
observedGenerationmakes it unclear whether reconciliation completedThis makes selector-based policy debugging harder and can hide unintended detachments.