Skip to content

fix: report BackendTrafficPolicy TargetNotFound when it attaches to no target#9416

Open
vishalsolanki-fs wants to merge 2 commits into
envoyproxy:mainfrom
vishalsolanki-fs:fix/btp-status-target-not-found
Open

fix: report BackendTrafficPolicy TargetNotFound when it attaches to no target#9416
vishalsolanki-fs wants to merge 2 commits into
envoyproxy:mainfrom
vishalsolanki-fs:fix/btp-status-target-not-found

Conversation

@vishalsolanki-fs

Copy link
Copy Markdown

What this fixes

A BackendTrafficPolicy that resolves to zero targets currently leaves its status stale. This PR makes the translator report such policies explicitly. It addresses two separate issues with the same symptom but different root causes:

The fix

After all target-processing loops in ProcessBackendTrafficPolicies, any policy that resolved to zero ancestors is now reported with:

  • Accepted = False
  • reason TargetNotFound
  • the current metadata.generation as observedGeneration

Policies that attached to at least one target are left untouched. Selector-only policies that matched nothing are added to the result set so their status is published.

Follows an existing pattern in the repo

This change reuses the status-reporting building blocks already used across the codebase rather than introducing new ones:

  • The gwapiv1.PolicyReasonTargetNotFound reason is already used in the repo for the "section within an existing target not found" case (e.g. validate.go and resolveClientTrafficPolicyTargetRef in clienttrafficpolicy.go). This PR extends that same reason to the "target object entirely missing / zero targets matched" case.
  • Conditions are set with the existing status.SetConditionForPolicyAncestor helper, the same helper already used throughout ProcessBackendTrafficPolicies.

Design note — ancestor reference for unattached policies

Per the Gateway API, each PolicyAncestorStatus entry must carry an ancestorRef. When a BackendTrafficPolicy resolves to zero targets there is no resolved Gateway to use as the ancestor, so this PR chooses one deterministically:

  • If the policy uses an explicit targetRef/targetRefs, the (unresolved) target is used as the ancestorRef, so the reported status points directly at the missing object.
  • If the policy uses only targetSelectors (which reference no named object), the policy references itself as the ancestorRef.

I'd appreciate maintainer confirmation that this ancestor convention is acceptable, since the Gateway API doesn't prescribe one for the "attached to nothing" case.

Scope

This PR is intentionally scoped to BackendTrafficPolicy to keep it focused on the two filed issues. The same early-return / never-added gap exists in the sibling policy types (SecurityPolicy, ClientTrafficPolicy, and others). If the maintainers are happy with the approach and the ancestor convention above, I'm glad to generalize it across the other policy types in a follow-up PR.

Testing

Release note

Added release-notes/current/bug_fixes/8926-btp-status-target-not-found.md.

Made with Cursor

…o target

A BackendTrafficPolicy that resolves to zero targets left its status stale:

