Repository navigation
feat(utils): sort and unify map iteration in NodeSelector and affinity generation sites - #6234
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @frenemy17. Thanks for your PR. I'm waiting for a fluid-cloudnative member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions 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/test-infra repository. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #6234 +/- ##
==========================================
+ Coverage 65.75% 65.79% +0.03%
==========================================
Files 486 486
Lines 34459 34444 -15
==========================================
+ Hits 22660 22663 +3
+ Misses 10044 10027 -17
+ Partials 1755 1754 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the deterministic affinity-ordering changes.
No open issues 🎉
What changed in this PR
Extends the deterministic affinity ordering introduced in #6217 to the webhook and persistent-volume creation sites tracked in #6220, reducing ordering-only differences in generated Kubernetes objects.
Changes:
- Adds a shared helper that converts selector maps into requirements sorted by key.
- Reuses the helper across affinity generation and sorts runtime-map iteration in the cache-locality webhook.
- Adds regression tests for sorted output, repeated-call consistency, and empty selectors.
| File | Description |
|---|---|
pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse.go |
Uses the shared helper for sorted FUSE selector requirements. |
pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse_test.go |
Tests stable ordering with multiple FUSE labels. |
pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache.go |
Sorts runtime keys before generating locality terms. |
pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache_test.go |
Tests deterministic preferred and required term ordering. |
pkg/utils/kubeclient/pod.go |
Replaces inline sorting with the shared helper. |
pkg/utils/dataset/volume/create.go |
Uses sorted selector requirements for PV affinity. |
pkg/utils/dataset/volume/create_test.go |
Tests multi-label PV affinity ordering. |
pkg/utils/affinity.go |
Adds ToNodeSelectorRequirements. |
pkg/utils/affinity_test.go |
Covers empty inputs, sorted requirements, and repeated calls. |
pkg/ddc/efc/create_volume.go |
Uses the shared helper for EFC PV affinity. |
pkg/ddc/efc/create_volume_test.go |
Tests sorted EFC PV selector requirements. |
Review effort: Balanced
cheyang
left a comment
There was a problem hiding this comment.
Both reviewers reproduced the #6220 nondeterminism on the merge-base and verified this head fixes all four enumerated sites: contract tests that were red on base (4–6 distinct orderings per site) are green at head in two independent harnesses, and a control test run on both revisions proves the MergeNodeSelectorAndNodeAffinity refactor is behavior-preserving. Required CI is green at head (all checks SUCCESS, PR mergeable — the round-start snapshot predated the runs completing). Two pre-existing gaps remain outside this PR's scope: RequireNodeWithFuse.Mutate still iterates its runtimeInfos map unsorted, so per-runtime term order stays random for multi-dataset pods (cosmetic — terms are OR-ed; a verified one-line utils.OrderedKeys fix would close it), and pkg/dataflow/helper.go has the same unsorted-map class in dataflow job affinity. Two nits on the new code: the determinism subtest derives its expectation from the function under test, and the len(selectors) > 0 guard at require_node_with_fuse.go:93 is redundant. Agreement between the reviewers was complete — every finding was independently confirmed in cross-critique with zero disputes; Copilot (Reviewer C) reviewed this head and raised no findings.
Next round
[MUST] none — nothing blocks merge; required CI is green at head
[SHOULD] fold in the one-line utils.OrderedKeys fix for the Mutate term order (F1), or track it as a follow-up issue
[SHOULD] file a follow-up issue for the unsorted annotation iteration in pkg/dataflow/helper.go (F2)
[SHOULD] make deterministic_order_over_repeated_calls assert a literal sorted expectation instead of the function's own output (F3)
[SHOULD] drop the redundant len(selectors) > 0 guard at require_node_with_fuse.go:93 (F4)
[Filed] none this round — F1 and F2 are proposed as follow-up issues, not yet opened
Non-blocking observations (4) — not part of this round's ask; fix now, follow up, or file an issue as you see fit
- pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse.go:61 [minor] — pre-existing, not introduced by this PR — Per-runtime NodeSelectorTerm order in RequireNodeWithFuse.Mutate is still map-random (consensus: both reviewers, independently found and cross-confirmed)
Mutate() still iteratesfor _, runtime := range runtimeInfos— a map[string]base.RuntimeInfoInterface — appending one NodeSelectorTerm per runtime, so for a pod bound to multiple datasets with global-fuse node selectors the ORDER of the injected NodeSelectorTerms in the mutated pod spec remains non-deterministic between admissions. The PR sorted the MatchExpressions inside each term (utils.ToNodeSelectorRequirements) and gave the sibling nodeaffinitywithcache plugin the full utils.OrderedKeys treatment over its runtime-map loops, but left this outer loop unordered — the same class of non-determinism this PR exists to eliminate, in a file it already touches. Impact is cosmetic: required NodeSelectorTerms are OR-ed, so scheduling semantics are unchanged; only byte-determinism of the mutated spec is affected. Pre-existing loop (untouched by the diff, byte-identical on merge-base), so it does not gate this merge. A verified one-line fix exists:for _, name := range utils.OrderedKeys(runtimeInfos)+runtimeInfos[name], mirroring the sibling plugin. - pkg/dataflow/helper.go:50 [minor] — pre-existing, not introduced by this PR — pkg/dataflow/helper.go builds MatchExpressions from unsorted annotation iteration (Reviewer A finding, CONFIRMed by Reviewer B in cross-critique)
for key, value := range annotationsappends one NodeSelectorRequirement per AnnotationDataFlowCustomizedAffinityPrefix annotation in map order — the same nondeterminism class as #6220, at a site the issue does not enumerate (dataflow job affinity, opt-in via AnnotationDataFlowAffinityInject: "true"). The PR fully fixes the enumerated list, so merging legitimately closes #6220, but the broader 'unify ... sites' ask stays incomplete here. The requirements land AND-ed in a single NodeSelectorTerm, so order is purely cosmetic in the generated job spec. Recommend a follow-up issue rather than expanding this PR. - pkg/utils/affinity_test.go:146 [nit] — determinism subtest derives its expectation from the function under test (PR-introduced nit)
The deterministic_order_over_repeated_calls subtest setsexpected := ToNodeSelectorRequirements(multi)(line 154) and compares 100 further calls against that self-derived value, so it detects cross-call nondeterminism but would stay green under a consistently-unsorted implementation. The multiple_entries_sorted table case pins a literal sorted expectation (arch, instance, region, zone), so real coverage exists; consider asserting a literal sorted expectation in the subtest too. Cross-critique: Reviewer B CONFIRMed (verified at head, including the table-case mitigation). - pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse.go:93 [nit] — redundant len(selectors) > 0 guard around the ToNodeSelectorRequirements append (PR-introduced nit; Reviewer B finding, CONFIRMed by Reviewer A)
Theif len(selectors) > 0guard aroundappend(..., utils.ToNodeSelectorRequirements(selectors)...)is redundant: ToNodeSelectorRequirements returns nil for an empty map and append(x, nil...) is a no-op. Harmless (and note it does NOT mirror the PV call sites, where the len>0 guard legitimately protects construction of the whole pv.Spec.NodeAffinity block — only here, wrapping a bare append onto a pre-initialized slice, is it dead weight). Drop it or keep it; either way it is a nit only.
Verified review delta
- F1-term-order-requirenodewithfuse: open (preexisting)
- F2-dataflow-helper-unsorted: open (preexisting)
- F3-self-referential-determinism-test: open (initial-pr)
- F4-redundant-empty-selector-guard: open (initial-pr)
Code: clear. CI: success; attribution: unknown. Required CI checks still apply.
…y generation sites Introduce utils.ToNodeSelectorRequirements in pkg/utils/affinity.go to convert selector maps into key-sorted NodeSelectorRequirements. Refactor MergeNodeSelectorAndNodeAffinity, RequireNodeWithFuse webhook (including ordered runtimeInfos iteration), PV volume creation sites, and NodeAffinityWithCache webhook to use deterministic sorted iteration. Signed-off-by: Siddhanth Sadashiv Raikar <raikarsiddhanth@gmail.com>
c9dfc9f to
5648784
Compare
|
@cheyang Thanks for the review — addressed all four
Re-verified with |
|
cheyang
left a comment
There was a problem hiding this comment.
Re-checked the delta since the 11-08 review (c9dfc9f -> 5648784); the four SHOULDs are handled. RequireNodeWithFuse.Mutate walks runtimeInfos via utils.OrderedKeys, with a spec pinning the term order across repeated calls; TestToNodeSelectorRequirements compares against a literal sorted slice; the redundant len(selectors) guard is gone (appending an empty result is a no-op); and the pkg/dataflow/helper.go iteration is tracked in #6238. CI is green on this head, and all touched packages build, vet and pass locally on top of current master. Thanks @frenemy17!



