Repository: kubernetes/kubernetes (language: go)

Issue:
Sched Framework: Request to expose NodeInfo to PreFilter plugins and Score plugins
### What would you like to be added?

In #119987 and #121954, the scheduling framework introduced `NodeInfo` as a parameter in the `RunPreScorePlugins` and `RunScorePlugins` functions of the `PluginsRunner` interface. Thank @Huang-Wei and @AxeZhan for these improvements that have made the framework easier to use. However, `NodeInfo` is still not exposed to the functions of the `ScorePlugin` and `PreFilterPlugin` interfaces (see [relevant code](https://github.com/kubernetes/kubernetes/blob/release-1.32/pkg/scheduler/framework/interface.go#L929)), which remains inconvenient for plugin users like [Koordinator](https://github.com/koordinator-sh/koordinator).

For instance, Koordinator aims to support [directive preemption](https://github.com/koordinator-sh/koordinator/blob/main/docs/proposals/scheduling/20220609-resource-reservation.md). To achieve this, it has had to propose [specific extensions](https://github.com/koordinator-sh/koordinator/blob/main/docs/proposals/scheduling/20220513-enhanced-scheduler-extension.md) to construct `NodeInfo` with reserved resources during the scheduling cycle.

Currently, while the `RunScorePlugins` function of the `PluginsRunner` interface accepts `NodeInfo` as a parameter, it does not pass it directly to the individual `Score` plugins. The relevant interface definitions are as follows:

```golang
type ScorePlugin interface {
	Plugin
	Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status)
	ScoreExtensions() ScoreExtensions
}

type PreFilterPlugin interface {
	Plugin
	PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) (*PreFilterResult, *Status)
	PreFilterExtensions() PreFilterExtensions
}

type PluginsRunner interface {
	RunPreScorePlugins(context.Context, *CycleState, *v1.Pod, []*NodeInfo) *Status
	RunScorePlugins(context.Context, *CycleState, *v1.Pod, []*NodeInfo) ([]NodePluginScores, *Status)
	RunFilterPlugins(context.Context, *CycleState, *v1.Pod, *NodeInfo) *Status
	RunPreFilterExtensionAddPod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod, podInfoToAdd *PodInfo, nodeInfo *NodeInfo) *Status
	RunPreFilterExtensionRemovePod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod, podInfoToRemove *PodInfo, nodeInfo *NodeInfo) *Status
}
```

We propose exposing `NodeInfo` in the `ScorePlugin` and `PreFilterPlugin` interfaces to allow plugin developers to access and utilize node information more conveniently. This would greatly enhance the flexibility and functionality of plugin development.

The interface can be updated as follows:

```golang
type ScorePlugin interface {
	Plugin
	Score(ctx context.Context, state *CycleState, p *v1.Pod, node *NodeInfo) (int64, *Status)
	ScoreExtensions() ScoreExtensions
}

type PreFilterPlugin interface {
	Plugin
	PreFilter(ctx context.Context, state *CycleState, p *v1.Pod, nodes []*NodeInfo) (*PreFilterResult, *Status)
	PreFilterExtensions() PreFilterExtensions
}
```

### Why is this needed?

Just like PR #119987 mentioned:

> - consistent function signature - all use NodeInfo
> - unleash more potential to use scheduler framework natively w/o hacks

We aim to align with and enhance the upstream framework for the following reasons, and we believe this change will benefit both the Kubernetes community and third-party plugin developers:

1. **Consistency**: Ensure that all plugins, including `ScorePlugin` and `PreFilterPlugin`, use a consistent `NodeInfo` parameter to simplify plugin development and reduce errors from mismatched data structures.

2. **Enhanced Flexibility**: Exposing `NodeInfo` directly allows plugin developers to implement advanced scheduling logic without workarounds, promoting cleaner and more maintainable code.

3. **Eliminate Workarounds**: Projects like [Koordinator](https://github.com/koordinator-sh/koordinator) currently need custom extensions to construct `NodeInfo` with reserved resources. Direct exposure of the `NodeInfo` would eliminate these workarounds.

4. **Future-Proofing**: Aligning with the upstream framework ensures easier adoption of future Kubernetes scheduler enhancements.

Looking forward to your feedback and support!


Discussion / hints:
This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
/sig scheduling
IIUC, the prefilter is used to determine whether this pod needs to enter the filter? 🤔  If nodeInfo is needed, why can't the logic be implemented in the filter?

FYI: https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/#pre-filter

In addition, can we do what you need in PreScorePlugin and store it in cycleState? 
@googs1025

Thank you for your questions.

> IIUC, the prefilter is used to determine whether this pod needs to enter the filter? 🤔 If nodeInfo is needed, why can't the logic be implemented in the filter?

To clarify, some PreFilter plugins indeed rely on the SharedLister to gather all nodeInfos to generate a PreFilterResult, including the InterPodAffinity and PodTopologySpread plugins.

> In addition, can we do what you need in PreScorePlugin and store it in cycleState?

Our primary concern is ensuring that all Score plugins can share the same NodeInfo with PreScorePlugins by default. Ideally, this shared NodeInfo should be obtained directly from the RunScorePlugins rather than getting from the SharedLister with the nodeName. For example, Koordinator aims to have Score plugins work on a local NodeInfo that includes nominated pods to do the [directive preemption](https://github.com/koordinator-sh/koordinator/blob/main/docs/proposals/scheduling/20220609-resource-reservation.md). If Score plugins were to fetch NodeInfo from the SharedLister, they would need to reconstruct the NodeInfo, leading to redundant implementations across plugins.

Moreover, we've observed the current pattern in the RunScorePlugins, where NodeInfo is passed as `nodeInfo.Node().Name` to the Score plugins, is somewhat counterintuitive and could be improved for better consistency and efficiency (see [relevant code](https://github.com/kubernetes/kubernetes/blob/release-1.32/pkg/scheduler/framework/runtime/framework.go#L1140)).

> Our primary concern is ensuring that all Score plugins can share the same NodeInfo with PreScorePlugins by default. Ideally, this shared NodeInfo should be obtained directly from the RunScorePlugins rather than getting from the SharedLister with the nodeName. For example, Koordinator aims to have Score plugins work on a local NodeInfo that includes nominated pods to do the [directive preemption](https://github.com/koordinator-sh/koordinator/blob/main/docs/proposals/scheduling/20220609-resource-reservation.md). If Score plugins were to fetch NodeInfo from the SharedLister, they would need to reconstruct the NodeInfo, leading to redundant implementations across plugins.


What I mean is: we can assemble the required info from PreScore and put it into cycleState, without using shareinformer in Score to get node information.

I think I fully understand the usecase of ScorePlugin. If we change the signature from `nodeName string` to `nodeInfo *NodeInfo`, the upstream code like:

https://github.com/kubernetes/kubernetes/blob/81f03c2f5b7bedee39762f587af7c3350cc83cce/pkg/scheduler/framework/plugins/imagelocality/image_locality.go#L55

can be simplified as well.

However, for PreFilterPlugin, a plugin can always get `nodeInfos []*NodeInfo` from the snapshotLister, so what's the usecase of change the signature to include `nodes []*NodeInfo`?

```go
# Proposed by @saintube
type PreFilterPlugin interface {
    PreFilter(ctx context.Context, state *CycleState, p *v1.Pod, nodes []*NodeInfo) (*PreFilterResult, *Status)
}
```
> What I mean is: we can assemble the required info from PreScore and put it into cycleState, without using shareinformer in Score to get node information.

@googs1025 
IMO, storing NodeInfos from PreScore into the CycleState and passing them to the ScorePlugin is indeed a workaround. However, if we already have access to NodeInfo within `RunScorePlugins`, it would be more straightforward and efficient to pass this information directly.
> I think I fully understand the usecase of ScorePlugin. If we change the signature from `nodeName string` to `nodeInfo *NodeInfo`, the upstream code like:
> 
> [kubernetes/pkg/scheduler/framework/plugins/imagelocality/image_locality.go](https://github.com/kubernetes/kubernetes/blob/81f03c2f5b7bedee39762f587af7c3350cc83cce/pkg/scheduler/framework/plugins/imagelocality/image_locality.go#L55)
> 
> Line 55 in [81f03c2](/kubernetes/kubernetes/commit/81f03c2f5b7bedee39762f587af7c3350cc83cce)
> 
>  nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) 
> can be simplified as well.

@Huang-Wei

Thanks for your explanation. 

You are correct about the `ScorePlugin`. Many plugins currently retrieve `NodeInfo` using the `nodeName`, which can be simplified by passing `NodeInfo` directly from `RunScorePlugins`. This change would streamline the argumentation process and eliminate redundant calls to fetch `NodeInfo`.

> However, for PreFilterPlugin, a plugin can always get `nodeInfos []*NodeInfo` from the snapshotLister, so what's the usecase of change the signature to include `nodes []*NodeInfo`?

For the `PreFilterPlugin`, there are additional considerations:

1. Consistent Resource View: In the Koordinator's directive preemption, the preemptor should consider nominated pods during the `PreFilter` phase to calculate pod affinities and topology spread constraints. Passing `NodeInfos` directly ensures that `PreFilter` plugins can access the most up-to-date and consistent resource view at the beginning of the scheduling cycle. This avoids the need for the preemptor to reconstruct the nominated information and re-run `PreFilter` plugins for verification.
2. Eliminate Redundancy: Many `PreFilter` plugins currently retrieve all `NodeInfos` from the `SharedLister`, which introduces unnecessary redundancy.

This kind of interface modification will affect all users to adapt to new k8s version. My understanding is: before changing it, we need to carefully evaluate whether it is really necessary.
> * Consistent Resource View: In the Koordinator's directive preemption, the preemptor should consider nominated pods during the `PreFilter` phase to calculate pod affinities and topology spread constraints. Passing `NodeInfos` directly ensures that `PreFilter` plugins can access the most up-to-date and consistent resource view at the beginning of the scheduling cycle. This avoids the need for the preemptor to reconstruct the nominated information and re-run `PreFilter` plugins for verification.
> * Eliminate Redundancy: Many `PreFilter` plugins currently retrieve all `NodeInfos` from the `SharedLister`, which introduces unnecessary redundancy.

I'm convinced. +1 for having this.

We should check which components are affected by this though, and notify them early and discuss whether it is feasible for them. 
https://github.com/kubernetes-sigs/scheduler-plugins is one, this will require them do some refactoring.
I checked CA and descheduler, should be ok for them, not sure.
@googs1025 @AxeZhan Thanks for your reminder.

I have investigated the impact of the proposed changes to the `PreFilter` and `Score` plugins. The following projects are likely affected. Please let me know if I've missed any:

1. **[scheduler-plugins](https://github.com/kubernetes-sigs/scheduler-plugins/)**
2. **[kube-scheduler-simulator](https://github.com/kubernetes-sigs/kube-scheduler-simulator/)**
3. **[kube-scheduler-wasm-extension](https://github.com/kubernetes-sigs/kube-scheduler-wasm-extension/)**
4. **[autoscaler](https://github.com/kubernetes/autoscaler)**

These projects currently rely on the existing plugin interfaces. Updating them will require adjustments to ensure compatibility with the new interface definitions, primarily replacing `SnapshotSharedLister` methods with direct `NodeInfo` arguments. 

I will coordinate with the maintainers of these projects to evaluate and implement the necessary changes.

/cc @Huang-Wei @sanposhiho @kerthcet 

> 1. Consistent Resource View: In the Koordinator's directive preemption, the preemptor should consider nominated pods during the `PreFilter` phase to calculate pod affinities and topology spread constraints. Passing `NodeInfos` directly ensures that `PreFilter` plugins can access the most up-to-date and consistent resource view at the beginning of the scheduling cycle. This avoids the need for the preemptor to reconstruct the nominated information and re-run `PreFilter` plugins for verification.

It seems the essential ask is to be able to mutate the in-memory `NodeInfo` instead of getting a _deepCopy_ of it, am I understanding right?
> > 1. Consistent Resource View: In the Koordinator's directive preemption, the preemptor should consider nominated pods during the `PreFilter` phase to calculate pod affinities and topology spread constraints. Passing `NodeInfos` directly ensures that `PreFilter` plugins can access the most up-to-date and consistent resource view at the beginning of the scheduling cycle. This avoids the need for the preemptor to reconstruct the nominated information and re-run `PreFilter` plugins for verification.
> 
> It seems the essential ask is to be able to mutate the in-memory `NodeInfo` instead of getting a _deepCopy_ of it, am I understanding right?

@Huang-Wei Yes, mutating the in-memory `NodeInfo` is an option to prepare the resource view for the PreFilter plugins. However, we should carefully consider whether mutating the in-memory NodeInfo is the best approach. To ensure better concurrency and consistency, we might prefer passing a copied NodeInfo to the PreFilter plugins to maintain immutability of the nodeSnapshot.

> Yes, mutating the in-memory NodeInfo is an option to prepare the resource view for the PreFilter plugins.

That's also a non-goal of scheduler framework - plugins should obtain NodeInfo from a deepCopy.

The proposal sounds good to me. Thanks for raising this.

FYI @kubernetes/sig-scheduling-misc 
> We should check which components are affected by this though, and notify them early and discuss whether it is feasible for them.

We will need to double check [cluster-autoscaler ](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler). And we have a couple of tests as well to ensure the SDK's compatibility: https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/autoscaler_contract.

So RunPreFilterPlugins is really used in cluster-autoscaler: https://github.com/kubernetes/autoscaler/blob/a58d346c09f2e06d2be3a06865f5ab44684c05ab/cluster-autoscaler/simulator/clustersnapshot/predicate/plugin_runner.go#L60
+1, I don't have any strong concerns. 
Actually, I was previously wondering if we should unify all pods and nodes into PodInfo and NodeInfo so that we can pre-calculate and pack things that might be useful to plugins in those. So, I mean, in addition to this proposal, we might also want to do the same for pods (v1.Pod -> PodInfo) in plugin interfaces.

For the actual example, InterPodAffinity plugin builds PodInfo from a given pod at PreFilter, which could be avoided if we just pass PodInfo to PreFilter interface in the first place:
https://github.com/kubernetes/kubernetes/blob/60d0d67fefd62c05275b75bbaa5391754f1bca5e/pkg/scheduler/framework/plugins/interpodaffinity/filtering.go#L286-L288
@sanposhiho let's handle one thing at a time ;)
yeah, I mean that's an off topic, out of scope from this issue, though.
> We will need to double check [cluster-autoscaler ](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler). And we have a couple of tests as well to ensure the SDK's compatibility: https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/autoscaler_contract.
> 
> So RunPreFilterPlugins is really used in cluster-autoscaler: https://github.com/kubernetes/autoscaler/blob/a58d346c09f2e06d2be3a06865f5ab44684c05ab/cluster-autoscaler/simulator/clustersnapshot/predicate/plugin_runner.go#L60

OK. I've updated the list of affected projects accordingly.
@saintube: Reiterating the mentions to trigger a notification: 
@kubernetes/sig-scheduling-misc, @kubernetes/sig-autoscaling-misc, @kubernetes/sig-scheduling-misc


<details>

In response to [this](https://github.com/kubernetes/kubernetes/issues/130404#issuecomment-2693170783):

>> > Yes, mutating the in-memory NodeInfo is an option to prepare the resource view for the PreFilter plugins.
>> 
>> That's also a non-goal of scheduler framework - plugins should obtain NodeInfo from a deepCopy.
>> 
>> The proposal sounds good to me. Thanks for raising this.
>> 
>> FYI @kubernetes/sig-scheduling-misc
>
>Thanks @Huang-Wei @sanposhiho for the review.
>
>To summarize, this proposal impacts projects that rely on the existing PreFilterPlugin or ScorePlugin interfaces. The changes primarily involve adding NodeInfo arguments to replace the SnapshotSharedLister methods in the following projects:
>
>- [scheduler-plugins](https://github.com/kubernetes-sigs/scheduler-plugins/)
>- [kube-scheduler-simulator](https://github.com/kubernetes-sigs/kube-scheduler-simulator/)
>- [kube-scheduler-wasm-extension](https://github.com/kubernetes-sigs/kube-scheduler-wasm-extension/)
>- [autoscaler](https://github.com/kubernetes/autoscaler)
>
>FYI: @x13n @kubernetes/sig-autoscaling-misc @kubernetes/sig-scheduling-misc 
>
>Please share any concerns you may have.


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
> > Yes, mutating the in-memory NodeInfo is an option to prepare the resource view for the PreFilter plugins.
> 
> That's also a non-goal of scheduler framework - plugins should obtain NodeInfo from a deepCopy.
> 
> The proposal sounds good to me. Thanks for raising this.
> 
> FYI @kubernetes/sig-scheduling-misc

Thanks @Huang-Wei @sanposhiho for the review.

To summarize, this proposal impacts projects that rely on the existing PreFilterPlugin or ScorePlugin interfaces. The changes primarily involve adding NodeInfo arguments to replace the SnapshotSharedLister methods in the following projects:

- [scheduler-plugins](https://github.com/kubernetes-sigs/scheduler-plugins/)
- [kube-scheduler-simulator](https://github.com/kubernetes-sigs/kube-scheduler-simulator/)
- [kube-scheduler-wasm-extension](https://github.com/kubernetes-sigs/kube-scheduler-wasm-extension/)
- [autoscaler](https://github.com/kubernetes/autoscaler)

FYI: @x13n, @kubernetes/sig-autoscaling-misc

Please share any concerns you may have.
@saintube: Reiterating the mentions to trigger a notification: 
@kubernetes/sig-scheduling-misc, @kubernetes/sig-autoscaling-misc


<details>

In response to [this](https://github.com/kubernetes/kubernetes/issues/130404#issuecomment-2693173189):

>> > Yes, mutating the in-memory NodeInfo is an option to prepare the resource view for the PreFilter plugins.
>> 
>> That's also a non-goal of scheduler framework - plugins should obtain NodeInfo from a deepCopy.
>> 
>> The proposal sounds good to me. Thanks for raising this.
>> 
>> FYI @kubernetes/sig-scheduling-misc
>
>Thanks @Huang-Wei @sanposhiho for the review.
>
>To summarize, this proposal impacts projects that rely on the existing PreFilterPlugin or ScorePlugin interfaces. The changes primarily involve adding NodeInfo arguments to replace the SnapshotSharedLister methods in the following projects:
>
>- [scheduler-plugins](https://github.com/kubernetes-sigs/scheduler-plugins/)
>- [kube-scheduler-simulator](https://github.com/kubernetes-sigs/kube-scheduler-simulator/)
>- [kube-scheduler-wasm-extension](https://github.com/kubernetes-sigs/kube-scheduler-wasm-extension/)
>- [autoscaler](https://github.com/kubernetes/autoscaler)
>
>FYI: @x13n, @kubernetes/sig-autoscaling-misc
>
>Please share any concerns you may have.


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
I'm the owner of simulator and wasn extension, and Wei is the owner of scheduler plugins. So, it's ok for SIG-Scheduling projects.
I think you can just go ahead and create a PR anyway. We've introduced a similar level of breaking changes some times, and I suppose SIG-Autoscaling would say also OK - and we can get a final agreement on the PR (they're an owner of contract files, so rather we have to)
Wait, the contract files don't include the plugin interfaces. @saintube do they (clusterautoscaler) actually use plugin interfaces that we're going to change?
https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/autoscaler_contract
> Wait, the contract files don't include the plugin interfaces. @saintube do they (clusterautoscaler) actually use plugin interfaces that we're going to change? https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/autoscaler_contract

@sanposhiho You are correct. It appears that cluster-autoscaler uses `RunPreFilterPlugins` from the `Framework` but does not rely on the PreFilter method. Therefore, the [plugin interfaces](https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/autoscaler_contract) for the autoscaler should remain **unchanged**.

Reference: [cluster-autoscaler/plugin_runner.go#L57-L65](https://github.com/kubernetes/autoscaler/blob/52147e1f1bee92ba1ceb218b89bc18702c8a0f49/cluster-autoscaler/simulator/clustersnapshot/predicate/plugin_runner.go#L57-L65)

> I think you can just go ahead and create a PR anyway. We've introduced a similar level of breaking changes some times, and I suppose SIG-Autoscaling would say also OK - and we can get a final agreement on the PR (they're an owner of contract files, so rather we have to)

OK. I’ll proceed with creating the PR.

/assign
+1 for the proposed change

> [Kensei Nakada](https://github.com/sanposhiho) let's handle one thing at a time ;)

@Huang-Wei maybe we should consider making these two changes together, so that we'd change the interface only once.
> so that we'd change the interface only once

By "once", do you mean consolidating the interface changes in _one release_. If so, it makes sense to me. The changes should be split into several PRs to ease reviewing.
> Wait, the contract files don't include the plugin interfaces. [@saintube](https://github.com/saintube) do they (clusterautoscaler) actually use plugin interfaces that we're going to change? https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/autoscaler_contract

Looks like you already discovered that, but just to confirm: yes, Cluster Autoscaler doesn't currently use scoring plugins at all, so there's no dependency there.
> yes, Cluster Autoscaler doesn't currently use scoring plugins at all, so there's no dependency there.

Apart from scoring, it's proposed to also change the PreFilter interface:

https://github.com/kubernetes/kubernetes/blob/a5dda5d879cdae6562134ca7881ddf7f672f595d/pkg/scheduler/framework/interface.go#L512

w/ a diff like:

```diff
diff --git a/pkg/scheduler/framework/interface.go b/pkg/scheduler/framework/interface.go
index cf6f39c6748..ec7dd0c0b93 100644
--- a/pkg/scheduler/framework/interface.go
+++ b/pkg/scheduler/framework/interface.go
@@ -520,7 +520,7 @@ type PreFilterPlugin interface {
 	//
 	// When it returns Skip status, returned PreFilterResult and other fields in status are just ignored,
 	// and coupled Filter plugin/PreFilterExtensions() will be skipped in this scheduling cycle.
-	PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) (*PreFilterResult, *Status)
+	PreFilter(ctx context.Context, state *CycleState, p *v1.Pod, nodes []*NodeInfo) (*PreFilterResult, *Status)
 	// PreFilterExtensions returns a PreFilterExtensions interface if the plugin implements one,
 	// or nil if it does not. A Pre-filter plugin can provide extensions to incrementally
 	// modify its pre-processed info. The framework guarantees that the extensions
```
Ah, good point, I missed this part. Do we need to update `RunPreFilterPlugins` then? It is always invoked in the context of a specific set of nodes (some of them virtual, not existing int the cluster), so if `PreFilter` needs a list of nodes, I suppose it should be provided through `RunPreFilterPlugins`?
RunPreFilterPlugins wouldn't get any change, right? @saintube 
> RunPreFilterPlugins wouldn't get any change, right? [@saintube](https://github.com/saintube)

@sanposhiho @x13n 
This is a good question. I initially overlooked the fact that `RunPreFilterPlugin` provides `NodeInfo` to PreFilter plugins. If we modify `PreFilter`, it would indeed require changes to `RunPreFilterPlugin`, potentially impacting projects like the [autoscaler](https://github.com/kubernetes/autoscaler). Thank you for raising this—it’s an important consideration.
FWIW, I think it would be relatively simple change in Cluster Autoscaler to pass a `NodeInfo` list to `RunPreFilterPlugins`.

Fix patch:
diff --git a/pkg/scheduler/framework/interface.go b/pkg/scheduler/framework/interface.go
index 10ee4203d7dca..f52a2a2abdb0e 100644
--- a/pkg/scheduler/framework/interface.go
+++ b/pkg/scheduler/framework/interface.go
@@ -520,7 +520,7 @@ type PreFilterPlugin interface {
 	//
 	// When it returns Skip status, returned PreFilterResult and other fields in status are just ignored,
 	// and coupled Filter plugin/PreFilterExtensions() will be skipped in this scheduling cycle.
-	PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) (*PreFilterResult, *Status)
+	PreFilter(ctx context.Context, state *CycleState, p *v1.Pod, nodes []*NodeInfo) (*PreFilterResult, *Status)
 	// PreFilterExtensions returns a PreFilterExtensions interface if the plugin implements one,
 	// or nil if it does not. A Pre-filter plugin can provide extensions to incrementally
 	// modify its pre-processed info. The framework guarantees that the extensions
diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go
index 3b792d0bd1775..fc07cbbdfd2a2 100644
--- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go
+++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go
@@ -344,7 +344,7 @@ func (pl *DynamicResources) foreachPodResourceClaim(pod *v1.Pod, cb func(podReso
 // PreFilter invoked at the prefilter extension point to check if pod has all
 // immediate claims bound. UnschedulableAndUnresolvable is returned if
 // the pod cannot be scheduled at the moment on any node.
-func (pl *DynamicResources) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *DynamicResources) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	if !pl.enabled {
 		return nil, framework.NewStatus(framework.Skip)
 	}
diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/filtering.go b/pkg/scheduler/framework/plugins/interpodaffinity/filtering.go
index 6d5106f02e29d..8a152a3c2ba4b 100644
--- a/pkg/scheduler/framework/plugins/interpodaffinity/filtering.go
+++ b/pkg/scheduler/framework/plugins/interpodaffinity/filtering.go
@@ -270,13 +270,9 @@ func (pl *InterPodAffinity) getIncomingAffinityAntiAffinityCounts(ctx context.Co
 }
 
 // PreFilter invoked at the prefilter extension point.
-func (pl *InterPodAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
-	var allNodes []*framework.NodeInfo
+func (pl *InterPodAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, allNodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	var nodesWithRequiredAntiAffinityPods []*framework.NodeInfo
 	var err error
-	if allNodes, err = pl.sharedLister.NodeInfos().List(); err != nil {
-		return nil, framework.AsStatus(fmt.Errorf("failed to list NodeInfos: %w", err))
-	}
 	if nodesWithRequiredAntiAffinityPods, err = pl.sharedLister.NodeInfos().HavePodsWithRequiredAntiAffinityList(); err != nil {
 		return nil, framework.AsStatus(fmt.Errorf("failed to list NodeInfos with pods with affinity: %w", err))
 	}
diff --git a/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go b/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go
index f63efb61616e6..d1d9f00fd0362 100644
--- a/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go
+++ b/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go
@@ -143,7 +143,7 @@ func (pl *NodeAffinity) isSchedulableAfterNodeChange(logger klog.Logger, pod *v1
 }
 
 // PreFilter builds and writes cycle state used by Filter.
-func (pl *NodeAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *NodeAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	affinity := pod.Spec.Affinity
 	noNodeAffinity := (affinity == nil ||
 		affinity.NodeAffinity == nil ||
diff --git a/pkg/scheduler/framework/plugins/nodeports/node_ports.go b/pkg/scheduler/framework/plugins/nodeports/node_ports.go
index 74350150c3c05..7466bbf882a85 100644
--- a/pkg/scheduler/framework/plugins/nodeports/node_ports.go
+++ b/pkg/scheduler/framework/plugins/nodeports/node_ports.go
@@ -83,7 +83,7 @@ func getContainerPorts(pods ...*v1.Pod) []*v1.ContainerPort {
 }
 
 // PreFilter invoked at the prefilter extension point.
-func (pl *NodePorts) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *NodePorts) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	s := getContainerPorts(pod)
 	// Skip if a pod has no ports.
 	if len(s) == 0 {
diff --git a/pkg/scheduler/framework/plugins/noderesources/fit.go b/pkg/scheduler/framework/plugins/noderesources/fit.go
index ef6c2244c6b01..d5d04084de12e 100644
--- a/pkg/scheduler/framework/plugins/noderesources/fit.go
+++ b/pkg/scheduler/framework/plugins/noderesources/fit.go
@@ -227,7 +227,7 @@ func computePodResourceRequest(pod *v1.Pod, opts ResourceRequestsOptions) *preFi
 }
 
 // PreFilter invoked at the prefilter extension point.
-func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	if !f.enableSidecarContainers && hasRestartableInitContainer(pod) {
 		// Scheduler will calculate resources usage for a Pod containing
 		// restartable init containers that will be equal or more than kubelet will
diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go b/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go
index b0c35b4c6603d..93972238646a5 100644
--- a/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go
+++ b/pkg/scheduler/framework/plugins/nodevolumelimits/csi.go
@@ -191,7 +191,7 @@ func (pl *CSILimits) isSchedulableAfterVolumeAttachmentDeleted(logger klog.Logge
 // PreFilter invoked at the prefilter extension point
 //
 // If the pod haven't those types of volumes, we'll skip the Filter phase
-func (pl *CSILimits) PreFilter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *CSILimits) PreFilter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, _ []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	volumes := pod.Spec.Volumes
 	for i := range volumes {
 		vol := &volumes[i]
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go b/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go
index 2b0d5d8f3cf1b..1c86aca8653b4 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go
@@ -136,8 +136,8 @@ func (p *criticalPaths) update(tpVal string, num int) {
 }
 
 // PreFilter invoked at the prefilter extension point.
-func (pl *PodTopologySpread) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
-	s, err := pl.calPreFilterState(ctx, pod)
+func (pl *PodTopologySpread) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
+	s, err := pl.calPreFilterState(ctx, pod, nodes)
 	if err != nil {
 		return nil, framework.AsStatus(err)
 	} else if s != nil && len(s.Constraints) == 0 {
@@ -231,7 +231,7 @@ type topologyCount struct {
 }
 
 // calPreFilterState computes preFilterState describing how pods are spread on topologies.
-func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod) (*preFilterState, error) {
+func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod, allNodes []*framework.NodeInfo) (*preFilterState, error) {
 	constraints, err := pl.getConstraints(pod)
 	if err != nil {
 		return nil, fmt.Errorf("get constraints from pod: %w", err)
@@ -240,11 +240,6 @@ func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod)
 		return &preFilterState{}, nil
 	}
 
-	allNodes, err := pl.sharedLister.NodeInfos().List()
-	if err != nil {
-		return nil, fmt.Errorf("listing NodeInfos: %w", err)
-	}
-
 	s := preFilterState{
 		Constraints:       constraints,
 		CriticalPaths:     make([]*criticalPaths, len(constraints)),
diff --git a/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go b/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go
index 6738384d5fdf9..880fcc34befe9 100644
--- a/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go
+++ b/pkg/scheduler/framework/plugins/volumebinding/volume_binding.go
@@ -347,7 +347,7 @@ func (pl *VolumeBinding) podHasPVCs(pod *v1.Pod) (bool, error) {
 // PreFilter invoked at the prefilter extension point to check if pod has all
 // immediate PVCs bound. If not all immediate PVCs are bound, an
 // UnschedulableAndUnresolvable is returned.
-func (pl *VolumeBinding) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *VolumeBinding) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	logger := klog.FromContext(ctx)
 	// If pod does not reference any PVC, we don't need to do anything.
 	if hasPVC, err := pl.podHasPVCs(pod); err != nil {
diff --git a/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions.go b/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions.go
index 6b22340b4ab59..5224df75545e2 100644
--- a/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions.go
+++ b/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions.go
@@ -162,7 +162,7 @@ func needsRestrictionsCheck(v v1.Volume) bool {
 }
 
 // PreFilter computes and stores cycleState containing details for enforcing ReadWriteOncePod.
-func (pl *VolumeRestrictions) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *VolumeRestrictions) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	needsCheck := false
 	for i := range pod.Spec.Volumes {
 		if needsRestrictionsCheck(pod.Spec.Volumes[i]) {
diff --git a/pkg/scheduler/framework/plugins/volumezone/volume_zone.go b/pkg/scheduler/framework/plugins/volumezone/volume_zone.go
index ec39f2ef7ca9e..43b619dd68d6d 100644
--- a/pkg/scheduler/framework/plugins/volumezone/volume_zone.go
+++ b/pkg/scheduler/framework/plugins/volumezone/volume_zone.go
@@ -108,7 +108,7 @@ func (pl *VolumeZone) Name() string {
 //
 // Currently, this is only supported with PersistentVolumeClaims,
 // and only looks for the bound PersistentVolume.
-func (pl *VolumeZone) PreFilter(ctx context.Context, cs *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *VolumeZone) PreFilter(ctx context.Context, cs *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	logger := klog.FromContext(ctx)
 	podPVTopologies, status := pl.getPVbyPod(logger, pod)
 	if !status.IsSuccess() {
diff --git a/pkg/scheduler/framework/runtime/framework.go b/pkg/scheduler/framework/runtime/framework.go
index dbdc6d0986ef3..74fceacaed9fe 100644
--- a/pkg/scheduler/framework/runtime/framework.go
+++ b/pkg/scheduler/framework/runtime/framework.go
@@ -713,6 +713,10 @@ func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, state *framewor
 		state.SkipFilterPlugins = skipPlugins
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PreFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
+	nodes, err := f.SnapshotSharedLister().NodeInfos().List()
+	if err != nil {
+		return nil, framework.AsStatus(fmt.Errorf("getting all nodes: %w", err)), nil
+	}
 	var result *framework.PreFilterResult
 	pluginsWithNodes := sets.New[string]()
 	logger := klog.FromContext(ctx)
@@ -727,7 +731,7 @@ func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, state *framewor
 			logger := klog.LoggerWithName(logger, pl.Name())
 			ctx = klog.NewContext(ctx, logger)
 		}
-		r, s := f.runPreFilterPlugin(ctx, pl, state, pod)
+		r, s := f.runPreFilterPlugin(ctx, pl, state, pod, nodes)
 		if s.IsSkip() {
 			skipPlugins.Insert(pl.Name())
 			continue
@@ -765,12 +769,12 @@ func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, state *framewor
 	return result, returnStatus, pluginsWithNodes
 }
 
-func (f *frameworkImpl) runPreFilterPlugin(ctx context.Context, pl framework.PreFilterPlugin, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (f *frameworkImpl) runPreFilterPlugin(ctx context.Context, pl framework.PreFilterPlugin, state *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	if !state.ShouldRecordPluginMetrics() {
-		return pl.PreFilter(ctx, state, pod)
+		return pl.PreFilter(ctx, state, pod, nodes)
 	}
 	startTime := time.Now()
-	result, status := pl.PreFilter(ctx, state, pod)
+	result, status := pl.PreFilter(ctx, state, pod, nodes)
 	f.metricsRecorder.ObservePluginDurationAsync(metrics.PreFilter, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime))
 	return result, status
 }
diff --git a/pkg/scheduler/framework/runtime/instrumented_plugins.go b/pkg/scheduler/framework/runtime/instrumented_plugins.go
index 04e9004c8c6d6..57d1a81427664 100644
--- a/pkg/scheduler/framework/runtime/instrumented_plugins.go
+++ b/pkg/scheduler/framework/runtime/instrumented_plugins.go
@@ -45,8 +45,8 @@ type instrumentedPreFilterPlugin struct {
 
 var _ framework.PreFilterPlugin = &instrumentedPreFilterPlugin{}
 
-func (p *instrumentedPreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
-	result, status := p.PreFilterPlugin.PreFilter(ctx, state, pod)
+func (p *instrumentedPreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
+	result, status := p.PreFilterPlugin.PreFilter(ctx, state, pod, nodes)
 	if !status.IsSkip() {
 		p.metric.Inc()
 	}


Test patch:
diff --git a/cmd/kube-scheduler/app/server_test.go b/cmd/kube-scheduler/app/server_test.go
index aa5f5e50bfb59..6e4518e87c9c0 100644
--- a/cmd/kube-scheduler/app/server_test.go
+++ b/cmd/kube-scheduler/app/server_test.go
@@ -532,7 +532,7 @@ func newFoo(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.
 	return &foo{}, nil
 }
 
-func (*foo) PreFilter(_ context.Context, _ *framework.CycleState, _ *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (*foo) PreFilter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	return nil, nil
 }
 
diff --git a/pkg/scheduler/extender_test.go b/pkg/scheduler/extender_test.go
index 4c2418c66597f..656627636f16f 100644
--- a/pkg/scheduler/extender_test.go
+++ b/pkg/scheduler/extender_test.go
@@ -343,6 +343,7 @@ func TestSchedulerWithExtenders(t *testing.T) {
 				runtime.WithInformerFactory(informerFactory),
 				runtime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)),
 				runtime.WithLogger(logger),
+				runtime.WithSnapshotSharedLister(emptySnapshot),
 			)
 			if err != nil {
 				t.Fatal(err)
diff --git a/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go b/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go
index df8703c8fa05c..3ec9b4dfeafa0 100644
--- a/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go
+++ b/pkg/scheduler/framework/plugins/defaultpreemption/default_preemption_test.go
@@ -140,7 +140,7 @@ func (pl *TestPlugin) PreFilterExtensions() framework.PreFilterExtensions {
 	return pl
 }
 
-func (pl *TestPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *TestPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	return nil, nil
 }
 
@@ -1428,16 +1428,16 @@ func TestSelectBestCandidate(t *testing.T) {
 			if err != nil {
 				t.Fatal(err)
 			}
+			nodeInfos, err := fwk.SnapshotSharedLister().NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 
 			state := framework.NewCycleState()
 			// Some tests rely on PreFilter plugin to compute its CycleState.
 			if _, status, _ := fwk.RunPreFilterPlugins(ctx, state, tt.pod); !status.IsSuccess() {
 				t.Errorf("Unexpected PreFilter Status: %v", status)
 			}
-			nodeInfos, err := snapshot.NodeInfos().List()
-			if err != nil {
-				t.Fatal(err)
-			}
 
 			pl := &DefaultPreemption{
 				fh:        fwk,
diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go
index 0b85ac77aacfa..386912e76cf33 100644
--- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go
+++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go
@@ -934,7 +934,7 @@ func TestPlugin(t *testing.T) {
 				return
 			}
 
-			result, status := testCtx.p.PreFilter(testCtx.ctx, testCtx.state, tc.pod)
+			result, status := testCtx.p.PreFilter(testCtx.ctx, testCtx.state, tc.pod, nil)
 			t.Run("prefilter", func(t *testing.T) {
 				assert.Equal(t, tc.want.preFilterResult, result)
 				testCtx.verify(t, tc.want.prefilter, initialObjects, result, status)
diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/filtering_test.go b/pkg/scheduler/framework/plugins/interpodaffinity/filtering_test.go
index a5899a8ec4802..4a69c06e2c238 100644
--- a/pkg/scheduler/framework/plugins/interpodaffinity/filtering_test.go
+++ b/pkg/scheduler/framework/plugins/interpodaffinity/filtering_test.go
@@ -548,9 +548,13 @@ func TestRequiredAffinitySingleNode(t *testing.T) {
 			ctx, cancel := context.WithCancel(ctx)
 			defer cancel()
 			snapshot := cache.NewSnapshot(test.pods, []*v1.Node{test.node})
+			nodeInfos, err := snapshot.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 			p := plugintesting.SetupPluginWithInformers(ctx, t, schedruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot, namespaces)
 			state := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod, nodeInfos)
 			if diff := cmp.Diff(test.wantPreFilterStatus, preFilterStatus); diff != "" {
 				t.Errorf("PreFilter: status does not match (-want,+got):\n%s", diff)
 			}
@@ -960,12 +964,16 @@ func TestRequiredAffinityMultipleNodes(t *testing.T) {
 			ctx, cancel := context.WithCancel(ctx)
 			defer cancel()
 			snapshot := cache.NewSnapshot(test.pods, test.nodes)
+			nodeInfos, err := snapshot.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 			p := plugintesting.SetupPluginWithInformers(ctx, t, schedruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot,
 				[]runtime.Object{
 					&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "NS1"}},
 				})
 			state := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod, nodeInfos)
 			if diff := cmp.Diff(test.wantPreFilterStatus, preFilterStatus); diff != "" {
 				t.Errorf("PreFilter: status does not match (-want,+got):\n%s", diff)
 			}
@@ -1236,9 +1244,13 @@ func TestPreFilterStateAddRemovePod(t *testing.T) {
 				_, ctx := ktesting.NewTestContext(t)
 				ctx, cancel := context.WithCancel(ctx)
 				defer cancel()
+				nodeInfos, err := snapshot.NodeInfos().List()
+				if err != nil {
+					t.Fatal(err)
+				}
 				p := plugintesting.SetupPluginWithInformers(ctx, t, schedruntime.FactoryAdapter(feature.Features{}, New), &config.InterPodAffinityArgs{}, snapshot, nil)
 				cycleState := framework.NewCycleState()
-				_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pendingPod)
+				_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pendingPod, nodeInfos)
 				if !preFilterStatus.IsSuccess() {
 					t.Errorf("prefilter failed with status: %v", preFilterStatus)
 				}
diff --git a/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity_test.go b/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity_test.go
index 1c48638150374..a9ec70cfd7a07 100644
--- a/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity_test.go
+++ b/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity_test.go
@@ -916,7 +916,7 @@ func TestNodeAffinity(t *testing.T) {
 			state := framework.NewCycleState()
 			var gotStatus *framework.Status
 			if test.runPreFilter {
-				gotPreFilterResult, gotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod)
+				gotPreFilterResult, gotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, state, test.pod, nil)
 				if diff := cmp.Diff(test.wantPreFilterStatus, gotStatus); diff != "" {
 					t.Errorf("unexpected PreFilter Status (-want,+got):\n%s", diff)
 				}
diff --git a/pkg/scheduler/framework/plugins/nodeports/node_ports_test.go b/pkg/scheduler/framework/plugins/nodeports/node_ports_test.go
index 354ac4925be93..67a9ec2b6845f 100644
--- a/pkg/scheduler/framework/plugins/nodeports/node_ports_test.go
+++ b/pkg/scheduler/framework/plugins/nodeports/node_ports_test.go
@@ -152,7 +152,7 @@ func TestNodePorts(t *testing.T) {
 				t.Fatalf("creating plugin: %v", err)
 			}
 			cycleState := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.wantPreFilterStatus, preFilterStatus); diff != "" {
 				t.Errorf("preFilter status does not match (-want,+got): %s", diff)
 			}
diff --git a/pkg/scheduler/framework/plugins/noderesources/fit_test.go b/pkg/scheduler/framework/plugins/noderesources/fit_test.go
index 84727423ca48f..2f19e173351f9 100644
--- a/pkg/scheduler/framework/plugins/noderesources/fit_test.go
+++ b/pkg/scheduler/framework/plugins/noderesources/fit_test.go
@@ -581,7 +581,7 @@ func TestEnoughRequests(t *testing.T) {
 				t.Fatal(err)
 			}
 			cycleState := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if !preFilterStatus.IsSuccess() {
 				t.Errorf("prefilter failed with status: %v", preFilterStatus)
 			}
@@ -665,7 +665,7 @@ func TestNotEnoughRequests(t *testing.T) {
 				t.Fatal(err)
 			}
 			cycleState := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if !preFilterStatus.IsSuccess() {
 				t.Errorf("prefilter failed with status: %v", preFilterStatus)
 			}
@@ -726,7 +726,7 @@ func TestStorageRequests(t *testing.T) {
 				t.Fatal(err)
 			}
 			cycleState := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if !preFilterStatus.IsSuccess() {
 				t.Errorf("prefilter failed with status: %v", preFilterStatus)
 			}
@@ -836,7 +836,7 @@ func TestRestartableInitContainers(t *testing.T) {
 				t.Fatal(err)
 			}
 			cycleState := framework.NewCycleState()
-			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.wantPreFilterStatus, preFilterStatus); diff != "" {
 				t.Error("prefilter status does not match (-expected +actual):\n", diff)
 			}
diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go b/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go
index 77dfcdedfdedc..13b4ab8b7e077 100644
--- a/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go
+++ b/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go
@@ -647,7 +647,7 @@ func TestCSILimits(t *testing.T) {
 				translator:           csiTranslator,
 			}
 			_, ctx := ktesting.NewTestContext(t)
-			_, gotPreFilterStatus := p.PreFilter(ctx, nil, test.newPod)
+			_, gotPreFilterStatus := p.PreFilter(ctx, nil, test.newPod, nil)
 			if diff := cmp.Diff(test.wantPreFilterStatus, gotPreFilterStatus, statusCmpOpts...); diff != "" {
 				t.Errorf("PreFilter status does not match (-want, +got):\n%s", diff)
 			}
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go b/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go
index 54ef48b55fee7..75a6d03c6cde2 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go
@@ -1490,9 +1490,13 @@ func TestPreFilterState(t *testing.T) {
 			p := plugintesting.SetupPluginWithInformers(ctx, t, topologySpreadFunc, args, cache.NewSnapshot(tt.existingPods, tt.nodes), tt.objs)
 			p.(*PodTopologySpread).enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclusionPolicy
 			p.(*PodTopologySpread).enableMatchLabelKeysInPodTopologySpread = tt.enableMatchLabelKeys
+			nodeInfos, err := p.(*PodTopologySpread).sharedLister.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 
 			cs := framework.NewCycleState()
-			_, s := p.(*PodTopologySpread).PreFilter(ctx, cs, tt.pod)
+			_, s := p.(*PodTopologySpread).PreFilter(ctx, cs, tt.pod, nodeInfos)
 			if !tt.wantPrefilterStatus.Equal(s) {
 				t.Errorf("PodTopologySpread#PreFilter() returned unexpected status. got: %v, want: %v", s, tt.wantPrefilterStatus)
 			}
@@ -1984,9 +1988,13 @@ func TestPreFilterStateAddPod(t *testing.T) {
 			pl := plugintesting.SetupPlugin(ctx, t, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, snapshot)
 			p := pl.(*PodTopologySpread)
 			p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclusionPolicy
+			nodeInfos, err := snapshot.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 
 			cs := framework.NewCycleState()
-			if _, s := p.PreFilter(ctx, cs, tt.preemptor); !s.IsSuccess() {
+			if _, s := p.PreFilter(ctx, cs, tt.preemptor, nodeInfos); !s.IsSuccess() {
 				t.Fatal(s.AsError())
 			}
 			nodeInfo, err := snapshot.Get(tt.nodes[tt.nodeIdx].Name)
@@ -2285,9 +2293,13 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 			pl := plugintesting.SetupPlugin(ctx, t, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, snapshot)
 			p := pl.(*PodTopologySpread)
 			p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclusionPolicy
+			nodeInfos, err := snapshot.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 
 			cs := framework.NewCycleState()
-			if _, s := p.PreFilter(ctx, cs, tt.preemptor); !s.IsSuccess() {
+			if _, s := p.PreFilter(ctx, cs, tt.preemptor, nodeInfos); !s.IsSuccess() {
 				t.Fatal(s.AsError())
 			}
 
@@ -2360,10 +2372,14 @@ func BenchmarkFilter(b *testing.B) {
 			_, ctx := ktesting.NewTestContext(b)
 			pl := plugintesting.SetupPlugin(ctx, b, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, cache.NewSnapshot(existingPods, allNodes))
 			p := pl.(*PodTopologySpread)
+			nodeInfos, err := p.sharedLister.NodeInfos().List()
+			if err != nil {
+				b.Fatal(err)
+			}
 			b.ResetTimer()
 			for i := 0; i < b.N; i++ {
 				state = framework.NewCycleState()
-				if _, s := p.PreFilter(ctx, state, tt.pod); !s.IsSuccess() {
+				if _, s := p.PreFilter(ctx, state, tt.pod, nodeInfos); !s.IsSuccess() {
 					b.Fatal(s.AsError())
 				}
 				filterNode := func(i int) {
@@ -2986,8 +3002,12 @@ func TestSingleConstraint(t *testing.T) {
 			pl := plugintesting.SetupPlugin(ctx, t, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, snapshot)
 			p := pl.(*PodTopologySpread)
 			p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclusionPolicy
+			nodeInfos, err := snapshot.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 			state := framework.NewCycleState()
-			if _, s := p.PreFilter(ctx, state, tt.pod); !s.IsSuccess() {
+			if _, s := p.PreFilter(ctx, state, tt.pod, nodeInfos); !s.IsSuccess() {
 				t.Errorf("preFilter failed with status: %v", s)
 			}
 
@@ -3391,8 +3411,12 @@ func TestMultipleConstraints(t *testing.T) {
 			pl := plugintesting.SetupPlugin(ctx, t, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, snapshot)
 			p := pl.(*PodTopologySpread)
 			p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclusionPolicy
+			nodeInfos, err := snapshot.NodeInfos().List()
+			if err != nil {
+				t.Fatal(err)
+			}
 			state := framework.NewCycleState()
-			if _, s := p.PreFilter(ctx, state, tt.pod); !s.IsSuccess() {
+			if _, s := p.PreFilter(ctx, state, tt.pod, nodeInfos); !s.IsSuccess() {
 				t.Errorf("preFilter failed with status: %v", s)
 			}
 
diff --git a/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go b/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go
index 00b2b1a4b8ffe..509003f03fe96 100644
--- a/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go
+++ b/pkg/scheduler/framework/plugins/volumebinding/volume_binding_test.go
@@ -777,7 +777,7 @@ func TestVolumeBinding(t *testing.T) {
 			state := framework.NewCycleState()
 
 			t.Logf("Verify: call PreFilter and check status")
-			gotPreFilterResult, gotPreFilterStatus := p.PreFilter(ctx, state, item.pod)
+			gotPreFilterResult, gotPreFilterStatus := p.PreFilter(ctx, state, item.pod, nil)
 			assert.Equal(t, item.wantPreFilterStatus, gotPreFilterStatus)
 			assert.Equal(t, item.wantPreFilterResult, gotPreFilterResult)
 
diff --git a/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions_test.go b/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions_test.go
index 4bace7c6312e7..d55b3f3f20b59 100644
--- a/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions_test.go
+++ b/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions_test.go
@@ -105,7 +105,7 @@ func TestGCEDiskConflicts(t *testing.T) {
 			defer cancel()
 			p := newPlugin(ctx, t)
 			cycleState := framework.NewCycleState()
-			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
 				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
 			}
@@ -180,7 +180,7 @@ func TestAWSDiskConflicts(t *testing.T) {
 			defer cancel()
 			p := newPlugin(ctx, t)
 			cycleState := framework.NewCycleState()
-			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
 				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
 			}
@@ -261,7 +261,7 @@ func TestRBDDiskConflicts(t *testing.T) {
 			defer cancel()
 			p := newPlugin(ctx, t)
 			cycleState := framework.NewCycleState()
-			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
 				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
 			}
@@ -342,7 +342,7 @@ func TestISCSIDiskConflicts(t *testing.T) {
 			defer cancel()
 			p := newPlugin(ctx, t)
 			cycleState := framework.NewCycleState()
-			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
 				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
 			}
@@ -470,7 +470,7 @@ func TestAccessModeConflicts(t *testing.T) {
 			defer cancel()
 			p := newPluginWithListers(ctx, t, test.existingPods, test.existingNodes, test.existingPVCs)
 			cycleState := framework.NewCycleState()
-			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
+			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod, nil)
 			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
 				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
 			}
diff --git a/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go b/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go
index 232517bb00ad0..822b05b9cd120 100644
--- a/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go
+++ b/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go
@@ -274,7 +274,7 @@ func TestSingleZone(t *testing.T) {
 				scLister:                  nil,
 				enableSchedulingQueueHint: false,
 			}
-			_, preFilterStatus := p.PreFilter(ctx, state, test.Pod)
+			_, preFilterStatus := p.PreFilter(ctx, state, test.Pod, nil)
 			if diff := cmp.Diff(preFilterStatus, test.wantPreFilterStatus); diff != "" {
 				t.Errorf("PreFilter: status does not match (-want,+got):\n%s", diff)
 			}
@@ -407,7 +407,7 @@ func TestMultiZone(t *testing.T) {
 				scLister:                  nil,
 				enableSchedulingQueueHint: false,
 			}
-			_, preFilterStatus := p.PreFilter(ctx, state, test.Pod)
+			_, preFilterStatus := p.PreFilter(ctx, state, test.Pod, nil)
 			if diff := cmp.Diff(preFilterStatus, test.wantPreFilterStatus); diff != "" {
 				t.Errorf("PreFilter: status does not match (-want,+got):\n%s", diff)
 			}
@@ -533,7 +533,7 @@ func TestWithBinding(t *testing.T) {
 				scLister:                  scLister,
 				enableSchedulingQueueHint: false,
 			}
-			_, preFilterStatus := p.PreFilter(ctx, state, test.Pod)
+			_, preFilterStatus := p.PreFilter(ctx, state, test.Pod, nil)
 			if diff := cmp.Diff(preFilterStatus, test.wantPreFilterStatus); diff != "" {
 				t.Errorf("PreFilter: status does not match (-want,+got):\n%s", diff)
 			}
@@ -826,7 +826,7 @@ func BenchmarkVolumeZone(b *testing.B) {
 
 			for i := 0; i < b.N; i++ {
 				if tt.PreFilter {
-					_, _ = p.PreFilter(ctx, state, tt.Pod)
+					_, _ = p.PreFilter(ctx, state, tt.Pod, nil)
 				}
 				for _, node := range nodeInfos {
 					_ = p.Filter(ctx, state, tt.Pod, node)
diff --git a/pkg/scheduler/framework/runtime/framework_test.go b/pkg/scheduler/framework/runtime/framework_test.go
index aee8d55960b0d..6b8d9ba756953 100644
--- a/pkg/scheduler/framework/runtime/framework_test.go
+++ b/pkg/scheduler/framework/runtime/framework_test.go
@@ -36,6 +36,7 @@ import (
 	"k8s.io/component-base/metrics/testutil"
 	"k8s.io/klog/v2/ktesting"
 	"k8s.io/kubernetes/pkg/scheduler/apis/config"
+	"k8s.io/kubernetes/pkg/scheduler/backend/cache"
 	internalqueue "k8s.io/kubernetes/pkg/scheduler/backend/queue"
 	"k8s.io/kubernetes/pkg/scheduler/framework"
 	"k8s.io/kubernetes/pkg/scheduler/metrics"
@@ -202,7 +203,7 @@ func (pl *TestPlugin) ScoreExtensions() framework.ScoreExtensions {
 	return nil
 }
 
-func (pl *TestPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *TestPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	return pl.inj.PreFilterResult, framework.NewStatus(framework.Code(pl.inj.PreFilterStatus), injectReason)
 }
 
@@ -272,7 +273,7 @@ func (pl *TestPreFilterPlugin) Name() string {
 	return preFilterPluginName
 }
 
-func (pl *TestPreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *TestPreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	pl.PreFilterCalled++
 	return nil, nil
 }
@@ -292,7 +293,7 @@ func (pl *TestPreFilterWithExtensionsPlugin) Name() string {
 	return preFilterWithExtensionsPluginName
 }
 
-func (pl *TestPreFilterWithExtensionsPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *TestPreFilterWithExtensionsPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	pl.PreFilterCalled++
 	return nil, nil
 }
@@ -320,7 +321,7 @@ func (dp *TestDuplicatePlugin) Name() string {
 	return duplicatePluginName
 }
 
-func (dp *TestDuplicatePlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (dp *TestDuplicatePlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	return nil, nil
 }
 
@@ -1547,7 +1548,7 @@ func TestPreFilterPlugins(t *testing.T) {
 		ctx, cancel := context.WithCancel(ctx)
 		defer cancel()
 
-		f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile)
+		f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile, WithSnapshotSharedLister(cache.NewEmptySnapshot()))
 		if err != nil {
 			t.Fatalf("Failed to create framework for testing: %v", err)
 		}
@@ -1739,6 +1740,7 @@ func TestRunPreFilterPlugins(t *testing.T) {
 				ctx,
 				r,
 				config.KubeSchedulerProfile{Plugins: &config.Plugins{PreFilter: config.PluginSet{Enabled: enabled}}},
+				WithSnapshotSharedLister(cache.NewEmptySnapshot()),
 			)
 			if err != nil {
 				t.Fatalf("Failed to create framework for testing: %v", err)
@@ -3065,6 +3067,7 @@ func TestRecordingMetrics(t *testing.T) {
 			f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile,
 				withMetricsRecorder(recorder),
 				WithWaitingPods(NewWaitingPodsMap()),
+				WithSnapshotSharedLister(cache.NewEmptySnapshot()),
 			)
 			if err != nil {
 				cancel()
diff --git a/pkg/scheduler/schedule_one_test.go b/pkg/scheduler/schedule_one_test.go
index 403fc88cc366b..c01dd1180bf33 100644
--- a/pkg/scheduler/schedule_one_test.go
+++ b/pkg/scheduler/schedule_one_test.go
@@ -2715,6 +2715,7 @@ func TestFindFitAllError(t *testing.T) {
 		},
 		"",
 		frameworkruntime.WithPodNominator(internalqueue.NewTestQueue(ctx, nil)),
+		frameworkruntime.WithSnapshotSharedLister(scheduler.nodeInfoSnapshot),
 	)
 	if err != nil {
 		t.Fatal(err)
@@ -2759,6 +2760,7 @@ func TestFindFitSomeError(t *testing.T) {
 		},
 		"",
 		frameworkruntime.WithPodNominator(internalqueue.NewTestQueue(ctx, nil)),
+		frameworkruntime.WithSnapshotSharedLister(scheduler.nodeInfoSnapshot),
 	)
 	if err != nil {
 		t.Fatal(err)
@@ -2836,20 +2838,20 @@ func TestFindFitPredicateCallCounts(t *testing.T) {
 			if err != nil {
 				t.Fatalf("Error adding pod to podInformer: %s", err)
 			}
-
+			scheduler := makeScheduler(ctx, nodes)
+			if err := scheduler.Cache.UpdateSnapshot(logger, scheduler.nodeInfoSnapshot); err != nil {
+				t.Fatal(err)
+			}
 			fwk, err := tf.NewFramework(
 				ctx,
 				registerPlugins, "",
 				frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)),
+				frameworkruntime.WithSnapshotSharedLister(scheduler.nodeInfoSnapshot),
 			)
 			if err != nil {
 				t.Fatal(err)
 			}
 
-			scheduler := makeScheduler(ctx, nodes)
-			if err := scheduler.Cache.UpdateSnapshot(logger, scheduler.nodeInfoSnapshot); err != nil {
-				t.Fatal(err)
-			}
 			podinfo, err := framework.NewPodInfo(st.MakePod().UID("nominated").Priority(midPriority).Obj())
 			if err != nil {
 				t.Fatal(err)
@@ -3518,6 +3520,7 @@ func TestFairEvaluationForNodes(t *testing.T) {
 		},
 		"",
 		frameworkruntime.WithPodNominator(internalqueue.NewTestQueue(ctx, nil)),
+		frameworkruntime.WithSnapshotSharedLister(sched.nodeInfoSnapshot),
 	)
 	if err != nil {
 		t.Fatal(err)
@@ -3595,16 +3598,17 @@ func TestPreferNominatedNodeFilterCallCounts(t *testing.T) {
 				tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
 				tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
 			}
+			snapshot := internalcache.NewSnapshot(nil, nodes)
 			fwk, err := tf.NewFramework(
 				ctx,
 				registerPlugins, "",
 				frameworkruntime.WithClientSet(client),
 				frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)),
+				frameworkruntime.WithSnapshotSharedLister(snapshot),
 			)
 			if err != nil {
 				t.Fatal(err)
 			}
-			snapshot := internalcache.NewSnapshot(nil, nodes)
 
 			sched := &Scheduler{
 				Cache:                    cache,
@@ -3749,6 +3753,7 @@ func setupTestScheduler(ctx context.Context, t *testing.T, queuedPodStore *clien
 	}
 	schedulingQueue := internalqueue.NewTestQueueWithInformerFactory(ctx, nil, informerFactory)
 	waitingPods := frameworkruntime.NewWaitingPodsMap()
+	snapshot := internalcache.NewEmptySnapshot()
 
 	fwk, _ := tf.NewFramework(
 		ctx,
@@ -3759,13 +3764,14 @@ func setupTestScheduler(ctx context.Context, t *testing.T, queuedPodStore *clien
 		frameworkruntime.WithInformerFactory(informerFactory),
 		frameworkruntime.WithPodNominator(schedulingQueue),
 		frameworkruntime.WithWaitingPods(waitingPods),
+		frameworkruntime.WithSnapshotSharedLister(snapshot),
 	)
 
 	errChan := make(chan error, 1)
 	sched := &Scheduler{
 		Cache:                    cache,
 		client:                   client,
-		nodeInfoSnapshot:         internalcache.NewEmptySnapshot(),
+		nodeInfoSnapshot:         snapshot,
 		percentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore,
 		NextPod: func(logger klog.Logger) (*framework.QueuedPodInfo, error) {
 			return &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(t, clientcache.Pop(queuedPodStore).(*v1.Pod))}, nil
diff --git a/pkg/scheduler/testing/framework/fake_plugins.go b/pkg/scheduler/testing/framework/fake_plugins.go
index c6c26b2f70444..252a687e0ea2a 100644
--- a/pkg/scheduler/testing/framework/fake_plugins.go
+++ b/pkg/scheduler/testing/framework/fake_plugins.go
@@ -148,7 +148,7 @@ func (pl *FakePreFilterPlugin) Name() string {
 }
 
 // PreFilter invoked at the PreFilter extension point.
-func (pl *FakePreFilterPlugin) PreFilter(_ context.Context, _ *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pl *FakePreFilterPlugin) PreFilter(_ context.Context, _ *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	return pl.Result, pl.Status
 }
 
diff --git a/test/integration/scheduler/plugins/plugins_test.go b/test/integration/scheduler/plugins/plugins_test.go
index d359b513d40d6..7715fbf3b4e49 100644
--- a/test/integration/scheduler/plugins/plugins_test.go
+++ b/test/integration/scheduler/plugins/plugins_test.go
@@ -546,7 +546,7 @@ func (pp *PreFilterPlugin) PreFilterExtensions() framework.PreFilterExtensions {
 }
 
 // PreFilter is a test function that returns (true, nil) or errors for testing.
-func (pp *PreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (pp *PreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	pp.numPreFilterCalled++
 	if pp.failPreFilter {
 		return nil, framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name))
@@ -2592,7 +2592,7 @@ func (j *JobPlugin) Name() string {
 	return jobPluginName
 }
 
-func (j *JobPlugin) PreFilter(_ context.Context, _ *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (j *JobPlugin) PreFilter(_ context.Context, _ *framework.CycleState, p *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	labelSelector := labels.SelectorFromSet(labels.Set{"driver": ""})
 	driverPods, err := j.podLister.Pods(p.Namespace).List(labelSelector)
 	if err != nil {
diff --git a/test/integration/scheduler/preemption/preemption_test.go b/test/integration/scheduler/preemption/preemption_test.go
index dc60edb10ce90..b4c735e9cabe5 100644
--- a/test/integration/scheduler/preemption/preemption_test.go
+++ b/test/integration/scheduler/preemption/preemption_test.go
@@ -136,7 +136,7 @@ func (fp *tokenFilter) Filter(ctx context.Context, state *framework.CycleState,
 	return framework.NewStatus(status, fmt.Sprintf("can't fit %v", pod.Name))
 }
 
-func (fp *tokenFilter) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
+func (fp *tokenFilter) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) (*framework.PreFilterResult, *framework.Status) {
 	if !fp.EnablePreFilter || fp.Tokens > 0 {
 		return nil, nil
 	}