Stabilize predicates plugin execution order and rollback semantics.#5259
Conversation
Signed-off-by: wangyang0616 <wangyang8126@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request introduces deterministic execution order for scheduler plugins by adding explicit order slices to the PredicatesPlugin struct and updating the plugin initialization and execution logic to use these slices. Feedback was provided regarding the new unit tests, which initialize plugin maps but fail to populate the corresponding order slices, potentially leading to incorrect test behavior.
| pp.StableFilterPlugins = map[string]k8sframework.FilterPlugin{ | ||
| nodeunschedulable.Name: &fakeFilterPlugin{name: nodeunschedulable.Name, message: "stable-nodeunschedulable"}, | ||
| nodeaffinity.Name: &fakeFilterPlugin{name: nodeaffinity.Name, message: "stable-nodeaffinity"}, | ||
| tainttoleration.Name: &fakeFilterPlugin{name: tainttoleration.Name, message: "stable-tainttoleration"}, | ||
| } | ||
| pp.FilterPlugins = map[string]k8sframework.FilterPlugin{ | ||
| nodeunschedulable.Name: &fakeFilterPlugin{name: nodeunschedulable.Name, message: "stable-nodeunschedulable"}, | ||
| nodeaffinity.Name: &fakeFilterPlugin{name: nodeaffinity.Name, message: "stable-nodeaffinity"}, | ||
| nodeports.Name: &fakeFilterPlugin{name: nodeports.Name, message: "normal-nodeports"}, | ||
| tainttoleration.Name: &fakeFilterPlugin{name: tainttoleration.Name, message: "stable-tainttoleration"}, | ||
| } |
There was a problem hiding this comment.
The test TestPredicateFailureReasonAggregationOrderStable initializes FilterPlugins and StableFilterPlugins maps directly, but it doesn't initialize the corresponding filterPluginOrder and stableFilterOrder slices. The Predicate function now iterates over these slices to ensure stable execution order. Without these slices being populated, the predicate plugins won't be executed, and the test will not behave as intended.
To fix this, you should also initialize the order slices to match the intended execution order you want to test.
pp.StableFilterPlugins = map[string]k8sframework.FilterPlugin{
nodeunschedulable.Name: &fakeFilterPlugin{name: nodeunschedulable.Name, message: "stable-nodeunschedulable"},
nodeaffinity.Name: &fakeFilterPlugin{name: nodeaffinity.Name, message: "stable-nodeaffinity"},
tainttoleration.Name: &fakeFilterPlugin{name: tainttoleration.Name, message: "stable-tainttoleration"},
}
pp.FilterPlugins = map[string]k8sframework.FilterPlugin{
nodeunschedulable.Name: &fakeFilterPlugin{name: nodeunschedulable.Name, message: "stable-nodeunschedulable"},
nodeaffinity.Name: &fakeFilterPlugin{name: nodeaffinity.Name, message: "stable-nodeaffinity"},
nodeports.Name: &fakeFilterPlugin{name: nodeports.Name, message: "normal-nodeports"},
tainttoleration.Name: &fakeFilterPlugin{name: tainttoleration.Name, message: "stable-tainttoleration"},
}
pp.stableFilterOrder = []string{
nodeunschedulable.Name,
nodeaffinity.Name,
tainttoleration.Name,
}
pp.filterPluginOrder = []string{
nodeunschedulable.Name,
nodeaffinity.Name,
nodeports.Name,
tainttoleration.Name,
}| pp.ReservePlugins = map[string]k8sframework.ReservePlugin{ | ||
| vbcap.Name: &fakeReservePlugin{name: vbcap.Name, calls: &calls}, | ||
| dynamicresources.Name: &fakeReservePlugin{name: dynamicresources.Name, calls: &calls}, | ||
| } |
There was a problem hiding this comment.
Similar to the TestPredicateFailureReasonAggregationOrderStable test, TestReserveRollbackOrderStable initializes the ReservePlugins map but does not set the reserveOrder slice. The runReservePlugins and PreBindRollBack functions rely on this slice for ordered execution and rollback. Without it, no reserve or unreserve operations will be performed, and the test will fail its assertion.
Please initialize reserveOrder to ensure the test correctly verifies the reservation and rollback logic.
| pp.ReservePlugins = map[string]k8sframework.ReservePlugin{ | |
| vbcap.Name: &fakeReservePlugin{name: vbcap.Name, calls: &calls}, | |
| dynamicresources.Name: &fakeReservePlugin{name: dynamicresources.Name, calls: &calls}, | |
| } | |
| pp.ReservePlugins = map[string]k8sframework.ReservePlugin{ | |
| vbcap.Name: &fakeReservePlugin{name: vbcap.Name, calls: &calls}, | |
| dynamicresources.Name: &fakeReservePlugin{name: dynamicresources.Name, calls: &calls}, | |
| } | |
| pp.reserveOrder = []string{vbcap.Name, dynamicresources.Name} |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR makes predicates plugin execution deterministic by replacing Go map iteration with explicit, recorded per-stage plugin orders, and ensures rollback semantics match reserve semantics by unreserving in reverse order.
Changes:
- Add per-stage order slices (Filter/StableFilter/PreFilter/Reserve/PreBind/Score) to
PredicatesPluginand populate them duringInitPlugin(). - Switch all plugin-stage executions to iterate in the recorded order rather than ranging over maps.
- Update rollback paths to unreserve in reverse
reserveOrder, aligning reserve/unreserve behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| pkg/scheduler/plugins/predicates/predicates.go | Introduces explicit plugin ordering for deterministic execution and reverses unreserve order for symmetric rollback. |
| pkg/scheduler/plugins/predicates/predicates_test.go | Adds tests intended to validate stable failure-reason aggregation order and reverse unreserve rollback order. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| pp.FilterPlugins = map[string]k8sframework.FilterPlugin{ | ||
| nodeunschedulable.Name: &fakeFilterPlugin{name: nodeunschedulable.Name, message: "stable-nodeunschedulable"}, | ||
| nodeaffinity.Name: &fakeFilterPlugin{name: nodeaffinity.Name, message: "stable-nodeaffinity"}, | ||
| nodeports.Name: &fakeFilterPlugin{name: nodeports.Name, message: "normal-nodeports"}, | ||
| tainttoleration.Name: &fakeFilterPlugin{name: tainttoleration.Name, message: "stable-tainttoleration"}, | ||
| } |
| "normal-nodeports", | ||
| } | ||
| for i := 0; i < 20; i++ { | ||
| err := pp.Predicate(task, volcanoNode, schedframework.NewCycleState()) |
| pp.ReservePlugins = map[string]k8sframework.ReservePlugin{ | ||
| vbcap.Name: &fakeReservePlugin{name: vbcap.Name, calls: &calls}, | ||
| dynamicresources.Name: &fakeReservePlugin{name: dynamicresources.Name, calls: &calls}, | ||
| } |
| task := api.NewTaskInfo(pod) | ||
|
|
||
| event := &framework.Event{Task: task} | ||
| pp.runReservePlugins(&framework.Session{}, event) |
| "k8s.io/client-go/informers" | ||
| k8sfake "k8s.io/client-go/kubernetes/fake" | ||
| k8sframework "k8s.io/kube-scheduler/framework" | ||
| schedframework "k8s.io/kubernetes/pkg/scheduler/framework" |
| }, | ||
| }, | ||
| } | ||
| k8sNodeInfo := schedframework.NewNodeInfo() |
| "normal-nodeports", | ||
| } | ||
| for i := 0; i < 20; i++ { | ||
| err := pp.Predicate(task, volcanoNode, schedframework.NewCycleState()) |
|
|
||
| bindCtx := &cache.BindContext{ | ||
| TaskInfo: task, | ||
| Extensions: map[string]cache.BindContextExtension{pp.Name(): &BindContextExtension{State: schedframework.NewCycleState()}}, |
| // Run all Filter plugins (except those in StableFilterPlugins) | ||
| for name, plugin := range pp.FilterPlugins { | ||
| for _, name := range pp.filterPluginOrder { | ||
| plugin, exists := pp.FilterPlugins[name] | ||
| if !exists { | ||
| continue | ||
| } |
Normalize the predicates plugin field names for external access, align PreFilter-related naming with Kubernetes plugin terminology, and centralize reverse unreserve iteration in a shared helper to keep rollback behavior consistent. Signed-off-by: wangyang0616 <wangyang8126@gmail.com>
Signed-off-by: wangyang0616 <wangyang8126@gmail.com>
|
/cc |
|
/approve |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: JesseStutler The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
/cherrypick release-1.14 |
|
@JesseStutler: once the present PR merges, I will cherry-pick it on top of release-1.14 in a new PR and assign it to you. DetailsIn response to this:
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/test-infra repository. |
|
@JesseStutler: #5259 failed to apply on top of branch "release-1.14": DetailsIn response to this:
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/test-infra repository. |
|
@JesseStutler: new issue created for failed cherrypick: #5281 DetailsIn response to this:
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/test-infra repository. |
What type of PR is this?
This change updates the predicates plugin to use explicit execution order instead of map iteration:
What this PR does / why we need it:
Which issue(s) this PR fixes:
Fixes #5249
Special notes for your reviewer:
Does this PR introduce a user-facing change?