Ⅰ. Describe what this PR does
Direct follow-up to #6217 and fixes #6220.
In #6216 and #6217,
MergeNodeSelectorAndNodeAffinitywas updated to sortnodeSelectorkeys alphabetically to ensure deterministic output order and prevent spurious status updates. During review of #6217, several other sites were identified where map iteration without key sorting caused non-deterministic ordering:pkg/utils/dataset/volume/create.go:CreatePersistentVolumeForRuntimeiterated overnodeSelectormap directly when building PV node affinityMatchExpressions.pkg/ddc/efc/create_volume.go:createPersistentVolumeForRuntimeiterated overnodeSelectormap directly when building PV node affinityMatchExpressions.pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse.go:getRequiredSchedulingTermiterated overselectorsmap directly to appendMatchExpressions.pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache.go:getTieredLocalityPreferredSchedulingTermsandgetTieredLocalityNodeSelectorTermsiterated overpreferredRuntimesandruntimeInfosmaps directly, producing non-deterministic term order in mutated pod specs for pods bound to multiple datasets/runtimes.This PR addresses all these sites:
utils.ToNodeSelectorRequirements(selectors map[string]string) []corev1.NodeSelectorRequirementinpkg/utils/affinity.go, which converts selector maps into[]corev1.NodeSelectorRequirementsorted alphabetically by key.MergeNodeSelectorAndNodeAffinity(pkg/utils/kubeclient/pod.go),CreatePersistentVolumeForRuntime(pkg/utils/dataset/volume/create.go),createPersistentVolumeForRuntime(pkg/ddc/efc/create_volume.go), andRequireNodeWithFusewebhook (pkg/webhook/plugins/requirenodewithfuse) to useutils.ToNodeSelectorRequirements.NodeAffinityWithCachewebhook (pkg/webhook/plugins/nodeaffinitywithcache) to iterate overutils.OrderedKeys(preferredRuntimes)andutils.OrderedKeys(runtimeInfos), guaranteeing deterministic term order.Ⅱ. Does this pull request fix one issue?
fixes #6220
Ⅲ. List the added test cases (unit test/integration test) if any, please explain if no tests are needed.
pkg/utils/affinity_test.go:TestToNodeSelectorRequirementscovering nil map, empty map, single entry, multi-entry sorted, and 100 repeated iterations asserting deterministic ordering.pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse_test.go:MatchExpressionsacross 50 iterations.pkg/utils/dataset/volume/create_test.go:pkg/ddc/efc/create_volume_test.go:TestEFCEngine_createFusePersistentVolumeWithNodeSelectorverifying PV node affinity match expressions are sorted alphabetically.pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache_test.go:TestTieredLocalityDeterministicOrderingverifying that both preferred and required scheduling terms for multi-runtime pods are strictly sorted and deterministic across 50 iterations.Ⅳ. Describe how to verify it
Ⅴ. Special notes for reviews
None.