- targetRef/targetRefs pointing at a nonexistent object kept Accepted=True
  with an out-of-date observedGeneration, and emitted no negative condition
  (envoyproxy#8926). The policy was added to the result set but processing returned
  early once the target object could not be resolved, so no ancestor status
  was ever written.
- targetSelectors that match no objects never added the policy to the result
  set at all, so its status was never reconciled and stayed stale (envoyproxy#8927).

After all target-processing loops, report any policy that resolved to zero
ancestors with Accepted=False, reason TargetNotFound, and the current
generation. Policies using explicit targetRefs use the unresolved target as
the ancestor ref; selector-only policies reference the policy itself.

Fixes envoyproxy#8926
Fixes envoyproxy#8927

Signed-off-by: Vishal <[email protected]>
Co-authored-by: Cursor <[email protected]>
@vishalsolanki-fs vishalsolanki-fs requested a review from a team as a code owner July 4, 2026 18:47
@netlify

netlify Bot commented Jul 4, 2026

Copy link
Copy Markdown

Deploy Preview for cerulean-figolla-1f9435 ready!

Name Link
🔨 Latest commit 751ff5e
🔍 Latest deploy log https://app.netlify.com/projects/cerulean-figolla-1f9435/deploys/6a49583cd9ee8400082c1146
😎 Deploy Preview https://deploy-preview-9416--cerulean-figolla-1f9435.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f799e2f21

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
// If the policy attached to at least one target, it already has ancestor
// status set by the processing loops above; leave it untouched.
if len(policy.Status.Ancestors) > 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not treat copied ancestors as current attachments

Because backendTrafficPolicyCopiesWithStatusDeepCopy preserves the policy's existing status, an unattached policy that previously had any ancestor (for example, a selector that used to match or a targetRef edited to a missing route) reaches this branch with stale ancestors even though none of the processing loops handled it. This continue leaves the old Accepted=True condition and observedGeneration in the returned status, so the stale-status scenario this block is meant to fix still reproduces for existing policies; only brand-new policies with empty status get TargetNotFound.

Useful? React with 👍 / 👎.

@vishalsolanki-fs vishalsolanki-fs Jul 4, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, inferring "attached" from existing ancestor status is fragile since the copies preserve prior status. Pushed a fix, the process funcs now return whether they actually attached, and any unattached policy has its carried-over ancestors cleared before TargetNotFound is set. Added a golden with pre-existing stale status to cover it. @codex review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 751ff5e35c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Track whether each policy actually attached to a target during translation
instead of inferring it from the presence of ancestor status. A policy that
carries ancestor status from a previous generation (e.g. it used to match a
target but its targetRef/targetSelectors were then changed to match nothing)
would otherwise skip the TargetNotFound handling and keep its stale
Accepted=True condition. Now any policy not attached to a target this pass has
its carried-over ancestors cleared before TargetNotFound is set with the
current generation.

Adds a golden case exercising a policy with pre-existing stale status.

Signed-off-by: Vishal <[email protected]>
Co-authored-by: Cursor <[email protected]>
@arkodg

arkodg commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

instead of the status generation layer, we probably should clean up older stale statuses in

// BackendTrafficPolicy object status updater

cc @zhaohuabing

@vishalsolanki-fs

Copy link
Copy Markdown
Author

instead of the status generation layer, we probably should clean up older stale statuses in

// BackendTrafficPolicy object status updater

cc @zhaohuabing

Thanks @arkodg , A couple of things I ran into while prototyping the status.go cleanup:

  1. When a BTP stops attaching to any target, should the ancestor status be fully cleared (leaving status.ancestors empty), or should we still surface a terminal Accepted=False/TargetNotFound condition, just refreshed (non-stale) instead of synthesized in the translator? The two filed issues (BackendTrafficPolicy status does not report missing targetRef and leaves stale observedGeneration #8926/BackendTrafficPolicy status becomes stale when targetSelectors change from matching routes to matching no routes #8927) are only about staleness, so clearing satisfies both, but clearing removes the user-facing "why isn't my policy attaching" signal.

  2. I found the two issues actually reach the status layer differently: the targetSelectors-match-nothing case (BackendTrafficPolicy status becomes stale when targetSelectors change from matching routes to matching no routes #8927) already falls out of the result set and produces a status delete event, so status.go can clean it up directly. But the missing-targetRef case (BackendTrafficPolicy status does not report missing targetRef and leaves stale observedGeneration #8926) stays in the result set carrying its deep-copied stale status, so it's re-published and never produces a delete. To route it through the same cleanup, the translator has to stop propagating that stale copy (a small subtractive change, no synthesized condition). Is that split acceptable, or do you want the whole thing handled purely in status.go?

  3. Since the if update.Delete { return } skip is identical across all ~17 policy/route status subscribers (and the route path already carries a TODO about dangling parent status for the same reason), do you want this generalized, or scoped to BackendTrafficPolicy first?

@vishalsolanki-fs

Copy link
Copy Markdown
Author

instead of the status generation layer, we probably should clean up older stale statuses in

// BackendTrafficPolicy object status updater

cc @zhaohuabing

I opened #9420 as an alternative implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants