This is an automated email from the ASF dual-hosted git repository.

wilfred-s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-k8shim.git


The following commit(s) were added to refs/heads/master by this push:
     new 1e54dced [YUNIKORN-3300] Use feasible nodes returned from K8s (#1043)
1e54dced is described below

commit 1e54dced2d0433753a66dfb9c980f8cabad56290
Author: Manikandan R <[email protected]>
AuthorDate: Tue Sep 8 18:39:10 2026 +1000

    [YUNIKORN-3300] Use feasible nodes returned from K8s (#1043)
    
    Made changes in preemption flow similar to the scheduling cycle flow in
    using prefilter and filter plugins. Use newly introduced SI API's to receive
    args and send respones of prefilter predicates API.
    Cache cycle state only when prefilter succeeds and delete as and when filter
    checks are over.
    Introduced Mocks for PreFilter & Filter Plugins. Used those mocks to improve
    test coverage for PreFilter and Filter predicate checks in PredicateManager.
    Delete cycle state even during preemption predicate checks and added tests.
    
    Closes: #1043
    
    Signed-off-by: Wilfred Spiegelenburg <[email protected]>
---
 go.mod                                          |  98 ++---
 go.sum                                          | 164 ++++----
 pkg/cache/context.go                            |  72 +++-
 pkg/cache/external/scheduler_cache.go           |  72 +++-
 pkg/cache/scheduler_callback.go                 |   9 +
 pkg/cache/scheduler_callback_test.go            |  95 ++++-
 pkg/cache/task.go                               |   7 +
 pkg/plugin/predicates/predicate_manager.go      | 154 ++++----
 pkg/plugin/predicates/predicate_manager_test.go | 499 ++++++++++++++++++++++--
 9 files changed, 884 insertions(+), 286 deletions(-)

diff --git a/go.mod b/go.mod
index 019eb8ab..7a20b685 100644
--- a/go.mod
+++ b/go.mod
@@ -26,82 +26,91 @@ require (
        github.com/google/go-cmp v0.7.0
        github.com/google/uuid v1.6.0
        github.com/looplab/fsm v1.0.3
-       github.com/onsi/ginkgo/v2 v2.28.1
-       github.com/onsi/gomega v1.40.0
+       github.com/onsi/ginkgo/v2 v2.32.1
+       github.com/onsi/gomega v1.42.1
        github.com/prometheus/client_golang v1.24.1
        github.com/sasha-s/go-deadlock v0.3.9
        go.uber.org/zap v1.28.0
        go.yaml.in/yaml/v3 v3.0.5
        gotest.tools/v3 v3.5.2
-       k8s.io/api v0.36.1
-       k8s.io/apimachinery v0.36.1
-       k8s.io/apiserver v0.36.1
-       k8s.io/cli-runtime v0.36.1
-       k8s.io/client-go v0.36.1
-       k8s.io/component-base v0.36.1
-       k8s.io/component-helpers v0.36.1
-       k8s.io/dynamic-resource-allocation v0.36.1
+       k8s.io/api v0.36.3
+       k8s.io/apimachinery v0.36.3
+       k8s.io/apiserver v0.36.3
+       k8s.io/cli-runtime v0.36.3
+       k8s.io/client-go v1.5.2
+       k8s.io/component-base v0.36.3
+       k8s.io/component-helpers v0.36.3
+       k8s.io/dynamic-resource-allocation v0.36.3
        k8s.io/klog/v2 v2.140.0
-       k8s.io/kube-scheduler v0.36.1
-       k8s.io/kubernetes v1.36.1
-       k8s.io/streaming v0.36.1
+       k8s.io/kube-scheduler v0.36.3
+       k8s.io/kubernetes v1.36.3
+       k8s.io/streaming v0.36.3
 )
 
 require (
        cel.dev/expr v0.25.2 // indirect
-       github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // 
indirect
+       github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // 
indirect
        github.com/Azure/go-ntlmssp v0.1.1 // indirect
-       github.com/Masterminds/semver/v3 v3.4.0 // indirect
+       github.com/Masterminds/semver/v3 v3.5.0 // indirect
        github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
        github.com/beorn7/perks v1.0.1 // indirect
        github.com/blang/semver/v4 v4.0.0 // indirect
        github.com/cespare/xxhash/v2 v2.3.0 // indirect
        github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 
indirect
        github.com/emicklei/go-restful/v3 v3.13.0 // indirect
-       github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+       github.com/fxamacker/cbor/v2 v2.9.2 // indirect
        github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
-       github.com/go-errors/errors v1.4.2 // indirect
+       github.com/go-errors/errors v1.5.1 // indirect
        github.com/go-ldap/ldap/v3 v3.4.14 // indirect
-       github.com/go-logr/logr v1.4.3 // indirect
-       github.com/go-openapi/jsonpointer v0.21.0 // indirect
-       github.com/go-openapi/jsonreference v0.20.2 // indirect
-       github.com/go-openapi/swag v0.23.0 // indirect
+       github.com/go-logr/logr v1.4.4 // indirect
+       github.com/go-openapi/jsonpointer v1.0.0 // indirect
+       github.com/go-openapi/jsonreference v1.0.0 // indirect
+       github.com/go-openapi/swag v0.28.0 // indirect
+       github.com/go-openapi/swag/cmdutils v0.28.0 // indirect
+       github.com/go-openapi/swag/conv v0.28.0 // indirect
+       github.com/go-openapi/swag/fileutils v0.28.0 // indirect
+       github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
+       github.com/go-openapi/swag/loading v0.28.0 // indirect
+       github.com/go-openapi/swag/mangling v0.28.0 // indirect
+       github.com/go-openapi/swag/netutils v0.28.0 // indirect
+       github.com/go-openapi/swag/pools v0.28.0 // indirect
+       github.com/go-openapi/swag/stringutils v0.28.0 // indirect
+       github.com/go-openapi/swag/typeutils v0.28.0 // indirect
+       github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
        github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
        github.com/google/btree v1.1.3 // indirect
-       github.com/google/cel-go v0.29.0 // indirect
-       github.com/google/gnostic-models v0.7.0 // indirect
-       github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
+       github.com/google/cel-go v0.31.0 // indirect
+       github.com/google/gnostic-models v0.7.1 // indirect
+       github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect
        github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // 
indirect
        github.com/inconshreveable/mousetrap v1.1.0 // indirect
-       github.com/josharian/intern v1.0.0 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
        github.com/julienschmidt/httprouter v1.3.0 // indirect
        github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // 
indirect
-       github.com/mailru/easyjson v0.7.7 // indirect
        github.com/moby/spdystream v0.5.1 // indirect
-       github.com/moby/term v0.5.0 // indirect
+       github.com/moby/term v0.5.2 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 
indirect
        github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // 
indirect
        github.com/monochromegane/go-gitignore 
v0.0.0-20200626010858-205db1a8cc00 // indirect
        github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 
indirect
        github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
-       github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75 // 
indirect
+       github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b // 
indirect
        github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 
indirect
        github.com/prometheus/client_model v0.6.2 // indirect
        github.com/prometheus/common v0.70.1 // indirect
        github.com/prometheus/procfs v0.21.1 // indirect
        github.com/spf13/cobra v1.10.2 // indirect
-       github.com/spf13/pflag v1.0.9 // indirect
+       github.com/spf13/pflag v1.0.10 // indirect
        github.com/tidwall/btree v1.8.1 // indirect
        github.com/x448/float16 v0.8.4 // indirect
        github.com/xlab/treeprint v1.2.0 // indirect
-       go.opentelemetry.io/otel v1.44.0 // indirect
-       go.opentelemetry.io/otel/trace v1.44.0 // indirect
+       go.opentelemetry.io/otel v1.45.0 // indirect
+       go.opentelemetry.io/otel/trace v1.45.0 // indirect
        go.uber.org/multierr v1.11.0 // indirect
        go.yaml.in/yaml/v2 v2.4.4 // indirect
        golang.org/x/crypto v0.56.0 // indirect
-       golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 // indirect
-       golang.org/x/mod v0.38.0 // indirect
+       golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 // indirect
+       golang.org/x/mod v0.40.0 // indirect
        golang.org/x/net v0.58.0 // indirect
        golang.org/x/oauth2 v0.36.0 // indirect
        golang.org/x/sync v0.22.0 // indirect
@@ -109,25 +118,24 @@ require (
        golang.org/x/term v0.45.0 // indirect
        golang.org/x/text v0.41.0 // indirect
        golang.org/x/time v0.15.0 // indirect
-       golang.org/x/tools v0.48.0 // indirect
-       google.golang.org/genproto/googleapis/api 
v0.0.0-20260526163538-3dc84a4a5aaa // indirect
-       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260803160001-6ac0973c030d // indirect
+       golang.org/x/tools v0.49.0 // indirect
+       google.golang.org/genproto/googleapis/api 
v0.0.0-20260810153831-ec0a7760b754 // indirect
+       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260810153831-ec0a7760b754 // indirect
        google.golang.org/grpc v1.83.1 // indirect
-       google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // 
indirect
+       google.golang.org/protobuf v1.36.12 // indirect
        gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
        gopkg.in/inf.v0 v0.9.1 // indirect
-       gopkg.in/yaml.v3 v3.0.1 // indirect
-       k8s.io/apiextensions-apiserver v0.0.0 // indirect
-       k8s.io/cloud-provider v0.34.2 // indirect
-       k8s.io/controller-manager v0.36.1 // indirect
-       k8s.io/csi-translation-lib v0.34.2 // indirect
-       k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
-       k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect
+       k8s.io/apiextensions-apiserver v0.36.3 // indirect
+       k8s.io/cloud-provider v0.36.3 // indirect
+       k8s.io/controller-manager v0.36.3 // indirect
+       k8s.io/csi-translation-lib v0.36.3 // indirect
+       k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
+       k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect
        sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
        sigs.k8s.io/kustomize/api v0.21.1 // indirect
        sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect
        sigs.k8s.io/randfill v1.0.0 // indirect
-       sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
+       sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
        sigs.k8s.io/yaml v1.6.0 // indirect
 )
 
diff --git a/go.sum b/go.sum
index 2abddadb..43150054 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,11 @@
 cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
 cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
-github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 
h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
-github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod 
h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c 
h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod 
h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/Azure/go-ntlmssp v0.1.1 
h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
 github.com/Azure/go-ntlmssp v0.1.1/go.mod 
h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
-github.com/Masterminds/semver/v3 v3.4.0 
h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
-github.com/Masterminds/semver/v3 v3.4.0/go.mod 
h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/Masterminds/semver/v3 v3.5.0 
h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
+github.com/Masterminds/semver/v3 v3.5.0/go.mod 
h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
 github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e 
h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
 github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod 
h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/antlr4-go/antlr/v4 v4.13.1 
h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
@@ -23,7 +23,6 @@ github.com/blang/semver/v4 v4.0.0/go.mod 
h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y
 github.com/cespare/xxhash/v2 v2.3.0 
h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod 
h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.9/go.mod 
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod 
h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -34,8 +33,8 @@ github.com/distribution/reference v0.6.0 
h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
 github.com/distribution/reference v0.6.0/go.mod 
h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/emicklei/go-restful/v3 v3.13.0 
h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
 github.com/emicklei/go-restful/v3 v3.13.0/go.mod 
h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
-github.com/fxamacker/cbor/v2 v2.9.0 
h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
-github.com/fxamacker/cbor/v2 v2.9.0/go.mod 
h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
+github.com/fxamacker/cbor/v2 v2.9.2 
h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
+github.com/fxamacker/cbor/v2 v2.9.2/go.mod 
h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
 github.com/gkampitakis/ciinfo v0.3.2 
h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
 github.com/gkampitakis/ciinfo v0.3.2/go.mod 
h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
 github.com/gkampitakis/go-diff v1.3.2 
h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
@@ -44,22 +43,48 @@ github.com/gkampitakis/go-snaps v0.5.15 
h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01
 github.com/gkampitakis/go-snaps v0.5.15/go.mod 
h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
 github.com/go-asn1-ber/asn1-ber v1.5.8 
h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
 github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod 
h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-errors/errors v1.4.2 
h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
-github.com/go-errors/errors v1.4.2/go.mod 
h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
+github.com/go-errors/errors v1.5.1 
h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
+github.com/go-errors/errors v1.5.1/go.mod 
h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
 github.com/go-ldap/ldap/v3 v3.4.14 
h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
 github.com/go-ldap/ldap/v3 v3.4.14/go.mod 
h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
+github.com/go-logr/logr v1.4.4/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod 
h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-openapi/jsonpointer v0.19.6/go.mod 
h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
-github.com/go-openapi/jsonpointer v0.21.0 
h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
-github.com/go-openapi/jsonpointer v0.21.0/go.mod 
h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
-github.com/go-openapi/jsonreference v0.20.2 
h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
-github.com/go-openapi/jsonreference v0.20.2/go.mod 
h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/swag v0.22.3/go.mod 
h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
-github.com/go-openapi/swag v0.23.0 
h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
-github.com/go-openapi/swag v0.23.0/go.mod 
h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
+github.com/go-openapi/jsonpointer v1.0.0 
h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
+github.com/go-openapi/jsonpointer v1.0.0/go.mod 
h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
+github.com/go-openapi/jsonreference v1.0.0 
h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
+github.com/go-openapi/jsonreference v1.0.0/go.mod 
h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
+github.com/go-openapi/swag v0.28.0 
h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw=
+github.com/go-openapi/swag v0.28.0/go.mod 
h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg=
+github.com/go-openapi/swag/cmdutils v0.28.0 
h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q=
+github.com/go-openapi/swag/cmdutils v0.28.0/go.mod 
h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
+github.com/go-openapi/swag/conv v0.28.0 
h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
+github.com/go-openapi/swag/conv v0.28.0/go.mod 
h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
+github.com/go-openapi/swag/fileutils v0.28.0 
h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU=
+github.com/go-openapi/swag/fileutils v0.28.0/go.mod 
h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
+github.com/go-openapi/swag/jsonutils v0.28.0 
h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
+github.com/go-openapi/swag/jsonutils v0.28.0/go.mod 
h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 
h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod 
h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
+github.com/go-openapi/swag/loading v0.28.0 
h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
+github.com/go-openapi/swag/loading v0.28.0/go.mod 
h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
+github.com/go-openapi/swag/mangling v0.28.0 
h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM=
+github.com/go-openapi/swag/mangling v0.28.0/go.mod 
h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
+github.com/go-openapi/swag/netutils v0.28.0 
h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k=
+github.com/go-openapi/swag/netutils v0.28.0/go.mod 
h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
+github.com/go-openapi/swag/pools v0.28.0 
h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
+github.com/go-openapi/swag/pools v0.28.0/go.mod 
h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
+github.com/go-openapi/swag/stringutils v0.28.0 
h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
+github.com/go-openapi/swag/stringutils v0.28.0/go.mod 
h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
+github.com/go-openapi/swag/typeutils v0.28.0 
h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
+github.com/go-openapi/swag/typeutils v0.28.0/go.mod 
h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
+github.com/go-openapi/swag/yamlutils v0.28.0 
h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
+github.com/go-openapi/swag/yamlutils v0.28.0/go.mod 
h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
+github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 
h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
+github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod 
h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
+github.com/go-openapi/testify/v2 v2.6.0 
h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
+github.com/go-openapi/testify/v2 v2.6.0/go.mod 
h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
 github.com/go-task/slim-sprig/v3 v3.0.0 
h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod 
h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
 github.com/goccy/go-yaml v1.18.0 
h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
@@ -68,15 +93,15 @@ github.com/golang/protobuf v1.5.4 
h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
 github.com/golang/protobuf v1.5.4/go.mod 
h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
 github.com/google/btree v1.1.3/go.mod 
h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
-github.com/google/cel-go v0.29.0 
h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4=
-github.com/google/cel-go v0.29.0/go.mod 
h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
-github.com/google/gnostic-models v0.7.0 
h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
-github.com/google/gnostic-models v0.7.0/go.mod 
h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
+github.com/google/cel-go v0.31.0 
h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A=
+github.com/google/cel-go v0.31.0/go.mod 
h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
+github.com/google/gnostic-models v0.7.1 
h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
+github.com/google/gnostic-models v0.7.1/go.mod 
h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod 
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/gofuzz v1.0.0/go.mod 
h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 
h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
-github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod 
h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
+github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 
h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
+github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod 
h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 
h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
@@ -97,8 +122,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 
h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod 
h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
 github.com/jcmturner/rpc/v2 v2.0.3 
h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
 github.com/jcmturner/rpc/v2 v2.0.3/go.mod 
h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/josharian/intern v1.0.0 
h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-github.com/josharian/intern v1.0.0/go.mod 
h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/joshdk/go-junit v1.0.0 
h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
 github.com/joshdk/go-junit v1.0.0/go.mod 
h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
 github.com/json-iterator/go v1.1.12 
h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -107,11 +130,8 @@ github.com/julienschmidt/httprouter v1.3.0 
h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
 github.com/julienschmidt/httprouter v1.3.0/go.mod 
h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/klauspost/compress v1.19.1 
h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
 github.com/klauspost/compress v1.19.1/go.mod 
h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
-github.com/kr/pretty v0.2.1/go.mod 
h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod 
h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod 
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 
h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@@ -120,16 +140,14 @@ github.com/liggitt/tabwriter 
v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod 
h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
 github.com/looplab/fsm v1.0.3 h1:qtxBsa2onOs0qFOtkqwf5zE0uP0+Te+wlIvXctPKpcw=
 github.com/looplab/fsm v1.0.3/go.mod 
h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4=
-github.com/mailru/easyjson v0.7.7 
h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod 
h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/maruel/natural v1.1.1 
h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
 github.com/maruel/natural v1.1.1/go.mod 
h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
 github.com/mfridman/tparse v0.18.0 
h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
 github.com/mfridman/tparse v0.18.0/go.mod 
h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
 github.com/moby/spdystream v0.5.1 
h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
 github.com/moby/spdystream v0.5.1/go.mod 
h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
-github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
-github.com/moby/term v0.5.0/go.mod 
h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
+github.com/moby/term v0.5.2/go.mod 
h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod 
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd 
h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod 
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -140,17 +158,17 @@ github.com/monochromegane/go-gitignore 
v0.0.0-20200626010858-205db1a8cc00 h1:n6/
 github.com/monochromegane/go-gitignore 
v0.0.0-20200626010858-205db1a8cc00/go.mod 
h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 
h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod 
h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/onsi/ginkgo/v2 v2.28.1 
h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
-github.com/onsi/ginkgo/v2 v2.28.1/go.mod 
h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
-github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
-github.com/onsi/gomega v1.40.0/go.mod 
h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A=
+github.com/onsi/ginkgo/v2 v2.32.1 
h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw=
+github.com/onsi/ginkgo/v2 v2.32.1/go.mod 
h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
+github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
+github.com/onsi/gomega v1.42.1/go.mod 
h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
 github.com/opencontainers/go-digest v1.0.0 
h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod 
h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/peterbourgon/diskv v2.0.1+incompatible 
h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod 
h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod 
h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
-github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75 
h1:VmZ6mKVkxavKEhEy4ZYyV7BwBYBFBP0TwIqmLk84fpU=
-github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75/go.mod 
h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b 
h1:sS7HLzwS+dO+gxATgQfeZDEdUZe2pKAB3nGoUwP5zU0=
+github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b/go.mod 
h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
 github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 
h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -173,18 +191,14 @@ github.com/sergi/go-diff v1.4.0 
h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
 github.com/sergi/go-diff v1.4.0/go.mod 
h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
 github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
 github.com/spf13/cobra v1.10.2/go.mod 
h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
-github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
 github.com/spf13/pflag v1.0.9/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod 
h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod 
h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 github.com/stretchr/objx v0.5.2/go.mod 
h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.3.0/go.mod 
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod 
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod 
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.11.1 
h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 github.com/stretchr/testify v1.11.1/go.mod 
h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
@@ -203,16 +217,16 @@ github.com/xlab/treeprint v1.2.0 
h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
 github.com/xlab/treeprint v1.2.0/go.mod 
h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
 go.opentelemetry.io/auto/sdk v1.2.1 
h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
 go.opentelemetry.io/auto/sdk v1.2.1/go.mod 
h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/otel v1.44.0 
h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
-go.opentelemetry.io/otel v1.44.0/go.mod 
h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
-go.opentelemetry.io/otel/metric v1.44.0 
h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
-go.opentelemetry.io/otel/metric v1.44.0/go.mod 
h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
+go.opentelemetry.io/otel v1.45.0 
h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
+go.opentelemetry.io/otel v1.45.0/go.mod 
h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
+go.opentelemetry.io/otel/metric v1.45.0 
h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
+go.opentelemetry.io/otel/metric v1.45.0/go.mod 
h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
 go.opentelemetry.io/otel/sdk v1.44.0 
h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
 go.opentelemetry.io/otel/sdk v1.44.0/go.mod 
h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
 go.opentelemetry.io/otel/sdk/metric v1.44.0 
h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
 go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod 
h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
-go.opentelemetry.io/otel/trace v1.44.0 
h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
-go.opentelemetry.io/otel/trace v1.44.0/go.mod 
h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
+go.opentelemetry.io/otel/trace v1.45.0 
h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
+go.opentelemetry.io/otel/trace v1.45.0/go.mod 
h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod 
h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -226,10 +240,10 @@ go.yaml.in/yaml/v3 v3.0.5 
h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
 go.yaml.in/yaml/v3 v3.0.5/go.mod 
h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
 golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
 golang.org/x/crypto v0.56.0/go.mod 
h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
-golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 
h1:ex206bKw+v3K0dm3andkrIF+ijyQKJG1pLgwQ2PYdQM=
-golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod 
h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
-golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
-golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
+golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 
h1:YXnL44eJ77R+ji4/ooy8UsXIhz+lbi2Qgdlc8iRN0gY=
+golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297/go.mod 
h1:Mkmymgv+uMpSQ/XxJ/7GpdrdYoqm3u72jEbpCLiJmNk=
+golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
+golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
 golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
 golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
 golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
@@ -245,21 +259,19 @@ golang.org/x/text v0.41.0 
h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
 golang.org/x/text v0.41.0/go.mod 
h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
 golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
 golang.org/x/time v0.15.0/go.mod 
h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
-golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
-golang.org/x/tools v0.48.0/go.mod 
h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
+golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
+golang.org/x/tools v0.49.0/go.mod 
h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
 gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
 gonum.org/v1/gonum v0.17.0/go.mod 
h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
-google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa 
h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
-google.golang.org/genproto/googleapis/api 
v0.0.0-20260526163538-3dc84a4a5aaa/go.mod 
h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d 
h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0=
-google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260803160001-6ac0973c030d/go.mod 
h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto/googleapis/api v0.0.0-20260810153831-ec0a7760b754 
h1:dWeMvEJ3JhYgqSCAHUZZJgMUyfniiiCvDc72x5EqJP0=
+google.golang.org/genproto/googleapis/api 
v0.0.0-20260810153831-ec0a7760b754/go.mod 
h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754 
h1:k5CJw9e5ONCcA/u0webKt092npXuY+KeGh3Q8NAVf0g=
+google.golang.org/genproto/googleapis/rpc 
v0.0.0-20260810153831-ec0a7760b754/go.mod 
h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
 google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
 google.golang.org/grpc v1.83.1/go.mod 
h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
-google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af 
h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
-google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod 
h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+google.golang.org/protobuf v1.36.12 
h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
+google.golang.org/protobuf v1.36.12/go.mod 
h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod 
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/evanphx/json-patch.v4 v4.13.0 
h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
 gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod 
h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -295,18 +307,18 @@ k8s.io/dynamic-resource-allocation v0.36.1 
h1:nMdVWmGuL+juHRvuBWGTngH2OYUmokwgRf
 k8s.io/dynamic-resource-allocation v0.36.1/go.mod 
h1:3dFGuVbN4Ui5M4EaXxFM+AktFQPKqiBAtyYb8zi3zw4=
 k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
 k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
-k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a 
h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
-k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod 
h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
+k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad 
h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A=
+k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod 
h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
 k8s.io/kube-scheduler v0.36.1 h1:khJErGq9+JaHniVCxAulvQ4WvYxPj9/95bjJS0bXnjA=
 k8s.io/kube-scheduler v0.36.1/go.mod 
h1:iPnJPkET29aL/Ox0qupytdKosDz0qPj7xGH4MM+dz14=
 k8s.io/kubelet v0.36.1 h1:FcHiG9wv92xerRPNxztuhYWqwS4IilOQNPxTPQewYgo=
 k8s.io/kubelet v0.36.1/go.mod h1:e6IeoCwqc2TbneCKu6P8HjmWLi7U6SOh3Pocs32iGFM=
-k8s.io/kubernetes v1.36.1 h1:Mt7NKigaZ2KmOmCLhX81lGlH9JU5wjXnYhXnxAun9XA=
-k8s.io/kubernetes v1.36.1/go.mod 
h1:MLdeJ3qw2CWH9BFml5GvptxQVQckz54fJOZ/WuixpFE=
-k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4=
-k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
-k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 
h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc=
-k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod 
h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+k8s.io/kubernetes v1.36.3 h1:qDQdoMiluAE2Eab6Fa52YV+WjiGz9mZFFoagEA6cI+o=
+k8s.io/kubernetes v1.36.3/go.mod 
h1:6oChkQeI7Yf6lV9lFpSdRzODdbY/ECp/4zUeBk8ONaw=
+k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w=
+k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
+k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 
h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
+k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod 
h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 
h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod 
h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
 sigs.k8s.io/kustomize/api v0.21.1 
h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
@@ -315,7 +327,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 
h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7
 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod 
h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
 sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
 sigs.k8s.io/randfill v1.0.0/go.mod 
h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v6 v6.3.2 
h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
-sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod 
h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/structured-merge-diff/v6 v6.4.2 
h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
+sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod 
h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
 sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
 sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/pkg/cache/context.go b/pkg/cache/context.go
index 68e54fec..e0878250 100644
--- a/pkg/cache/context.go
+++ b/pkg/cache/context.go
@@ -41,6 +41,7 @@ import (
        "k8s.io/klog/v2"
        fwk "k8s.io/kube-scheduler/framework"
        "k8s.io/kubernetes/pkg/features"
+       "k8s.io/kubernetes/pkg/scheduler/framework/plugins"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding"
        "k8s.io/kubernetes/pkg/scheduler/util/assumecache"
@@ -64,8 +65,9 @@ import (
 const registerNodeContextHandler = "RegisterNodeContextHandler"
 
 var (
-       ErrorPodNotFound  = errors.New("predicates were not run because pod was 
not found in cache")
-       ErrorNodeNotFound = errors.New("predicates were not run because node 
was not found in cache")
+       ErrorPodNotFound        = errors.New("predicates were not run because 
pod was not found in cache")
+       ErrorNodeNotFound       = errors.New("predicates were not run because 
node was not found in cache")
+       ErrorCycleStateNotFound = errors.New("predicates were not run because 
pod's cycle state was not found in cache")
 )
 
 // context maintains scheduling state, like apps and apps' tasks.
@@ -127,7 +129,12 @@ func NewContextWithBootstrapConfigMaps(apis 
client.APIProvider, bootstrapConfigM
                }
                sharedDRAManager = 
dynamicresources.NewDRAManager(context.TODO(), resourceClaimCache, 
resourceSliceTracker, informerFactory)
        }
-       ctx.predManager = 
predicates.NewPredicateManager(support.NewFrameworkHandle(sharedLister, 
informerFactory, clientSet, csiManager, sharedDRAManager))
+
+       config, err := predicates.DefaultConfig()
+       if err != nil {
+               log.Log(log.ShimClient).Error("unable to create the predicates 
config", zap.Error(err))
+       }
+       ctx.predManager = 
predicates.NewPredicateManager(support.NewFrameworkHandle(sharedLister, 
informerFactory, clientSet, csiManager, sharedDRAManager), 
plugins.NewInTreeRegistry(), config)
        return ctx
 }
 
@@ -690,6 +697,38 @@ func (ctx *Context) EventsToRegister(queueingHintFn 
fwk.QueueingHintFn) []fwk.Cl
        return ctx.predManager.EventsToRegister(queueingHintFn)
 }
 
+// PreFilter evaluates given prefilter based predicates based on current 
context
+func (ctx *Context) PreFilter(name string, allocate bool) 
*si.PreFilterPredicatesResponse {
+       ctx.lock.RLock()
+       defer ctx.lock.RUnlock()
+       pod := ctx.schedulerCache.GetPod(name)
+       if pod == nil {
+               log.Log(log.ShimContext).Error("failed running PreFilter 
plugin",
+                       zap.String("pod", name),
+                       zap.Error(ErrorPodNotFound))
+               return &si.PreFilterPredicatesResponse{
+                       FeasibleNodes: make(map[string]*si.Empty),
+                       Success:       false,
+               }
+       }
+       // if pod exists in cache, try to run predicates
+       // need to lock cache here as predicates need a stable view into the 
cache
+       ctx.schedulerCache.LockForReads()
+       feasibleNodes, cycleState, err := ctx.predManager.PreFilter(pod, 
allocate)
+       ctx.schedulerCache.UnlockForReads()
+       if err == nil {
+               ctx.schedulerCache.StoreCycleState(pod, cycleState)
+               return &si.PreFilterPredicatesResponse{
+                       FeasibleNodes: feasibleNodes,
+                       Success:       true,
+               }
+       }
+       return &si.PreFilterPredicatesResponse{
+               FeasibleNodes: make(map[string]*si.Empty),
+               Success:       false,
+       }
+}
+
 // IsPodFitNode evaluates given predicates based on current context
 func (ctx *Context) IsPodFitNode(name, node string, allocate bool) error {
        ctx.lock.RLock()
@@ -706,7 +745,11 @@ func (ctx *Context) IsPodFitNode(name, node string, 
allocate bool) error {
        // need to lock cache here as predicates need a stable view into the 
cache
        ctx.schedulerCache.LockForReads()
        defer ctx.schedulerCache.UnlockForReads()
-       plugin, err := ctx.predManager.Predicates(pod, targetNode, allocate)
+       cycleState := ctx.schedulerCache.GetCycleState(pod)
+       if cycleState == nil {
+               return ErrorCycleStateNotFound
+       }
+       plugin, err := ctx.predManager.Filter(pod, targetNode, cycleState, 
allocate)
        if err != nil {
                err = errors.Join(fmt.Errorf("failed plugin: '%s'", plugin), 
err)
        }
@@ -722,17 +765,18 @@ func (ctx *Context) IsPodFitNodeViaPreemption(name, node 
string, allocations []s
                        // need to lock cache here as predicates need a stable 
view into the cache
                        ctx.schedulerCache.LockForReads()
                        defer ctx.schedulerCache.UnlockForReads()
+                       if cycleState := ctx.schedulerCache.GetCycleState(pod); 
cycleState != nil {
+                               // look up each victim in the scheduler cache
+                               victims := make([]*v1.Pod, len(allocations))
+                               for index, uid := range allocations {
+                                       victim := 
ctx.schedulerCache.GetPodNoLock(uid)
+                                       victims[index] = victim
+                               }
 
-                       // look up each victim in the scheduler cache
-                       victims := make([]*v1.Pod, len(allocations))
-                       for index, uid := range allocations {
-                               victim := ctx.schedulerCache.GetPodNoLock(uid)
-                               victims[index] = victim
-                       }
-
-                       // check predicates for a match
-                       if index := ctx.predManager.PreemptionPredicates(pod, 
targetNode, victims, startIndex); index != -1 {
-                               return index, true
+                               // check predicates for a match
+                               if index := 
ctx.predManager.PreemptionFilter(pod, targetNode, cycleState, victims, 
startIndex); index != -1 {
+                                       return index, true
+                               }
                        }
                }
        }
diff --git a/pkg/cache/external/scheduler_cache.go 
b/pkg/cache/external/scheduler_cache.go
index 31cea350..9b8ab4a4 100644
--- a/pkg/cache/external/scheduler_cache.go
+++ b/pkg/cache/external/scheduler_cache.go
@@ -41,16 +41,17 @@ import (
 // Nodes are cached in the form of de-scheduler nodeInfo. Instead of 
re-creating all nodes info from scratch,
 // we replicate nodes info from de-scheduler, in order to re-use predicates 
functions.
 type SchedulerCache struct {
-       nodesMap     map[string]*framework.NodeInfo // node name to NodeInfo map
-       podsMap      map[string]*v1.Pod
-       pcMap        map[string]*schedulingv1.PriorityClass
-       assignedPods map[string]string  // map of pods to the node they are 
currently assigned to
-       assumedPods  map[string]bool    // map of assumed pods, value indicates 
if pod volumes are all bound
-       orphanedPods map[string]*v1.Pod // map of orphaned pods, keyed by pod 
UID
-       pvcRefCounts map[string]map[string]int
-       lock         locking.RWMutex
-       clients      *client.Clients // client APIs
-       klogger      klog.Logger
+       nodesMap       map[string]*framework.NodeInfo // node name to NodeInfo 
map
+       podsMap        map[string]*v1.Pod
+       pcMap          map[string]*schedulingv1.PriorityClass
+       assignedPods   map[string]string  // map of pods to the node they are 
currently assigned to
+       assumedPods    map[string]bool    // map of assumed pods, value 
indicates if pod volumes are all bound
+       orphanedPods   map[string]*v1.Pod // map of orphaned pods, keyed by pod 
UID
+       pvcRefCounts   map[string]map[string]int
+       podsCycleState map[string]*framework.CycleState // map of pods to the 
cycle state
+       lock           locking.RWMutex
+       clients        *client.Clients // client APIs
+       klogger        klog.Logger
 
        // cached data, re-calculated on demand from nodesMap
        nodesInfo                        []fwk.NodeInfo
@@ -60,15 +61,16 @@ type SchedulerCache struct {
 
 func NewSchedulerCache(clients *client.Clients) *SchedulerCache {
        cache := &SchedulerCache{
-               nodesMap:     make(map[string]*framework.NodeInfo),
-               podsMap:      make(map[string]*v1.Pod),
-               pcMap:        make(map[string]*schedulingv1.PriorityClass),
-               assignedPods: make(map[string]string),
-               assumedPods:  make(map[string]bool),
-               orphanedPods: make(map[string]*v1.Pod),
-               pvcRefCounts: make(map[string]map[string]int),
-               clients:      clients,
-               klogger:      klog.NewKlogr(),
+               nodesMap:       make(map[string]*framework.NodeInfo),
+               podsMap:        make(map[string]*v1.Pod),
+               pcMap:          make(map[string]*schedulingv1.PriorityClass),
+               assignedPods:   make(map[string]string),
+               assumedPods:    make(map[string]bool),
+               orphanedPods:   make(map[string]*v1.Pod),
+               pvcRefCounts:   make(map[string]map[string]int),
+               podsCycleState: make(map[string]*framework.CycleState),
+               clients:        clients,
+               klogger:        klog.NewKlogr(),
        }
        return cache
 }
@@ -135,6 +137,14 @@ func (cache *SchedulerCache) UnlockForReads() {
        cache.lock.RUnlock()
 }
 
+func (cache *SchedulerCache) LockForWrites() {
+       cache.lock.Lock()
+}
+
+func (cache *SchedulerCache) UnlockForWrites() {
+       cache.lock.Unlock()
+}
+
 func (cache *SchedulerCache) GetNode(name string) *framework.NodeInfo {
        cache.lock.RLock()
        defer cache.lock.RUnlock()
@@ -386,6 +396,7 @@ func (cache *SchedulerCache) updatePod(pod *v1.Pod) bool {
                delete(cache.assignedPods, key)
                delete(cache.assumedPods, key)
                delete(cache.orphanedPods, key)
+               delete(cache.podsCycleState, key)
        }
 
        return result
@@ -420,6 +431,7 @@ func (cache *SchedulerCache) removePod(pod *v1.Pod) {
        delete(cache.assignedPods, key)
        delete(cache.assumedPods, key)
        delete(cache.orphanedPods, key)
+       delete(cache.podsCycleState, key)
        cache.nodesInfoPodsWithAffinity = nil
        cache.nodesInfoPodsWithReqAntiAffinity = nil
 }
@@ -607,6 +619,28 @@ func (cache *SchedulerCache) updatePVCRefCounts(node 
*framework.NodeInfo, remove
        }
 }
 
+func (cache *SchedulerCache) GetCycleState(pod *v1.Pod) *framework.CycleState {
+       return cache.podsCycleState[string(pod.UID)]
+}
+
+func (cache *SchedulerCache) StoreCycleState(pod *v1.Pod, cycleState 
*framework.CycleState) {
+       cache.lock.Lock()
+       defer cache.lock.Unlock()
+       if pod == nil {
+               return
+       }
+       cache.podsCycleState[string(pod.UID)] = cycleState
+}
+
+func (cache *SchedulerCache) DeleteCycleState(pod *v1.Pod) {
+       cache.lock.Lock()
+       defer cache.lock.Unlock()
+       if pod == nil {
+               return
+       }
+       delete(cache.podsCycleState, string(pod.UID))
+}
+
 func (cache *SchedulerCache) GetSchedulerCacheDao() SchedulerCacheDao {
        cache.lock.RLock()
        defer cache.lock.RUnlock()
diff --git a/pkg/cache/scheduler_callback.go b/pkg/cache/scheduler_callback.go
index dd53828f..381d223c 100644
--- a/pkg/cache/scheduler_callback.go
+++ b/pkg/cache/scheduler_callback.go
@@ -97,6 +97,8 @@ func (callback *AsyncRMCallback) UpdateAllocation(response 
*si.AllocationRespons
                        if wait.Interrupted(err) && lastErr != nil {
                                err = lastErr
                        }
+                       // delete the cycle state
+                       
callback.context.schedulerCache.DeleteCycleState(task.GetTaskPod())
                        if task.IsPlaceholder() {
                                // Placeholder tasks do not have volume 
bindings, so AssumePod failure
                                // is unexpected and unrecoverable; wrap the 
error with context.
@@ -111,6 +113,9 @@ func (callback *AsyncRMCallback) UpdateAllocation(response 
*si.AllocationRespons
                if utils.IsAssignedPod(task.GetTaskPod()) {
                        // task is already bound, fixup state and continue
                        task.MarkPreviouslyAllocated(alloc.AllocationKey, 
alloc.NodeID)
+
+                       // delete the cycle state
+                       
callback.context.schedulerCache.DeleteCycleState(task.GetTaskPod())
                } else {
                        ev := NewAllocateTaskEvent(alloc.ApplicationID, 
task.taskID, alloc.AllocationKey, alloc.NodeID)
                        dispatcher.Dispatch(ev)
@@ -220,6 +225,10 @@ func (callback *AsyncRMCallback) UpdateNode(response 
*si.NodeResponse) error {
        return nil
 }
 
+func (callback *AsyncRMCallback) PreFilterPredicates(args 
*si.PreFilterPredicatesArgs) *si.PreFilterPredicatesResponse {
+       return callback.context.PreFilter(args.AllocationKey, args.Allocate)
+}
+
 func (callback *AsyncRMCallback) Predicates(args *si.PredicatesArgs) error {
        return callback.context.IsPodFitNode(args.AllocationKey, args.NodeID, 
args.Allocate)
 }
diff --git a/pkg/cache/scheduler_callback_test.go 
b/pkg/cache/scheduler_callback_test.go
index bf97c437..c71ce820 100644
--- a/pkg/cache/scheduler_callback_test.go
+++ b/pkg/cache/scheduler_callback_test.go
@@ -537,6 +537,33 @@ func testUpdateNode(t *testing.T, expectedEvent string, 
response *si.NodeRespons
        assert.NilError(t, err)
 }
 
+func TestPredicatesPreFilter(t *testing.T) {
+       callback, context := initCallbackTest(t, false, false)
+       defer dispatcher.UnregisterAllEventHandlers()
+       defer dispatcher.Stop()
+       context.predManager = &mockPredicateManager{}
+
+       // pod not found
+       predicatesResults := 
callback.PreFilterPredicates(&si.PreFilterPredicatesArgs{AllocationKey: 
"unknown", Allocate: true})
+       assert.Equal(t, predicatesResults.Success, false)
+       assert.Equal(t, len(predicatesResults.FeasibleNodes), 0)
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: "unknown",
+               UID:  "unknown",
+       },
+       }) == nil)
+
+       // pod found
+       predicatesResults = 
callback.PreFilterPredicates(&si.PreFilterPredicatesArgs{AllocationKey: 
taskUID1, Allocate: true})
+       assert.Equal(t, predicatesResults.Success, true)
+       assert.Equal(t, len(predicatesResults.FeasibleNodes), 0)
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: taskUID1,
+               UID:  taskUID1,
+       },
+       }) != nil)
+}
+
 func TestPredicates(t *testing.T) {
        callback, context := initCallbackTest(t, false, false)
        defer dispatcher.UnregisterAllEventHandlers()
@@ -546,10 +573,36 @@ func TestPredicates(t *testing.T) {
        // pod not found
        err := callback.Predicates(&si.PredicatesArgs{AllocationKey: "unknown", 
NodeID: fakeNodeName, Allocate: true})
        assert.Error(t, err, "predicates were not run because pod was not found 
in cache")
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: "unknown",
+               UID:  "unknown",
+       },
+       }) == nil)
 
-       // pod found
+       // pod found, node not found
+       err = callback.Predicates(&si.PredicatesArgs{AllocationKey: taskUID1, 
NodeID: "unknown", Allocate: true})
+       assert.Error(t, err, "predicates were not run because node was not 
found in cache")
+
+       // both pod and node found, cycle state not found
+       err = callback.Predicates(&si.PredicatesArgs{AllocationKey: taskUID1, 
NodeID: fakeNodeName, Allocate: true})
+       assert.Error(t, err, "predicates were not run because pod's cycle state 
was not found in cache")
+
+       // pod, node & cycle state found
+       results := 
callback.PreFilterPredicates(&si.PreFilterPredicatesArgs{AllocationKey: 
taskUID1, Allocate: true})
+       assert.Equal(t, results.Success, true)
+       assert.Equal(t, len(results.FeasibleNodes), 0)
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: taskUID1,
+               UID:  taskUID1,
+       },
+       }) != nil)
        err = callback.Predicates(&si.PredicatesArgs{AllocationKey: taskUID1, 
NodeID: fakeNodeName, Allocate: true})
        assert.NilError(t, err)
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: taskUID1,
+               UID:  taskUID1,
+       },
+       }) != nil)
 }
 
 func TestPreemptionPredicates(t *testing.T) {
@@ -561,11 +614,37 @@ func TestPreemptionPredicates(t *testing.T) {
        // pod not found
        resp := 
callback.PreemptionPredicates(&si.PreemptionPredicatesArgs{AllocationKey: 
"unknown", NodeID: fakeNodeName, StartIndex: 0})
        assert.Assert(t, !resp.Success, "response should have failed")
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: "unknown",
+               UID:  "unknown",
+       },
+       }) == nil)
+
+       // pod found, node not found
+       resp = 
callback.PreemptionPredicates(&si.PreemptionPredicatesArgs{AllocationKey: 
taskUID1, NodeID: "unknown", StartIndex: 0})
+       assert.Assert(t, !resp.Success, "response should have failed")
 
-       // pod found
+       // both pod and node found, cycle state not found
+       resp = 
callback.PreemptionPredicates(&si.PreemptionPredicatesArgs{AllocationKey: 
taskUID1, NodeID: fakeNodeName, StartIndex: 0})
+       assert.Assert(t, !resp.Success, "response should have failed")
+
+       // pod, node & cycle state found
+       results := 
callback.PreFilterPredicates(&si.PreFilterPredicatesArgs{AllocationKey: 
taskUID1, Allocate: true})
+       assert.Equal(t, results.Success, true)
+       assert.Equal(t, len(results.FeasibleNodes), 0)
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: taskUID1,
+               UID:  taskUID1,
+       },
+       }) != nil)
        resp = 
callback.PreemptionPredicates(&si.PreemptionPredicatesArgs{AllocationKey: 
taskUID1, NodeID: fakeNodeName, StartIndex: 0, PreemptAllocationKeys: 
[]string{taskUID1}})
        assert.Assert(t, resp.Success, "response should have succeeded")
        assert.Equal(t, int32(0), resp.Index)
+       assert.Assert(t, 
callback.context.schedulerCache.GetCycleState(&v1.Pod{ObjectMeta: 
apis.ObjectMeta{
+               Name: taskUID1,
+               UID:  taskUID1,
+       },
+       }) != nil)
 }
 
 func TestSendEvent(t *testing.T) {
@@ -631,18 +710,22 @@ var _ predicates.PredicateManager = 
&mockPredicateManager{}
 
 type mockPredicateManager struct{}
 
-func (m *mockPredicateManager) EventsToRegister(_ fwk.QueueingHintFn) 
[]fwk.ClusterEventWithHint {
-       return nil
+func (m *mockPredicateManager) PreFilter(_ *v1.Pod, _ bool) 
(map[string]*si.Empty, *framework.CycleState, error) {
+       return map[string]*si.Empty{}, framework.NewCycleState(), nil
 }
 
-func (m *mockPredicateManager) Predicates(_ *v1.Pod, _ *framework.NodeInfo, _ 
bool) (string, error) {
+func (m *mockPredicateManager) Filter(_ *v1.Pod, _ *framework.NodeInfo, _ 
*framework.CycleState, _ bool) (string, error) {
        return "", nil
 }
 
-func (m *mockPredicateManager) PreemptionPredicates(_ *v1.Pod, _ 
*framework.NodeInfo, _ []*v1.Pod, _ int) int {
+func (m *mockPredicateManager) PreemptionFilter(_ *v1.Pod, _ 
*framework.NodeInfo, _ *framework.CycleState, _ []*v1.Pod, _ int) int {
        return 0
 }
 
+func (m *mockPredicateManager) EventsToRegister(_ fwk.QueueingHintFn) 
[]fwk.ClusterEventWithHint {
+       return nil
+}
+
 func initCallbackTest(t *testing.T, podAssigned, placeholder bool) 
(*AsyncRMCallback, *Context) {
        context, apiProvider := initContextAndAPIProviderForTest()
        dispatcher.Start()
diff --git a/pkg/cache/task.go b/pkg/cache/task.go
index 2519b69e..20da78b6 100644
--- a/pkg/cache/task.go
+++ b/pkg/cache/task.go
@@ -392,8 +392,15 @@ func (task *Task) postTaskAllocated() {
                        task.lock.Unlock()
                        task.rescheduleOnBindFailure(allocationKey, nodeName, 
"PodVolumesBindFailure",
                                fmt.Sprintf("Failed to bind volumes for %s on 
node %s, it will be retried", alias, nodeName))
+
+                       // delete the cycle state
+                       task.context.schedulerCache.DeleteCycleState(pod)
                        return
                }
+
+               // delete the cycle state
+               task.context.schedulerCache.DeleteCycleState(pod)
+
                log.Log(log.ShimCacheTask).Debug("bind pod",
                        zap.String("podName", pod.Name),
                        zap.String("podUID", string(pod.UID)))
diff --git a/pkg/plugin/predicates/predicate_manager.go 
b/pkg/plugin/predicates/predicate_manager.go
index 9b30b99a..085be78b 100644
--- a/pkg/plugin/predicates/predicate_manager.go
+++ b/pkg/plugin/predicates/predicate_manager.go
@@ -27,6 +27,7 @@ import (
        "go.uber.org/zap"
        v1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/util/sets"
        "k8s.io/apiserver/pkg/util/feature"
        "k8s.io/component-base/config/v1alpha1"
        "k8s.io/klog/v2"
@@ -36,22 +37,23 @@ import (
        apiConfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
        "k8s.io/kubernetes/pkg/scheduler/apis/config/scheme"
        "k8s.io/kubernetes/pkg/scheduler/framework"
-       "k8s.io/kubernetes/pkg/scheduler/framework/plugins"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names"
        fwruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
        "k8s.io/kubernetes/pkg/scheduler/metrics"
 
        "github.com/apache/yunikorn-k8shim/pkg/log"
+       "github.com/apache/yunikorn-scheduler-interface/lib/go/si"
 )
 
 type PredicateManager interface {
        EventsToRegister(queueingHintFn fwk.QueueingHintFn) 
[]fwk.ClusterEventWithHint
-       // Predicates checks if a pod can fit on a node.
+       PreFilter(pod *v1.Pod, allocate bool) (feasibleNodes 
map[string]*si.Empty, cycleState *framework.CycleState, error error)
+       // Filter Predicates checks if a pod can fit on a node.
        // Returns the name of the predicate plugin that failed (may be empty) 
and any error encountered.
-       Predicates(pod *v1.Pod, node *framework.NodeInfo, allocate bool) 
(string, error)
+       Filter(pod *v1.Pod, node *framework.NodeInfo, cycleState 
*framework.CycleState, allocate bool) (plugin string, error error)
        // PreemptionPredicates checks if a pod can be scheduled on the node by 
preempting victims.
        // Returns the victim index that allows the pod to fit, or -1 if none.
-       PreemptionPredicates(pod *v1.Pod, node *framework.NodeInfo, victims 
[]*v1.Pod, startIndex int) int
+       PreemptionFilter(pod *v1.Pod, node *framework.NodeInfo, cycleState 
*framework.CycleState, victims []*v1.Pod, startIndex int) (index int)
 }
 
 var _ PredicateManager = &predicateManagerImpl{}
@@ -131,28 +133,8 @@ func buildClusterEvents(actionMap 
map[fwk.EventResource]fwk.ActionType, queueing
        return events
 }
 
-func (p *predicateManagerImpl) Predicates(pod *v1.Pod, node 
*framework.NodeInfo, allocate bool) (string, error) {
-       if allocate {
-               return p.predicatesAllocate(pod, node)
-       }
-       return p.predicatesReserve(pod, node)
-}
-
-func (p *predicateManagerImpl) PreemptionPredicates(pod *v1.Pod, node 
*framework.NodeInfo, victims []*v1.Pod, startIndex int) int {
+func (p *predicateManagerImpl) PreemptionFilter(pod *v1.Pod, node 
*framework.NodeInfo, cycleState *framework.CycleState, victims []*v1.Pod, 
startIndex int) int {
        ctx := context.Background()
-       state := framework.NewCycleState()
-
-       // run prefilter checks as pod cannot be scheduled otherwise
-       s, plugin, skip := p.runPreFilterPlugins(ctx, state, 
*p.allocationPreFilters, pod, node)
-       if !s.IsSuccess() && !s.IsSkip() {
-               // prefilter check failed, log and return
-               log.Log(log.ShimPredicates).Debug("PreFilter check failed 
during preemption check",
-                       zap.String("podUID", string(pod.UID)),
-                       zap.String("plugin", plugin),
-                       zap.String("message", s.Message()))
-
-               return -1
-       }
 
        // clone node so that we can modify it here for predicate checks
        preemptingNode := node.Snapshot()
@@ -165,7 +147,7 @@ func (p *predicateManagerImpl) PreemptionPredicates(pod 
*v1.Pod, node *framework
        // loop through remaining pods
        for i := startIndex; i < len(victims); i++ {
                p.removePodFromNodeNoFail(preemptingNode, victims[i])
-               status, _ := p.runFilterPlugins(ctx, *p.allocationFilters, 
state, pod, preemptingNode, skip)
+               status, _ := p.runFilterPlugins(ctx, *p.allocationFilters, 
cycleState, pod, preemptingNode)
                if status.IsSuccess() {
                        return i
                }
@@ -191,80 +173,90 @@ func (p *predicateManagerImpl) 
removePodFromNodeNoFail(node fwk.NodeInfo, pod *v
        }
 }
 
-func (p *predicateManagerImpl) predicatesReserve(pod *v1.Pod, node 
*framework.NodeInfo) (string, error) {
-       ctx := context.Background()
-       state := framework.NewCycleState()
-       return p.podFitsNode(ctx, state, *p.reservationPreFilters, 
*p.reservationFilters, pod, node)
-}
-
-func (p *predicateManagerImpl) predicatesAllocate(pod *v1.Pod, node 
*framework.NodeInfo) (string, error) {
+func (p *predicateManagerImpl) PreFilter(pod *v1.Pod, allocate bool) 
(map[string]*si.Empty, *framework.CycleState, error) {
        ctx := context.Background()
-       state := framework.NewCycleState()
-       return p.podFitsNode(ctx, state, *p.allocationPreFilters, 
*p.allocationFilters, pod, node)
-}
+       cycleState := framework.NewCycleState()
 
-func (p *predicateManagerImpl) podFitsNode(ctx context.Context, state 
*framework.CycleState, preFilters []fwk.PreFilterPlugin, filters 
[]fwk.FilterPlugin, pod *v1.Pod, node *framework.NodeInfo) (string, error) {
-       // Run "prefilter" plugins.
-       status, plugin, skip := p.runPreFilterPlugins(ctx, state, preFilters, 
pod, node)
-       if !status.IsSuccess() && !status.IsSkip() {
-               return plugin, errors.New(status.Message())
+       var status *fwk.Status
+       var feasibleNodes map[string]*si.Empty
+       if allocate {
+               status, feasibleNodes = p.runPreFilterPlugins(ctx, cycleState, 
*p.allocationPreFilters, pod)
+       } else {
+               status, feasibleNodes = p.runPreFilterPlugins(ctx, cycleState, 
*p.reservationPreFilters, pod)
        }
-
-       // Run "filter" plugins on node
-       status, plugin = p.runFilterPlugins(ctx, filters, state, pod, node, 
skip)
-       if !status.IsSuccess() {
-               return plugin, errors.New(status.Message())
+       if !status.IsSuccess() && !status.IsSkip() {
+               return map[string]*si.Empty{}, nil, errors.New(status.Message())
        }
-       return "", nil
+       return feasibleNodes, cycleState, nil
 }
 
-func (p *predicateManagerImpl) runPreFilterPlugins(ctx context.Context, state 
*framework.CycleState, plugins []fwk.PreFilterPlugin, pod *v1.Pod, node 
*framework.NodeInfo) (*fwk.Status, string, map[string]bool) {
-       var mergedNodes *fwk.PreFilterResult
-       skip := make(map[string]bool)
+func (p *predicateManagerImpl) runPreFilterPlugins(ctx context.Context, 
cycleState *framework.CycleState, plugins []fwk.PreFilterPlugin, pod *v1.Pod) 
(*fwk.Status, map[string]*si.Empty) {
+       skipPlugins := sets.New[string]()
+       feasibleNodes := make(map[string]*si.Empty)
        allNodes, err := p.sharedLister.NodeInfos().List()
        if err != nil {
                log.Log(log.ShimPredicates).Error("failed to list nodes",
                        zap.Error(err))
-               return fwk.AsStatus(err), "", skip
+               return fwk.AsStatus(err), feasibleNodes
        }
+       mergedPreFilterResults := &fwk.PreFilterResult{}
        for _, pl := range plugins {
                plugin := pl.Name()
-               nodes, status := p.runPreFilterPlugin(ctx, pl, state, pod, 
allNodes)
+               nodes, status := pl.PreFilter(ctx, cycleState, pod, allNodes)
                if status.IsSkip() {
-                       skip[plugin] = true
-               } else if !status.IsSuccess() {
-                       if status.IsRejected() {
-                               return status, "", skip
-                       }
+                       skipPlugins.Insert(plugin)
+                       continue
+               }
+               if status.IsRejected() {
+                       return status, map[string]*si.Empty{}
+               }
+               if !status.IsSuccess() {
                        err := errors.New(status.Message())
                        log.Log(log.ShimPredicates).Error("failed running 
PreFilter plugin",
                                zap.String("pluginName", plugin),
                                zap.String("pod", fmt.Sprintf("%s/%s", 
pod.Namespace, pod.Name)),
                                zap.Error(err))
-                       return fwk.AsStatus(errors.Join(fmt.Errorf("running 
PreFilter plugin %q: ", plugin), err)), plugin, skip
+                       return fwk.AsStatus(errors.Join(fmt.Errorf("running 
PreFilter plugin %q: ", plugin), err)), map[string]*si.Empty{}
                }
-               // Merge is nil safe and returns a new PreFilterResult result 
if mergedNodes was nil
-               mergedNodes = mergedNodes.Merge(nodes)
-               if !mergedNodes.AllNodes() && 
!mergedNodes.NodeNames.Has(node.Node().Name) {
-                       return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, 
"node not eligible"), plugin, skip
+               mergedPreFilterResults = mergedPreFilterResults.Merge(nodes)
+       }
+       if mergedPreFilterResults != nil {
+               for n := range mergedPreFilterResults.NodeNames {
+                       feasibleNodes[n] = &si.Empty{}
                }
        }
-
-       return nil, "", skip
+       if skipPlugins.Len() > 0 {
+               cycleState.SetSkipFilterPlugins(skipPlugins)
+       }
+       return nil, feasibleNodes
 }
 
-func (p *predicateManagerImpl) runPreFilterPlugin(ctx context.Context, pl 
fwk.PreFilterPlugin, state *framework.CycleState, pod *v1.Pod, allNodes 
[]fwk.NodeInfo) (*fwk.PreFilterResult, *fwk.Status) {
-       return pl.PreFilter(ctx, state, pod, allNodes)
+func (p *predicateManagerImpl) Filter(pod *v1.Pod, node *framework.NodeInfo, 
cycleState *framework.CycleState, allocate bool) (string, error) {
+       ctx := context.Background()
+
+       var status *fwk.Status
+       var plugin string
+       if allocate {
+               status, plugin = p.runFilterPlugins(ctx, *p.allocationFilters, 
cycleState, pod, node)
+       } else {
+               status, plugin = p.runFilterPlugins(ctx, *p.reservationFilters, 
cycleState, pod, node)
+       }
+       if !status.IsSuccess() {
+               return plugin, errors.New(status.Message())
+       }
+       return "", nil
 }
 
-func (p *predicateManagerImpl) runFilterPlugins(ctx context.Context, plugins 
[]fwk.FilterPlugin, state *framework.CycleState, pod *v1.Pod, nodeInfo 
fwk.NodeInfo, skip map[string]bool) (*fwk.Status, string) {
+func (p *predicateManagerImpl) runFilterPlugins(ctx context.Context, plugins 
[]fwk.FilterPlugin, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo 
fwk.NodeInfo) (*fwk.Status, string) {
+       skipPlugins := cycleState.GetSkipFilterPlugins()
        for _, pl := range plugins {
                plugin := pl.Name()
+
                // skip plugin if prefilter returned skip
-               if skip[plugin] {
+               if skipPlugins.Has(plugin) {
                        continue
                }
-               status := p.runFilterPlugin(ctx, pl, state, pod, nodeInfo)
+               status := pl.Filter(ctx, cycleState, pod, nodeInfo)
                if !status.IsSuccess() {
                        if !status.IsRejected() {
                                // Filter plugins are not supposed to return 
any status other than
@@ -282,10 +274,6 @@ func (p *predicateManagerImpl) runFilterPlugins(ctx 
context.Context, plugins []f
        return fwk.NewStatus(fwk.Success), ""
 }
 
-func (p *predicateManagerImpl) runFilterPlugin(ctx context.Context, pl 
fwk.FilterPlugin, state *framework.CycleState, pod *v1.Pod, nodeInfo 
fwk.NodeInfo) *fwk.Status {
-       return pl.Filter(ctx, state, pod, nodeInfo)
-}
-
 // EnableOptionalKubernetesFeatureGates ensures that any optional Kubernetes 
feature gates that YuniKorn supports are
 // enabled. Currently, as of Kubernetes 1.32, this includes PodLevelResources 
and InPlacePodVerticalScaling. These are
 // both safe to enable as part of our default configuration, as they also 
require the appropriate feature gates to be
@@ -303,7 +291,7 @@ func EnableOptionalKubernetesFeatureGates() {
        }
 }
 
-func NewPredicateManager(handle fwk.Handle) PredicateManager {
+func NewPredicateManager(handle fwk.Handle, registry fwruntime.Registry, 
config *apiConfig.KubeSchedulerConfiguration) PredicateManager {
        /*
                Default K8S plugins as of 1.32 that implement PreFilter:
                        NodeAffinity
@@ -372,11 +360,13 @@ func NewPredicateManager(handle fwk.Handle) 
PredicateManager {
                "*": true,
        }
 
-       return newPredicateManagerInternal(handle, reservationPreFilters, 
allocationPreFilters, reservationFilters, allocationFilters)
+       return newPredicateManagerInternal(handle, registry, config, 
reservationPreFilters, allocationPreFilters, reservationFilters, 
allocationFilters)
 }
 
 func newPredicateManagerInternal(
        handle fwk.Handle,
+       registry fwruntime.Registry,
+       config *apiConfig.KubeSchedulerConfiguration,
        reservationPreFilters map[string]bool,
        allocationPreFilters map[string]bool,
        reservationFilters map[string]bool,
@@ -385,21 +375,13 @@ func newPredicateManagerInternal(
        if metrics.Goroutines == nil {
                metrics.InitMetrics()
        }
-
-       pluginRegistry := plugins.NewInTreeRegistry()
-
-       cfg, err := defaultConfig() // latest.Default()
-       if err != nil {
-               log.Log(log.ShimPredicates).Fatal("Unable to get default 
predicate config", zap.Error(err))
-       }
-
-       profile := cfg.Profiles[0] // first profile is default
+       profile := config.Profiles[0] // first profile is default
        registeredPlugins := profile.Plugins
        createdPlugins := make([]fwk.Plugin, 0)
 
        // As of SchedulerConfiguration v1, all plugins implement MultiPoint, 
therefore we need to instantiate each one and
        // check to see what interfaces it implements dynamically
-       createPlugins(handle, pluginRegistry, &registeredPlugins.MultiPoint, 
&createdPlugins)
+       createPlugins(handle, registry, &registeredPlugins.MultiPoint, 
&createdPlugins)
 
        resPre := make([]fwk.Plugin, 0)
        allocPre := make([]fwk.Plugin, 0)
@@ -445,7 +427,7 @@ func filterPlugins(plugins []fwk.Plugin) 
*[]fwk.FilterPlugin {
        return &result
 }
 
-func defaultConfig() (*apiConfig.KubeSchedulerConfiguration, error) {
+func DefaultConfig() (*apiConfig.KubeSchedulerConfiguration, error) {
        versionedCfg := schedConfig.KubeSchedulerConfiguration{}
        versionedCfg.DebuggingConfiguration = 
*v1alpha1.NewRecommendedDebuggingConfiguration()
 
diff --git a/pkg/plugin/predicates/predicate_manager_test.go 
b/pkg/plugin/predicates/predicate_manager_test.go
index 0ab2b52c..eb592161 100644
--- a/pkg/plugin/predicates/predicate_manager_test.go
+++ b/pkg/plugin/predicates/predicate_manager_test.go
@@ -19,6 +19,8 @@
 package predicates
 
 import (
+       "context"
+       "errors"
        "strconv"
        "strings"
        "testing"
@@ -28,12 +30,16 @@ import (
        v1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/api/resource"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       runtime2 "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/util/sets"
        "k8s.io/apiserver/pkg/util/feature"
        "k8s.io/klog/v2"
        fwk "k8s.io/kube-scheduler/framework"
        v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
        "k8s.io/kubernetes/pkg/features"
+       apiConfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
        "k8s.io/kubernetes/pkg/scheduler/framework"
+       "k8s.io/kubernetes/pkg/scheduler/framework/plugins"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodename"
@@ -42,6 +48,7 @@ import (
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeunschedulable"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodevolumelimits"
        "k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread"
+       "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
        "k8s.io/kubernetes/pkg/util/taints"
 
        "github.com/apache/yunikorn-k8shim/pkg/client"
@@ -55,23 +62,60 @@ var (
        hugePageResourceA = 
v1helper.HugePageResourceName(resource.MustParse("2Mi"))
 )
 
-func TestPreemptionPredicatesEmpty(t *testing.T) {
-       ep := enabledPlugins(noderesources.Name)
-       handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+const (
+       mockPreFilterPlugin = "mock-prefilter-plugin"
+       mockFilterPlugin    = "mock-filter-plugin"
+       injectReason        = "injected status"
+       injectFilterReason  = "injected filter status"
+)
 
-       pod := &v1.Pod{}
-       node := framework.NewNodeInfo()
-       node.SetNode(&v1.Node{})
-       victims := make([]*v1.Pod, 0)
-       index := predicateManager.PreemptionPredicates(pod, node, victims, 0)
-       assert.Equal(t, index, -1, "should not find any victim index after 
preemption check")
+type injectedResult struct {
+       PreFilterResult *fwk.PreFilterResult `json:"preFilterResult,omitempty"`
+       PreFilterStatus int                  `json:"preFilterStatus,omitempty"`
+       FilterStatus    int                  `json:"filterStatus,omitempty"`
+}
+
+// MockPreFilterPlugin implements PreFilter interface.
+type MockPreFilterPlugin struct {
+       name string
+       inj  injectedResult
+}
+
+func (pl *MockPreFilterPlugin) PreFilterExtensions() fwk.PreFilterExtensions {
+       return nil
+}
+
+func (pl *MockPreFilterPlugin) Name() string {
+       return pl.name
 }
 
-func TestPreemptionPredicates(t *testing.T) {
+func (pl *MockPreFilterPlugin) PreFilter(ctx context.Context, state 
fwk.CycleState, p *v1.Pod, nodes []fwk.NodeInfo) (*fwk.PreFilterResult, 
*fwk.Status) {
+       return pl.inj.PreFilterResult, 
fwk.NewStatus(fwk.Code(pl.inj.PreFilterStatus), injectReason)
+}
+
+// MockFilterPlugin implements Filter interface.
+type MockFilterPlugin struct {
+       name string
+       inj  injectedResult
+}
+
+func (pl *MockFilterPlugin) Filter(ctx context.Context, state fwk.CycleState, 
pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status {
+       return fwk.NewStatus(fwk.Code(pl.inj.FilterStatus), injectFilterReason)
+}
+
+func (pl *MockFilterPlugin) Name() string {
+       return pl.name
+}
+
+func TestPreemptionFilterWithVictims(t *testing.T) {
        ep := enabledPlugins(noderesources.Name)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
+
+       emptyNode := framework.NewNodeInfo()
+       emptyNode.SetNode(&v1.Node{})
 
        pod := newResourcePod(framework.Resource{MilliCPU: 500, Memory: 
5000000})
        pod.Name = "smallpod"
@@ -103,23 +147,39 @@ func TestPreemptionPredicates(t *testing.T) {
        node.AddPod(victims[2])
        node.AddPod(victims[3])
 
-       // all but 1 existing pod should need removing
-       index := predicateManager.PreemptionPredicates(pod, node, victims, 1)
-       assert.Equal(t, index, 2, "wrong victim index")
-
        // try again, but with too many resources requested
-       pod = newResourcePod(framework.Resource{MilliCPU: 1500, Memory: 
15000000})
-       pod.Name = "largepod"
-       pod.UID = "largepod"
+       largePod := newResourcePod(framework.Resource{MilliCPU: 1500, Memory: 
15000000})
+       largePod.Name = "largepod"
+       largePod.UID = "largepod"
+
+       tests := []struct {
+               name          string
+               pod           *v1.Pod
+               node          *framework.NodeInfo
+               victims       []*v1.Pod
+               expectedIndex int
+       }{
+               {"invalid pod and no victims", &v1.Pod{}, emptyNode, 
make([]*v1.Pod, 0), -1},
+               {"valid pod with available victims", pod, node, victims, 2},
+               {"valid pod with not suitable victims", largePod, node, 
victims, -1},
+       }
 
-       index = predicateManager.PreemptionPredicates(pod, node, victims, 1)
-       assert.Equal(t, index, -1, "should not find any victim index after 
preemption check")
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       _, cycleState, err := 
predicateManager.PreFilter(tt.pod, true)
+                       assert.NilError(t, err)
+                       index := predicateManager.PreemptionFilter(tt.pod, 
tt.node, cycleState, tt.victims, 1)
+                       assert.Equal(t, index, tt.expectedIndex, "wrong victim 
index")
+               })
+       }
 }
 
 func TestEventsToRegister(t *testing.T) {
        ep := enabledPlugins(nodename.Name, interpodaffinity.Name, 
podtopologyspread.Name)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
 
        var queueingHintFn fwk.QueueingHintFn = func(logger klog.Logger, pod 
*v1.Pod, oldObj, newObj interface{}) (fwk.QueueingHint, error) {
                // illegal sentinel to ensure we called the correct function
@@ -141,7 +201,9 @@ func TestEventsToRegister(t *testing.T) {
 func TestPodFitsHost(t *testing.T) {
        ep := enabledPlugins(nodename.Name)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
        tests := []struct {
                pod  *v1.Pod
                node *v1.Node
@@ -188,7 +250,7 @@ func TestPodFitsHost(t *testing.T) {
                t.Run(test.name, func(t *testing.T) {
                        nodeInfo := framework.NewNodeInfo()
                        nodeInfo.SetNode(test.node)
-                       plugin, err := predicateManager.Predicates(test.pod, 
nodeInfo, true)
+                       plugin, err := predicateManager.Filter(test.pod, 
nodeInfo, framework.NewCycleState(), true)
                        if (err == nil) != test.fits {
                                t.Errorf("%s expected fit state '%t' did not 
match real state and err = %v, plugin = %v", test.name, test.fits, err, plugin)
                        }
@@ -224,7 +286,9 @@ func newPod(host string, hostPortInfos ...string) *v1.Pod {
 func TestPodFitsHostPorts(t *testing.T) {
        ep := enabledPlugins(nodeports.Name)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
 
        tests := []struct {
                pod      *v1.Pod
@@ -326,7 +390,10 @@ func TestPodFitsHostPorts(t *testing.T) {
 
        for _, test := range tests {
                t.Run(test.name, func(t *testing.T) {
-                       plugin, err := predicateManager.Predicates(test.pod, 
test.nodeInfo, true)
+                       _, cycleState, err := 
predicateManager.PreFilter(test.pod, true)
+                       assert.NilError(t, err)
+                       assert.Assert(t, cycleState != nil)
+                       plugin, err := predicateManager.Filter(test.pod, 
test.nodeInfo, cycleState, true)
                        if (err == nil) != test.fits {
                                t.Errorf("%s expected fit state '%t' did not 
match real state and err = %v, plugin = %v", test.name, test.fits, err, plugin)
                        }
@@ -338,7 +405,9 @@ func TestPodFitsHostPorts(t *testing.T) {
 func TestPodFitsSelector(t *testing.T) {
        ep := enabledPlugins(nodeports.Name, nodeaffinity.Name)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
 
        tests := []struct {
                pod      *v1.Pod
@@ -1020,8 +1089,10 @@ func TestPodFitsSelector(t *testing.T) {
                        }}
                        nodeInfo := framework.NewNodeInfo()
                        nodeInfo.SetNode(&node)
-
-                       plugin, err := predicateManager.Predicates(test.pod, 
nodeInfo, true)
+                       _, cycleState, err := 
predicateManager.PreFilter(test.pod, true)
+                       assert.NilError(t, err)
+                       assert.Assert(t, cycleState != nil)
+                       plugin, err := predicateManager.Filter(test.pod, 
nodeInfo, cycleState, true)
                        if (err == nil) != test.fits {
                                t.Errorf("%s expected fit state '%t' did not 
match real state and err = %v, plugin = %v", test.name, test.fits, err, plugin)
                        }
@@ -1095,7 +1166,9 @@ func TestEnableOptionalKubernetesFeatureGates(t 
*testing.T) {
 func TestRunGeneralPredicates(t *testing.T) {
        ep := enabledPlugins(noderesources.Name, nodename.Name, nodeports.Name, 
nodevolumelimits.CSIName)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
 
        resourceTests := []struct {
                pod      *v1.Pod
@@ -1159,7 +1232,10 @@ func TestRunGeneralPredicates(t *testing.T) {
        for _, test := range resourceTests {
                t.Run(test.name, func(t *testing.T) {
                        test.nodeInfo.SetNode(test.node)
-                       plugin, err := predicateManager.Predicates(test.pod, 
test.nodeInfo, true)
+                       _, cycleState, err := 
predicateManager.PreFilter(test.pod, true)
+                       assert.NilError(t, err)
+                       assert.Assert(t, cycleState != nil)
+                       plugin, err := predicateManager.Filter(test.pod, 
test.nodeInfo, cycleState, true)
                        if (err == nil) != test.fits {
                                t.Errorf("%s expected fit state '%t' did not 
match real state and err = %v, plugin = %v", test.name, test.fits, err, plugin)
                        }
@@ -1171,7 +1247,9 @@ func TestRunGeneralPredicates(t *testing.T) {
 func TestInterPodAffinity(t *testing.T) {
        ep := enabledPlugins(interpodaffinity.Name, nodeaffinity.Name)
        handle, lister := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
 
        podLabel := map[string]string{"service": "securityscan"}
        labels1 := map[string]string{
@@ -2104,7 +2182,11 @@ func TestInterPodAffinity(t *testing.T) {
                        nodeInfo := framework.NewNodeInfo(podsOnNode...)
                        nodeInfo.SetNode(test.node)
                        lister.NodeLister().Set([]fwk.NodeInfo{nodeInfo})
-                       pl, err := predicateManager.Predicates(test.pod, 
nodeInfo, true)
+
+                       _, cycleState, err := 
predicateManager.PreFilter(test.pod, true)
+                       assert.NilError(t, err)
+                       assert.Assert(t, cycleState != nil)
+                       pl, err := predicateManager.Filter(test.pod, nodeInfo, 
cycleState, true)
                        if (err == nil) != test.fits {
                                t.Errorf("%s expected fit state '%t' did not 
match real state and err = %v, plugin = %v", test.name, test.fits, err, pl)
                        }
@@ -2129,14 +2211,16 @@ func TestReserveAlloc(t *testing.T) {
        // no predicates configured that are run by reservations
        ep := enabledPlugins()
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
-       _, err := predicateManager.Predicates(pod, nodeInfo, false)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
+       _, err = predicateManager.Filter(pod, nodeInfo, 
framework.NewCycleState(), false)
        assert.NilError(t, err, "error should have been nil, no predicates 
given")
 
        // add one predicate also run by reservations
        ep[nodeunschedulable.Name] = true
-       predicateManager = newPredicateManagerInternal(handle, ep, ep, ep, ep)
-       _, err = predicateManager.Predicates(pod, nodeInfo, false)
+       predicateManager = newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
+       _, err = predicateManager.Filter(pod, nodeInfo, 
framework.NewCycleState(), false)
        assert.NilError(t, err, "error should have been nil, node is 
schedulable")
 
        // make the node unschedulable
@@ -2147,7 +2231,7 @@ func TestReserveAlloc(t *testing.T) {
        node.Spec.Unschedulable = true
        assert.NilError(t, err, "failed to add taint")
        nodeInfo.SetNode(node)
-       _, err = predicateManager.Predicates(pod, nodeInfo, false)
+       _, err = predicateManager.Filter(pod, nodeInfo, 
framework.NewCycleState(), false)
        if err == nil {
                t.Errorf("error should not have been nil, predicate should have 
failed")
        }
@@ -2170,7 +2254,9 @@ func TestReserveNodeSelector(t *testing.T) {
 
        ep := enabledPlugins(nodename.Name, nodeports.Name, 
podtopologyspread.Name, nodeaffinity.Name)
        handle, _ := getFrameworkHandle()
-       predicateManager := newPredicateManagerInternal(handle, ep, ep, ep, ep)
+       config, err := DefaultConfig()
+       assert.NilError(t, err)
+       predicateManager := newPredicateManagerInternal(handle, 
plugins.NewInTreeRegistry(), config, ep, ep, ep, ep)
 
        testCases := []struct {
                name          string
@@ -2187,7 +2273,10 @@ func TestReserveNodeSelector(t *testing.T) {
                t.Run(tc.name, func(t *testing.T) {
                        pod.Spec.NodeSelector = tc.nodeSelectors
                        node.Labels = tc.nodeLabels
-                       plugin, err := predicateManager.Predicates(pod, 
nodeInfo, false)
+                       _, cycleState, err := predicateManager.PreFilter(pod, 
true)
+                       assert.NilError(t, err)
+                       assert.Assert(t, cycleState != nil)
+                       plugin, err := predicateManager.Filter(pod, nodeInfo, 
cycleState, false)
                        log.Log(log.Test).Info("reservation predicates called", 
zap.Error(err), zap.String("plugin", plugin))
                        if tc.errorExpected {
                                assert.Assert(t, err != nil, "An error is 
expected")
@@ -2198,6 +2287,336 @@ func TestReserveNodeSelector(t *testing.T) {
        }
 }
 
+func TestPreFilter(t *testing.T) {
+       node1 := "node1"
+       node2 := "node2"
+       node3 := "node3"
+       nodes, nodes1, nodes2, nodes3 := make(sets.Set[string]), 
make(sets.Set[string]), make(sets.Set[string]), make(sets.Set[string])
+       nodes.Insert(node1)
+       nodes1.Insert(node1)
+       nodes1.Insert(node2)
+       nodes2.Insert(node1)
+       nodes2.Insert(node3)
+       nodes3.Insert(node2)
+
+       pod := &v1.Pod{
+               Spec: v1.PodSpec{
+                       NodeName: "foo",
+               },
+       }
+       testCases := []struct {
+               name           string
+               pod            *v1.Pod
+               plugins        []*MockPreFilterPlugin
+               feasibleNodes  []string
+               skippedPlugins []string
+               errorExpected  error
+       }{
+               {"success", pod,
+                       []*MockPreFilterPlugin{
+                               {name: mockPreFilterPlugin, inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{NodeNames: nodes}, 
PreFilterStatus: int(fwk.Success)}},
+                               {name: mockPreFilterPlugin + "-skip", inj: 
injectedResult{PreFilterStatus: int(fwk.Skip)}},
+                       },
+                       []string{node1},
+                       []string{"mock-prefilter-plugin-skip"}, nil,
+               },
+               {"intersection of nodes", pod,
+                       []*MockPreFilterPlugin{
+                               {name: mockPreFilterPlugin + "-1", inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{NodeNames: nodes1}, 
PreFilterStatus: int(fwk.Success)}},
+                               {name: mockPreFilterPlugin + "-2", inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{NodeNames: nodes2}, 
PreFilterStatus: int(fwk.Success)}},
+                       },
+                       []string{node1},
+                       nil, nil,
+               },
+
+               {"disjoint set of nodes", pod,
+                       []*MockPreFilterPlugin{
+                               {name: mockPreFilterPlugin + "-1", inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{NodeNames: nodes}, 
PreFilterStatus: int(fwk.Success)}},
+                               {name: mockPreFilterPlugin + "-2", inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{NodeNames: nodes3}, 
PreFilterStatus: int(fwk.Success)}},
+                       },
+                       []string{},
+                       nil, nil,
+               },
+
+               {"rejected - unschedulable and unresolvable", pod,
+                       []*MockPreFilterPlugin{
+                               {name: mockPreFilterPlugin, inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{}, PreFilterStatus: 
int(fwk.UnschedulableAndUnresolvable)}},
+                       },
+                       []string{},
+                       nil, errors.New("UnschedulableAndUnresolvable"),
+               },
+               {"rejected - unschedulable", pod,
+                       []*MockPreFilterPlugin{
+                               {name: mockPreFilterPlugin, inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{}, PreFilterStatus: 
int(fwk.Unschedulable)}},
+                       },
+                       []string{},
+                       nil, errors.New("unschedulable"),
+               },
+               {"error", pod,
+                       []*MockPreFilterPlugin{
+                               {name: mockPreFilterPlugin, inj: 
injectedResult{PreFilterResult: &fwk.PreFilterResult{}, PreFilterStatus: 
int(fwk.Error)}},
+                       },
+                       []string{},
+                       nil, errors.New("error"),
+               },
+       }
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       r := make(runtime.Registry)
+                       enabled := make([]apiConfig.Plugin, len(tc.plugins))
+                       var eps []string
+                       for i, p := range tc.plugins {
+                               enabled[i].Name = p.name
+                               eps = append(eps, p.name)
+                               if err := r.Register(p.name, func(_ 
context.Context, _ runtime2.Object, fh fwk.Handle) (fwk.Plugin, error) {
+                                       return p, nil
+                               }); err != nil {
+                                       t.Fatalf("fail to register PreFilter 
plugin (%s)", p.Name())
+                               }
+                       }
+                       for _, allocate := range []bool{true, false} {
+                               ep := enabledPlugins(eps...)
+                               handle, _ := getFrameworkHandle()
+                               config, err := prepareConfig(eps)
+                               assert.NilError(t, err)
+                               var resPreFilters map[string]bool
+                               var allocPreFilters map[string]bool
+                               if allocate {
+                                       allocPreFilters = ep
+                               } else {
+                                       resPreFilters = ep
+                               }
+                               p := newPredicateManagerInternal(handle, r, 
config, resPreFilters, allocPreFilters, nil, nil)
+                               feasibleNodes, cycleState, filterErr := 
p.PreFilter(tc.pod, allocate)
+                               if tc.skippedPlugins != nil {
+                                       for _, sp := range tc.skippedPlugins {
+                                               assert.Assert(t, 
cycleState.GetSkipFilterPlugins().Has(sp) == true, nil)
+                                       }
+                               }
+                               if tc.feasibleNodes != nil {
+                                       assert.Equal(t, len(tc.feasibleNodes), 
len(feasibleNodes))
+                                       for _, fn := range tc.feasibleNodes {
+                                               assert.Assert(t, 
feasibleNodes[fn] != nil, nil)
+                                       }
+                               }
+                               if tc.errorExpected != nil {
+                                       assert.Assert(t, filterErr != nil, 
"error should not be nil")
+                               }
+                       }
+               })
+       }
+}
+
+func TestFilter(t *testing.T) {
+       pod := &v1.Pod{
+               Spec: v1.PodSpec{
+                       NodeName: "foo",
+               },
+       }
+       node := &v1.Node{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name: "foo",
+               }}
+       skip := make(sets.Set[string])
+       testCases := []struct {
+               name           string
+               pod            *v1.Pod
+               filterPlugins  []*MockFilterPlugin
+               skippedPlugins sets.Set[string]
+               errorExpected  error
+       }{
+               {"success", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.Success)}},
+                       },
+                       nil, nil,
+               },
+               {
+                       "skip", pod,
+                       []*MockFilterPlugin{
+                               // Inject error code so that if not skipped 
error would be thrown
+                               {name: mockFilterPlugin + "-skip", inj: 
injectedResult{FilterStatus: int(fwk.Error)}},
+                       },
+                       skip.Insert("mock-filter-plugin-skip"), nil,
+               },
+               {"rejected - unschedulable and unresolvable", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.UnschedulableAndUnresolvable)}},
+                       },
+                       nil, errors.New("UnschedulableAndUnresolvable"),
+               },
+               {"rejected - unschedulable", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.Unschedulable)}},
+                       },
+                       nil, errors.New("unschedulable"),
+               },
+               {"error", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.Error)}},
+                       },
+                       nil, errors.New("error"),
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       r := make(runtime.Registry)
+                       enabled := make([]apiConfig.Plugin, 
len(tc.filterPlugins))
+                       var eps []string
+                       for i, p := range tc.filterPlugins {
+                               enabled[i].Name = p.name
+                               eps = append(eps, p.name)
+                               if err := r.Register(p.name, func(_ 
context.Context, _ runtime2.Object, fh fwk.Handle) (fwk.Plugin, error) {
+                                       return p, nil
+                               }); err != nil {
+                                       t.Fatalf("fail to register PreFilter 
plugin (%s)", p.Name())
+                               }
+                       }
+                       for _, allocate := range []bool{true, false} {
+                               ep := enabledPlugins(eps...)
+                               handle, _ := getFrameworkHandle()
+                               config, err := prepareConfig(eps)
+                               assert.NilError(t, err)
+                               var resFilters map[string]bool
+                               var allocFilters map[string]bool
+                               if allocate {
+                                       allocFilters = ep
+                               } else {
+                                       resFilters = ep
+                               }
+                               p := newPredicateManagerInternal(handle, r, 
config, nil, nil, resFilters, allocFilters)
+                               cycleState := framework.NewCycleState()
+                               if tc.skippedPlugins != nil {
+                                       
cycleState.SetSkipFilterPlugins(tc.skippedPlugins)
+                               }
+                               nodeInfo := framework.NewNodeInfo()
+                               nodeInfo.SetNode(node)
+                               filter, err := p.Filter(tc.pod, nodeInfo, 
cycleState, allocate)
+                               if tc.errorExpected != nil {
+                                       assert.Assert(t, err != nil, "error 
should not be nil")
+                                       assert.Equal(t, filter, 
tc.filterPlugins[0].Name())
+                               } else {
+                                       assert.Equal(t, filter, "")
+                               }
+                       }
+               })
+       }
+}
+
+func TestPreemptionFilter(t *testing.T) {
+       pod := &v1.Pod{
+               Spec: v1.PodSpec{
+                       NodeName: "foo",
+               },
+       }
+       node := &v1.Node{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name: "foo",
+               }}
+       skip := make(sets.Set[string])
+       testCases := []struct {
+               name           string
+               pod            *v1.Pod
+               filterPlugins  []*MockFilterPlugin
+               skippedPlugins sets.Set[string]
+               errorExpected  error
+       }{
+               {"success", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.Success)}},
+                       },
+                       nil, nil,
+               },
+               {
+                       "skip", pod,
+                       []*MockFilterPlugin{
+                               // Inject error code so that if not skipped 
error would be thrown
+                               {name: mockFilterPlugin + "-skip", inj: 
injectedResult{FilterStatus: int(fwk.Error)}},
+                       },
+                       skip.Insert("mock-filter-plugin-skip"), nil,
+               },
+               {"rejected - unschedulable and unresolvable", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.UnschedulableAndUnresolvable)}},
+                       },
+                       nil, errors.New("UnschedulableAndUnresolvable"),
+               },
+               {"rejected - unschedulable", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.Unschedulable)}},
+                       },
+                       nil, errors.New("unschedulable"),
+               },
+               {"error", pod,
+                       []*MockFilterPlugin{
+                               {name: mockFilterPlugin, inj: 
injectedResult{FilterStatus: int(fwk.Error)}},
+                       },
+                       nil, errors.New("error"),
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       r := make(runtime.Registry)
+                       enabled := make([]apiConfig.Plugin, 
len(tc.filterPlugins))
+                       var eps []string
+                       for i, p := range tc.filterPlugins {
+                               enabled[i].Name = p.name
+                               eps = append(eps, p.name)
+                               if err := r.Register(p.name, func(_ 
context.Context, _ runtime2.Object, fh fwk.Handle) (fwk.Plugin, error) {
+                                       return p, nil
+                               }); err != nil {
+                                       t.Fatalf("fail to register PreFilter 
plugin (%s)", p.Name())
+                               }
+                       }
+                       ep := enabledPlugins(eps...)
+                       handle, _ := getFrameworkHandle()
+                       config, err := prepareConfig(eps)
+                       assert.NilError(t, err)
+                       p := newPredicateManagerInternal(handle, r, config, 
nil, nil, nil, ep)
+                       cycleState := framework.NewCycleState()
+                       if tc.skippedPlugins != nil {
+                               
cycleState.SetSkipFilterPlugins(tc.skippedPlugins)
+                       }
+                       nodeInfo := framework.NewNodeInfo()
+                       nodeInfo.SetNode(node)
+
+                       victims := []*v1.Pod{
+                               newResourcePod(framework.Resource{MilliCPU: 
100, Memory: 1000000}),
+                       }
+                       victims[0].Name = "pod0"
+                       idx := p.PreemptionFilter(tc.pod, nodeInfo, cycleState, 
victims, 0)
+                       if tc.errorExpected != nil {
+                               assert.Equal(t, idx, -1)
+                       } else {
+                               assert.Equal(t, idx, 0)
+                       }
+               })
+       }
+}
+
+func prepareConfig(plugins []string) (*apiConfig.KubeSchedulerConfiguration, 
error) {
+       var ep []apiConfig.Plugin
+       cfg := apiConfig.KubeSchedulerConfiguration{}
+       if len(plugins) > 0 {
+               for _, pl := range plugins {
+                       ep = append(ep, apiConfig.Plugin{Name: pl})
+               }
+               cfg.Profiles = []apiConfig.KubeSchedulerProfile{
+                       {
+                               SchedulerName: "default-scheduler",
+                               Plugins: &apiConfig.Plugins{
+                                       MultiPoint: apiConfig.PluginSet{
+                                               Enabled: ep,
+                                       },
+                               },
+                       },
+               }
+       }
+       return &cfg, nil
+}
+
 func enabledPlugins(name ...string) map[string]bool {
        pm := make(map[string]bool)
        for _, k := range name {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to