Repository: clusternet/clusternet
Pull request #388 (2022-06-22 09:17:18)

add scheduler cycle state
Signed-off-by: Garrybest <garrybest@foxmail.com>

<!--  Thanks for sending a pull request!

-->

#### What type of PR is this?

#### What this PR does / why we need it:

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Special notes for your reviewer:



Patch:
diff --git a/pkg/scheduler/algorithm/generic.go b/pkg/scheduler/algorithm/generic.go
index 3dc4838..f69c040 100644
--- a/pkg/scheduler/algorithm/generic.go
+++ b/pkg/scheduler/algorithm/generic.go
@@ -66,7 +66,7 @@ type genericScheduler struct {
 // Schedule tries to schedule the given subscription to multiple clusters.
 // If it succeeds, it will return the namespaced names of ManagedClusters.
 // If it fails, it will return a FitError error with reasons.
-func (g *genericScheduler) Schedule(ctx context.Context, fwk framework.Framework, sub *appsapi.Subscription, finv *appsapi.FeedInventory) (result ScheduleResult, err error) {
+func (g *genericScheduler) Schedule(ctx context.Context, fwk framework.Framework, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory) (result ScheduleResult, err error) {
 	trace := utiltrace.New("Scheduling", utiltrace.Field{Key: "namespace", Value: sub.Namespace}, utiltrace.Field{Key: "name", Value: sub.Name})
 	defer trace.LogIfLong(100 * time.Millisecond)
 
@@ -75,7 +75,7 @@ func (g *genericScheduler) Schedule(ctx context.Context, fwk framework.Framework
 	}
 
 	// Step 1: Filter clusters.
-	feasibleClusters, diagnosis, err := g.findClustersThatFitSubscription(ctx, fwk, sub)
+	feasibleClusters, diagnosis, err := g.findClustersThatFitSubscription(ctx, fwk, state, sub)
 	if err != nil {
 		return result, err
 	}
@@ -90,18 +90,18 @@ func (g *genericScheduler) Schedule(ctx context.Context, fwk framework.Framework
 	}
 
 	// Step 2: Predict max available replicas if necessary.
-	availableList, err := predictReplicas(ctx, fwk, sub, finv, feasibleClusters)
+	availableList, err := predictReplicas(ctx, fwk, state, sub, finv, feasibleClusters)
 	if err != nil {
 		return result, err
 	}
 
 	// Step 3: Prioritize clusters.
-	priorityList, err := prioritizeClusters(ctx, fwk, sub, feasibleClusters, availableList)
+	priorityList, err := prioritizeClusters(ctx, fwk, state, sub, feasibleClusters, availableList)
 	if err != nil {
 		return result, err
 	}
 
-	clusters, err := g.selectClusters(ctx, priorityList, fwk, sub, finv)
+	clusters, err := g.selectClusters(ctx, state, priorityList, fwk, sub, finv)
 	trace.Step("Prioritizing done")
 
 	return ScheduleResult{
@@ -111,7 +111,7 @@ func (g *genericScheduler) Schedule(ctx context.Context, fwk framework.Framework
 	}, err
 }
 
-func predictReplicas(ctx context.Context, fwk framework.Framework, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) (framework.ClusterScoreList, error) {
+func predictReplicas(ctx context.Context, fwk framework.Framework, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) (framework.ClusterScoreList, error) {
 	availableList := make(framework.ClusterScoreList, len(clusters))
 	for i := range clusters {
 		availableList[i] = framework.ClusterScore{
@@ -134,13 +134,13 @@ func predictReplicas(ctx context.Context, fwk framework.Framework, sub *appsapi.
 	}
 
 	// Run PrePredict plugins.
-	prePredictStatus := fwk.RunPrePredictPlugins(ctx, sub, finv, clusters)
+	prePredictStatus := fwk.RunPrePredictPlugins(ctx, state, sub, finv, clusters)
 	if !prePredictStatus.IsSuccess() {
 		return nil, prePredictStatus.AsError()
 	}
 
 	// Run Predict plugins.
-	availableList, predictStatus := fwk.RunPredictPlugins(ctx, sub, finv, clusters, availableList)
+	availableList, predictStatus := fwk.RunPredictPlugins(ctx, state, sub, finv, clusters, availableList)
 	if !predictStatus.IsSuccess() {
 		return nil, predictStatus.AsError()
 	}
@@ -155,7 +155,7 @@ func predictReplicas(ctx context.Context, fwk framework.Framework, sub *appsapi.
 
 // selectClusters takes a prioritized list of clusters and then picks a fraction of clusters
 // in a reservoir sampling manner from the clusters that had the highest score.
-func (g *genericScheduler) selectClusters(ctx context.Context, clusterScoreList framework.ClusterScoreList, fwk framework.Framework, sub *appsapi.Subscription, finv *appsapi.FeedInventory) (framework.TargetClusters, error) {
+func (g *genericScheduler) selectClusters(ctx context.Context, state *framework.CycleState, clusterScoreList framework.ClusterScoreList, fwk framework.Framework, sub *appsapi.Subscription, finv *appsapi.FeedInventory) (framework.TargetClusters, error) {
 	if len(clusterScoreList) == 0 {
 		return framework.TargetClusters{}, fmt.Errorf("empty clusterScoreList")
 	}
@@ -180,13 +180,13 @@ func (g *genericScheduler) selectClusters(ctx context.Context, clusterScoreList
 	}
 
 	// Run PreAssign plugins.
-	preAssignStatus := fwk.RunPreAssignPlugins(ctx, sub, finv, selected)
+	preAssignStatus := fwk.RunPreAssignPlugins(ctx, state, sub, finv, selected)
 	if !preAssignStatus.IsSuccess() {
 		return framework.TargetClusters{}, preAssignStatus.AsError()
 	}
 
 	// Run the Assign plugins.
-	selected, assignStatus := fwk.RunAssignPlugins(ctx, sub, finv, selected)
+	selected, assignStatus := fwk.RunAssignPlugins(ctx, state, sub, finv, selected)
 	if assignStatus.IsSuccess() {
 		return selected, nil
 	}
@@ -222,7 +222,7 @@ func (g *genericScheduler) numFeasibleClustersToFind(numAllClusters int32, sched
 }
 
 // Filters the clusters to find the ones that fit the subscription based on the framework filter plugins.
-func (g *genericScheduler) findClustersThatFitSubscription(ctx context.Context, fwk framework.Framework, sub *appsapi.Subscription) ([]*clusterapi.ManagedCluster, framework.Diagnosis, error) {
+func (g *genericScheduler) findClustersThatFitSubscription(ctx context.Context, fwk framework.Framework, state *framework.CycleState, sub *appsapi.Subscription) ([]*clusterapi.ManagedCluster, framework.Diagnosis, error) {
 	diagnosis := framework.Diagnosis{
 		ClusterToStatusMap:   make(framework.ClusterToStatusMap),
 		UnschedulablePlugins: sets.NewString(),
@@ -243,7 +243,7 @@ func (g *genericScheduler) findClustersThatFitSubscription(ctx context.Context,
 	}
 
 	// Run "prefilter" plugins.
-	s := fwk.RunPreFilterPlugins(ctx, sub)
+	s := fwk.RunPreFilterPlugins(ctx, state, sub)
 	if !s.IsSuccess() {
 		if !s.IsUnschedulable() {
 			return nil, diagnosis, s.AsError()
@@ -258,7 +258,7 @@ func (g *genericScheduler) findClustersThatFitSubscription(ctx context.Context,
 		return nil, diagnosis, nil
 	}
 
-	feasibleClusters, err := g.findClustersThatPassFilters(ctx, fwk, sub, diagnosis, allClusters)
+	feasibleClusters, err := g.findClustersThatPassFilters(ctx, state, fwk, sub, diagnosis, allClusters)
 	if err != nil {
 		return nil, diagnosis, err
 	}
@@ -266,9 +266,7 @@ func (g *genericScheduler) findClustersThatFitSubscription(ctx context.Context,
 }
 
 // findClustersThatPassFilters finds the clusters that fit the filter plugins.
-func (g *genericScheduler) findClustersThatPassFilters(ctx context.Context, fwk framework.Framework,
-	sub *appsapi.Subscription, diagnosis framework.Diagnosis,
-	clusters []*clusterapi.ManagedCluster) ([]*clusterapi.ManagedCluster, error) {
+func (g *genericScheduler) findClustersThatPassFilters(ctx context.Context, state *framework.CycleState, fwk framework.Framework, sub *appsapi.Subscription, diagnosis framework.Diagnosis, clusters []*clusterapi.ManagedCluster) ([]*clusterapi.ManagedCluster, error) {
 	numClustersToFind := g.numFeasibleClustersToFind(int32(len(clusters)), sub.Spec.SchedulingStrategy)
 
 	// Create feasible list with enough space to avoid growing it
@@ -293,7 +291,7 @@ func (g *genericScheduler) findClustersThatPassFilters(ctx context.Context, fwk
 		// this is to make sure all clusters have the same chance of being examined across subscriptions.
 		cluster := clusters[(g.nextStartClusterIndex+i)%len(clusters)]
 
-		status := fwk.RunFilterPlugins(ctx, sub, cluster).Merge()
+		status := fwk.RunFilterPlugins(ctx, state, sub, cluster).Merge()
 		if status.Code() == framework.Error {
 			errCh.SendErrorWithCancel(status.AsError(), cancel)
 			return
@@ -341,7 +339,7 @@ func (g *genericScheduler) findClustersThatPassFilters(ctx context.Context, fwk
 // The scores from each plugin are added together to make the score for that cluster, then
 // any extenders are run as well.
 // All scores are finally combined (added) to get the total weighted scores of all clusters
-func prioritizeClusters(ctx context.Context, fwk framework.Framework, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster, result framework.ClusterScoreList) (framework.ClusterScoreList, error) {
+func prioritizeClusters(ctx context.Context, fwk framework.Framework, state *framework.CycleState, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster, result framework.ClusterScoreList) (framework.ClusterScoreList, error) {
 	// If no priority configs are provided, then all clusters will have a score of one.
 	// This is required to generate the priority list in the required format
 	if !fwk.HasScorePlugins() {
@@ -352,13 +350,13 @@ func prioritizeClusters(ctx context.Context, fwk framework.Framework, sub *appsa
 	}
 
 	// Run PreScore plugins.
-	preScoreStatus := fwk.RunPreScorePlugins(ctx, sub, clusters)
+	preScoreStatus := fwk.RunPreScorePlugins(ctx, state, sub, clusters)
 	if !preScoreStatus.IsSuccess() {
 		return nil, preScoreStatus.AsError()
 	}
 
 	// Run the Score plugins.
-	scoresMap, scoreStatus := fwk.RunScorePlugins(ctx, sub, clusters)
+	scoresMap, scoreStatus := fwk.RunScorePlugins(ctx, state, sub, clusters)
 	if !scoreStatus.IsSuccess() {
 		return nil, scoreStatus.AsError()
 	}
diff --git a/pkg/scheduler/algorithm/interface.go b/pkg/scheduler/algorithm/interface.go
index 2ecbc22..9e6d4d4 100644
--- a/pkg/scheduler/algorithm/interface.go
+++ b/pkg/scheduler/algorithm/interface.go
@@ -26,7 +26,7 @@ import (
 // ScheduleAlgorithm is an interface implemented by things that know how to schedule resources to target
 // managed clusters.
 type ScheduleAlgorithm interface {
-	Schedule(context.Context, framework.Framework, *appsapi.Subscription, *appsapi.FeedInventory) (scheduleResult ScheduleResult, err error)
+	Schedule(context.Context, framework.Framework, *framework.CycleState, *appsapi.Subscription, *appsapi.FeedInventory) (scheduleResult ScheduleResult, err error)
 }
 
 // ScheduleResult represents the result of one subscription scheduled. It will contain
diff --git a/pkg/scheduler/framework/interfaces/cycle_state.go b/pkg/scheduler/framework/interfaces/cycle_state.go
new file mode 100644
index 0000000..e494923
--- /dev/null
+++ b/pkg/scheduler/framework/interfaces/cycle_state.go
@@ -0,0 +1,111 @@
+/*
+Copyright 2022 The Clusternet Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// This file was copied from k8s.io/kubernetes/pkg/scheduler/framework/cycle_state.go
+
+package interfaces
+
+import (
+	"errors"
+	"sync"
+)
+
+var (
+	// ErrNotFound is the not found error message.
+	ErrNotFound = errors.New("not found")
+)
+
+// StateData is a generic type for arbitrary data stored in CycleState.
+type StateData interface {
+	// Clone is an interface to make a copy of StateData. For performance reasons,
+	// clone should make shallow copies for members (e.g., slices or maps) that are not
+	// impacted by PreFilter's optional AddPod/RemovePod methods.
+	Clone() StateData
+}
+
+// StateKey is the type of keys stored in CycleState.
+type StateKey string
+
+// CycleState provides a mechanism for plugins to store and retrieve arbitrary data.
+// StateData stored by one plugin can be read, altered, or deleted by another plugin.
+// CycleState does not provide any data protection, as all plugins are assumed to be
+// trusted.
+// Note: CycleState uses a sync.Map to back the storage. It's aimed to optimize for the "write once and read many times" scenarios.
+// It is the recommended pattern used in all in-tree plugins - plugin-specific state is written once in PreFilter/PreScore and afterwards read many times in Filter/Score.
+type CycleState struct {
+	// storage is keyed with StateKey, and valued with StateData.
+	storage sync.Map
+	// if recordPluginMetrics is true, PluginExecutionDuration will be recorded for this cycle.
+	recordPluginMetrics bool
+}
+
+// NewCycleState initializes a new CycleState and returns its pointer.
+func NewCycleState() *CycleState {
+	return &CycleState{}
+}
+
+// ShouldRecordPluginMetrics returns whether PluginExecutionDuration metrics should be recorded.
+func (c *CycleState) ShouldRecordPluginMetrics() bool {
+	if c == nil {
+		return false
+	}
+	return c.recordPluginMetrics
+}
+
+// SetRecordPluginMetrics sets recordPluginMetrics to the given value.
+func (c *CycleState) SetRecordPluginMetrics(flag bool) {
+	if c == nil {
+		return
+	}
+	c.recordPluginMetrics = flag
+}
+
+// Clone creates a copy of CycleState and returns its pointer. Clone returns
+// nil if the context being cloned is nil.
+func (c *CycleState) Clone() *CycleState {
+	if c == nil {
+		return nil
+	}
+	copy := NewCycleState()
+	c.storage.Range(func(k, v interface{}) bool {
+		copy.storage.Store(k, v.(StateData).Clone())
+		return true
+	})
+
+	return copy
+}
+
+// Read retrieves data with the given "key" from CycleState. If the key is not
+// present an error is returned.
+// This function is thread safe by using sync.Map.
+func (c *CycleState) Read(key StateKey) (StateData, error) {
+	if v, ok := c.storage.Load(key); ok {
+		return v.(StateData), nil
+	}
+	return nil, ErrNotFound
+}
+
+// Write stores the given "val" in CycleState with the given "key".
+// This function is thread safe by using sync.Map.
+func (c *CycleState) Write(key StateKey, val StateData) {
+	c.storage.Store(key, val)
+}
+
+// Delete deletes data with the given key from CycleState.
+// This function is thread safe by using sync.Map.
+func (c *CycleState) Delete(key StateKey) {
+	c.storage.Delete(key)
+}
diff --git a/pkg/scheduler/framework/interfaces/interface.go b/pkg/scheduler/framework/interfaces/interface.go
index f98f52a..cf0191f 100644
--- a/pkg/scheduler/framework/interfaces/interface.go
+++ b/pkg/scheduler/framework/interfaces/interface.go
@@ -157,7 +157,7 @@ type PreFilterPlugin interface {
 
 	// PreFilter is called at the beginning of the scheduling cycle. All PreFilter
 	// plugins must return success or the subscription will be rejected.
-	PreFilter(ctx context.Context, sub *appsapi.Subscription) *Status
+	PreFilter(ctx context.Context, state *CycleState, sub *appsapi.Subscription) *Status
 }
 
 // FilterPlugin is an interface for Filter plugins. These plugins are called at the
@@ -176,7 +176,7 @@ type FilterPlugin interface {
 	// it will return Unschedulable, UnschedulableAndUnresolvable or Error.
 	// For the cluster being evaluated, Filter plugins should look at the passed
 	// cluster's information (e.g., subscriptions considered to be running on the cluster).
-	Filter(ctx context.Context, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) *Status
+	Filter(ctx context.Context, state *CycleState, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) *Status
 }
 
 // PostFilterPlugin is an interface for "PostFilter" plugins. These plugins are called
@@ -194,7 +194,7 @@ type PostFilterPlugin interface {
 	// Optionally, a non-nil PostFilterResult may be returned along with a Success status. For example,
 	// a preemption plugin may choose to return nominatedClusterName, so that framework can reuse that to update the
 	// preemptor subscription's .spec.status.nominatedClusterName field.
-	PostFilter(ctx context.Context, sub *appsapi.Subscription, filteredClusterStatusMap ClusterToStatusMap) (*PostFilterResult, *Status)
+	PostFilter(ctx context.Context, state *CycleState, sub *appsapi.Subscription, filteredClusterStatusMap ClusterToStatusMap) (*PostFilterResult, *Status)
 }
 
 // PrePredictPlugin is an interface for "PrePredict" plugin. PrePredict is an
@@ -206,7 +206,7 @@ type PrePredictPlugin interface {
 	// PrePredict is called by the scheduling framework after a list of managed clusters
 	// passed the filtering phase. All pre-predict plugins must return success or
 	// the subscription will be rejected.
-	PrePredict(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) *Status
+	PrePredict(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) *Status
 }
 
 // PredictPlugin is an interface that must be implemented by "Predict" plugins to predict
@@ -217,7 +217,7 @@ type PredictPlugin interface {
 	// Predict is called on each filtered cluster. It must return success and available
 	// replicas indicating the feasible domain for sake of division. All predict plugins
 	// must return success or the subscription will be rejected.
-	Predict(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, cluster *clusterapi.ManagedCluster) (FeedReplicas, *Status)
+	Predict(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, cluster *clusterapi.ManagedCluster) (FeedReplicas, *Status)
 }
 
 // PreScorePlugin is an interface for "PreScore" plugin. PreScore is an
@@ -230,7 +230,7 @@ type PreScorePlugin interface {
 	// PreScore is called by the scheduling framework after a list of managed clusters
 	// passed the filtering phase. All prescore plugins must return success or
 	// the subscription will be rejected
-	PreScore(ctx context.Context, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) *Status
+	PreScore(ctx context.Context, state *CycleState, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) *Status
 }
 
 // ScoreExtensions is an interface for Score extended functionality.
@@ -238,7 +238,7 @@ type ScoreExtensions interface {
 	// NormalizeScore is called for all cluster scores produced by the same plugin's "Score"
 	// method. A successful run of NormalizeScore will update the scores list and return
 	// a success status.
-	NormalizeScore(ctx context.Context, scores ClusterScoreList) *Status
+	NormalizeScore(ctx context.Context, state *CycleState, sub *appsapi.Subscription, scores ClusterScoreList) *Status
 }
 
 // ScorePlugin is an interface that must be implemented by "Score" plugins to rank
@@ -249,7 +249,7 @@ type ScorePlugin interface {
 	// Score is called on each filtered cluster. It must return success and an integer
 	// indicating the rank of the cluster. All scoring plugins must return success or
 	// the subscription will be rejected.
-	Score(ctx context.Context, sub *appsapi.Subscription, namespacedCluster string) (int64, *Status)
+	Score(ctx context.Context, state *CycleState, sub *appsapi.Subscription, namespacedCluster string) (int64, *Status)
 
 	// ScoreExtensions returns a ScoreExtensions interface if it implements one, or nil if not.
 	ScoreExtensions() ScoreExtensions
@@ -264,7 +264,7 @@ type PreAssignPlugin interface {
 	// PreAssign is called by the scheduling framework after a list of managed clusters
 	// passed the filtering phase. All pre-predict plugins must return success or
 	// the subscription will be rejected.
-	PreAssign(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas TargetClusters) *Status
+	PreAssign(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas TargetClusters) *Status
 }
 
 // AssignPlugin is an interface that must be implemented by "Assign" plugins to assign replicas
@@ -275,7 +275,7 @@ type AssignPlugin interface {
 	// Assign is called on each selected cluster. It will assign replicas for each
 	// selected cluster. The return result is a map whose key is feed key and
 	// value is respective assigned replicas for each selected cluster.
-	Assign(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas TargetClusters) (TargetClusters, *Status)
+	Assign(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas TargetClusters) (TargetClusters, *Status)
 }
 
 // ReservePlugin is an interface for plugins with Reserve and Unreserve
@@ -290,13 +290,13 @@ type ReservePlugin interface {
 	// Reserve is called by the scheduling framework when the scheduler cache is
 	// updated. If this method returns a failed Status, the scheduler will call
 	// the Unreserve method for all enabled ReservePlugins.
-	Reserve(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	Reserve(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 	// Unreserve is called by the scheduling framework when a reserved subscription was
 	// rejected, an error occurred during reservation of subsequent plugins, or
 	// in a later phase. The Unreserve method implementation must be idempotent
 	// and may be called by the scheduler even if the corresponding Reserve
 	// method for the same plugin was not called.
-	Unreserve(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters)
+	Unreserve(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters)
 }
 
 // PreBindPlugin is an interface that must be implemented by "PreBind" plugins.
@@ -306,7 +306,7 @@ type PreBindPlugin interface {
 
 	// PreBind is called before binding a subscription. All prebind plugins must return
 	// success or the subscription will be rejected and won't be sent for binding.
-	PreBind(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	PreBind(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 }
 
 // PostBindPlugin is an interface that must be implemented by "PostBind" plugins.
@@ -318,7 +318,7 @@ type PostBindPlugin interface {
 	// informational. A common application of this extension point is for cleaning
 	// up. If a plugin needs to clean-up its state after a subscription is scheduled and
 	// bound, PostBind is the extension point that it should register.
-	PostBind(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters)
+	PostBind(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters)
 }
 
 // PermitPlugin is an interface that must be implemented by "Permit" plugins.
@@ -332,7 +332,7 @@ type PermitPlugin interface {
 	// The subscription will also be rejected if the wait timeout or the subscription is rejected while
 	// waiting. Note that if the plugin returns "wait", the framework will wait only
 	// after running the remaining plugins given that no other plugin rejects the subscription.
-	Permit(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) (*Status, time.Duration)
+	Permit(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) (*Status, time.Duration)
 }
 
 // BindPlugin is an interface that must be implemented by "Bind" plugins. Bind
@@ -346,7 +346,7 @@ type BindPlugin interface {
 	// remaining bind plugins are skipped. When a bind plugin does not handle a Subscription,
 	// it must return Skip in its Status code. If a bind plugin returns an Error, the
 	// subscription is rejected and will not be bound.
-	Bind(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	Bind(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 }
 
 // Framework manages the set of plugins in use by the scheduling framework.
@@ -358,48 +358,48 @@ type Framework interface {
 	// *Status and its code is set to non-success if any of the plugins returns
 	// anything but Success. If a non-success status is returned, then the scheduling
 	// cycle is aborted.
-	RunPreFilterPlugins(ctx context.Context, sub *appsapi.Subscription) *Status
+	RunPreFilterPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription) *Status
 
 	// RunPostFilterPlugins runs the set of configured PostFilter plugins.
 	// PostFilter plugins can either be informational, in which case should be configured
 	// to execute first and return Unschedulable status, or ones that try to change the
 	// cluster state to make the subscription potentially schedulable in a future scheduling cycle.
-	RunPostFilterPlugins(ctx context.Context, sub *appsapi.Subscription, filteredClusterStatusMap ClusterToStatusMap) (*PostFilterResult, *Status)
+	RunPostFilterPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, filteredClusterStatusMap ClusterToStatusMap) (*PostFilterResult, *Status)
 
 	// RunPrePredictPlugins runs the set of configured PrePredict plugins. It returns
 	// *Status and its code is set to non-success if any of the plugins returns
 	// anything but Success. If a non-success status is returned, then the scheduling
 	// cycle is aborted.
-	RunPrePredictPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) *Status
+	RunPrePredictPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) *Status
 
 	// RunPredictPlugins runs the set of configured Predict plugins. It returns a map that
 	// stores for each Predict plugin name the corresponding ClusterScoreList(s).
 	// It also returns *Status, which is set to non-success if any of the plugins returns
 	// a non-success status.
-	RunPredictPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster, availableList ClusterScoreList) (ClusterScoreList, *Status)
+	RunPredictPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster, availableList ClusterScoreList) (ClusterScoreList, *Status)
 
 	// RunPreAssignPlugins runs the set of configured PreAssign plugins. It returns
 	// *Status and its code is set to non-success if any of the plugins returns
 	// anything but Success. If a non-success status is returned, then the scheduling
 	// cycle is aborted.
-	RunPreAssignPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, selected TargetClusters) *Status
+	RunPreAssignPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, selected TargetClusters) *Status
 
 	// RunAssignPlugins runs the set of configured Assign plugins. An Assign plugin may choose
 	// whether to handle the given subscription. If an Assign plugin chooses to skip the
 	// assigning, it should return code=5("skip") status. Otherwise, it should return "Error"
 	// or "Success". If none of the plugins handled assigning, RunAssignPlugins returns
 	// code=5("skip") status.
-	RunAssignPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, selected TargetClusters) (TargetClusters, *Status)
+	RunAssignPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, selected TargetClusters) (TargetClusters, *Status)
 
 	// RunReservePluginsReserve runs the Reserve method of the set of
 	// configured Reserve plugins. If any of these calls returns an error, it
 	// does not continue running the remaining ones and returns the error. In
 	// such case, subscription will not be scheduled.
-	RunReservePluginsReserve(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	RunReservePluginsReserve(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 
 	// RunReservePluginsUnreserve runs the Unreserve method of the set of
 	// configured Reserve plugins.
-	RunReservePluginsUnreserve(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters)
+	RunReservePluginsUnreserve(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters)
 
 	// RunPermitPlugins runs the set of configured Permit plugins. If any of these
 	// plugins returns a status other than "Success" or "Wait", it does not continue
@@ -407,7 +407,7 @@ type Framework interface {
 	// plugins returns "Wait", then this function will create and add waiting subscription
 	// to a map of currently waiting subscriptions and return status with "Wait" code.
 	// Subscription will remain waiting subscription for the minimum duration returned by the Permit plugins.
-	RunPermitPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	RunPermitPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 
 	// WaitOnPermit will block, if the subscription is a waiting subscription, until the waiting subscription is rejected or allowed.
 	WaitOnPermit(ctx context.Context, sub *appsapi.Subscription) *Status
@@ -417,17 +417,17 @@ type Framework interface {
 	// anything but Success. If the Status code is Unschedulable, it is
 	// considered as a scheduling check failure, otherwise, it is considered as an
 	// internal error. In either case the subscription is not going to be bound.
-	RunPreBindPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	RunPreBindPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 
 	// RunPostBindPlugins runs the set of configured PostBind plugins.
-	RunPostBindPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters)
+	RunPostBindPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters)
 
 	// RunBindPlugins runs the set of configured Bind plugins. A Bind plugin may choose
 	// whether or not to handle the given subscription. If a Bind plugin chooses to skip the
 	// binding, it should return code=5("skip") status. Otherwise, it should return "Error"
 	// or "Success". If none of the plugins handled binding, RunBindPlugins returns
 	// code=5("skip") status.
-	RunBindPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
+	RunBindPlugins(ctx context.Context, state *CycleState, sub *appsapi.Subscription, targetClusters TargetClusters) *Status
 
 	// HasFilterPlugins returns true if at least one Filter plugin is defined.
 	HasFilterPlugins() bool
@@ -493,15 +493,15 @@ type PostFilterResult struct {
 type PluginsRunner interface {
 	// RunPreScorePlugins runs the set of configured PreScore plugins. If any
 	// of these plugins returns any status other than "Success", the given subscription is rejected.
-	RunPreScorePlugins(context.Context, *appsapi.Subscription, []*clusterapi.ManagedCluster) *Status
+	RunPreScorePlugins(context.Context, *CycleState, *appsapi.Subscription, []*clusterapi.ManagedCluster) *Status
 
 	// RunScorePlugins runs the set of configured Score plugins. It returns a map that
 	// stores for each Score plugin name the corresponding ClusterScoreList(s).
 	// It also returns *Status, which is set to non-success if any of the plugins returns
 	// a non-success status.
-	RunScorePlugins(context.Context, *appsapi.Subscription, []*clusterapi.ManagedCluster) (PluginToClusterScores, *Status)
+	RunScorePlugins(context.Context, *CycleState, *appsapi.Subscription, []*clusterapi.ManagedCluster) (PluginToClusterScores, *Status)
 
 	// RunFilterPlugins runs the set of configured Filter plugins for subscription on
 	// the given cluster.
-	RunFilterPlugins(context.Context, *appsapi.Subscription, *clusterapi.ManagedCluster) PluginToStatus
+	RunFilterPlugins(context.Context, *CycleState, *appsapi.Subscription, *clusterapi.ManagedCluster) PluginToStatus
 }
diff --git a/pkg/scheduler/framework/interfaces/types.go b/pkg/scheduler/framework/interfaces/types.go
index d4bca97..a51252a 100644
--- a/pkg/scheduler/framework/interfaces/types.go
+++ b/pkg/scheduler/framework/interfaces/types.go
@@ -65,6 +65,34 @@ func (t TargetClusters) Swap(i, j int) {
 	}
 }
 
+func (t *TargetClusters) DeepCopy() *TargetClusters {
+	if t == nil {
+		return nil
+	}
+	obj := new(TargetClusters)
+	if t.BindingClusters != nil {
+		in, out := &t.BindingClusters, &obj.BindingClusters
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if t.Replicas != nil {
+		in, out := &t.Replicas, &obj.Replicas
+		*out = make(map[string][]int32, len(*in))
+		for key, val := range *in {
+			var outVal []int32
+			if val == nil {
+				(*out)[key] = nil
+			} else {
+				in, out := &val, &outVal
+				*out = make([]int32, len(*in))
+				copy(*out, *in)
+			}
+			(*out)[key] = outVal
+		}
+	}
+	return obj
+}
+
 const (
 	// NoClusterAvailableMsg is used to format message when no clusters available.
 	NoClusterAvailableMsg = "0/%v clusters are available"
diff --git a/pkg/scheduler/framework/plugins/defaultassigner/dynamic.go b/pkg/scheduler/framework/plugins/defaultassigner/dynamic.go
index c0a2849..9c813c0 100644
--- a/pkg/scheduler/framework/plugins/defaultassigner/dynamic.go
+++ b/pkg/scheduler/framework/plugins/defaultassigner/dynamic.go
@@ -35,7 +35,7 @@ type DynamicAssigner struct {
 
 var _ framework.AssignPlugin = &DynamicAssigner{}
 
-// New creates a DefaultAssigner.
+// NewDynamicAssigner creates a default dynamic assigner.
 func NewDynamicAssigner(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {
 	return &DynamicAssigner{handle: handle}, nil
 }
@@ -46,7 +46,7 @@ func (pl *DynamicAssigner) Name() string {
 }
 
 // Assign assigns subscriptions to clusters using the clusternet client.
-func (pl *DynamicAssigner) Assign(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (framework.TargetClusters, *framework.Status) {
+func (pl *DynamicAssigner) Assign(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (framework.TargetClusters, *framework.Status) {
 	klog.V(3).InfoS("Attempting to assign replicas to clusters",
 		"subscription", klog.KObj(sub), "clusters", availableReplicas.BindingClusters)
 	if sub.Spec.DividingScheduling == nil || sub.Spec.DividingScheduling.Type != appsapi.DynamicReplicaDividingType {
diff --git a/pkg/scheduler/framework/plugins/defaultassigner/static.go b/pkg/scheduler/framework/plugins/defaultassigner/static.go
index 3773472..de52a6b 100644
--- a/pkg/scheduler/framework/plugins/defaultassigner/static.go
+++ b/pkg/scheduler/framework/plugins/defaultassigner/static.go
@@ -35,8 +35,8 @@ type StaticAssigner struct {
 
 var _ framework.AssignPlugin = &StaticAssigner{}
 
-// New creates a DefaultAssigner.
-func New(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {
+// NewStaticAssigner creates a Default Assigner.
+func NewStaticAssigner(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {
 	return &StaticAssigner{handle: handle}, nil
 }
 
@@ -46,7 +46,7 @@ func (pl *StaticAssigner) Name() string {
 }
 
 // Assign assigns subscriptions to clusters using the clusternet client.
-func (pl *StaticAssigner) Assign(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (framework.TargetClusters, *framework.Status) {
+func (pl *StaticAssigner) Assign(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (framework.TargetClusters, *framework.Status) {
 	klog.V(3).InfoS("Attempting to assign replicas to clusters",
 		"subscription", klog.KObj(sub), "clusters", availableReplicas.BindingClusters)
 	if sub.Spec.DividingScheduling == nil || sub.Spec.DividingScheduling.Type != appsapi.StaticReplicaDividingType {
diff --git a/pkg/scheduler/framework/plugins/defaultbinder/default_binder.go b/pkg/scheduler/framework/plugins/defaultbinder/default_binder.go
index 021801f..5bb5770 100644
--- a/pkg/scheduler/framework/plugins/defaultbinder/default_binder.go
+++ b/pkg/scheduler/framework/plugins/defaultbinder/default_binder.go
@@ -48,7 +48,7 @@ func (pl *DefaultBinder) Name() string {
 }
 
 // Bind binds subscriptions to clusters using the clusternet client.
-func (pl *DefaultBinder) Bind(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
+func (pl *DefaultBinder) Bind(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
 	klog.V(3).InfoS("Attempting to bind subscription to clusters",
 		"subscription", klog.KObj(sub), "clusters", targetClusters.BindingClusters)
 
diff --git a/pkg/scheduler/framework/plugins/predictor/predictor.go b/pkg/scheduler/framework/plugins/predictor/predictor.go
index 1a08249..5127285 100644
--- a/pkg/scheduler/framework/plugins/predictor/predictor.go
+++ b/pkg/scheduler/framework/plugins/predictor/predictor.go
@@ -13,17 +13,19 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
+
 package predictor
 
 import (
 	"context"
 
+	"k8s.io/apimachinery/pkg/runtime"
+
 	appsapi "github.com/clusternet/clusternet/pkg/apis/apps/v1alpha1"
 	clsapi "github.com/clusternet/clusternet/pkg/apis/clusters/v1beta1"
-	scheduler "github.com/clusternet/clusternet/pkg/apis/scheduler"
+	"github.com/clusternet/clusternet/pkg/apis/scheduler"
 	framework "github.com/clusternet/clusternet/pkg/scheduler/framework/interfaces"
 	"github.com/clusternet/clusternet/pkg/scheduler/framework/plugins/names"
-	"k8s.io/apimachinery/pkg/runtime"
 )
 
 // Predictor is a plugin than checks if a subscription need resources
@@ -39,13 +41,10 @@ func (pl *Predictor) Name() string {
 }
 
 // Predict invoked by scheduler predictor plugin
-func (pl *Predictor) Predict(ctx context.Context,
-	sub *appsapi.Subscription,
-	finv *appsapi.FeedInventory,
-	clusters *clsapi.ManagedCluster) (feedReplicas framework.FeedReplicas, s *framework.Status) {
+func (pl *Predictor) Predict(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, cluster *clsapi.ManagedCluster) (feedReplicas framework.FeedReplicas, s *framework.Status) {
 	var pp scheduler.PredictorProvider
 
-	if clusters.Status.PredictorEnabled {
+	if cluster.Status.PredictorEnabled {
 		// TODO: use pkg/predictor create a new predictor provider
 	} else {
 		return nil, framework.NewStatus(framework.Skip, "predictor is disabled")
@@ -59,7 +58,7 @@ func (pl *Predictor) Predict(ctx context.Context,
 			if err != nil {
 				return nil, framework.AsStatus(err)
 			}
-			feedReplicas = append(feedReplicas, replica[clusters.Name])
+			feedReplicas = append(feedReplicas, replica[cluster.Name])
 		}
 	}
 	return
diff --git a/pkg/scheduler/framework/plugins/registry.go b/pkg/scheduler/framework/plugins/registry.go
index 6763956..69dea8a 100644
--- a/pkg/scheduler/framework/plugins/registry.go
+++ b/pkg/scheduler/framework/plugins/registry.go
@@ -30,7 +30,7 @@ func NewInTreeRegistry() runtime.Registry {
 	return runtime.Registry{
 		names.DefaultBinder:   defaultbinder.New,
 		names.TaintToleration: tainttoleration.New,
-		names.StaticAssigner:  defaultassigner.New,
+		names.StaticAssigner:  defaultassigner.NewStaticAssigner,
 		names.DynamicAssigner: defaultassigner.NewDynamicAssigner,
 		names.Predictor:       predictor.New,
 	}
diff --git a/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go b/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go
index f6e3916..4e02fa4 100644
--- a/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go
+++ b/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go
@@ -46,7 +46,7 @@ func (pl *TaintToleration) Name() string {
 }
 
 // Filter invoked at the filter extension point.
-func (pl *TaintToleration) Filter(ctx context.Context, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) *framework.Status {
+func (pl *TaintToleration) Filter(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) *framework.Status {
 	if cluster == nil {
 		return framework.AsStatus(fmt.Errorf("invalid cluster"))
 	}
@@ -95,7 +95,7 @@ func countIntolerableTaintsPreferNoSchedule(taints []v1.Taint, tolerations []v1.
 }
 
 // Score invoked at the Score extension point.
-func (pl *TaintToleration) Score(ctx context.Context, sub *appsapi.Subscription, namespacedCluster string) (int64, *framework.Status) {
+func (pl *TaintToleration) Score(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, namespacedCluster string) (int64, *framework.Status) {
 	// Convert the namespace/name string into a distinct namespace and name
 	ns, name, err := cache.SplitMetaNamespaceKey(namespacedCluster)
 	if err != nil {
@@ -113,7 +113,7 @@ func (pl *TaintToleration) Score(ctx context.Context, sub *appsapi.Subscription,
 }
 
 // NormalizeScore invoked after scoring all clusters.
-func (pl *TaintToleration) NormalizeScore(ctx context.Context, scores framework.ClusterScoreList) *framework.Status {
+func (pl *TaintToleration) NormalizeScore(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, scores framework.ClusterScoreList) *framework.Status {
 	return helper.DefaultNormalizeScore(framework.MaxClusterScore, true, scores)
 }
 
diff --git a/pkg/scheduler/framework/runtime/framework.go b/pkg/scheduler/framework/runtime/framework.go
index e25bf71..b5d3d51 100644
--- a/pkg/scheduler/framework/runtime/framework.go
+++ b/pkg/scheduler/framework/runtime/framework.go
@@ -291,13 +291,13 @@ func updatePluginList(pluginList interface{}, pluginSet schedulerapis.PluginSet,
 // *Status and its code is set to non-success if any of the plugins returns
 // anything but Success. If a non-success status is returned, then the scheduling
 // cycle is aborted.
-func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, sub *appsapi.Subscription) (status *framework.Status) {
+func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(preFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.preFilterPlugins {
-		status = f.runPreFilterPlugin(ctx, pl, sub)
+		status = f.runPreFilterPlugin(ctx, pl, state, sub)
 		if !status.IsSuccess() {
 			status.SetFailedPlugin(pl.Name())
 			if status.IsUnschedulable() {
@@ -310,9 +310,9 @@ func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, sub *appsapi.Su
 	return nil
 }
 
-func (f *frameworkImpl) runPreFilterPlugin(ctx context.Context, pl framework.PreFilterPlugin, sub *appsapi.Subscription) *framework.Status {
+func (f *frameworkImpl) runPreFilterPlugin(ctx context.Context, pl framework.PreFilterPlugin, state *framework.CycleState, sub *appsapi.Subscription) *framework.Status {
 	startTime := time.Now()
-	status := pl.PreFilter(ctx, sub)
+	status := pl.PreFilter(ctx, state, sub)
 	f.metricsRecorder.observePluginDurationAsync(preFilter, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
@@ -321,10 +321,10 @@ func (f *frameworkImpl) runPreFilterPlugin(ctx context.Context, pl framework.Pre
 // the given cluster. If any of these plugins doesn't return "Success", the
 // given cluster is not suitable for running subscription.
 // Meanwhile, the failure message and status are set for the given cluster.
-func (f *frameworkImpl) RunFilterPlugins(ctx context.Context, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) framework.PluginToStatus {
+func (f *frameworkImpl) RunFilterPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) framework.PluginToStatus {
 	statuses := make(framework.PluginToStatus)
 	for _, pl := range f.filterPlugins {
-		pluginStatus := f.runFilterPlugin(ctx, pl, sub, cluster)
+		pluginStatus := f.runFilterPlugin(ctx, pl, state, sub, cluster)
 		if !pluginStatus.IsSuccess() {
 			if !pluginStatus.IsUnschedulable() {
 				// Filter plugins are not supposed to return any status other than
@@ -344,16 +344,16 @@ func (f *frameworkImpl) RunFilterPlugins(ctx context.Context, sub *appsapi.Subsc
 	return statuses
 }
 
-func (f *frameworkImpl) runFilterPlugin(ctx context.Context, pl framework.FilterPlugin, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) *framework.Status {
+func (f *frameworkImpl) runFilterPlugin(ctx context.Context, pl framework.FilterPlugin, state *framework.CycleState, sub *appsapi.Subscription, cluster *clusterapi.ManagedCluster) *framework.Status {
 	startTime := time.Now()
-	status := pl.Filter(ctx, sub, cluster)
+	status := pl.Filter(ctx, state, sub, cluster)
 	f.metricsRecorder.observePluginDurationAsync(Filter, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
 // RunPostFilterPlugins runs the set of configured PostFilter plugins until the first
 // Success or Error is met, otherwise continues to execute all plugins.
-func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, sub *appsapi.Subscription, filteredClusterStatusMap framework.ClusterToStatusMap) (_ *framework.PostFilterResult, status *framework.Status) {
+func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, filteredClusterStatusMap framework.ClusterToStatusMap) (_ *framework.PostFilterResult, status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(postFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -361,7 +361,7 @@ func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, sub *appsapi.S
 
 	statuses := make(framework.PluginToStatus)
 	for _, pl := range f.postFilterPlugins {
-		r, s := f.runPostFilterPlugin(ctx, pl, sub, filteredClusterStatusMap)
+		r, s := f.runPostFilterPlugin(ctx, pl, state, sub, filteredClusterStatusMap)
 		if s.IsSuccess() {
 			return r, s
 		} else if !s.IsUnschedulable() {
@@ -374,20 +374,20 @@ func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, sub *appsapi.S
 	return nil, statuses.Merge()
 }
 
-func (f *frameworkImpl) runPostFilterPlugin(ctx context.Context, pl framework.PostFilterPlugin, sub *appsapi.Subscription, filteredClusterStatusMap framework.ClusterToStatusMap) (*framework.PostFilterResult, *framework.Status) {
+func (f *frameworkImpl) runPostFilterPlugin(ctx context.Context, pl framework.PostFilterPlugin, state *framework.CycleState, sub *appsapi.Subscription, filteredClusterStatusMap framework.ClusterToStatusMap) (*framework.PostFilterResult, *framework.Status) {
 	startTime := time.Now()
-	r, s := pl.PostFilter(ctx, sub, filteredClusterStatusMap)
+	r, s := pl.PostFilter(ctx, state, sub, filteredClusterStatusMap)
 	f.metricsRecorder.observePluginDurationAsync(postFilter, pl.Name(), s, metrics.SinceInSeconds(startTime))
 	return r, s
 }
 
-func (f *frameworkImpl) RunPrePredictPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) (status *framework.Status) {
+func (f *frameworkImpl) RunPrePredictPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(prePredict, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.prePredictPlugins {
-		status = f.runPrePredictPlugin(ctx, pl, sub, finv, clusters)
+		status = f.runPrePredictPlugin(ctx, pl, state, sub, finv, clusters)
 		if !status.IsSuccess() {
 			return framework.AsStatus(fmt.Errorf("running PrePredict plugin %q: %w", pl.Name(), status.AsError()))
 		}
@@ -396,14 +396,14 @@ func (f *frameworkImpl) RunPrePredictPlugins(ctx context.Context, sub *appsapi.S
 	return nil
 }
 
-func (f *frameworkImpl) runPrePredictPlugin(ctx context.Context, pl framework.PrePredictPlugin, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) *framework.Status {
+func (f *frameworkImpl) runPrePredictPlugin(ctx context.Context, pl framework.PrePredictPlugin, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster) *framework.Status {
 	startTime := time.Now()
-	status := pl.PrePredict(ctx, sub, finv, clusters)
+	status := pl.PrePredict(ctx, state, sub, finv, clusters)
 	f.metricsRecorder.observePluginDurationAsync(prePredict, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
-func (f *frameworkImpl) RunPredictPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster, availableList framework.ClusterScoreList) (res framework.ClusterScoreList, status *framework.Status) {
+func (f *frameworkImpl) RunPredictPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, clusters []*clusterapi.ManagedCluster, availableList framework.ClusterScoreList) (res framework.ClusterScoreList, status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(predict, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -414,7 +414,7 @@ func (f *frameworkImpl) RunPredictPlugins(ctx context.Context, sub *appsapi.Subs
 	// Run Predict method for each cluster in parallel.
 	f.Parallelizer().Until(ctx, len(clusters), func(index int) {
 		for i, pl := range f.predictPlugins {
-			replicas, status := f.runPredictPlugin(ctx, pl, sub, finv, clusters[index])
+			replicas, status := f.runPredictPlugin(ctx, pl, state, sub, finv, clusters[index])
 			if !status.IsSuccess() {
 				err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError())
 				errCh.SendErrorWithCancel(err, cancel)
@@ -436,22 +436,22 @@ func (f *frameworkImpl) RunPredictPlugins(ctx context.Context, sub *appsapi.Subs
 	return availableList, nil
 }
 
-func (f *frameworkImpl) runPredictPlugin(ctx context.Context, pl framework.PredictPlugin, sub *appsapi.Subscription, finv *appsapi.FeedInventory, cluster *clusterapi.ManagedCluster) (framework.FeedReplicas, *framework.Status) {
+func (f *frameworkImpl) runPredictPlugin(ctx context.Context, pl framework.PredictPlugin, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, cluster *clusterapi.ManagedCluster) (framework.FeedReplicas, *framework.Status) {
 	startTime := time.Now()
-	replicas, status := pl.Predict(ctx, sub, finv, cluster)
+	replicas, status := pl.Predict(ctx, state, sub, finv, cluster)
 	f.metricsRecorder.observePluginDurationAsync(predict, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return replicas, status
 }
 
 // RunPreScorePlugins runs the set of configured pre-score plugins. If any
 // of these plugins returns any status other than "Success", the given subscription is rejected.
-func (f *frameworkImpl) RunPreScorePlugins(ctx context.Context, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) (status *framework.Status) {
+func (f *frameworkImpl) RunPreScorePlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(preScore, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.preScorePlugins {
-		status = f.runPreScorePlugin(ctx, pl, sub, clusters)
+		status = f.runPreScorePlugin(ctx, pl, state, sub, clusters)
 		if !status.IsSuccess() {
 			return framework.AsStatus(fmt.Errorf("running PreScore plugin %q: %w", pl.Name(), status.AsError()))
 		}
@@ -460,9 +460,9 @@ func (f *frameworkImpl) RunPreScorePlugins(ctx context.Context, sub *appsapi.Sub
 	return nil
 }
 
-func (f *frameworkImpl) runPreScorePlugin(ctx context.Context, pl framework.PreScorePlugin, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) *framework.Status {
+func (f *frameworkImpl) runPreScorePlugin(ctx context.Context, pl framework.PreScorePlugin, state *framework.CycleState, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) *framework.Status {
 	startTime := time.Now()
-	status := pl.PreScore(ctx, sub, clusters)
+	status := pl.PreScore(ctx, state, sub, clusters)
 	f.metricsRecorder.observePluginDurationAsync(preScore, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
@@ -471,7 +471,7 @@ func (f *frameworkImpl) runPreScorePlugin(ctx context.Context, pl framework.PreS
 // stores for each scoring plugin name the corresponding  ClusterScoreList(s).
 // It also returns *Status, which is set to non-success if any of the plugins returns
 // a non-success status.
-func (f *frameworkImpl) RunScorePlugins(ctx context.Context, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) (ps framework.PluginToClusterScores, status *framework.Status) {
+func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, clusters []*clusterapi.ManagedCluster) (ps framework.PluginToClusterScores, status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(score, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -487,7 +487,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, sub *appsapi.Subscr
 	f.Parallelizer().Until(ctx, len(clusters), func(index int) {
 		for _, pl := range f.scorePlugins {
 			clusterNamespacedName := klog.KObj(clusters[index]).String()
-			s, status := f.runScorePlugin(ctx, pl, sub, clusterNamespacedName)
+			s, status := f.runScorePlugin(ctx, pl, state, sub, clusterNamespacedName)
 			if !status.IsSuccess() {
 				err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError())
 				errCh.SendErrorWithCancel(err, cancel)
@@ -510,7 +510,7 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, sub *appsapi.Subscr
 		if pl.ScoreExtensions() == nil {
 			return
 		}
-		status := f.runScoreExtension(ctx, pl, ClusterScoreList)
+		status := f.runScoreExtension(ctx, pl, state, sub, ClusterScoreList)
 		if !status.IsSuccess() {
 			err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError())
 			errCh.SendErrorWithCancel(err, cancel)
@@ -545,27 +545,27 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, sub *appsapi.Subscr
 	return pluginToClusterScores, nil
 }
 
-func (f *frameworkImpl) runScorePlugin(ctx context.Context, pl framework.ScorePlugin, sub *appsapi.Subscription, clusterNamespace string) (int64, *framework.Status) {
+func (f *frameworkImpl) runScorePlugin(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, sub *appsapi.Subscription, clusterNamespace string) (int64, *framework.Status) {
 	startTime := time.Now()
-	s, status := pl.Score(ctx, sub, clusterNamespace)
+	s, status := pl.Score(ctx, state, sub, clusterNamespace)
 	f.metricsRecorder.observePluginDurationAsync(score, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return s, status
 }
 
-func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl framework.ScorePlugin, ClusterScoreList framework.ClusterScoreList) *framework.Status {
+func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, sub *appsapi.Subscription, ClusterScoreList framework.ClusterScoreList) *framework.Status {
 	startTime := time.Now()
-	status := pl.ScoreExtensions().NormalizeScore(ctx, ClusterScoreList)
+	status := pl.ScoreExtensions().NormalizeScore(ctx, state, sub, ClusterScoreList)
 	f.metricsRecorder.observePluginDurationAsync(scoreExtensionNormalize, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
-func (f *frameworkImpl) RunPreAssignPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (status *framework.Status) {
+func (f *frameworkImpl) RunPreAssignPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(preAssign, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.preAssignPlugins {
-		status = f.runPreAssignPlugin(ctx, pl, sub, finv, availableReplicas)
+		status = f.runPreAssignPlugin(ctx, pl, state, sub, finv, availableReplicas)
 		if !status.IsSuccess() {
 			err := status.AsError()
 			klog.ErrorS(err, "Failed running PreAssign plugin", "plugin", pl.Name(), "sub", klog.KObj(sub))
@@ -575,14 +575,14 @@ func (f *frameworkImpl) RunPreAssignPlugins(ctx context.Context, sub *appsapi.Su
 	return nil
 }
 
-func (f *frameworkImpl) runPreAssignPlugin(ctx context.Context, pl framework.PreAssignPlugin, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) *framework.Status {
+func (f *frameworkImpl) runPreAssignPlugin(ctx context.Context, pl framework.PreAssignPlugin, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) *framework.Status {
 	startTime := time.Now()
-	status := pl.PreAssign(ctx, sub, finv, availableReplicas)
+	status := pl.PreAssign(ctx, state, sub, finv, availableReplicas)
 	f.metricsRecorder.observePluginDurationAsync(preAssign, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
-func (f *frameworkImpl) RunAssignPlugins(ctx context.Context, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (result framework.TargetClusters, status *framework.Status) {
+func (f *frameworkImpl) RunAssignPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (result framework.TargetClusters, status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(assign, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -591,7 +591,7 @@ func (f *frameworkImpl) RunAssignPlugins(ctx context.Context, sub *appsapi.Subsc
 		return framework.TargetClusters{}, framework.NewStatus(framework.Skip, "empty assign plugins")
 	}
 	for _, ap := range f.assignPlugins {
-		result, status = f.runAssignPlugin(ctx, ap, sub, finv, availableReplicas)
+		result, status = f.runAssignPlugin(ctx, ap, state, sub, finv, availableReplicas)
 		if status != nil && status.Code() == framework.Skip {
 			continue
 		}
@@ -605,9 +605,9 @@ func (f *frameworkImpl) RunAssignPlugins(ctx context.Context, sub *appsapi.Subsc
 	return result, status
 }
 
-func (f *frameworkImpl) runAssignPlugin(ctx context.Context, ap framework.AssignPlugin, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (framework.TargetClusters, *framework.Status) {
+func (f *frameworkImpl) runAssignPlugin(ctx context.Context, ap framework.AssignPlugin, state *framework.CycleState, sub *appsapi.Subscription, finv *appsapi.FeedInventory, availableReplicas framework.TargetClusters) (framework.TargetClusters, *framework.Status) {
 	startTime := time.Now()
-	result, status := ap.Assign(ctx, sub, finv, availableReplicas)
+	result, status := ap.Assign(ctx, state, sub, finv, availableReplicas)
 	f.metricsRecorder.observePluginDurationAsync(assign, ap.Name(), status, metrics.SinceInSeconds(startTime))
 	return result, status
 }
@@ -615,13 +615,13 @@ func (f *frameworkImpl) runAssignPlugin(ctx context.Context, ap framework.Assign
 // RunPreBindPlugins runs the set of configured prebind plugins. It returns a
 // failure (bool) if any of the plugins returns an error. It also returns an
 // error containing the rejection message or the error occurred in the plugin.
-func (f *frameworkImpl) RunPreBindPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
+func (f *frameworkImpl) RunPreBindPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(preBind, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.preBindPlugins {
-		status = f.runPreBindPlugin(ctx, pl, sub, targetClusters)
+		status = f.runPreBindPlugin(ctx, pl, state, sub, targetClusters)
 		if !status.IsSuccess() {
 			err := status.AsError()
 			klog.ErrorS(err, "Failed running PreBind plugin", "plugin", pl.Name(), "sub", klog.KObj(sub))
@@ -631,15 +631,15 @@ func (f *frameworkImpl) RunPreBindPlugins(ctx context.Context, sub *appsapi.Subs
 	return nil
 }
 
-func (f *frameworkImpl) runPreBindPlugin(ctx context.Context, pl framework.PreBindPlugin, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
+func (f *frameworkImpl) runPreBindPlugin(ctx context.Context, pl framework.PreBindPlugin, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
 	startTime := time.Now()
-	status := pl.PreBind(ctx, sub, targetClusters)
+	status := pl.PreBind(ctx, state, sub, targetClusters)
 	f.metricsRecorder.observePluginDurationAsync(preBind, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
 // RunBindPlugins runs the set of configured bind plugins until one returns a non `Skip` status.
-func (f *frameworkImpl) RunBindPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
+func (f *frameworkImpl) RunBindPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(bind, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -648,7 +648,7 @@ func (f *frameworkImpl) RunBindPlugins(ctx context.Context, sub *appsapi.Subscri
 		return framework.NewStatus(framework.Skip, "")
 	}
 	for _, bp := range f.bindPlugins {
-		status = f.runBindPlugin(ctx, bp, sub, targetClusters)
+		status = f.runBindPlugin(ctx, bp, state, sub, targetClusters)
 		if status != nil && status.Code() == framework.Skip {
 			continue
 		}
@@ -662,27 +662,27 @@ func (f *frameworkImpl) RunBindPlugins(ctx context.Context, sub *appsapi.Subscri
 	return status
 }
 
-func (f *frameworkImpl) runBindPlugin(ctx context.Context, bp framework.BindPlugin, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
+func (f *frameworkImpl) runBindPlugin(ctx context.Context, bp framework.BindPlugin, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
 	startTime := time.Now()
-	status := bp.Bind(ctx, sub, targetClusters)
+	status := bp.Bind(ctx, state, sub, targetClusters)
 	f.metricsRecorder.observePluginDurationAsync(bind, bp.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
 // RunPostBindPlugins runs the set of configured postbind plugins.
-func (f *frameworkImpl) RunPostBindPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
+func (f *frameworkImpl) RunPostBindPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(postBind, framework.Success.String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.postBindPlugins {
-		f.runPostBindPlugin(ctx, pl, sub, targetClusters)
+		f.runPostBindPlugin(ctx, pl, state, sub, targetClusters)
 	}
 }
 
-func (f *frameworkImpl) runPostBindPlugin(ctx context.Context, pl framework.PostBindPlugin, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
+func (f *frameworkImpl) runPostBindPlugin(ctx context.Context, pl framework.PostBindPlugin, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
 	startTime := time.Now()
-	pl.PostBind(ctx, sub, targetClusters)
+	pl.PostBind(ctx, state, sub, targetClusters)
 	f.metricsRecorder.observePluginDurationAsync(postBind, pl.Name(), nil, metrics.SinceInSeconds(startTime))
 }
 
@@ -691,13 +691,13 @@ func (f *frameworkImpl) runPostBindPlugin(ctx context.Context, pl framework.Post
 // continue running the remaining ones and returns the error. In such a case,
 // the subscription will not be scheduled and the caller will be expected to call
 // RunReservePluginsUnreserve.
-func (f *frameworkImpl) RunReservePluginsReserve(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
+func (f *frameworkImpl) RunReservePluginsReserve(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(reserve, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
 	}()
 	for _, pl := range f.reservePlugins {
-		status = f.runReservePluginReserve(ctx, pl, sub, targetClusters)
+		status = f.runReservePluginReserve(ctx, pl, state, sub, targetClusters)
 		if !status.IsSuccess() {
 			err := status.AsError()
 			klog.ErrorS(err, "Failed running Reserve plugin", "plugin", pl.Name(), "subscription", klog.KObj(sub))
@@ -707,16 +707,16 @@ func (f *frameworkImpl) RunReservePluginsReserve(ctx context.Context, sub *appsa
 	return nil
 }
 
-func (f *frameworkImpl) runReservePluginReserve(ctx context.Context, pl framework.ReservePlugin, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
+func (f *frameworkImpl) runReservePluginReserve(ctx context.Context, pl framework.ReservePlugin, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) *framework.Status {
 	startTime := time.Now()
-	status := pl.Reserve(ctx, sub, targetClusters)
+	status := pl.Reserve(ctx, state, sub, targetClusters)
 	f.metricsRecorder.observePluginDurationAsync(reserve, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status
 }
 
 // RunReservePluginsUnreserve runs the Unreserve method in the set of
 // configured reserve plugins.
-func (f *frameworkImpl) RunReservePluginsUnreserve(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
+func (f *frameworkImpl) RunReservePluginsUnreserve(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(unreserve, framework.Success.String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -724,13 +724,13 @@ func (f *frameworkImpl) RunReservePluginsUnreserve(ctx context.Context, sub *app
 	// Execute the Unreserve operation of each reserve plugin in the
 	// *reverse* order in which the Reserve operation was executed.
 	for i := len(f.reservePlugins) - 1; i >= 0; i-- {
-		f.runReservePluginUnreserve(ctx, f.reservePlugins[i], sub, targetClusters)
+		f.runReservePluginUnreserve(ctx, f.reservePlugins[i], state, sub, targetClusters)
 	}
 }
 
-func (f *frameworkImpl) runReservePluginUnreserve(ctx context.Context, pl framework.ReservePlugin, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
+func (f *frameworkImpl) runReservePluginUnreserve(ctx context.Context, pl framework.ReservePlugin, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) {
 	startTime := time.Now()
-	pl.Unreserve(ctx, sub, targetClusters)
+	pl.Unreserve(ctx, state, sub, targetClusters)
 	f.metricsRecorder.observePluginDurationAsync(unreserve, pl.Name(), nil, metrics.SinceInSeconds(startTime))
 }
 
@@ -740,7 +740,7 @@ func (f *frameworkImpl) runReservePluginUnreserve(ctx context.Context, pl framew
 // plugins returns "Wait", then this function will create and add waiting subscription
 // to a map of currently waiting subs and return status with "Wait" code.
 // Subscription will remain waiting subscription for the minimum duration returned by the permit plugins.
-func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
+func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (status *framework.Status) {
 	startTime := time.Now()
 	defer func() {
 		metrics.FrameworkExtensionPointDuration.WithLabelValues(permit, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
@@ -748,7 +748,7 @@ func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, sub *appsapi.Subsc
 	pluginsWaitTime := make(map[string]time.Duration)
 	statusCode := framework.Success
 	for _, pl := range f.permitPlugins {
-		status, timeout := f.runPermitPlugin(ctx, pl, sub, targetClusters)
+		status, timeout := f.runPermitPlugin(ctx, pl, state, sub, targetClusters)
 		if !status.IsSuccess() {
 			if status.IsUnschedulable() {
 				msg := fmt.Sprintf("rejected subscription %q by permit plugin %q: %v", sub.Name, pl.Name(), status.Message())
@@ -780,9 +780,9 @@ func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, sub *appsapi.Subsc
 	return nil
 }
 
-func (f *frameworkImpl) runPermitPlugin(ctx context.Context, pl framework.PermitPlugin, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (*framework.Status, time.Duration) {
+func (f *frameworkImpl) runPermitPlugin(ctx context.Context, pl framework.PermitPlugin, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (*framework.Status, time.Duration) {
 	startTime := time.Now()
-	status, timeout := pl.Permit(ctx, sub, targetClusters)
+	status, timeout := pl.Permit(ctx, state, sub, targetClusters)
 	f.metricsRecorder.observePluginDurationAsync(permit, pl.Name(), status, metrics.SinceInSeconds(startTime))
 	return status, timeout
 }
diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go
index 7d09430..01b89a0 100644
--- a/pkg/scheduler/scheduler.go
+++ b/pkg/scheduler/scheduler.go
@@ -262,11 +262,12 @@ func (sched *Scheduler) scheduleOne(ctx context.Context) {
 
 	// Synchronously attempt to find a fit for the subscription.
 	start := time.Now()
+	state := framework.NewCycleState()
 
 	schedulingCycleCtx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
-	scheduleResult, err := sched.scheduleAlgorithm.Schedule(schedulingCycleCtx, sched.framework, sub, finv)
+	scheduleResult, err := sched.scheduleAlgorithm.Schedule(schedulingCycleCtx, sched.framework, state, sub, finv)
 	if err != nil {
 		sched.recordSchedulingFailure(sub, err, ReasonUnschedulable)
 		if !strings.Contains(err.Error(), "clusters are available") {
@@ -277,16 +278,16 @@ func (sched *Scheduler) scheduleOne(ctx context.Context) {
 
 	// Run the Reserve method of reserve plugins.
 	targetClusters := scheduleResult.SuggestedClusters
-	if sts := sched.framework.RunReservePluginsReserve(schedulingCycleCtx, sub, targetClusters); !sts.IsSuccess() {
+	if sts := sched.framework.RunReservePluginsReserve(schedulingCycleCtx, state, sub, targetClusters); !sts.IsSuccess() {
 		metrics.SubscriptionScheduleError(sched.framework.ProfileName(), metrics.SinceInSeconds(start))
 		// trigger un-reserve to clean up state associated with the reserved subscription
-		sched.framework.RunReservePluginsUnreserve(schedulingCycleCtx, sub, targetClusters)
+		sched.framework.RunReservePluginsUnreserve(schedulingCycleCtx, state, sub, targetClusters)
 		sched.recordSchedulingFailure(sub, sts.AsError(), SchedulerError)
 		return
 	}
 
 	// Run "permit" plugins.
-	runPermitStatus := sched.framework.RunPermitPlugins(schedulingCycleCtx, sub, targetClusters)
+	runPermitStatus := sched.framework.RunPermitPlugins(schedulingCycleCtx, state, sub, targetClusters)
 	if runPermitStatus.Code() != framework.Wait && !runPermitStatus.IsSuccess() {
 		var reason string
 		if runPermitStatus.IsUnschedulable() {
@@ -297,7 +298,7 @@ func (sched *Scheduler) scheduleOne(ctx context.Context) {
 			reason = SchedulerError
 		}
 		// One of the plugins returned status different from success or wait.
-		sched.framework.RunReservePluginsUnreserve(schedulingCycleCtx, sub, targetClusters)
+		sched.framework.RunReservePluginsUnreserve(schedulingCycleCtx, state, sub, targetClusters)
 		sched.recordSchedulingFailure(sub, runPermitStatus.AsError(), reason)
 		return
 	}
@@ -320,39 +321,39 @@ func (sched *Scheduler) scheduleOne(ctx context.Context) {
 				reason = SchedulerError
 			}
 			// trigger un-reserve plugins to clean up state associated with the reserved subscription
-			sched.framework.RunReservePluginsUnreserve(bindingCycleCtx, sub, targetClusters)
+			sched.framework.RunReservePluginsUnreserve(bindingCycleCtx, state, sub, targetClusters)
 			sched.recordSchedulingFailure(sub, waitOnPermitStatus.AsError(), reason)
 			return
 		}
 
 		// Run "prebind" plugins.
-		preBindStatus := sched.framework.RunPreBindPlugins(bindingCycleCtx, sub, targetClusters)
+		preBindStatus := sched.framework.RunPreBindPlugins(bindingCycleCtx, state, sub, targetClusters)
 		if !preBindStatus.IsSuccess() {
 			metrics.SubscriptionScheduleError(sched.framework.ProfileName(), metrics.SinceInSeconds(start))
 			// trigger un-reserve plugins to clean up state associated with the reserved subscription
-			sched.framework.RunReservePluginsUnreserve(bindingCycleCtx, sub, targetClusters)
+			sched.framework.RunReservePluginsUnreserve(bindingCycleCtx, state, sub, targetClusters)
 			sched.recordSchedulingFailure(sub, preBindStatus.AsError(), SchedulerError)
 			return
 		}
 
-		err := sched.bind(bindingCycleCtx, sub, targetClusters)
+		err := sched.bind(bindingCycleCtx, state, sub, targetClusters)
 		if err != nil {
 			metrics.SubscriptionScheduleError(sched.framework.ProfileName(), metrics.SinceInSeconds(start))
 			// trigger un-reserve plugins to clean up state associated with the reserved subscription
-			sched.framework.RunReservePluginsUnreserve(bindingCycleCtx, sub, targetClusters)
+			sched.framework.RunReservePluginsUnreserve(bindingCycleCtx, state, sub, targetClusters)
 			sched.recordSchedulingFailure(sub, fmt.Errorf("binding rejected: %w", err), SchedulerError)
 		} else {
 			metrics.SubscriptionScheduled(sched.framework.ProfileName(), metrics.SinceInSeconds(start))
 
 			// Run "postbind" plugins.
-			sched.framework.RunPostBindPlugins(bindingCycleCtx, sub, targetClusters)
+			sched.framework.RunPostBindPlugins(bindingCycleCtx, state, sub, targetClusters)
 		}
 	}()
 }
 
 // bind a subscription to given clusters.
 // We expect this to run asynchronously, so we handle binding metrics internally.
-func (sched *Scheduler) bind(ctx context.Context, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (err error) {
+func (sched *Scheduler) bind(ctx context.Context, state *framework.CycleState, sub *appsapi.Subscription, targetClusters framework.TargetClusters) (err error) {
 	defer func() {
 		// finish binding
 		if err != nil {
@@ -368,7 +369,7 @@ func (sched *Scheduler) bind(ctx context.Context, sub *appsapi.Subscription, tar
 		)
 	}()
 
-	bindStatus := sched.framework.RunBindPlugins(ctx, sub, targetClusters)
+	bindStatus := sched.framework.RunBindPlugins(ctx, state, sub, targetClusters)
 	if bindStatus.IsSuccess() {
 		return nil
 	}


Test patch:
diff --git a/pkg/scheduler/framework/interfaces/cycle_state_test.go b/pkg/scheduler/framework/interfaces/cycle_state_test.go
new file mode 100644
index 0000000..fdcbc71
--- /dev/null
+++ b/pkg/scheduler/framework/interfaces/cycle_state_test.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2022 The Clusternet Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// This file was copied from k8s.io/kubernetes/pkg/scheduler/framework/cycle_state_test.go
+
+package interfaces
+
+import (
+	"testing"
+)
+
+type fakeData struct {
+	data string
+}
+
+func (f *fakeData) Clone() StateData {
+	copy := &fakeData{
+		data: f.data,
+	}
+	return copy
+}
+
+func TestCycleStateClone(t *testing.T) {
+	var key StateKey = "key"
+	data1 := "value1"
+	data2 := "value2"
+
+	state := NewCycleState()
+	originalValue := &fakeData{
+		data: data1,
+	}
+	state.Write(key, originalValue)
+	stateCopy := state.Clone()
+
+	valueCopy, err := stateCopy.Read(key)
+	if err != nil {
+		t.Errorf("failed to read copied value: %v", err)
+	}
+	if v, ok := valueCopy.(*fakeData); ok && v.data != data1 {
+		t.Errorf("clone failed, got %q, expected %q", v.data, data1)
+	}
+
+	originalValue.data = data2
+	original, err := state.Read(key)
+	if err != nil {
+		t.Errorf("failed to read original value: %v", err)
+	}
+	if v, ok := original.(*fakeData); ok && v.data != data2 {
+		t.Errorf("original value should change, got %q, expected %q", v.data, data2)
+	}
+
+	if v, ok := valueCopy.(*fakeData); ok && v.data != data1 {
+		t.Errorf("cloned copy should not change, got %q, expected %q", v.data, data1)
+	}
+}
+
+func TestCycleStateCloneNil(t *testing.T) {
+	var state *CycleState
+	stateCopy := state.Clone()
+	if stateCopy != nil {
+		t.Errorf("clone expected to be nil")
+	}
+}
diff --git a/pkg/scheduler/framework/plugins/defaultassigner/algorithm_test.go b/pkg/scheduler/framework/plugins/defaultassigner/algorithm_test.go
index 5a668af..5153f89 100644
--- a/pkg/scheduler/framework/plugins/defaultassigner/algorithm_test.go
+++ b/pkg/scheduler/framework/plugins/defaultassigner/algorithm_test.go
@@ -398,7 +398,7 @@ func TestDivideReplicas(t *testing.T) {
 			clusterInformer := clusternetInformerFactory.Clusters().V1beta1().ManagedClusters()
 
 			fwk, _ := runtime.NewFramework(nil, nil, runtime.WithCache(schedulercache.New(clusterInformer.Lister())))
-			assigner, err := New(nil, fwk)
+			assigner, err := NewStaticAssigner(nil, fwk)
 			if err != nil {
 				t.Fatalf("Creating plugin error: %v", err)
 			}
@@ -408,7 +408,7 @@ func TestDivideReplicas(t *testing.T) {
 				t.Fatalf("Assign() error = %v, wantErr %v", fmt.Errorf("failed to wait for cache sync"), tt.wantErr)
 			}
 
-			result, status := assigner.(framework.AssignPlugin).Assign(ctx, tt.args.sub, tt.args.finv, *tt.args.selected)
+			result, status := assigner.(framework.AssignPlugin).Assign(ctx, nil, tt.args.sub, tt.args.finv, *tt.args.selected)
 			if (status != nil) != tt.wantErr {
 				t.Errorf("Assign() error = %v, wantErr %v", err, tt.wantErr)
 			}
diff --git a/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go b/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go
index eff3951..e17ace7 100644
--- a/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go
+++ b/pkg/scheduler/framework/plugins/defaultbinder/default_binder_test.go
@@ -82,7 +82,7 @@ func TestDefaultBinder(t *testing.T) {
 				t.Fatal(err)
 			}
 			binder := &DefaultBinder{handle: fh}
-			status := binder.Bind(context.Background(), testSubscription, testClusters)
+			status := binder.Bind(context.Background(), nil, testSubscription, testClusters)
 			if got := status.AsError(); (tt.injectErr != nil) != (got != nil) {
 				t.Errorf("got error %q, want %q", got, tt.injectErr)
 			}
diff --git a/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration_test.go b/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration_test.go
index badbe7f..abfd75d 100644
--- a/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration_test.go
+++ b/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration_test.go
@@ -256,14 +256,14 @@ func TestTaintTolerationScore(t *testing.T) {
 			p, _ := New(nil, fh)
 			var gotList framework.ClusterScoreList
 			for _, n := range test.clusters {
-				score, status := p.(framework.ScorePlugin).Score(context.Background(), test.subscription, klog.KObj(n).String())
+				score, status := p.(framework.ScorePlugin).Score(context.Background(), nil, test.subscription, klog.KObj(n).String())
 				if !status.IsSuccess() {
 					t.Errorf("unexpected error: %v", status)
 				}
 				gotList = append(gotList, framework.ClusterScore{NamespacedName: klog.KObj(n).String(), Score: score})
 			}
 
-			status := p.(framework.ScorePlugin).ScoreExtensions().NormalizeScore(context.Background(), gotList)
+			status := p.(framework.ScorePlugin).ScoreExtensions().NormalizeScore(context.Background(), nil, nil, gotList)
 			if !status.IsSuccess() {
 				t.Errorf("unexpected error: %v", status)
 			}
@@ -347,7 +347,7 @@ func TestTaintTolerationFilter(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			p, _ := New(nil, nil)
-			gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), test.subscription, test.cluster)
+			gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), nil, test.subscription, test.cluster)
 			if !reflect.DeepEqual(gotStatus, test.wantStatus) {
 				t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus)
 			}