Skip to content

BackendTrafficPolicy status becomes stale when targetSelectors change from matching routes to matching no routes #8927

@zhaohuabing

Description

@zhaohuabing

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

  1. Install Envoy Gateway from current main
  2. Apply the quickstart example
  3. Label the quickstart HTTPRoute
  4. Create a BackendTrafficPolicy using targetSelectors that matches that route
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions