Skip to content

test(conformance): preserve Route status entries owned by other controllers - #5138

Open
lexfrei wants to merge 1 commit into
kubernetes-sigs:mainfrom
lexfrei:conformance/preserve-foreign-status
Open

test(conformance): preserve Route status entries owned by other controllers#5138
lexfrei wants to merge 1 commit into
kubernetes-sigs:mainfrom
lexfrei:conformance/preserve-foreign-status

Conversation

@lexfrei

@lexfrei lexfrei commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind test

/area conformance-test

/area conformance-machinery

What this PR does / why we need it:

Two controllers on one cluster is a normal setup, and the spec is explicit that an implementation may only touch status.parents entries carrying its own controllerName. The suite had no coverage for that, so an implementation could wipe another controller's entry and still pass.

The harness had to move first. RouteMustHaveParents walked every entry in status.parents and required each one's conditions to observe the Route's current generation. A second controller's entry does not track our Route's generation (and per the same MUST, the implementation under test is forbidden from refreshing it), so any Route carrying a foreign entry stalled the wait until timeout. That is a real problem on a shared cluster, and this PR fixes only as much of it as the test needs: the check now skips entries whose controllerName is StaleControllerName, a reserved value tests put on a status entry to stand in for a second implementation, so a seeded entry no longer stalls the wait. An entry carrying any other controllerName still has to observe the generation. Generalising that means passing the caller's controllerName into the helper, which is the signature change the scope note below defers. One side note on the same helper: the exact-set worry in the issue turned out not to apply, parentsForRouteMatch is already a subset check and ignores extra entries.

One behaviour change worth calling out. The observedGeneration check in RouteMustHaveParents was dead on the fast path: actual was assigned after the loop that reads it, so the first poll iteration ran the loop over an empty slice, and if parentsForRouteMatch was already satisfied the helper returned right there. That is the normal path on a passing run, so for those runs the check never ran at all. It runs now, and that reaches every *RouteMustHaveParents caller in the suite: an implementation whose status matches on type, status and reason but has not caught up with the Route generation will now keep the wait going and can time out where it passed before. A unit test pins the ordering, it fails with the old ordering and passes with the new one.

The new test attaches an HTTPRoute to same-namespace plus a second parentRef the implementation cannot resolve, writes the entry a second controller would have written for that parentRef, then changes the Route spec so the implementation has to do a full read-modify-write of a status that already holds the foreign entry. Waiting for the implementation's own entry to observe the bumped generation is what proves it wrote status while the foreign entry was there; the entry is then compared field for field, so a removal, a rewrite, or a dropped condition all fail.

It is marked Provisional, and gated on SupportGateway + SupportHTTPRoute only: no new feature is involved, the MUST applies to every implementation that writes Route status.

The harness change is covered by unit tests against a fake client. Test_staleParentStatus pins which entries the observedGeneration check skips: the reserved sentinel is exempt, an entry carrying any other controllerName is not. TestRouteMustHaveParentsIgnoresStaleControllerEntries drives HTTPRouteMustHaveParents over a Route holding a deliberately stale sentinel entry; it times out on main and passes here. TestRouteMustHaveParentsChecksTheStatusItJustRead pins the read ordering by counting reads: with the old ordering the wait is satisfied by the first read, and the test fails.

Scope note, per the plan in the issue: this is Route parent status only. Gateway and Listener conditions and policy ancestor status are separate follow-ups. Some sibling helpers are still single-controller by construction (HTTPRouteMustHaveNoAcceptedParents and its TLS/TCP/UDP twins hard-require at most one entry, and HTTPRouteMustHaveLatestConditions walks every entry), but none of them takes a controllerName today, so relaxing them means changing exported signatures. I left them alone rather than smuggle that into this PR.

Locally: build, vet, lint, and the unit tests above. I also ran the new conformance test against my own implementation on kind with a real Cloudflare Tunnel, it passes and does not silently skip. That says something about the test, not about anyone's conformance. Prow has not run anything here at all, the needs-ok-to-test label is still on it. I have not done a full suite run producing a fresh conformance report.

AI assistance: drafted with Claude, reviewed and verified by me before submitting.

Which issue(s) this PR fixes:

Part of #5105

Does this PR introduce a user-facing change?:

Conformance: RouteMustHaveParents now runs the observedGeneration check against the status it just read. Previously the check was skipped whenever a Route's parents already matched on the first poll, which is the normal case on a passing run, so implementations whose status lagged the Route generation could pass without it ever running; those runs will now wait for observedGeneration to catch up and may fail where they passed before. Entries carrying the reserved controllerName that conformance tests use to stand in for a second implementation are exempt; entries written by real controllers are still checked.

AI disclosure

This PR was prepared with AIL:3. I personally reviewed each line of the submission prior to opening this PR.

@kubernetes-prow kubernetes-prow Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. kind/test area/conformance-test Issues or PRs related to Conformance tests. area/conformance-machinery Issues or PRs related to the machinery and the suite used to run conformance tests. labels Aug 4, 2026
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lexfrei
Once this PR has been reviewed and has the lgtm label, please assign mikemorris for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow
kubernetes-prow Bot requested review from kl52752 and mikemorris August 4, 2026 15:20
@kubernetes-prow kubernetes-prow Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Aug 4, 2026
@kubernetes-prow

Copy link
Copy Markdown

Hi @lexfrei. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@snorwin

snorwin commented Aug 5, 2026

Copy link
Copy Markdown
Member

/cc

@kubernetes-prow
kubernetes-prow Bot requested a review from snorwin August 5, 2026 07:18
Comment on lines +34 to +36
// foreignControllerName stands in for a second implementation sharing the
// cluster. Nothing reconciles it, so any change to its status entry can only
// have come from the implementation under test.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In general, AI-generated comments aren't worth to be added to the code, because if I want them, I can easily generate them myself by asking a model of my choice to explain me the code.
However, if you feel some part of the code needs further explanation because it does something unusual, goes against best practices, or is really complex, feel free to write those comments by hand.

Suggested change
// foreignControllerName stands in for a second implementation sharing the
// cluster. Nothing reconciles it, so any change to its status entry can only
// have come from the implementation under test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped. Swept the rest of the PR too, kept only the comments that say something the code cannot, like the lastTransitionTime truncation.

// foreignControllerName stands in for a second implementation sharing the
// cluster. Nothing reconciles it, so any change to its status entry can only
// have come from the implementation under test.
const foreignControllerName = gatewayv1.GatewayController("example.com/foreign-controller")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since we want to create further tests with a second controller, I would introduce a sentinel in helpers.go, e.g., gateway.networking.k8s.io/skip-this-for-observed-generation or gateway.networking.k8s.io/stale-controller.

This would also simplify the changes in RouteMustHaveParents, which could then be used to assert the parent status of both controllers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Went with stale-controller over skip-this-for-observed-generation: the constant is also the fixture owner in the test, so it has to read like a controller name.

One gap: a real second implementation still stalls the wait with its own controllerName, same as on main today. Per the scope rule in the troubleshooting guide it should not be writing status on another implementation Routes at all, so I left it. I can keep the filter alongside the sentinel if you want that covered too.

@lexfrei
lexfrei force-pushed the conformance/preserve-foreign-status branch from 123ea37 to ddb3e4b Compare August 5, 2026 08:19
@kl52752

kl52752 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

hi lexfrei thank's for this PR. Can run this test with at least one implemenatation to verify that the flow is correct? I will try to run it against GKE today

@lexfrei

lexfrei commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Ran it against my own implementation (cloudflare-tunnel-gateway-controller) on kind with a real Cloudflare Tunnel. Passes, and it is not silently skipping: === RUN present, 2.16s, zero skip lines in the log.

Worth knowing what the run actually shows. After the spec bump my controller entry moves to observedGeneration 2 while the seeded entry stays at 1 and is otherwise untouched. That gap never closes, so the sentinel skip is what lets RouteMustHaveParents converge at all.

Curious what GKE does with it.


// The implementation's own entry catching up to the bumped generation is
// what proves it wrote status while the seeded entry was there.
kubernetes.HTTPRouteMustHaveParents(t, suite.Client, suite.TimeoutConfig, routeNN,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@lexfrei is there a reason the stale controller status isn't also asserted using HTTPRouteMustHaveParents?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No reason, added it.

Kept the comparison below because findConditionInList only looks at type, status and reason, and RouteMustHaveParents skips observedGeneration for the sentinel. An implementation that rewrites the entry with its own observedGeneration and lastTransitionTime, still Accepted=True, passes the helper. So the helper catches removal and the comparison catches a rewrite.

@lexfrei
lexfrei force-pushed the conformance/preserve-foreign-status branch from ddb3e4b to 95f45c5 Compare August 6, 2026 22:31
@lexfrei
lexfrei force-pushed the conformance/preserve-foreign-status branch 2 times, most recently from 7e57ab5 to d75d825 Compare August 14, 2026 08:52
@lexfrei

lexfrei commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@snorwin added the HTTPRouteMustHaveParents assertion you asked about.

Writing the tests turned up that the reorder fixes more than I thought. On main actual is assigned after the loop, so the first poll checks an empty slice, and if the status already matches the wait returns right there without ever looking at observedGeneration. That is the normal path on a green run. There is a test for it now, it fails with the old ordering. Description and release note updated for that.

@kl52752 did you get to run this against GKE?

Still needs an /ok-to-test before prow picks it up.

…ollers

RouteStatus.Parents is namespaced by (parentRef, controllerName), and an
implementation MUST NOT update entries whose controllerName is not its own.
Nothing in the suite checked that, and the harness could not have hosted
such a check: RouteMustHaveParents required every entry in status.parents
to carry the Route's current generation, so a stale entry left behind by a
second controller stalled the wait until it timed out.

Add a StaleControllerName sentinel that tests can put on a parent status
entry to stand in for another implementation, and skip those entries in the
observedGeneration check. Nothing reconciles them, so they cannot be
expected to keep up with the Route's generation. On top of that, add a test
that seeds such an entry, bumps the Route's generation to force a status
write, and checks that the seeded entry comes back byte for byte alongside
the implementation's own entry.

The observedGeneration check also ran against the parent list read on the
previous poll rather than the one just fetched. On the first iteration that
list was still empty, so a Route whose status already matched satisfied the
wait without the check running at all. It now runs on the snapshot it just
read, which tightens every RouteMustHaveParents caller in the suite: a
status that matches but has not caught up with the Route generation no
longer ends the wait early. The check moves into staleParentStatus so both
halves of it can be pinned directly.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei

lexfrei commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

@kl52752 did you get a chance to run this against GKE? I rebased the branch on current main in the meantime; the Netlify failure was stale and is green now.

@kl52752

kl52752 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

@lexfrei I managed to run this test on GKE with success.
Thanks for you PR
/lgtm

@kubernetes-prow kubernetes-prow Bot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/conformance-machinery Issues or PRs related to the machinery and the suite used to run conformance tests. area/conformance-test Issues or PRs related to Conformance tests. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/test lgtm "Looks good to me", indicates that a PR is ready to be merged. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants