Repository: koordinator-sh/koordinator

Issue:
koord-scheduler supports NUMA Topology Alignment policy
<!-- Please only use this template for submitting proposal -->

**What is your proposal**:

The Topology Manager is a kubelet component that aims to coordinate the set of components that are responsible for these optimizations. After Topology Manager was introduced the problem of launching pod in the cluster where worker nodes have different NUMA topology and different amount of resources in that topology became actual. The Pod could be scheduled in the node where the total amount of resources is enough, but resource distribution could not satisfy the appropriate Topology policy. 

When allocating CPUs for LSE/LSR Pods to implement static binding, NUMA Topology also needs to be considered.

More details please refer to #96 

TODO:
- [x] koordlet reports complete NUMA Node information
- [x] koordlet supports NUMA Share Pool
- [x] koord-scheduler supports NUMA Topology Alignment scheduling with CPU/Memory
- [x] koord-scheduler supports scoring
- [x] [koord-scheduler supports numa-node level scoring](https://github.com/koordinator-sh/koordinator/issues/1680)
- [x] [koord-scheduler supports NUMA Topology Alignment scheduling with Device](https://github.com/koordinator-sh/koordinator/pull/1774)
- [x] koord-scheduler supports Pod allocate from Reservation according NUMA Topology
- [x] koord-scheduler supports preemption according NUMA Topology

Discussion:
@FillZpp: /milestone v1.2@AdrianMachao: do you need help? i can do one task@songtao98:  - [ ] koord-scheduler supports Pod allocate from Reservation according NUMA Topology 1.6
- [ ]  koord-scheduler supports preemption according NUMA Topology someday@songtao98:  - [ ] koord-scheduler supports Pod allocate from Reservation according NUMA Topology 1.6
- [ ]  koord-scheduler supports preemption according NUMA Topology someday@ZiMengSheng: Already implemented but lack a concept doc!

Solution patch:
diff --git a/.github/workflows/e2e-k8s-1.22.yaml b/.github/workflows/e2e-k8s-1.22.yaml
index 69c27e76..65303332 100644
--- a/.github/workflows/e2e-k8s-1.22.yaml
+++ b/.github/workflows/e2e-k8s-1.22.yaml
@@ -30,7 +30,7 @@ jobs:
         with:
           go-version: ${{ env.GO_VERSION }}
       - name: Setup Kind Cluster
-        uses: helm/kind-action@v1.5.0
+        uses: helm/kind-action@v1.8.0
         with:
           node_image: ${{ env.KIND_IMAGE }}
           cluster_name: ${{ env.KIND_CLUSTER_NAME }}
diff --git a/.github/workflows/e2e-k8s-1.24.yaml b/.github/workflows/e2e-k8s-1.24.yaml
index 5619a431..87f54057 100644
--- a/.github/workflows/e2e-k8s-1.24.yaml
+++ b/.github/workflows/e2e-k8s-1.24.yaml
@@ -30,7 +30,7 @@ jobs:
         with:
           go-version: ${{ env.GO_VERSION }}
       - name: Setup Kind Cluster
-        uses: helm/kind-action@v1.5.0
+        uses: helm/kind-action@v1.8.0
         with:
           node_image: ${{ env.KIND_IMAGE }}
           cluster_name: ${{ env.KIND_CLUSTER_NAME }}
diff --git a/.github/workflows/e2e-k8s-latest.yaml b/.github/workflows/e2e-k8s-latest.yaml
index fafe2759..4d62c1fd 100644
--- a/.github/workflows/e2e-k8s-latest.yaml
+++ b/.github/workflows/e2e-k8s-latest.yaml
@@ -29,7 +29,7 @@ jobs:
         with:
           go-version: ${{ env.GO_VERSION }}
       - name: Setup Kind Cluster
-        uses: helm/kind-action@v1.5.0
+        uses: helm/kind-action@v1.8.0
         with:
           cluster_name: ${{ env.KIND_CLUSTER_NAME }}
           config: ./test/kind-conf.yaml
diff --git a/apis/extension/cpu_normalization.go b/apis/extension/cpu_normalization.go
new file mode 100644
index 00000000..a51bd138
--- /dev/null
+++ b/apis/extension/cpu_normalization.go
@@ -0,0 +1,117 @@
+/*
+Copyright 2022 The Koordinator 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.
+*/
+
+package extension
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+
+	corev1 "k8s.io/api/core/v1"
+)
+
+const (
+	// AnnotationCPUNormalizationRatio denotes the cpu-normalization ratio of the node.
+	AnnotationCPUNormalizationRatio = NodeDomainPrefix + "/cpu-normalization-ratio"
+
+	// AnnotationCPUBasicInfo denotes the basic CPU info of the node.
+	AnnotationCPUBasicInfo = NodeDomainPrefix + "/cpu-basic-info"
+)
+
+// GetCPUNormalizationRatio gets the cpu normalization ratio from the node.
+// It returns -1 without an error when the cpu normalization annotation is missing.
+func GetCPUNormalizationRatio(node *corev1.Node) (float64, error) {
+	if node.Annotations == nil {
+		return -1, nil
+	}
+	s, ok := node.Annotations[AnnotationCPUNormalizationRatio]
+	if !ok {
+		return -1, nil
+	}
+
+	v, err := strconv.ParseFloat(s, 64)
+	if err != nil {
+		return -1, fmt.Errorf("parse cpu normalization ratio failed, err: %w", err)
+	}
+	if v <= 0 {
+		return -1, fmt.Errorf("illegal cpu normalization ratio: %v", v)
+	}
+
+	return v, nil
+}
+
+// SetCPUNormalizationRatio sets the node annotation according to the cpu-normalization-ratio.
+// It returns true if the label value changes.
+// NOTE: The ratio will be converted to string with the precision 2. e.g. 3.1415926 -> 3.14.
+func SetCPUNormalizationRatio(node *corev1.Node, ratio float64) bool {
+	s := strconv.FormatFloat(ratio, 'f', 2, 64)
+	if node.Annotations == nil {
+		node.Annotations = map[string]string{}
+	}
+	if old := node.Annotations[AnnotationCPUNormalizationRatio]; old == s {
+		return false
+	}
+
+	node.Annotations[AnnotationCPUNormalizationRatio] = s
+	return true
+}
+
+// CPUBasicInfo describes the cpu basic features and status.
+type CPUBasicInfo struct {
+	CPUModel           string `json:"cpuModel,omitempty"`
+	HyperThreadEnabled bool   `json:"hyperThreadEnabled,omitempty"`
+	TurboEnabled       bool   `json:"turboEnabled,omitempty"`
+	CatL3CbmMask       string `json:"catL3CbmMask,omitempty"`
+	VendorID           string `json:"vendorID,omitempty"`
+}
+
+func (c *CPUBasicInfo) Key() string {
+	return fmt.Sprintf("%s_%v_%v", c.CPUModel, c.HyperThreadEnabled, c.TurboEnabled)
+}
+
+// GetCPUBasicInfo gets the cpu basic info from the node-level annotations.
+// It returns nil info without an error when the cpu basic info annotation is missing.
+func GetCPUBasicInfo(annotations map[string]string) (*CPUBasicInfo, error) {
+	if annotations == nil {
+		return nil, nil
+	}
+	s, ok := annotations[AnnotationCPUBasicInfo]
+	if !ok {
+		return nil, nil
+	}
+
+	var info CPUBasicInfo
+	err := json.Unmarshal([]byte(s), &info)
+	if err != nil {
+		return nil, fmt.Errorf("unmarshal cpu basic info failed, err: %w", err)
+	}
+	return &info, nil
+}
+
+// SetCPUBasicInfo sets the cpu basic info at the node-level annotations.
+// It returns true if the annotations changes.
+func SetCPUBasicInfo(annotations map[string]string, info *CPUBasicInfo) bool {
+	b, _ := json.Marshal(info)
+	s := string(b)
+
+	if old := annotations[AnnotationCPUBasicInfo]; s == old {
+		return false
+	}
+
+	annotations[AnnotationCPUBasicInfo] = s
+	return true
+}
diff --git a/apis/extension/elastic_quota.go b/apis/extension/elastic_quota.go
index d1fd2920..4835e017 100644
--- a/apis/extension/elastic_quota.go
+++ b/apis/extension/elastic_quota.go
@@ -42,7 +42,7 @@ const (
 
 func GetParentQuotaName(quota *v1alpha1.ElasticQuota) string {
 	parentName := quota.Labels[LabelQuotaParent]
-	if parentName == "" {
+	if parentName == "" && quota.Name != RootQuotaName {
 		return RootQuotaName //default return RootQuotaName
 	}
 	return parentName
diff --git a/docs/proposals/scheduling/20230811-node-resource-amplification.md b/docs/proposals/scheduling/20230811-node-resource-amplification.md
new file mode 100644
index 00000000..3ad7e1bf
--- /dev/null
+++ b/docs/proposals/scheduling/20230811-node-resource-amplification.md
@@ -0,0 +1,88 @@
+---
+title: Node Resource Amplification
+authors:
+  - "@zqzten"
+reviewers:
+  - "@saintube"
+  - "@hormes"
+creation-date: 2023-08-11
+last-updated: 2023-08-29
+---
+
+# Node Resource Amplification
+
+## Table of Contents
+
+<!-- TOC -->
+* [Node Resource Amplification](#node-resource-amplification)
+  * [Table of Contents](#table-of-contents)
+  * [Summary](#summary)
+  * [Motivation](#motivation)
+    * [Goals](#goals)
+    * [Non-Goals/Future Work](#non-goalsfuture-work)
+  * [Proposal](#proposal)
+    * [User Stories](#user-stories)
+    * [Workflow](#workflow)
+    * [API](#api)
+    * [Constraints](#constraints)
+    * [Risks and Mitigations](#risks-and-mitigations)
+      * [Amplification Ratio Change](#amplification-ratio-change)
+      * [Unexpected Resource Overcommitment](#unexpected-resource-overcommitment)
+<!-- TOC -->
+
+## Summary
+
+Introduce a mechanism to amplify the resources of Kubernetes Nodes **at API level**, which enables the node to meet higher pod CPU requests. This feature can then be utilized by other higher-level ones such as CPU normalization.
+
+## Motivation
+
+There are cases where users would like to amplify the resources of a node, for example:
+
+- The node is equipped with a new-gen CPU, which is way faster than those on other nodes. In this case, users would like to amplify the allocatable CPU of the node in order to make most of the fast CPU of the node.
+- The node's allocatable CPU is nearly full but the actual CPU usage remains at an extremely low level. In this case, users would like to amplify the allocatable CPU of the node in order to improve the CPU utilization of the node as well as to increase the amount of schedulable pods of the cluster.
+
+### Goals
+
+- Introduce a mechanism to amplify resources (capacity & allocatable) of Kubernetes Nodes at API level.
+
+### Non-Goals/Future Work
+
+- Arrange underlying resources on Node with those amplified.
+
+## Proposal
+
+### User Stories
+
+This has been covered in [motivation](#motivation).
+
+### Workflow
+
+On k8s side, user (end user or other system) annotates the Node to specify its resource amplification ratio.
+
+On koord-manager side, the Node mutating webhook intercepts the Node status updating request from kubelet and modifies its `capacity` and `allocatable` according to the amplification ratio. It also records their raw values in annotations of the Node.
+
+### API
+
+- Introduce a new Node annotation `node.koordinator.sh/resource-amplification-ratio` whose value is a JSON object string of type `map[corev1.ResourceName]float64`. The float value must > 1. This is used by user to specify the amplification ratio for each resource of the Node.
+- Introduce two new Node annotations `node.koordinator.sh/raw-capacity` and `node.koordinator.sh/raw-allocatable` whose value is a JSON object string of type `corev1.ResourceList`. These are used by koord-manager to record the Node's original resources (the `status.capacity` and `status.allocatable`), just informational for now.
+
+### Constraints
+
+To impose clear semantics, the Node resource amplification ratio must not be smaller than 1.
+
+### Risks and Mitigations
+
+#### Amplification Ratio Change
+
+If user disables the resource amplification or reduces its ratio for a Node, there's a chance that resource requests of Pods on the Node would exceed the Node's allocatable which causes kubelet to reject/evict Pods. This can lead to unexpected behavior.
+
+To mitigate this risk, a Node validating webhook can be introduced to validate the setting of resource amplification. It will check if the current resource requests of Pods on the Node would exceed the Node's allocatable after resource amplification setting changed, and only allow settings that would not lead to such a case.
+
+#### Unexpected Resource Overcommitment
+
+Since the resource amplification only takes effect at API level, unexpected resource overcommitment may happen if this feature is not used carefully, for example:
+
+- A Node with 4 allocatable physical CPUs and CPU amplification 2 would claim 8 allocatable CPUs, if there's a CPUSet Pod with CPU request 4 on it, it would occupy all the physical CPUs of the Node. However, the scheduler can still schedule Pods with total CPU request <= 4 to this Node, and thus leads to unexpected CPU starvation.
+- A Node with 4 allocatable physical CPUs and CPU amplification 2 would claim 8 allocatable CPUs, if there's two CPUShare Pods with the same CPU request and limit 4 on this Node, user would suppose that there would be no CPU competition (because their total limits equal to the Node's allocatable CPU), however, each Pod would be given a CFS quota to use all the 4 physical CPUs of the Node actually, so if both Pods use CPU more than 2, there would be a CPU competition.
+
+To mitigate this risk, users should do amplification very carefully, and it is recommended to do amplification only on Nodes that are regularly idle. Another way is to utilize end-to-end resource-specific features such as CPU normalization which is covered in another individual proposal.
diff --git a/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile.go b/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile.go
index 2db131aa..f076242c 100644
--- a/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile.go
+++ b/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile.go
@@ -165,7 +165,7 @@ func initCatGroupIfNotExist(group string) error {
 	return nil
 }
 
-func calculateMbaPercentForGroup(group string, mbaPercentConfig *int64, cpuBasicInfo koordletutil.CPUBasicInfo) string {
+func calculateMbaPercentForGroup(group string, mbaPercentConfig *int64, cpuBasicInfo extension.CPUBasicInfo) string {
 	if mbaPercentConfig == nil {
 		klog.Warningf("cat MBA will not change, since MBAPercent is nil for group %v, "+
 			"mbaPercentConfig %v", mbaPercentConfig, group)
@@ -282,7 +282,7 @@ func (r *resctrlReconcile) calculateAndApplyCatL3PolicyForGroup(group string, cb
 	return nil
 }
 
-func (r *resctrlReconcile) calculateAndApplyCatMbPolicyForGroup(group string, l3Num int, cpuBasicInfo koordletutil.CPUBasicInfo, resourceQoS *slov1alpha1.ResourceQOS) error {
+func (r *resctrlReconcile) calculateAndApplyCatMbPolicyForGroup(group string, l3Num int, cpuBasicInfo extension.CPUBasicInfo, resourceQoS *slov1alpha1.ResourceQOS) error {
 	if resourceQoS == nil || resourceQoS.ResctrlQOS == nil {
 		klog.Warningf("skipped, since resourceQoS or ResctrlQOS is nil for group %v, "+
 			"resourceQoS %v", resourceQoS, group)
diff --git a/pkg/koordlet/util/cpuinfo.go b/pkg/koordlet/util/cpuinfo.go
index fa953eed..3872fe32 100644
--- a/pkg/koordlet/util/cpuinfo.go
+++ b/pkg/koordlet/util/cpuinfo.go
@@ -29,18 +29,12 @@ import (
 
 	"k8s.io/klog/v2"
 
+	"github.com/koordinator-sh/koordinator/apis/extension"
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/util/system"
 )
 
 const cpuCmdTimeout = 5 * time.Second // maybe run slowly on some platforms
 
-// CPUBasicInfo describes the cpu basic features and status
-type CPUBasicInfo struct {
-	HyperThreadEnabled bool   `json:"hyperThreadEnabled,omitempty"`
-	CatL3CbmMask       string `json:"catL3CbmMask,omitempty"`
-	VendorID           string `json:"vendorID,omitempty"`
-}
-
 // ProcessorInfo describes the processor topology information of a single logic cpu, including the core, socket and numa
 // node it belongs to
 type ProcessorInfo struct {
@@ -72,7 +66,7 @@ type CPUTotalInfo struct {
 // LocalCPUInfo contains the cpu information collected from the node
 type LocalCPUInfo struct {
 	// BasicInfo describe the cpu features and their status
-	BasicInfo CPUBasicInfo `json:"basicInfo,omitempty"`
+	BasicInfo extension.CPUBasicInfo `json:"basicInfo,omitempty"`
 	// ProcessorInfos contains topology information of all available CPUs
 	ProcessorInfos []ProcessorInfo `json:"processorInfos,omitempty"`
 	// TotalInfo stores the numbers of cpu processors, cores, sockets and nodes
@@ -108,8 +102,8 @@ func getHyperThreadEnabled() (bool, error) {
 	return false, nil
 }
 
-func getCPUBasicInfo() (*CPUBasicInfo, error) {
-	cpuBasicInfo := &CPUBasicInfo{}
+func getCPUBasicInfo() (*extension.CPUBasicInfo, error) {
+	cpuBasicInfo := &extension.CPUBasicInfo{}
 	var err error
 	if cpuBasicInfo.HyperThreadEnabled, err = getHyperThreadEnabled(); err != nil {
 		klog.V(5).Infof("get hyperthreadEnabled info error: %v", err)
diff --git a/pkg/scheduler/plugins/deviceshare/plugin.go b/pkg/scheduler/plugins/deviceshare/plugin.go
index 54995de6..a9bde6bd 100644
--- a/pkg/scheduler/plugins/deviceshare/plugin.go
+++ b/pkg/scheduler/plugins/deviceshare/plugin.go
@@ -293,7 +293,7 @@ func (p *Plugin) Filter(ctx context.Context, cycleState *framework.CycleState, p
 
 	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(node.Name, false)
 	if nodeDeviceInfo == nil {
-		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrMissingDevice)
+		return nil
 	}
 
 	reservationRestoreState := getReservationRestoreState(cycleState)
@@ -343,7 +343,7 @@ func (p *Plugin) FilterReservation(ctx context.Context, cycleState *framework.Cy
 
 	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(nodeName, false)
 	if nodeDeviceInfo == nil {
-		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrMissingDevice)
+		return nil
 	}
 
 	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[nodeName])
@@ -372,7 +372,7 @@ func (p *Plugin) Reserve(ctx context.Context, cycleState *framework.CycleState,
 
 	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(nodeName, false)
 	if nodeDeviceInfo == nil {
-		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrMissingDevice)
+		return nil
 	}
 
 	reservationRestoreState := getReservationRestoreState(cycleState)
diff --git a/pkg/scheduler/plugins/elasticquota/core/group_quota_manager.go b/pkg/scheduler/plugins/elasticquota/core/group_quota_manager.go
index 62215d2c..faae497b 100644
--- a/pkg/scheduler/plugins/elasticquota/core/group_quota_manager.go
+++ b/pkg/scheduler/plugins/elasticquota/core/group_quota_manager.go
@@ -22,7 +22,9 @@ import (
 	"sync"
 
 	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/labels"
 	quotav1 "k8s.io/apiserver/pkg/quota/v1"
+	listerv1 "k8s.io/client-go/listers/core/v1"
 	"k8s.io/klog/v2"
 	"k8s.io/kubernetes/pkg/api/v1/resource"
 	"sigs.k8s.io/scheduler-plugins/pkg/apis/scheduling/v1alpha1"
@@ -50,9 +52,16 @@ type GroupQuotaManager struct {
 	// scaleMinQuotaManager is used when overRootResource
 	scaleMinQuotaManager *ScaleMinQuotaManager
 	once                 sync.Once
+
+	// nodeResourceMapLock  used to lock the nodeResourceMapLock.
+	nodeResourceMapLock sync.Mutex
+	// nodeResourceMap store the nodes belong to the manager.
+	nodeResourceMap map[string]struct{}
+	// nodeLister used to list nodes.
+	nodeLister listerv1.NodeLister
 }
 
-func NewGroupQuotaManager(systemGroupMax, defaultGroupMax v1.ResourceList) *GroupQuotaManager {
+func NewGroupQuotaManager(systemGroupMax, defaultGroupMax v1.ResourceList, nodeLister listerv1.NodeLister) *GroupQuotaManager {
 	quotaManager := &GroupQuotaManager{
 		totalResourceExceptSystemAndDefaultUsed: v1.ResourceList{},
 		totalResource:                           v1.ResourceList{},
@@ -61,11 +70,14 @@ func NewGroupQuotaManager(systemGroupMax, defaultGroupMax v1.ResourceList) *Grou
 		runtimeQuotaCalculatorMap:               make(map[string]*RuntimeQuotaCalculator),
 		quotaTopoNodeMap:                        make(map[string]*QuotaTopoNode),
 		scaleMinQuotaManager:                    NewScaleMinQuotaManager(),
+		nodeResourceMap:                         make(map[string]struct{}),
+		nodeLister:                              nodeLister,
 	}
 	quotaManager.quotaInfoMap[extension.SystemQuotaName] = NewQuotaInfo(false, true, extension.SystemQuotaName, extension.RootQuotaName)
 	quotaManager.quotaInfoMap[extension.SystemQuotaName].setMaxQuotaNoLock(systemGroupMax)
 	quotaManager.quotaInfoMap[extension.DefaultQuotaName] = NewQuotaInfo(false, true, extension.DefaultQuotaName, extension.RootQuotaName)
 	quotaManager.quotaInfoMap[extension.DefaultQuotaName].setMaxQuotaNoLock(defaultGroupMax)
+	quotaManager.quotaInfoMap[extension.RootQuotaName] = NewQuotaInfo(true, false, extension.RootQuotaName, "")
 	quotaManager.runtimeQuotaCalculatorMap[extension.RootQuotaName] = NewRuntimeQuotaCalculator(extension.RootQuotaName)
 	quotaManager.setScaleMinQuotaEnabled(true)
 	return quotaManager
@@ -137,7 +149,7 @@ func (gqm *GroupQuotaManager) recursiveUpdateGroupTreeWithDeltaRequest(deltaReq
 		curQuotaInfo := curToAllParInfos[i]
 		oldSubLimitReq := curQuotaInfo.getLimitRequestNoLock()
 		curQuotaInfo.addRequestNonNegativeNoLock(deltaReq)
-		if curQuotaInfo.Name == extension.SystemQuotaName || curQuotaInfo.Name == extension.DefaultQuotaName {
+		if curQuotaInfo.Name == extension.RootQuotaName {
 			return
 		}
 		newSubLimitReq := curQuotaInfo.getLimitRequestNoLock()
@@ -188,6 +200,10 @@ func (gqm *GroupQuotaManager) RefreshRuntimeNoLock(quotaName string) v1.Resource
 		return nil
 	}
 
+	if quotaName == extension.RootQuotaName {
+		return gqm.totalResourceExceptSystemAndDefaultUsed.DeepCopy()
+	}
+
 	if quotaName == extension.SystemQuotaName || quotaName == extension.DefaultQuotaName {
 		return quotaInfo.getMax()
 	}
@@ -199,6 +215,9 @@ func (gqm *GroupQuotaManager) RefreshRuntimeNoLock(quotaName string) v1.Resource
 	totalRes := gqm.totalResourceExceptSystemAndDefaultUsed.DeepCopy()
 	for i := len(curToAllParInfos) - 1; i >= 0; i-- {
 		quotaInfo = curToAllParInfos[i]
+		if quotaInfo.Name == extension.RootQuotaName {
+			continue
+		}
 		parRuntimeQuotaCalculator := gqm.getRuntimeQuotaCalculatorByNameNoLock(quotaInfo.ParentName)
 		if parRuntimeQuotaCalculator == nil {
 			klog.Errorf("treeWrapper not exist! parentQuotaName:%v", quotaInfo.ParentName)
@@ -254,7 +273,7 @@ func (gqm *GroupQuotaManager) getCurToAllParentGroupQuotaInfoNoLock(quotaName st
 
 	for true {
 		curToAllParInfos = append(curToAllParInfos, quotaInfo)
-		if quotaInfo.ParentName == extension.RootQuotaName {
+		if quotaInfo.Name == extension.RootQuotaName {
 			break
 		}
 
@@ -370,6 +389,7 @@ func (gqm *GroupQuotaManager) resetAllGroupQuotaNoLock() {
 	childRequestMap, childUsedMap := make(quotaResMapType), make(quotaResMapType)
 	for quotaName, topoNode := range gqm.quotaTopoNodeMap {
 		if quotaName == extension.RootQuotaName {
+			gqm.resetRootQuotaUsedAndRequest()
 			continue
 		}
 		topoNode.quotaInfo.lock.Lock()
@@ -710,3 +730,85 @@ func getPodName(oldPod, newPod *v1.Pod) string {
 	}
 	return ""
 }
+
+func (gqm *GroupQuotaManager) OnNodeAdd(node *v1.Node) {
+	gqm.nodeResourceMapLock.Lock()
+	defer gqm.nodeResourceMapLock.Unlock()
+
+	if _, ok := gqm.nodeResourceMap[node.Name]; ok {
+		return
+	}
+	gqm.nodeResourceMap[node.Name] = struct{}{}
+	gqm.UpdateClusterTotalResource(node.Status.Allocatable)
+	klog.V(5).Infof("OnNodeAddFunc success %v", node.Name)
+}
+
+func (gqm *GroupQuotaManager) OnNodeUpdate(oldNode, newNode *v1.Node) {
+	gqm.nodeResourceMapLock.Lock()
+	defer gqm.nodeResourceMapLock.Unlock()
+
+	if _, exist := gqm.nodeResourceMap[newNode.Name]; !exist {
+		gqm.nodeResourceMap[newNode.Name] = struct{}{}
+		gqm.UpdateClusterTotalResource(newNode.Status.Allocatable)
+		return
+	}
+
+	oldNodeAllocatable := oldNode.Status.Allocatable
+	newNodeAllocatable := newNode.Status.Allocatable
+	if quotav1.Equals(oldNodeAllocatable, newNodeAllocatable) {
+		return
+	}
+
+	deltaNodeAllocatable := quotav1.Subtract(newNodeAllocatable, oldNodeAllocatable)
+	gqm.UpdateClusterTotalResource(deltaNodeAllocatable)
+	klog.V(5).Infof("OnNodeUpdateFunc success:%v [%v]", newNode.Name, newNodeAllocatable)
+}
+
+func (gqm *GroupQuotaManager) OnNodeDelete(node *v1.Node) {
+	gqm.nodeResourceMapLock.Lock()
+	defer gqm.nodeResourceMapLock.Unlock()
+
+	if _, exist := gqm.nodeResourceMap[node.Name]; !exist {
+		return
+	}
+
+	delta := quotav1.Subtract(v1.ResourceList{}, node.Status.Allocatable)
+	gqm.UpdateClusterTotalResource(delta)
+	delete(gqm.nodeResourceMap, node.Name)
+	klog.V(5).Infof("OnNodeDeleteFunc success:%v [%v]", node.Name, delta)
+}
+
+func (gqm *GroupQuotaManager) ResyncNodes() {
+	nodes, err := gqm.nodeLister.List(labels.Everything())
+	if err != nil {
+		klog.Errorf("failed to list nodes for ResyncNodes")
+		return
+	}
+
+	for _, node := range nodes {
+		func() {
+			gqm.nodeResourceMapLock.Lock()
+			defer gqm.nodeResourceMapLock.Unlock()
+			if _, exist := gqm.nodeResourceMap[node.Name]; exist {
+				return
+			}
+
+			gqm.nodeResourceMap[node.Name] = struct{}{}
+			gqm.UpdateClusterTotalResource(node.Status.Allocatable)
+		}()
+	}
+
+	// TODO: remove none-exist node
+}
+
+func (gqm *GroupQuotaManager) resetRootQuotaUsedAndRequest() {
+	rootQuotaInfo := gqm.getQuotaInfoByNameNoLock(extension.RootQuotaName)
+	rootQuotaInfo.lock.Lock()
+	defer rootQuotaInfo.lock.Unlock()
+
+	systemQuotaInfo := gqm.getQuotaInfoByNameNoLock(extension.SystemQuotaName)
+	defaultQuotaInfo := gqm.getQuotaInfoByNameNoLock(extension.DefaultQuotaName)
+
+	rootQuotaInfo.CalculateInfo.Used = quotav1.Add(systemQuotaInfo.GetUsed(), defaultQuotaInfo.GetUsed())
+	rootQuotaInfo.CalculateInfo.Request = quotav1.Add(systemQuotaInfo.GetRequest(), defaultQuotaInfo.GetRequest())
+}
diff --git a/pkg/scheduler/plugins/elasticquota/node_handler.go b/pkg/scheduler/plugins/elasticquota/node_handler.go
index 1f808321..4d269bd3 100644
--- a/pkg/scheduler/plugins/elasticquota/node_handler.go
+++ b/pkg/scheduler/plugins/elasticquota/node_handler.go
@@ -18,7 +18,6 @@ package elasticquota
 
 import (
 	corev1 "k8s.io/api/core/v1"
-	quotav1 "k8s.io/apiserver/pkg/quota/v1"
 	"k8s.io/klog/v2"
 )
 
@@ -27,53 +26,28 @@ func (g *Plugin) OnNodeAdd(obj interface{}) {
 	if !ok {
 		return
 	}
-
 	if node.DeletionTimestamp != nil {
-		klog.Errorf("node is deleting:%v", node.Name)
+		klog.V(5).Infof("OnNodeAddFunc add:%v delete:%v", node.Name, node.DeletionTimestamp)
 		return
 	}
 
-	g.nodeResourceMapLock.Lock()
-	defer g.nodeResourceMapLock.Unlock()
-
-	if _, ok := g.nodeResourceMap[node.Name]; ok {
-		return
-	}
-	g.nodeResourceMap[node.Name] = struct{}{}
-	g.groupQuotaManager.UpdateClusterTotalResource(node.Status.Allocatable)
-	klog.V(5).Infof("OnNodeAddFunc success %v", node.Name)
+	g.groupQuotaManager.OnNodeAdd(node)
 }
 
 func (g *Plugin) OnNodeUpdate(oldObj, newObj interface{}) {
 	newNode := newObj.(*corev1.Node)
 	oldNode := oldObj.(*corev1.Node)
 
-	g.nodeResourceMapLock.Lock()
-	defer g.nodeResourceMapLock.Unlock()
-
-	if _, exist := g.nodeResourceMap[newNode.Name]; !exist {
-		return
-	}
-
 	if newNode.ResourceVersion == oldNode.ResourceVersion {
 		klog.Warningf("update node warning, update version for the same, nodeName:%v", newNode.Name)
 		return
 	}
-
 	if newNode.DeletionTimestamp != nil {
 		klog.V(5).Infof("OnNodeUpdateFunc update:%v delete:%v", newNode.Name, newNode.DeletionTimestamp)
 		return
 	}
 
-	oldNodeAllocatable := oldNode.Status.Allocatable
-	newNodeAllocatable := newNode.Status.Allocatable
-	if quotav1.Equals(oldNodeAllocatable, newNodeAllocatable) {
-		return
-	}
-
-	deltaNodeAllocatable := quotav1.Subtract(newNodeAllocatable, oldNodeAllocatable)
-	g.groupQuotaManager.UpdateClusterTotalResource(deltaNodeAllocatable)
-	klog.V(5).Infof("OnNodeUpdateFunc success:%v [%v]", newNode.Name, newNodeAllocatable)
+	g.groupQuotaManager.OnNodeUpdate(oldNode, newNode)
 }
 
 func (g *Plugin) OnNodeDelete(obj interface{}) {
@@ -83,15 +57,9 @@ func (g *Plugin) OnNodeDelete(obj interface{}) {
 		return
 	}
 
-	g.nodeResourceMapLock.Lock()
-	defer g.nodeResourceMapLock.Unlock()
-
-	if _, exist := g.nodeResourceMap[node.Name]; !exist {
-		return
-	}
+	g.groupQuotaManager.OnNodeDelete(node)
+}
 
-	delta := quotav1.Subtract(corev1.ResourceList{}, node.Status.Allocatable)
-	g.groupQuotaManager.UpdateClusterTotalResource(delta)
-	delete(g.nodeResourceMap, node.Name)
-	klog.V(5).Infof("OnNodeDeleteFunc success:%v [%v]", node.Name, delta)
+func (g *Plugin) ResyncNodes() {
+	g.groupQuotaManager.ResyncNodes()
 }
diff --git a/pkg/scheduler/plugins/elasticquota/plugin.go b/pkg/scheduler/plugins/elasticquota/plugin.go
index 560322c9..f0af1397 100644
--- a/pkg/scheduler/plugins/elasticquota/plugin.go
+++ b/pkg/scheduler/plugins/elasticquota/plugin.go
@@ -19,7 +19,6 @@ package elasticquota
 import (
 	"context"
 	"fmt"
-	"sync"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
@@ -68,17 +67,14 @@ func (p *PostFilterState) Clone() framework.StateData {
 }
 
 type Plugin struct {
-	handle      framework.Handle
-	client      versioned.Interface
-	pluginArgs  *config.ElasticQuotaArgs
-	quotaLister v1alpha1.ElasticQuotaLister
-	podLister   v1.PodLister
-	pdbLister   policylisters.PodDisruptionBudgetLister
-	nodeLister  v1.NodeLister
-	// only used in OnNodeAdd,in case Recover and normal Watch double call OnNodeAdd
-	nodeResourceMapLock sync.Mutex
-	nodeResourceMap     map[string]struct{}
-	groupQuotaManager   *core.GroupQuotaManager
+	handle            framework.Handle
+	client            versioned.Interface
+	pluginArgs        *config.ElasticQuotaArgs
+	quotaLister       v1alpha1.ElasticQuotaLister
+	podLister         v1.PodLister
+	pdbLister         policylisters.PodDisruptionBudgetLister
+	nodeLister        v1.NodeLister
+	groupQuotaManager *core.GroupQuotaManager
 }
 
 var (
@@ -108,19 +104,19 @@ func New(args runtime.Object, handle framework.Handle) (framework.Plugin, error)
 	elasticQuotaInformer := scheSharedInformerFactory.Scheduling().V1alpha1().ElasticQuotas()
 
 	elasticQuota := &Plugin{
-		handle:            handle,
-		client:            client,
-		pluginArgs:        pluginArgs,
-		podLister:         handle.SharedInformerFactory().Core().V1().Pods().Lister(),
-		quotaLister:       elasticQuotaInformer.Lister(),
-		pdbLister:         getPDBLister(handle),
-		nodeLister:        handle.SharedInformerFactory().Core().V1().Nodes().Lister(),
-		groupQuotaManager: core.NewGroupQuotaManager(pluginArgs.SystemQuotaGroupMax, pluginArgs.DefaultQuotaGroupMax),
-		nodeResourceMap:   make(map[string]struct{}),
+		handle:      handle,
+		client:      client,
+		pluginArgs:  pluginArgs,
+		podLister:   handle.SharedInformerFactory().Core().V1().Pods().Lister(),
+		quotaLister: elasticQuotaInformer.Lister(),
+		pdbLister:   getPDBLister(handle),
+		nodeLister:  handle.SharedInformerFactory().Core().V1().Nodes().Lister(),
 	}
+	elasticQuota.groupQuotaManager = core.NewGroupQuotaManager(pluginArgs.SystemQuotaGroupMax, pluginArgs.DefaultQuotaGroupMax, elasticQuota.nodeLister)
 
 	ctx := context.TODO()
 
+	elasticQuota.createRootQuotaIfNotPresent()
 	elasticQuota.createSystemQuotaIfNotPresent()
 	elasticQuota.createDefaultQuotaIfNotPresent()
 	frameworkexthelper.ForceSyncFromInformer(ctx.Done(), scheSharedInformerFactory, elasticQuotaInformer.Informer(), cache.ResourceEventHandlerFuncs{
diff --git a/pkg/scheduler/plugins/elasticquota/plugin_helper.go b/pkg/scheduler/plugins/elasticquota/plugin_helper.go
index 7b1413fc..64702bd6 100644
--- a/pkg/scheduler/plugins/elasticquota/plugin_helper.go
+++ b/pkg/scheduler/plugins/elasticquota/plugin_helper.go
@@ -144,6 +144,33 @@ func (g *Plugin) createSystemQuotaIfNotPresent() {
 	klog.Infof("create SystemQuota successfully")
 }
 
+// createRootQuotaIfNotPresent create RootQuotaGroup's CRD
+func (g *Plugin) createRootQuotaIfNotPresent() {
+	eq, _ := g.quotaLister.ElasticQuotas(g.pluginArgs.QuotaGroupNamespace).Get(extension.RootQuotaName)
+	if eq != nil {
+		klog.Infof("RootQuota already exists, skip create it.")
+		return
+	}
+	rootElasticQuota := &schedulerv1alpha1.ElasticQuota{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:        extension.RootQuotaName,
+			Namespace:   g.pluginArgs.QuotaGroupNamespace,
+			Labels:      make(map[string]string),
+			Annotations: make(map[string]string),
+		},
+	}
+	rootElasticQuota.Labels[extension.LabelQuotaIsParent] = "true"
+	rootElasticQuota.Labels[extension.LabelAllowLentResource] = "false"
+	rootElasticQuota.Labels[extension.LabelQuotaParent] = ""
+	_, err := g.client.SchedulingV1alpha1().ElasticQuotas(g.pluginArgs.QuotaGroupNamespace).
+		Create(context.TODO(), rootElasticQuota, metav1.CreateOptions{})
+	if err != nil {
+		klog.Errorf("create root group fail, err:%v", err.Error())
+		return
+	}
+	klog.Infof("create RootQuota successfully")
+}
+
 func (g *Plugin) snapshotPostFilterState(quotaInfo *core.QuotaInfo, state *framework.CycleState) *PostFilterState {
 	postFilterState := &PostFilterState{
 		quotaInfo: quotaInfo,
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator.go b/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator.go
index cc16bfc1..ba0aa843 100644
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator.go
+++ b/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator.go
@@ -37,16 +37,51 @@ func takePreferredCPUs(
 	cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy,
 	numaAllocatedStrategy schedulingconfig.NUMAAllocateStrategy,
 ) (cpuset.CPUSet, error) {
+	result := cpuset.CPUSet{}
 	preferredCPUs = availableCPUs.Intersection(preferredCPUs)
-	size := preferredCPUs.Size()
-	if size == 0 {
-		return cpuset.CPUSet{}, nil
-	}
-	if numCPUsNeeded > size {
-		numCPUsNeeded = size
-	}
-
-	return takeCPUs(topology, maxRefCount, preferredCPUs, allocatedCPUs, numCPUsNeeded, cpuBindPolicy, cpuExclusivePolicy, numaAllocatedStrategy)
+	if !preferredCPUs.IsEmpty() {
+		needed := numCPUsNeeded
+		if needed > preferredCPUs.Size() {
+			needed = preferredCPUs.Size()
+		}
+		var err error
+		result, err = takeCPUs(
+			topology,
+			maxRefCount,
+			preferredCPUs,
+			allocatedCPUs,
+			needed,
+			cpuBindPolicy,
+			cpuExclusivePolicy,
+			numaAllocatedStrategy)
+		if err != nil {
+			return result, err
+		}
+		numCPUsNeeded -= result.Size()
+		availableCPUs = availableCPUs.Difference(preferredCPUs)
+	}
+
+	if numCPUsNeeded > 0 {
+		cpus, err := takeCPUs(
+			topology,
+			maxRefCount,
+			availableCPUs,
+			allocatedCPUs,
+			numCPUsNeeded,
+			cpuBindPolicy,
+			cpuExclusivePolicy,
+			numaAllocatedStrategy)
+		if err != nil {
+			return cpuset.CPUSet{}, err
+		}
+		if result.IsEmpty() {
+			result = cpus
+		} else {
+			result = result.Union(cpus)
+		}
+	}
+
+	return result, nil
 }
 
 func takeCPUs(
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_allocation.go b/pkg/scheduler/plugins/nodenumaresource/cpu_allocation.go
deleted file mode 100644
index 1eaafb0b..00000000
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_allocation.go
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
-Copyright 2022 The Koordinator 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.
-*/
-
-package nodenumaresource
-
-import (
-	"sync"
-
-	"k8s.io/apimachinery/pkg/types"
-
-	schedulingconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
-	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
-)
-
-type cpuAllocation struct {
-	lock          sync.Mutex
-	nodeName      string
-	allocatedPods map[types.UID]cpuset.CPUSet
-	allocatedCPUs CPUDetails
-}
-
-func newCPUAllocation(nodeName string) *cpuAllocation {
-	return &cpuAllocation{
-		nodeName:      nodeName,
-		allocatedPods: map[types.UID]cpuset.CPUSet{},
-		allocatedCPUs: NewCPUDetails(),
-	}
-}
-
-func (n *cpuAllocation) updateAllocatedCPUSet(cpuTopology *CPUTopology, podUID types.UID, cpuset cpuset.CPUSet, cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy) {
-	n.releaseCPUs(podUID)
-	n.addCPUs(cpuTopology, podUID, cpuset, cpuExclusivePolicy)
-}
-
-func (n *cpuAllocation) getCPUs(podUID types.UID) (cpuset.CPUSet, bool) {
-	cpuset, ok := n.allocatedPods[podUID]
-	return cpuset, ok
-}
-
-func (n *cpuAllocation) addCPUs(cpuTopology *CPUTopology, podUID types.UID, cpuset cpuset.CPUSet, exclusivePolicy schedulingconfig.CPUExclusivePolicy) {
-	if _, ok := n.allocatedPods[podUID]; ok {
-		return
-	}
-	n.allocatedPods[podUID] = cpuset
-
-	for _, cpuID := range cpuset.ToSliceNoSort() {
-		cpuInfo, ok := n.allocatedCPUs[cpuID]
-		if !ok {
-			cpuInfo = cpuTopology.CPUDetails[cpuID]
-		}
-		cpuInfo.ExclusivePolicy = exclusivePolicy
-		cpuInfo.RefCount++
-		n.allocatedCPUs[cpuID] = cpuInfo
-	}
-}
-
-func (n *cpuAllocation) releaseCPUs(podUID types.UID) {
-	cpuset, ok := n.allocatedPods[podUID]
-	if !ok {
-		return
-	}
-	delete(n.allocatedPods, podUID)
-
-	for _, cpuID := range cpuset.ToSliceNoSort() {
-		cpuInfo, ok := n.allocatedCPUs[cpuID]
-		if !ok {
-			continue
-		}
-		cpuInfo.RefCount--
-		if cpuInfo.RefCount == 0 {
-			delete(n.allocatedCPUs, cpuID)
-		} else {
-			n.allocatedCPUs[cpuID] = cpuInfo
-		}
-	}
-}
-
-func (n *cpuAllocation) getAvailableCPUs(cpuTopology *CPUTopology, maxRefCount int, reservedCPUs, preferredCPUs cpuset.CPUSet) (availableCPUs cpuset.CPUSet, allocateInfo CPUDetails) {
-	allocateInfo = n.allocatedCPUs.Clone()
-	if !preferredCPUs.IsEmpty() {
-		for _, cpuID := range preferredCPUs.ToSliceNoSort() {
-			cpuInfo, ok := allocateInfo[cpuID]
-			if ok {
-				cpuInfo.RefCount--
-				if cpuInfo.RefCount == 0 {
-					delete(allocateInfo, cpuID)
-				} else {
-					allocateInfo[cpuID] = cpuInfo
-				}
-			}
-		}
-	}
-	allocated := allocateInfo.CPUs().Filter(func(cpuID int) bool {
-		return allocateInfo[cpuID].RefCount >= maxRefCount
-	})
-	availableCPUs = cpuTopology.CPUDetails.CPUs().Difference(allocated).Difference(reservedCPUs)
-	return
-}
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_manager.go b/pkg/scheduler/plugins/nodenumaresource/cpu_manager.go
deleted file mode 100644
index 95f81f38..00000000
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_manager.go
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
-Copyright 2022 The Koordinator 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.
-*/
-
-package nodenumaresource
-
-import (
-	"errors"
-	"fmt"
-	"math"
-	"sync"
-
-	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/tools/cache"
-	"k8s.io/kubernetes/pkg/scheduler/framework"
-
-	"github.com/koordinator-sh/koordinator/apis/extension"
-	schedulingconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
-	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
-)
-
-type CPUManager interface {
-	Allocate(
-		node *corev1.Node,
-		numCPUsNeeded int,
-		cpuBindPolicy schedulingconfig.CPUBindPolicy,
-		cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy,
-		preferredCPUs cpuset.CPUSet,
-	) (cpuset.CPUSet, error)
-
-	UpdateAllocatedCPUSet(nodeName string, podUID types.UID, cpuset cpuset.CPUSet, cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy)
-
-	GetAllocatedCPUSet(nodeName string, podUID types.UID) (cpuset.CPUSet, bool)
-
-	Free(nodeName string, podUID types.UID)
-
-	Score(
-		node *corev1.Node,
-		numCPUsNeeded int,
-		cpuBindPolicy schedulingconfig.CPUBindPolicy,
-		cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy,
-		preferredCPUs cpuset.CPUSet,
-	) int64
-
-	GetAvailableCPUs(nodeName string) (availableCPUs cpuset.CPUSet, allocated CPUDetails, err error)
-}
-
-type cpuManagerImpl struct {
-	numaAllocateStrategy schedulingconfig.NUMAAllocateStrategy
-	topologyManager      CPUTopologyManager
-	lock                 sync.Mutex
-	allocationStates     map[string]*cpuAllocation
-}
-
-func NewCPUManager(
-	handle framework.Handle,
-	defaultNUMAAllocateStrategy schedulingconfig.NUMAAllocateStrategy,
-	topologyManager CPUTopologyManager,
-) CPUManager {
-	manager := &cpuManagerImpl{
-		numaAllocateStrategy: defaultNUMAAllocateStrategy,
-		topologyManager:      topologyManager,
-		allocationStates:     map[string]*cpuAllocation{},
-	}
-	handle.SharedInformerFactory().Core().V1().Nodes().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{DeleteFunc: manager.onNodeDelete})
-	return manager
-}
-
-func (c *cpuManagerImpl) onNodeDelete(obj interface{}) {
-	var node *corev1.Node
-	switch t := obj.(type) {
-	case *corev1.Node:
-		node = t
-	case cache.DeletedFinalStateUnknown:
-		var ok bool
-		node, ok = t.Obj.(*corev1.Node)
-		if !ok {
-			return
-		}
-	default:
-		break
-	}
-
-	if node == nil {
-		return
-	}
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	delete(c.allocationStates, node.Name)
-}
-
-func (c *cpuManagerImpl) getOrCreateAllocation(nodeName string) *cpuAllocation {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	v := c.allocationStates[nodeName]
-	if v == nil {
-		v = newCPUAllocation(nodeName)
-		c.allocationStates[nodeName] = v
-	}
-	return v
-}
-
-func (c *cpuManagerImpl) Allocate(
-	node *corev1.Node,
-	numCPUsNeeded int,
-	cpuBindPolicy schedulingconfig.CPUBindPolicy,
-	cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy,
-	preferredCPUs cpuset.CPUSet,
-) (cpuset.CPUSet, error) {
-	result := cpuset.CPUSet{}
-	// The Pod requires the CPU to be allocated according to CPUBindPolicy,
-	// but the current node does not have a NodeResourceTopology or a valid CPUTopology,
-	// so this error should be exposed to the user
-	cpuTopologyOptions := c.topologyManager.GetCPUTopologyOptions(node.Name)
-	if cpuTopologyOptions.CPUTopology == nil {
-		return result, errors.New(ErrNotFoundCPUTopology)
-	}
-	if !cpuTopologyOptions.CPUTopology.IsValid() {
-		return result, errors.New(ErrInvalidCPUTopology)
-	}
-
-	reservedCPUs := cpuTopologyOptions.ReservedCPUs
-
-	allocation := c.getOrCreateAllocation(node.Name)
-	allocation.lock.Lock()
-	defer allocation.lock.Unlock()
-
-	availableCPUs, allocated := allocation.getAvailableCPUs(cpuTopologyOptions.CPUTopology, cpuTopologyOptions.MaxRefCount, reservedCPUs, preferredCPUs)
-	numaAllocateStrategy := c.getNUMAAllocateStrategy(node)
-	if !preferredCPUs.IsEmpty() {
-		var err error
-		result, err = takePreferredCPUs(
-			cpuTopologyOptions.CPUTopology,
-			cpuTopologyOptions.MaxRefCount,
-			availableCPUs,
-			preferredCPUs,
-			allocated,
-			numCPUsNeeded,
-			cpuBindPolicy,
-			cpuExclusivePolicy,
-			numaAllocateStrategy)
-		if err != nil {
-			return result, err
-		}
-		numCPUsNeeded -= result.Size()
-		availableCPUs = availableCPUs.Difference(preferredCPUs)
-	}
-	if numCPUsNeeded > 0 {
-		cpus, err := takeCPUs(
-			cpuTopologyOptions.CPUTopology,
-			cpuTopologyOptions.MaxRefCount,
-			availableCPUs,
-			allocated,
-			numCPUsNeeded,
-			cpuBindPolicy,
-			cpuExclusivePolicy,
-			numaAllocateStrategy,
-		)
-		if err != nil {
-			return cpuset.CPUSet{}, err
-		}
-		if result.IsEmpty() {
-			result = cpus
-		} else {
-			result = result.Union(cpus)
-		}
-	}
-	return result, nil
-}
-
-func (c *cpuManagerImpl) UpdateAllocatedCPUSet(nodeName string, podUID types.UID, cpuset cpuset.CPUSet, cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy) {
-	cpuTopologyOptions := c.topologyManager.GetCPUTopologyOptions(nodeName)
-	if cpuTopologyOptions.CPUTopology == nil || !cpuTopologyOptions.CPUTopology.IsValid() {
-		return
-	}
-
-	allocation := c.getOrCreateAllocation(nodeName)
-	allocation.lock.Lock()
-	defer allocation.lock.Unlock()
-
-	allocation.updateAllocatedCPUSet(cpuTopologyOptions.CPUTopology, podUID, cpuset, cpuExclusivePolicy)
-}
-
-func (c *cpuManagerImpl) GetAllocatedCPUSet(nodeName string, podUID types.UID) (cpuset.CPUSet, bool) {
-	allocation := c.getOrCreateAllocation(nodeName)
-	allocation.lock.Lock()
-	defer allocation.lock.Unlock()
-
-	return allocation.getCPUs(podUID)
-}
-
-func (c *cpuManagerImpl) Free(nodeName string, podUID types.UID) {
-	allocation := c.getOrCreateAllocation(nodeName)
-	allocation.lock.Lock()
-	defer allocation.lock.Unlock()
-	allocation.releaseCPUs(podUID)
-}
-
-func (c *cpuManagerImpl) Score(node *corev1.Node, numCPUsNeeded int, cpuBindPolicy schedulingconfig.CPUBindPolicy, cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy, preferredCPUs cpuset.CPUSet) int64 {
-	cpuTopologyOptions := c.topologyManager.GetCPUTopologyOptions(node.Name)
-	if cpuTopologyOptions.CPUTopology == nil || !cpuTopologyOptions.CPUTopology.IsValid() {
-		return 0
-	}
-
-	numaAllocateStrategy := c.getNUMAAllocateStrategy(node)
-	reservedCPUs := cpuTopologyOptions.ReservedCPUs
-
-	allocation := c.getOrCreateAllocation(node.Name)
-	allocation.lock.Lock()
-	defer allocation.lock.Unlock()
-
-	// TODO(joseph): should support score with preferredCPUs.
-
-	cpuTopology := cpuTopologyOptions.CPUTopology
-	availableCPUs, allocated := allocation.getAvailableCPUs(cpuTopology, cpuTopologyOptions.MaxRefCount, reservedCPUs, preferredCPUs)
-	acc := newCPUAccumulator(
-		cpuTopology,
-		cpuTopologyOptions.MaxRefCount,
-		availableCPUs,
-		allocated,
-		numCPUsNeeded,
-		cpuExclusivePolicy,
-		numaAllocateStrategy,
-	)
-
-	var freeCPUs [][]int
-	if cpuBindPolicy == schedulingconfig.CPUBindPolicyFullPCPUs {
-		if numCPUsNeeded <= cpuTopology.CPUsPerNode() {
-			freeCPUs = acc.freeCoresInNode(true, true)
-		} else if numCPUsNeeded <= cpuTopology.CPUsPerSocket() {
-			freeCPUs = acc.freeCoresInSocket(true)
-		}
-	} else {
-		if numCPUsNeeded <= cpuTopology.CPUsPerNode() {
-			freeCPUs = acc.freeCPUsInNode(true)
-		} else if numCPUsNeeded <= cpuTopology.CPUsPerSocket() {
-			freeCPUs = acc.freeCPUsInSocket(true)
-		}
-	}
-
-	scoreFn := mostRequestedScore
-	if numaAllocateStrategy == schedulingconfig.NUMALeastAllocated {
-		scoreFn = leastRequestedScore
-	}
-
-	var maxScore int64
-	for _, cpus := range freeCPUs {
-		if len(cpus) < numCPUsNeeded {
-			continue
-		}
-
-		numaScore := scoreFn(int64(numCPUsNeeded), int64(len(cpus)))
-		if numaScore > maxScore {
-			maxScore = numaScore
-		}
-	}
-
-	// If the requested CPUs can be aligned according to NUMA Socket, it should be scored,
-	// but in order to avoid the situation where the number of CPUs in the NUMA Socket of
-	// some special models in the cluster is equal to the number of CPUs in the NUMA Node
-	// of other models, it is necessary to reduce the weight of the score of such machines.
-	if numCPUsNeeded > cpuTopology.CPUsPerNode() && numCPUsNeeded <= cpuTopology.CPUsPerSocket() {
-		maxScore = int64(math.Ceil(math.Log(float64(maxScore)) * socketScoreWeight))
-	}
-
-	return maxScore
-}
-
-// The used capacity is calculated on a scale of 0-MaxNodeScore (MaxNodeScore is
-// constant with value set to 100).
-// 0 being the lowest priority and 100 being the highest.
-// The more resources are used the higher the score is. This function
-// is almost a reversed version of leastRequestedScore.
-func mostRequestedScore(requested, capacity int64) int64 {
-	if capacity == 0 {
-		return 0
-	}
-	if requested > capacity {
-		return 0
-	}
-
-	return (requested * framework.MaxNodeScore) / capacity
-}
-
-// The unused capacity is calculated on a scale of 0-MaxNodeScore
-// 0 being the lowest priority and `MaxNodeScore` being the highest.
-// The more unused resources the higher the score is.
-func leastRequestedScore(requested, capacity int64) int64 {
-	if capacity == 0 {
-		return 0
-	}
-	if requested > capacity {
-		return 0
-	}
-
-	return ((capacity - requested) * framework.MaxNodeScore) / capacity
-}
-
-func (c *cpuManagerImpl) getNUMAAllocateStrategy(node *corev1.Node) schedulingconfig.NUMAAllocateStrategy {
-	numaAllocateStrategy := c.numaAllocateStrategy
-	if val := schedulingconfig.NUMAAllocateStrategy(node.Labels[extension.LabelNodeNUMAAllocateStrategy]); val != "" {
-		numaAllocateStrategy = val
-	}
-	return numaAllocateStrategy
-}
-
-func (c *cpuManagerImpl) GetAvailableCPUs(nodeName string) (availableCPUs cpuset.CPUSet, allocated CPUDetails, err error) {
-	cpuTopologyOptions := c.topologyManager.GetCPUTopologyOptions(nodeName)
-	if cpuTopologyOptions.CPUTopology == nil {
-		return cpuset.NewCPUSet(), nil, errors.New(ErrNotFoundCPUTopology)
-	}
-	if !cpuTopologyOptions.CPUTopology.IsValid() {
-		return cpuset.NewCPUSet(), nil, fmt.Errorf("cpuTopology is invalid")
-	}
-
-	allocation := c.getOrCreateAllocation(nodeName)
-	allocation.lock.Lock()
-	defer allocation.lock.Unlock()
-	emptyCPUs := cpuset.NewCPUSet()
-	availableCPUs, allocated = allocation.getAvailableCPUs(cpuTopologyOptions.CPUTopology, cpuTopologyOptions.MaxRefCount, cpuTopologyOptions.ReservedCPUs, emptyCPUs)
-	return availableCPUs, allocated, nil
-}
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_topology_manager.go b/pkg/scheduler/plugins/nodenumaresource/cpu_topology_manager.go
deleted file mode 100644
index 65c8e977..00000000
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_topology_manager.go
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-Copyright 2022 The Koordinator 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.
-*/
-
-package nodenumaresource
-
-import (
-	"sync"
-
-	"github.com/koordinator-sh/koordinator/apis/extension"
-	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
-)
-
-// CPUTopologyManager manages the CPU Topology and CPU assignments options.
-type CPUTopologyManager interface {
-	GetCPUTopologyOptions(nodeName string) CPUTopologyOptions
-	UpdateCPUTopologyOptions(nodeName string, updateFn func(options *CPUTopologyOptions))
-	Delete(nodeName string)
-}
-
-type CPUTopologyOptions struct {
-	CPUTopology  *CPUTopology                       `json:"cpuTopology,omitempty"`
-	ReservedCPUs cpuset.CPUSet                      `json:"reservedCPUs,omitempty"`
-	MaxRefCount  int                                `json:"maxRefCount,omitempty"`
-	Policy       *extension.KubeletCPUManagerPolicy `json:"policy,omitempty"`
-}
-
-type cpuTopologyManager struct {
-	lock            sync.Mutex
-	topologyOptions map[string]CPUTopologyOptions
-}
-
-func NewCPUTopologyManager() CPUTopologyManager {
-	manager := &cpuTopologyManager{
-		topologyOptions: map[string]CPUTopologyOptions{},
-	}
-	return manager
-}
-
-func (m *cpuTopologyManager) GetCPUTopologyOptions(nodeName string) CPUTopologyOptions {
-	m.lock.Lock()
-	defer m.lock.Unlock()
-	return m.topologyOptions[nodeName]
-}
-
-func (m *cpuTopologyManager) UpdateCPUTopologyOptions(nodeName string, updateFn func(options *CPUTopologyOptions)) {
-	m.lock.Lock()
-	defer m.lock.Unlock()
-	options := m.topologyOptions[nodeName]
-	updateFn(&options)
-	if options.MaxRefCount == 0 {
-		options.MaxRefCount = 1
-	}
-	m.topologyOptions[nodeName] = options
-}
-
-func (m *cpuTopologyManager) Delete(nodeName string) {
-	m.lock.Lock()
-	defer m.lock.Unlock()
-	delete(m.topologyOptions, nodeName)
-}
diff --git a/pkg/scheduler/plugins/nodenumaresource/node_allocation.go b/pkg/scheduler/plugins/nodenumaresource/node_allocation.go
new file mode 100644
index 00000000..60b7d885
--- /dev/null
+++ b/pkg/scheduler/plugins/nodenumaresource/node_allocation.go
@@ -0,0 +1,166 @@
+/*
+Copyright 2022 The Koordinator 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.
+*/
+
+package nodenumaresource
+
+import (
+	"sync"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	quotav1 "k8s.io/apiserver/pkg/quota/v1"
+
+	schedulingconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
+	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
+)
+
+type NodeAllocation struct {
+	lock               sync.RWMutex
+	nodeName           string
+	allocatedPods      map[types.UID]PodAllocation
+	allocatedCPUs      CPUDetails
+	allocatedResources map[int]*NUMANodeResource
+}
+
+type PodAllocation struct {
+	UID                types.UID                           `json:"uid,omitempty"`
+	Namespace          string                              `json:"namespace,omitempty"`
+	Name               string                              `json:"name,omitempty"`
+	CPUSet             cpuset.CPUSet                       `json:"cpuset,omitempty"`
+	CPUExclusivePolicy schedulingconfig.CPUExclusivePolicy `json:"cpuExclusivePolicy,omitempty"`
+	NUMANodeResources  []NUMANodeResource                  `json:"numaNodeResources,omitempty"`
+}
+
+func NewNodeAllocation(nodeName string) *NodeAllocation {
+	return &NodeAllocation{
+		nodeName:           nodeName,
+		allocatedPods:      map[types.UID]PodAllocation{},
+		allocatedCPUs:      NewCPUDetails(),
+		allocatedResources: map[int]*NUMANodeResource{},
+	}
+}
+
+func (n *NodeAllocation) update(allocation *PodAllocation, cpuTopology *CPUTopology) {
+	n.release(allocation.UID)
+	n.addPodAllocation(allocation, cpuTopology)
+}
+
+func (n *NodeAllocation) getCPUs(podUID types.UID) (cpuset.CPUSet, bool) {
+	request, ok := n.allocatedPods[podUID]
+	return request.CPUSet, ok
+}
+
+func (n *NodeAllocation) addCPUs(cpuTopology *CPUTopology, podUID types.UID, cpuset cpuset.CPUSet, exclusivePolicy schedulingconfig.CPUExclusivePolicy) {
+	n.addPodAllocation(&PodAllocation{
+		UID:                podUID,
+		CPUSet:             cpuset,
+		CPUExclusivePolicy: exclusivePolicy,
+	}, cpuTopology)
+}
+
+func (n *NodeAllocation) addPodAllocation(request *PodAllocation, cpuTopology *CPUTopology) {
+	if _, ok := n.allocatedPods[request.UID]; ok {
+		return
+	}
+	n.allocatedPods[request.UID] = *request
+
+	for _, cpuID := range request.CPUSet.ToSliceNoSort() {
+		cpuInfo, ok := n.allocatedCPUs[cpuID]
+		if !ok {
+			cpuInfo = cpuTopology.CPUDetails[cpuID]
+		}
+		cpuInfo.ExclusivePolicy = request.CPUExclusivePolicy
+		cpuInfo.RefCount++
+		n.allocatedCPUs[cpuID] = cpuInfo
+	}
+
+	for nodeID, numaNodeRes := range request.NUMANodeResources {
+		res := n.allocatedResources[numaNodeRes.Node]
+		if res == nil {
+			res = &NUMANodeResource{
+				Node:      nodeID,
+				Resources: make(corev1.ResourceList),
+			}
+			n.allocatedResources[numaNodeRes.Node] = res
+		}
+		res.Resources = quotav1.Add(res.Resources, numaNodeRes.Resources)
+	}
+}
+
+func (n *NodeAllocation) release(podUID types.UID) {
+	request, ok := n.allocatedPods[podUID]
+	if !ok {
+		return
+	}
+	delete(n.allocatedPods, podUID)
+
+	for _, cpuID := range request.CPUSet.ToSliceNoSort() {
+		cpuInfo, ok := n.allocatedCPUs[cpuID]
+		if !ok {
+			continue
+		}
+		cpuInfo.RefCount--
+		if cpuInfo.RefCount == 0 {
+			delete(n.allocatedCPUs, cpuID)
+		} else {
+			n.allocatedCPUs[cpuID] = cpuInfo
+		}
+	}
+
+	for _, numaNodeRes := range request.NUMANodeResources {
+		res := n.allocatedResources[numaNodeRes.Node]
+		if res != nil {
+			res.Resources = quotav1.SubtractWithNonNegativeResult(res.Resources, numaNodeRes.Resources)
+		}
+	}
+}
+
+func (n *NodeAllocation) getAvailableCPUs(cpuTopology *CPUTopology, maxRefCount int, reservedCPUs, preferredCPUs cpuset.CPUSet) (availableCPUs cpuset.CPUSet, allocateInfo CPUDetails) {
+	allocateInfo = n.allocatedCPUs.Clone()
+	if !preferredCPUs.IsEmpty() {
+		for _, cpuID := range preferredCPUs.ToSliceNoSort() {
+			cpuInfo, ok := allocateInfo[cpuID]
+			if ok {
+				cpuInfo.RefCount--
+				if cpuInfo.RefCount == 0 {
+					delete(allocateInfo, cpuID)
+				} else {
+					allocateInfo[cpuID] = cpuInfo
+				}
+			}
+		}
+	}
+	allocated := allocateInfo.CPUs().Filter(func(cpuID int) bool {
+		return allocateInfo[cpuID].RefCount >= maxRefCount
+	})
+	availableCPUs = cpuTopology.CPUDetails.CPUs().Difference(allocated).Difference(reservedCPUs)
+	return
+}
+
+func (n *NodeAllocation) getAvailableNUMANodeResources(topologyOptions TopologyOptions) (totalAvailable, totalAllocated map[int]corev1.ResourceList) {
+	totalAvailable = make(map[int]corev1.ResourceList)
+	totalAllocated = make(map[int]corev1.ResourceList)
+	for _, numaNodeRes := range topologyOptions.NUMANodeResources {
+		var allocatedRes corev1.ResourceList
+		allocated := n.allocatedResources[numaNodeRes.Node]
+		if allocated != nil {
+			allocatedRes = allocated.Resources
+			totalAllocated[numaNodeRes.Node] = allocatedRes.DeepCopy()
+		}
+		totalAvailable[numaNodeRes.Node] = quotav1.SubtractWithNonNegativeResult(numaNodeRes.Resources, allocatedRes)
+	}
+	return totalAvailable, totalAllocated
+}
diff --git a/pkg/scheduler/plugins/nodenumaresource/plugin.go b/pkg/scheduler/plugins/nodenumaresource/plugin.go
index 3ff035fd..a0d99fae 100644
--- a/pkg/scheduler/plugins/nodenumaresource/plugin.go
+++ b/pkg/scheduler/plugins/nodenumaresource/plugin.go
@@ -23,6 +23,7 @@ import (
 	"fmt"
 
 	nrtv1alpha1 "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha1"
+	topologylister "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/generated/listers/topology/v1alpha1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -33,6 +34,7 @@ import (
 	schedulingv1alpha1 "github.com/koordinator-sh/koordinator/apis/scheduling/v1alpha1"
 	schedulingconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
 	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
 	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
 	reservationutil "github.com/koordinator-sh/koordinator/pkg/util/reservation"
 )
@@ -65,33 +67,36 @@ var (
 	_ framework.ReservePlugin   = &Plugin{}
 	_ framework.PreBindPlugin   = &Plugin{}
 
-	_ frameworkext.ReservationRestorePlugin = &Plugin{}
-	_ frameworkext.ReservationPreBindPlugin = &Plugin{}
+	_ frameworkext.ReservationRestorePlugin    = &Plugin{}
+	_ frameworkext.ReservationPreBindPlugin    = &Plugin{}
+	_ topologymanager.NUMATopologyHintProvider = &Plugin{}
 )
 
 type Plugin struct {
 	handle          framework.Handle
 	pluginArgs      *schedulingconfig.NodeNUMAResourceArgs
-	topologyManager CPUTopologyManager
-	cpuManager      CPUManager
+	nrtLister       topologylister.NodeResourceTopologyLister
+	resourceManager ResourceManager
+
+	topologyOptionsManager TopologyOptionsManager
 }
 
 type Option func(*pluginOptions)
 
 type pluginOptions struct {
-	topologyManager CPUTopologyManager
-	cpuManager      CPUManager
+	topologyOptionsManager TopologyOptionsManager
+	resourceManager        ResourceManager
 }
 
-func WithCPUTopologyManager(topologyManager CPUTopologyManager) Option {
+func WithTopologyOptionsManager(topologyOptionsManager TopologyOptionsManager) Option {
 	return func(opts *pluginOptions) {
-		opts.topologyManager = topologyManager
+		opts.topologyOptionsManager = topologyOptionsManager
 	}
 }
 
-func WithCPUManager(cpuManager CPUManager) Option {
+func WithResourceManager(resourceManager ResourceManager) Option {
 	return func(opts *pluginOptions) {
-		opts.cpuManager = cpuManager
+		opts.resourceManager = resourceManager
 	}
 }
 
@@ -106,25 +111,32 @@ func NewWithOptions(args runtime.Object, handle framework.Handle, opts ...Option
 		optFnc(options)
 	}
 
-	if options.topologyManager == nil {
-		options.topologyManager = NewCPUTopologyManager()
+	if options.topologyOptionsManager == nil {
+		options.topologyOptionsManager = NewTopologyOptionsManager()
 	}
 
-	if options.cpuManager == nil {
+	if options.resourceManager == nil {
 		defaultNUMAAllocateStrategy := GetDefaultNUMAAllocateStrategy(pluginArgs)
-		options.cpuManager = NewCPUManager(handle, defaultNUMAAllocateStrategy, options.topologyManager)
+		options.resourceManager = NewResourceManager(handle, defaultNUMAAllocateStrategy, options.topologyOptionsManager)
 	}
 
-	if err := registerNodeResourceTopologyEventHandler(handle, options.topologyManager); err != nil {
+	nrtInformerFactory, err := initNRTInformerFactory(handle)
+	if err != nil {
+		return nil, err
+	}
+	if err := registerNodeResourceTopologyEventHandler(nrtInformerFactory, options.topologyOptionsManager); err != nil {
 		return nil, err
 	}
-	registerPodEventHandler(handle, options.cpuManager)
+	registerPodEventHandler(handle, options.resourceManager)
+
+	nrtLister := nrtInformerFactory.Topology().V1alpha1().NodeResourceTopologies().Lister()
 
 	return &Plugin{
-		handle:          handle,
-		pluginArgs:      pluginArgs,
-		topologyManager: options.topologyManager,
-		cpuManager:      options.cpuManager,
+		handle:                 handle,
+		pluginArgs:             pluginArgs,
+		nrtLister:              nrtLister,
+		resourceManager:        options.resourceManager,
+		topologyOptionsManager: options.topologyOptionsManager,
 	}, nil
 }
 
@@ -142,31 +154,32 @@ func GetDefaultNUMAAllocateStrategy(pluginArgs *schedulingconfig.NodeNUMAResourc
 
 func (p *Plugin) Name() string { return Name }
 
-func (p *Plugin) GetCPUManager() CPUManager {
-	return p.cpuManager
+func (p *Plugin) GetResourceManager() ResourceManager {
+	return p.resourceManager
 }
 
-func (p *Plugin) GetCPUTopologyManager() CPUTopologyManager {
-	return p.topologyManager
+func (p *Plugin) GetTopologyOptionsManager() TopologyOptionsManager {
+	return p.topologyOptionsManager
 }
 
 type preFilterState struct {
-	skip                        bool
+	requests                    corev1.ResourceList
+	requestCPUBind              bool
 	resourceSpec                *extension.ResourceSpec
 	preferredCPUBindPolicy      schedulingconfig.CPUBindPolicy
 	preferredCPUExclusivePolicy schedulingconfig.CPUExclusivePolicy
 	numCPUsNeeded               int
-	allocatedCPUs               cpuset.CPUSet
+	allocation                  *PodAllocation
 }
 
 func (s *preFilterState) Clone() framework.StateData {
 	ns := &preFilterState{
-		skip:                        s.skip,
+		requestCPUBind:              s.requestCPUBind,
 		resourceSpec:                s.resourceSpec,
 		preferredCPUBindPolicy:      s.preferredCPUBindPolicy,
 		preferredCPUExclusivePolicy: s.preferredCPUExclusivePolicy,
 		numCPUsNeeded:               s.numCPUsNeeded,
-		allocatedCPUs:               s.allocatedCPUs.Clone(),
+		allocation:                  s.allocation,
 	}
 	return ns
 }
@@ -195,8 +208,10 @@ func (p *Plugin) PreFilter(ctx context.Context, cycleState *framework.CycleState
 		return nil, framework.NewStatus(framework.Error, err.Error())
 	}
 
+	requests, _ := resourceapi.PodRequestsAndLimits(pod)
 	state := &preFilterState{
-		skip: true,
+		requestCPUBind: false,
+		requests:       requests,
 	}
 	if AllowUseCPUSet(pod) {
 		preferredCPUBindPolicy := schedulingconfig.CPUBindPolicy(resourceSpec.PreferredCPUBindPolicy)
@@ -205,14 +220,13 @@ func (p *Plugin) PreFilter(ctx context.Context, cycleState *framework.CycleState
 		}
 		if preferredCPUBindPolicy == schedulingconfig.CPUBindPolicyFullPCPUs ||
 			preferredCPUBindPolicy == schedulingconfig.CPUBindPolicySpreadByPCPUs {
-			requests, _ := resourceapi.PodRequestsAndLimits(pod)
 			requestedCPU := requests.Cpu().MilliValue()
 			if requestedCPU%1000 != 0 {
 				return nil, framework.NewStatus(framework.Error, "the requested CPUs must be integer")
 			}
 
 			if requestedCPU > 0 {
-				state.skip = false
+				state.requestCPUBind = true
 				state.resourceSpec = resourceSpec
 				state.preferredCPUBindPolicy = preferredCPUBindPolicy
 				state.preferredCPUExclusivePolicy = resourceSpec.PreferredCPUExclusivePolicy
@@ -243,37 +257,36 @@ func (p *Plugin) Filter(ctx context.Context, cycleState *framework.CycleState, p
 	if !status.IsSuccess() {
 		return status
 	}
-	if state.skip {
-		return nil
-	}
 
 	node := nodeInfo.Node()
-	if node == nil {
-		return framework.NewStatus(framework.Error, "node not found")
-	}
-
-	cpuTopologyOptions := p.topologyManager.GetCPUTopologyOptions(node.Name)
-	if cpuTopologyOptions.CPUTopology == nil {
-		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrNotFoundCPUTopology)
-	}
-
-	// It's necessary to force node to have NodeResourceTopology and CPUTopology
-	// We must satisfy the user's CPUSet request. Even if some nodes in the cluster have resources,
-	// they cannot be allocated without valid CPU topology.
-	if !cpuTopologyOptions.CPUTopology.IsValid() {
-		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrInvalidCPUTopology)
-	}
+	topologyOptions := p.topologyOptionsManager.GetTopologyOptions(node.Name)
+	if state.requestCPUBind {
+		if topologyOptions.CPUTopology == nil {
+			return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrNotFoundCPUTopology)
+		}
 
-	kubeletCPUPolicy := cpuTopologyOptions.Policy
-	if extension.GetNodeCPUBindPolicy(node.Labels, kubeletCPUPolicy) == extension.NodeCPUBindPolicyFullPCPUsOnly {
-		if state.numCPUsNeeded%cpuTopologyOptions.CPUTopology.CPUsPerCore() != 0 {
-			return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrSMTAlignmentError)
+		// It's necessary to force node to have NodeResourceTopology and CPUTopology
+		// We must satisfy the user's CPUSet request. Even if some nodes in the cluster have resources,
+		// they cannot be allocated without valid CPU topology.
+		if !topologyOptions.CPUTopology.IsValid() {
+			return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrInvalidCPUTopology)
 		}
-		if state.preferredCPUBindPolicy != schedulingconfig.CPUBindPolicyFullPCPUs {
-			return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrRequiredFullPCPUsPolicy)
+		kubeletCPUPolicy := topologyOptions.Policy
+		if extension.GetNodeCPUBindPolicy(node.Labels, kubeletCPUPolicy) == extension.NodeCPUBindPolicyFullPCPUsOnly {
+			if state.numCPUsNeeded%topologyOptions.CPUTopology.CPUsPerCore() != 0 {
+				return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrSMTAlignmentError)
+			}
+			if state.preferredCPUBindPolicy != schedulingconfig.CPUBindPolicyFullPCPUs {
+				return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrRequiredFullPCPUsPolicy)
+			}
 		}
 	}
 
+	policyType := getNUMATopologyPolicy(node.Labels, topologyOptions.NUMATopologyPolicy)
+	if policyType != extension.NUMATopologyPolicyNone {
+		return p.FilterByNUMANode(ctx, cycleState, pod, node.Name, policyType)
+	}
+
 	return nil
 }
 
@@ -282,7 +295,7 @@ func (p *Plugin) Score(ctx context.Context, cycleState *framework.CycleState, po
 	if !status.IsSuccess() {
 		return 0, status
 	}
-	if state.skip {
+	if !state.requestCPUBind {
 		return 0, nil
 	}
 
@@ -291,20 +304,11 @@ func (p *Plugin) Score(ctx context.Context, cycleState *framework.CycleState, po
 		return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
 	}
 	node := nodeInfo.Node()
-	if node == nil {
-		return 0, framework.NewStatus(framework.Error, "node not found")
-	}
-
-	preferredCPUBindPolicy, err := p.getPreferredCPUBindPolicy(node, state.preferredCPUBindPolicy)
+	resourceOptions, err := p.getResourceOptions(cycleState, state, node, pod, topologymanager.NUMATopologyHint{})
 	if err != nil {
 		return 0, nil
 	}
-
-	reservationReservedCPUs, err := p.getReservationReservedCPUs(cycleState, pod, node)
-	if err != nil {
-		return 0, framework.AsStatus(err)
-	}
-	score := p.cpuManager.Score(node, state.numCPUsNeeded, preferredCPUBindPolicy, state.preferredCPUExclusivePolicy, reservationReservedCPUs)
+	score := p.resourceManager.Score(node, pod, resourceOptions)
 	return score, nil
 }
 
@@ -317,70 +321,58 @@ func (p *Plugin) Reserve(ctx context.Context, cycleState *framework.CycleState,
 	if !status.IsSuccess() {
 		return status
 	}
-	if state.skip {
-		return nil
-	}
 
 	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
 	if err != nil {
 		return framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
 	}
 	node := nodeInfo.Node()
-	if node == nil {
-		return framework.NewStatus(framework.Error, "node not found")
+	topologyOptions := p.topologyOptionsManager.GetTopologyOptions(node.Name)
+	if state.requestCPUBind {
+		if topologyOptions.CPUTopology == nil {
+			return framework.NewStatus(framework.Error, ErrNotFoundCPUTopology)
+		}
+		if !topologyOptions.CPUTopology.IsValid() {
+			return framework.NewStatus(framework.Error, ErrInvalidCPUTopology)
+		}
 	}
 
-	preferredCPUBindPolicy, err := p.getPreferredCPUBindPolicy(node, state.preferredCPUBindPolicy)
-	if err != nil {
-		return framework.AsStatus(err)
+	policyType := getNUMATopologyPolicy(node.Labels, topologyOptions.NUMATopologyPolicy)
+	if policyType != extension.NUMATopologyPolicyNone {
+		status := p.ReserveByNUMANode(ctx, cycleState, pod, nodeName, policyType)
+		if !status.IsSuccess() {
+			return status
+		}
+		return nil
 	}
 
-	reservationReservedCPUs, err := p.getReservationReservedCPUs(cycleState, pod, node)
+	// Only request CPUs by bind policy and exclusive policy.
+	if !state.requestCPUBind {
+		return nil
+	}
+
+	resourceOptions, err := p.getResourceOptions(cycleState, state, node, pod, topologymanager.NUMATopologyHint{})
 	if err != nil {
 		return framework.AsStatus(err)
 	}
-	result, err := p.cpuManager.Allocate(node, state.numCPUsNeeded, preferredCPUBindPolicy, state.preferredCPUExclusivePolicy, reservationReservedCPUs)
+	result, err := p.resourceManager.Allocate(node, pod, resourceOptions)
 	if err != nil {
 		return framework.AsStatus(err)
 	}
-	p.cpuManager.UpdateAllocatedCPUSet(nodeName, pod.UID, result, state.preferredCPUExclusivePolicy)
-	state.allocatedCPUs = result
-	state.preferredCPUBindPolicy = preferredCPUBindPolicy
+	p.resourceManager.Update(nodeName, result)
+	state.allocation = result
+	state.preferredCPUBindPolicy = resourceOptions.cpuBindPolicy
 	return nil
 }
 
-func (p *Plugin) getReservationReservedCPUs(cycleState *framework.CycleState, pod *corev1.Pod, node *corev1.Node) (cpuset.CPUSet, error) {
-	var result cpuset.CPUSet
-	if reservationutil.IsReservePod(pod) {
-		return result, nil
-	}
-	nominatedReservation := frameworkext.GetNominatedReservation(cycleState, node.Name)
-	if nominatedReservation == nil {
-		return result, nil
-	}
-
-	allocatedCPUs, _ := p.cpuManager.GetAllocatedCPUSet(node.Name, nominatedReservation.UID())
-	if allocatedCPUs.IsEmpty() {
-		return result, nil
-	}
-	reservationRestoreState := getReservationRestoreState(cycleState)
-	nodeReservationRestoreState := reservationRestoreState.getNodeState(node.Name)
-	reservedCPUs := nodeReservationRestoreState.reservedCPUs[nominatedReservation.UID()]
-	if !reservedCPUs.IsEmpty() && !reservedCPUs.IsSubsetOf(allocatedCPUs) {
-		return result, fmt.Errorf("reservation reserved CPUs are invalid")
-	}
-	return reservedCPUs, nil
-}
-
 func (p *Plugin) Unreserve(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) {
 	state, status := getPreFilterState(cycleState)
 	if !status.IsSuccess() {
 		return
 	}
-	if state.skip || state.allocatedCPUs.IsEmpty() {
-		return
+	if state.allocation != nil {
+		p.resourceManager.Release(nodeName, pod.UID)
 	}
-	p.cpuManager.Free(nodeName, pod.UID)
 }
 
 func (p *Plugin) PreBind(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) *framework.Status {
@@ -396,14 +388,85 @@ func (p *Plugin) preBindObject(ctx context.Context, cycleState *framework.CycleS
 	if !status.IsSuccess() {
 		return status
 	}
-	if state.skip {
+	if state.allocation == nil {
 		return nil
 	}
 
-	if state.allocatedCPUs.IsEmpty() {
-		return nil
+	if state.requestCPUBind {
+		if err := appendResourceSpecIfMissed(object, state); err != nil {
+			return framework.AsStatus(err)
+		}
+	}
+
+	resourceStatus := &extension.ResourceStatus{
+		CPUSet: state.allocation.CPUSet.String(),
+	}
+	for _, nodeRes := range state.allocation.NUMANodeResources {
+		resourceStatus.NUMANodeResources = append(resourceStatus.NUMANodeResources, extension.NUMANodeResource{
+			Node:      int32(nodeRes.Node),
+			Resources: nodeRes.Resources,
+		})
+	}
+	if err := extension.SetResourceStatus(object, resourceStatus); err != nil {
+		return framework.AsStatus(err)
+	}
+	return nil
+}
+
+func getNUMATopologyPolicy(nodeLabels map[string]string, kubeletTopologyManagerPolicy extension.NUMATopologyPolicy) extension.NUMATopologyPolicy {
+	policyType := extension.GetNodeNUMATopologyPolicy(nodeLabels)
+	if policyType != extension.NUMATopologyPolicyNone {
+		return policyType
+	}
+	return kubeletTopologyManagerPolicy
+}
+
+func (p *Plugin) getResourceOptions(cycleState *framework.CycleState, state *preFilterState, node *corev1.Node, pod *corev1.Pod, affinity topologymanager.NUMATopologyHint) (*ResourceOptions, error) {
+	preferredCPUBindPolicy, err := p.getPreferredCPUBindPolicy(node, state.preferredCPUBindPolicy)
+	if err != nil {
+		return nil, err
+	}
+
+	reservationReservedCPUs, err := p.getReservationReservedCPUs(cycleState, pod, node.Name)
+	if err != nil {
+		return nil, err
 	}
+	options := &ResourceOptions{
+		requests:           state.requests,
+		numCPUsNeeded:      state.numCPUsNeeded,
+		requestCPUBind:     state.requestCPUBind,
+		cpuBindPolicy:      preferredCPUBindPolicy,
+		cpuExclusivePolicy: state.preferredCPUExclusivePolicy,
+		preferredCPUs:      reservationReservedCPUs,
+		hint:               affinity,
+	}
+	return options, nil
+}
+
+func (p *Plugin) getReservationReservedCPUs(cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) (cpuset.CPUSet, error) {
+	var result cpuset.CPUSet
+	if reservationutil.IsReservePod(pod) {
+		return result, nil
+	}
+	nominatedReservation := frameworkext.GetNominatedReservation(cycleState, nodeName)
+	if nominatedReservation == nil {
+		return result, nil
+	}
+
+	allocatedCPUs, _ := p.resourceManager.GetAllocatedCPUSet(nodeName, nominatedReservation.UID())
+	if allocatedCPUs.IsEmpty() {
+		return result, nil
+	}
+	reservationRestoreState := getReservationRestoreState(cycleState)
+	nodeReservationRestoreState := reservationRestoreState.getNodeState(nodeName)
+	reservedCPUs := nodeReservationRestoreState.reservedCPUs[nominatedReservation.UID()]
+	if !reservedCPUs.IsEmpty() && !reservedCPUs.IsSubsetOf(allocatedCPUs) {
+		return result, fmt.Errorf("reservation reserved CPUs are invalid")
+	}
+	return reservedCPUs, nil
+}
 
+func appendResourceSpecIfMissed(object metav1.Object, state *preFilterState) error {
 	annotations := object.GetAnnotations()
 	// Write back ResourceSpec annotation if LSR Pod hasn't specified CPUBindPolicy
 	preferredCPUBindPolicy := schedulingconfig.CPUBindPolicy(state.resourceSpec.PreferredCPUBindPolicy)
@@ -415,7 +478,7 @@ func (p *Plugin) preBindObject(ctx context.Context, cycleState *framework.CycleS
 		}
 		resourceSpecData, err := json.Marshal(resourceSpec)
 		if err != nil {
-			return framework.AsStatus(err)
+			return err
 		}
 		if annotations == nil {
 			annotations = make(map[string]string)
@@ -423,24 +486,19 @@ func (p *Plugin) preBindObject(ctx context.Context, cycleState *framework.CycleS
 		annotations[extension.AnnotationResourceSpec] = string(resourceSpecData)
 		object.SetAnnotations(annotations)
 	}
-
-	resourceStatus := &extension.ResourceStatus{CPUSet: state.allocatedCPUs.String()}
-	if err := extension.SetResourceStatus(object, resourceStatus); err != nil {
-		return framework.AsStatus(err)
-	}
 	return nil
 }
 
 func (p *Plugin) getPreferredCPUBindPolicy(node *corev1.Node, preferredCPUBindPolicy schedulingconfig.CPUBindPolicy) (schedulingconfig.CPUBindPolicy, error) {
-	cpuTopologyOptions := p.topologyManager.GetCPUTopologyOptions(node.Name)
-	if cpuTopologyOptions.CPUTopology == nil {
+	topologyOptions := p.topologyOptionsManager.GetTopologyOptions(node.Name)
+	if topologyOptions.CPUTopology == nil {
 		return preferredCPUBindPolicy, errors.New(ErrNotFoundCPUTopology)
 	}
-	if !cpuTopologyOptions.CPUTopology.IsValid() {
+	if !topologyOptions.CPUTopology.IsValid() {
 		return preferredCPUBindPolicy, errors.New(ErrInvalidCPUTopology)
 	}
 
-	kubeletCPUPolicy := cpuTopologyOptions.Policy
+	kubeletCPUPolicy := topologyOptions.Policy
 	nodeCPUBindPolicy := extension.GetNodeCPUBindPolicy(node.Labels, kubeletCPUPolicy)
 	switch nodeCPUBindPolicy {
 	default:
diff --git a/pkg/scheduler/plugins/nodenumaresource/plugin_service.go b/pkg/scheduler/plugins/nodenumaresource/plugin_service.go
index 22950140..3362a015 100644
--- a/pkg/scheduler/plugins/nodenumaresource/plugin_service.go
+++ b/pkg/scheduler/plugins/nodenumaresource/plugin_service.go
@@ -27,28 +27,72 @@ import (
 
 var _ services.APIServiceProvider = &Plugin{}
 
-type AvailableCPUsResponse struct {
-	AvailableCPUs cpuset.CPUSet `json:"availableCPUs,omitempty"`
-	Allocated     CPUDetails    `json:"allocated,omitempty"`
+type NodeResponse struct {
+	Name                       string `json:"name,omitempty"`
+	TopologyOptions            `json:",inline"`
+	RemainingNUMANodeResources []NUMANodeResource `json:"remainingNUMANodeResources"`
+	AllocatedNUMANodeResources []NUMANodeResource `json:"allocatedNUMANodeResources"`
+	AvailableCPUs              cpuset.CPUSet      `json:"availableCPUs"`
+	AllocatedCPUs              CPUDetails         `json:"allocatedCPUs"`
+	AllocatedPods              []PodAllocation    `json:"allocatedPods"`
 }
 
 func (p *Plugin) RegisterEndpoints(group *gin.RouterGroup) {
-	group.GET("/cpuTopologyOptions/:nodeName", func(c *gin.Context) {
+	group.GET("/nodes/:nodeName", func(c *gin.Context) {
 		nodeName := c.Param("nodeName")
-		topologyOptions := p.topologyManager.GetCPUTopologyOptions(nodeName)
-		c.JSON(http.StatusOK, topologyOptions)
-	})
-	group.GET("/availableCPUs/:nodeName", func(c *gin.Context) {
-		nodeName := c.Param("nodeName")
-		availableCPUs, allocated, err := p.cpuManager.GetAvailableCPUs(nodeName)
+		nodeLister := p.handle.SharedInformerFactory().Core().V1().Nodes().Lister()
+		node, err := nodeLister.Get(nodeName)
 		if err != nil {
 			services.ResponseErrorMessage(c, http.StatusInternalServerError, err.Error())
 			return
 		}
-		r := &AvailableCPUsResponse{
-			AvailableCPUs: availableCPUs,
-			Allocated:     allocated,
+
+		topologyOptions := p.topologyOptionsManager.GetTopologyOptions(nodeName)
+		if topologyOptions.CPUTopology == nil || !topologyOptions.CPUTopology.IsValid() {
+			services.ResponseErrorMessage(c, http.StatusInternalServerError, "invalid topology, please check the NodeResourceTopology object")
+			return
+		}
+		topologyOptions.NUMATopologyPolicy = getNUMATopologyPolicy(node.Labels, topologyOptions.NUMATopologyPolicy)
+
+		nodeAllocation := p.resourceManager.GetNodeAllocation(nodeName)
+		if nodeAllocation == nil {
+			services.ResponseErrorMessage(c, http.StatusNotFound, "cannot find target node")
+			return
 		}
-		c.JSON(http.StatusOK, r)
+		resp := dumpNodeAllocation(nodeAllocation, topologyOptions)
+		c.JSON(http.StatusOK, resp)
 	})
 }
+
+func dumpNodeAllocation(nodeAllocation *NodeAllocation, topologyOptions TopologyOptions) *NodeResponse {
+	resp := &NodeResponse{
+		Name:            nodeAllocation.nodeName,
+		TopologyOptions: topologyOptions,
+	}
+
+	nodeAllocation.lock.RLock()
+	defer nodeAllocation.lock.RUnlock()
+	if len(nodeAllocation.allocatedPods) != 0 {
+		podAllocations := make([]PodAllocation, 0, len(nodeAllocation.allocatedPods))
+		for _, v := range nodeAllocation.allocatedPods {
+			podAllocations = append(podAllocations, v)
+		}
+		resp.AllocatedPods = podAllocations
+	}
+	resp.AvailableCPUs, resp.AllocatedCPUs = nodeAllocation.getAvailableCPUs(topologyOptions.CPUTopology, topologyOptions.MaxRefCount, topologyOptions.ReservedCPUs, cpuset.CPUSet{})
+	availableResources, allocatedResource := nodeAllocation.getAvailableNUMANodeResources(topologyOptions)
+	for nodeID, v := range availableResources {
+		resp.RemainingNUMANodeResources = append(resp.RemainingNUMANodeResources, NUMANodeResource{
+			Node:      nodeID,
+			Resources: v.DeepCopy(),
+		})
+	}
+	for nodeID, v := range allocatedResource {
+		resp.AllocatedNUMANodeResources = append(resp.AllocatedNUMANodeResources, NUMANodeResource{
+			Node:      nodeID,
+			Resources: v.DeepCopy(),
+		})
+	}
+
+	return resp
+}
diff --git a/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler.go b/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler.go
index 871d323b..4606cbb7 100644
--- a/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler.go
+++ b/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler.go
@@ -32,13 +32,13 @@ import (
 )
 
 type podEventHandler struct {
-	cpuManager CPUManager
+	resourceManager ResourceManager
 }
 
-func registerPodEventHandler(handle framework.Handle, cpuManager CPUManager) {
+func registerPodEventHandler(handle framework.Handle, resourceManager ResourceManager) {
 	podInformer := handle.SharedInformerFactory().Core().V1().Pods().Informer()
 	eventHandler := &podEventHandler{
-		cpuManager: cpuManager,
+		resourceManager: resourceManager,
 	}
 	frameworkexthelper.ForceSyncFromInformer(context.TODO().Done(), handle.SharedInformerFactory(), podInformer, eventHandler)
 	extendedHandle, ok := handle.(frameworkext.ExtendedHandle)
@@ -104,17 +104,35 @@ func (c *podEventHandler) updatePod(oldPod, pod *corev1.Pod) {
 	if err != nil {
 		return
 	}
-	cpus, err := cpuset.Parse(resourceStatus.CPUSet)
-	if err != nil || cpus.IsEmpty() {
+	resourceSpec, err := extension.GetResourceSpec(pod.Annotations)
+	if err != nil {
 		return
 	}
 
-	resourceSpec, err := extension.GetResourceSpec(pod.Annotations)
+	cpus, err := cpuset.Parse(resourceStatus.CPUSet)
 	if err != nil {
 		return
 	}
+	if len(resourceStatus.NUMANodeResources) == 0 && cpus.IsEmpty() {
+		return
+	}
+
+	allocation := &PodAllocation{
+		UID:                pod.UID,
+		Namespace:          pod.Namespace,
+		Name:               pod.Name,
+		CPUSet:             cpus,
+		CPUExclusivePolicy: resourceSpec.PreferredCPUExclusivePolicy,
+		NUMANodeResources:  make([]NUMANodeResource, 0, len(resourceStatus.NUMANodeResources)),
+	}
+	for _, numaNodeRes := range resourceStatus.NUMANodeResources {
+		allocation.NUMANodeResources = append(allocation.NUMANodeResources, NUMANodeResource{
+			Node:      int(numaNodeRes.Node),
+			Resources: numaNodeRes.Resources,
+		})
+	}
 
-	c.cpuManager.UpdateAllocatedCPUSet(pod.Spec.NodeName, pod.UID, cpus, resourceSpec.PreferredCPUExclusivePolicy)
+	c.resourceManager.Update(pod.Spec.NodeName, allocation)
 }
 
 func (c *podEventHandler) deletePod(pod *corev1.Pod) {
@@ -122,14 +140,5 @@ func (c *podEventHandler) deletePod(pod *corev1.Pod) {
 		return
 	}
 
-	resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
-	if err != nil {
-		return
-	}
-	cpus, err := cpuset.Parse(resourceStatus.CPUSet)
-	if err != nil || cpus.IsEmpty() {
-		return
-	}
-
-	c.cpuManager.Free(pod.Spec.NodeName, pod.UID)
+	c.resourceManager.Release(pod.Spec.NodeName, pod.UID)
 }
diff --git a/pkg/scheduler/plugins/nodenumaresource/reservation.go b/pkg/scheduler/plugins/nodenumaresource/reservation.go
index 93093b12..70dfb50e 100644
--- a/pkg/scheduler/plugins/nodenumaresource/reservation.go
+++ b/pkg/scheduler/plugins/nodenumaresource/reservation.go
@@ -82,13 +82,13 @@ func (p *Plugin) RestoreReservation(ctx context.Context, cycleState *framework.C
 	nodeName := nodeInfo.Node().Name
 	reservedCPUs := map[types.UID]cpuset.CPUSet{}
 	for _, rInfo := range matched {
-		allocatedCPUs, ok := p.cpuManager.GetAllocatedCPUSet(nodeName, rInfo.UID())
+		allocatedCPUs, ok := p.resourceManager.GetAllocatedCPUSet(nodeName, rInfo.UID())
 		if !ok || allocatedCPUs.IsEmpty() {
 			continue
 		}
 
 		for _, pod := range rInfo.AssignedPods {
-			podCPUs, ok := p.cpuManager.GetAllocatedCPUSet(nodeName, pod.UID)
+			podCPUs, ok := p.resourceManager.GetAllocatedCPUSet(nodeName, pod.UID)
 			if !ok || podCPUs.IsEmpty() {
 				continue
 			}
diff --git a/pkg/scheduler/plugins/nodenumaresource/resource_manager.go b/pkg/scheduler/plugins/nodenumaresource/resource_manager.go
new file mode 100644
index 00000000..1336cd3d
--- /dev/null
+++ b/pkg/scheduler/plugins/nodenumaresource/resource_manager.go
@@ -0,0 +1,585 @@
+/*
+Copyright 2022 The Koordinator 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.
+*/
+
+package nodenumaresource
+
+import (
+	"errors"
+	"fmt"
+	"math"
+	"sync"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	"k8s.io/apimachinery/pkg/types"
+	quotav1 "k8s.io/apiserver/pkg/quota/v1"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/kubernetes/pkg/scheduler/framework"
+
+	"github.com/koordinator-sh/koordinator/apis/extension"
+	schedulingconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
+	"github.com/koordinator-sh/koordinator/pkg/util/bitmask"
+	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
+)
+
+type ResourceManager interface {
+	GetTopologyHints(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) (map[string][]topologymanager.NUMATopologyHint, error)
+	Allocate(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) (*PodAllocation, error)
+	Score(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) int64
+
+	Update(nodeName string, allocation *PodAllocation)
+	Release(nodeName string, podUID types.UID)
+
+	GetNodeAllocation(nodeName string) *NodeAllocation
+	GetAllocatedCPUSet(nodeName string, podUID types.UID) (cpuset.CPUSet, bool)
+	GetAvailableCPUs(nodeName string, preferredCPUs cpuset.CPUSet) (availableCPUs cpuset.CPUSet, allocated CPUDetails, err error)
+}
+
+type ResourceOptions struct {
+	numCPUsNeeded      int
+	requestCPUBind     bool
+	requests           corev1.ResourceList
+	cpuBindPolicy      schedulingconfig.CPUBindPolicy
+	cpuExclusivePolicy schedulingconfig.CPUExclusivePolicy
+	preferredCPUs      cpuset.CPUSet
+	hint               topologymanager.NUMATopologyHint
+}
+
+type resourceManager struct {
+	numaAllocateStrategy schedulingconfig.NUMAAllocateStrategy
+	topologyManager      TopologyOptionsManager
+	lock                 sync.Mutex
+	allocationStates     map[string]*NodeAllocation
+}
+
+func NewResourceManager(
+	handle framework.Handle,
+	defaultNUMAAllocateStrategy schedulingconfig.NUMAAllocateStrategy,
+	topologyManager TopologyOptionsManager,
+) ResourceManager {
+	manager := &resourceManager{
+		numaAllocateStrategy: defaultNUMAAllocateStrategy,
+		topologyManager:      topologyManager,
+		allocationStates:     map[string]*NodeAllocation{},
+	}
+	handle.SharedInformerFactory().Core().V1().Nodes().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{DeleteFunc: manager.onNodeDelete})
+	return manager
+}
+
+func (c *resourceManager) onNodeDelete(obj interface{}) {
+	var node *corev1.Node
+	switch t := obj.(type) {
+	case *corev1.Node:
+		node = t
+	case cache.DeletedFinalStateUnknown:
+		var ok bool
+		node, ok = t.Obj.(*corev1.Node)
+		if !ok {
+			return
+		}
+	default:
+		break
+	}
+
+	if node == nil {
+		return
+	}
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	delete(c.allocationStates, node.Name)
+}
+
+func (c *resourceManager) getOrCreateNodeAllocation(nodeName string) *NodeAllocation {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	v := c.allocationStates[nodeName]
+	if v == nil {
+		v = NewNodeAllocation(nodeName)
+		c.allocationStates[nodeName] = v
+	}
+	return v
+}
+
+func (c *resourceManager) GetTopologyHints(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) (map[string][]topologymanager.NUMATopologyHint, error) {
+	topologyOptions := c.topologyManager.GetTopologyOptions(node.Name)
+	if len(topologyOptions.NUMANodeResources) == 0 {
+		return nil, fmt.Errorf("insufficient resources on NUMA Node")
+	}
+
+	totalAvailable, _, err := c.getAvailableNUMANodeResources(node.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	hints := make(map[string][]topologymanager.NUMATopologyHint)
+	podRequests := options.requests.DeepCopy()
+	if options.requestCPUBind {
+		if topologyOptions.CPUTopology == nil || !topologyOptions.CPUTopology.IsValid() {
+			return nil, errors.New(ErrInvalidCPUTopology)
+		}
+		availableCPUs, _, err := c.GetAvailableCPUs(node.Name, options.preferredCPUs)
+		if err != nil {
+			return nil, err
+		}
+		result := generateCPUSetHints(topologyOptions.CPUTopology, availableCPUs, options.preferredCPUs, options.numCPUsNeeded)
+		hints[string(corev1.ResourceCPU)] = result
+		delete(podRequests, corev1.ResourceCPU)
+	}
+	nodes := make([]int, 0, len(topologyOptions.NUMANodeResources))
+	for _, v := range topologyOptions.NUMANodeResources {
+		nodes = append(nodes, v.Node)
+	}
+	result := generateResourceHints(nodes, podRequests, totalAvailable)
+	for k, v := range result {
+		hints[k] = v
+	}
+	return hints, nil
+}
+
+func (c *resourceManager) Allocate(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) (*PodAllocation, error) {
+	allocation := &PodAllocation{
+		UID:                pod.UID,
+		Namespace:          pod.Namespace,
+		Name:               pod.Name,
+		CPUExclusivePolicy: options.cpuExclusivePolicy,
+	}
+	if options.hint.NUMANodeAffinity != nil {
+		resources, err := c.allocateResourcesByHint(node, pod, options)
+		if err != nil {
+			return nil, err
+		}
+		allocation.NUMANodeResources = resources
+	}
+	if options.requestCPUBind {
+		cpus, err := c.allocateCPUSet(node, pod, options)
+		if err != nil {
+			return nil, err
+		}
+		allocation.CPUSet = cpus
+	}
+	return allocation, nil
+}
+
+func (c *resourceManager) allocateResourcesByHint(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) ([]NUMANodeResource, error) {
+	topologyOptions := c.topologyManager.GetTopologyOptions(node.Name)
+	if len(topologyOptions.NUMANodeResources) == 0 {
+		return nil, fmt.Errorf("insufficient resources on NUMA Node")
+	}
+
+	totalAvailable, _, err := c.getAvailableNUMANodeResources(node.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	requests := options.requests.DeepCopy()
+	var result []NUMANodeResource
+	for _, numaNodeID := range options.hint.NUMANodeAffinity.GetBits() {
+		available := totalAvailable[numaNodeID]
+		r := NUMANodeResource{
+			Node:      numaNodeID,
+			Resources: corev1.ResourceList{},
+		}
+		for resourceName, quantity := range requests {
+			var allocated resource.Quantity
+			available[resourceName], requests[resourceName], allocated = allocateRes(available[resourceName], quantity)
+			if !allocated.IsZero() {
+				r.Resources[resourceName] = allocated
+			}
+		}
+		if !quotav1.IsZero(r.Resources) {
+			result = append(result, r)
+		}
+		if quotav1.IsZero(requests) {
+			break
+		}
+	}
+	requests = quotav1.RemoveZeros(requests)
+	if len(requests) > 0 {
+		resourceNames := quotav1.ResourceNames(requests)
+		return nil, fmt.Errorf("insufficient resources: %v", resourceNames)
+	}
+	return result, nil
+}
+
+func allocateRes(available, request resource.Quantity) (resource.Quantity, resource.Quantity, resource.Quantity) {
+	switch available.Cmp(request) {
+	case 1:
+		available = available.DeepCopy()
+		available.Sub(request)
+		allocated := request.DeepCopy()
+		request.Sub(request)
+		return available, request, allocated
+	case -1:
+		request = request.DeepCopy()
+		request.Sub(available)
+		allocated := available.DeepCopy()
+		available.Sub(available)
+		return available, request, allocated
+	default:
+		request = request.DeepCopy()
+		request.Sub(request)
+		return request, request, available.DeepCopy()
+	}
+}
+
+func (c *resourceManager) allocateCPUSet(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) (cpuset.CPUSet, error) {
+	empty := cpuset.CPUSet{}
+	// The Pod requires the CPU to be allocated according to CPUBindPolicy,
+	// but the current node does not have a NodeResourceTopology or a valid CPUTopology,
+	// so this error should be exposed to the user
+	topologyOptions := c.topologyManager.GetTopologyOptions(node.Name)
+	if topologyOptions.CPUTopology == nil {
+		return empty, errors.New(ErrNotFoundCPUTopology)
+	}
+	if !topologyOptions.CPUTopology.IsValid() {
+		return empty, errors.New(ErrInvalidCPUTopology)
+	}
+
+	availableCPUs, allocatedCPUs, err := c.GetAvailableCPUs(node.Name, options.preferredCPUs)
+	if err != nil {
+		return empty, err
+	}
+
+	result := cpuset.CPUSet{}
+	numaAllocateStrategy := c.getNUMAAllocateStrategy(node)
+	numCPUsNeeded := options.numCPUsNeeded
+	if options.hint.NUMANodeAffinity != nil {
+		alignedCPUs := cpuset.NewCPUSet()
+		for _, numaNodeID := range options.hint.NUMANodeAffinity.GetBits() {
+			cpusInNUMANode := topologyOptions.CPUTopology.CPUDetails.CPUsInNUMANodes(numaNodeID)
+			alignedCPUs = alignedCPUs.Union(availableCPUs.Intersection(cpusInNUMANode))
+		}
+
+		numCPUs := alignedCPUs.Size()
+		if numCPUsNeeded < numCPUs {
+			numCPUs = numCPUsNeeded
+		}
+
+		alignedCPUs, err = takePreferredCPUs(
+			topologyOptions.CPUTopology,
+			topologyOptions.MaxRefCount,
+			alignedCPUs,
+			options.preferredCPUs,
+			allocatedCPUs,
+			numCPUs,
+			options.cpuBindPolicy,
+			options.cpuExclusivePolicy,
+			numaAllocateStrategy,
+		)
+		if err != nil {
+			return empty, err
+		}
+
+		result = result.Union(alignedCPUs)
+		numCPUsNeeded -= result.Size()
+	}
+
+	if numCPUsNeeded > 0 {
+		availableCPUs = availableCPUs.Difference(result)
+		remainingCPUs, err := takePreferredCPUs(
+			topologyOptions.CPUTopology,
+			topologyOptions.MaxRefCount,
+			availableCPUs,
+			options.preferredCPUs,
+			allocatedCPUs,
+			numCPUsNeeded,
+			options.cpuBindPolicy,
+			options.cpuExclusivePolicy,
+			numaAllocateStrategy,
+		)
+		if err != nil {
+			return empty, err
+		}
+		result = result.Union(remainingCPUs)
+	}
+	return result, err
+}
+
+func (c *resourceManager) Score(node *corev1.Node, pod *corev1.Pod, options *ResourceOptions) int64 {
+	topologyOptions := c.topologyManager.GetTopologyOptions(node.Name)
+	if topologyOptions.CPUTopology == nil || !topologyOptions.CPUTopology.IsValid() {
+		return 0
+	}
+
+	numaAllocateStrategy := c.getNUMAAllocateStrategy(node)
+	reservedCPUs := topologyOptions.ReservedCPUs
+
+	allocation := c.getOrCreateNodeAllocation(node.Name)
+	allocation.lock.RLock()
+	defer allocation.lock.RUnlock()
+
+	// TODO(joseph): should support score with preferredCPUs.
+
+	var (
+		cpuTopology        = topologyOptions.CPUTopology
+		numCPUsNeeded      = options.numCPUsNeeded
+		cpuBindPolicy      = options.cpuBindPolicy
+		cpuExclusivePolicy = options.cpuExclusivePolicy
+		preferredCPUs      = options.preferredCPUs
+	)
+	availableCPUs, allocated := allocation.getAvailableCPUs(cpuTopology, topologyOptions.MaxRefCount, reservedCPUs, preferredCPUs)
+	acc := newCPUAccumulator(
+		cpuTopology,
+		topologyOptions.MaxRefCount,
+		availableCPUs,
+		allocated,
+		numCPUsNeeded,
+		cpuExclusivePolicy,
+		numaAllocateStrategy,
+	)
+
+	var freeCPUs [][]int
+	if cpuBindPolicy == schedulingconfig.CPUBindPolicyFullPCPUs {
+		if numCPUsNeeded <= cpuTopology.CPUsPerNode() {
+			freeCPUs = acc.freeCoresInNode(true, true)
+		} else if numCPUsNeeded <= cpuTopology.CPUsPerSocket() {
+			freeCPUs = acc.freeCoresInSocket(true)
+		}
+	} else {
+		if numCPUsNeeded <= cpuTopology.CPUsPerNode() {
+			freeCPUs = acc.freeCPUsInNode(true)
+		} else if numCPUsNeeded <= cpuTopology.CPUsPerSocket() {
+			freeCPUs = acc.freeCPUsInSocket(true)
+		}
+	}
+
+	scoreFn := mostRequestedScore
+	if numaAllocateStrategy == schedulingconfig.NUMALeastAllocated {
+		scoreFn = leastRequestedScore
+	}
+
+	var maxScore int64
+	for _, cpus := range freeCPUs {
+		if len(cpus) < numCPUsNeeded {
+			continue
+		}
+
+		numaScore := scoreFn(int64(numCPUsNeeded), int64(len(cpus)))
+		if numaScore > maxScore {
+			maxScore = numaScore
+		}
+	}
+
+	// If the requested CPUs can be aligned according to NUMA Socket, it should be scored,
+	// but in order to avoid the situation where the number of CPUs in the NUMA Socket of
+	// some special models in the cluster is equal to the number of CPUs in the NUMA Node
+	// of other models, it is necessary to reduce the weight of the score of such machines.
+	if numCPUsNeeded > cpuTopology.CPUsPerNode() && numCPUsNeeded <= cpuTopology.CPUsPerSocket() {
+		maxScore = int64(math.Ceil(math.Log(float64(maxScore)) * socketScoreWeight))
+	}
+
+	return maxScore
+}
+
+// The used capacity is calculated on a scale of 0-MaxNodeScore (MaxNodeScore is
+// constant with value set to 100).
+// 0 being the lowest priority and 100 being the highest.
+// The more resources are used the higher the score is. This function
+// is almost a reversed version of leastRequestedScore.
+func mostRequestedScore(requested, capacity int64) int64 {
+	if capacity == 0 {
+		return 0
+	}
+	if requested > capacity {
+		return 0
+	}
+
+	return (requested * framework.MaxNodeScore) / capacity
+}
+
+// The unused capacity is calculated on a scale of 0-MaxNodeScore
+// 0 being the lowest priority and `MaxNodeScore` being the highest.
+// The more unused resources the higher the score is.
+func leastRequestedScore(requested, capacity int64) int64 {
+	if capacity == 0 {
+		return 0
+	}
+	if requested > capacity {
+		return 0
+	}
+
+	return ((capacity - requested) * framework.MaxNodeScore) / capacity
+}
+
+func (c *resourceManager) Update(nodeName string, allocation *PodAllocation) {
+	topologyOptions := c.topologyManager.GetTopologyOptions(nodeName)
+	if topologyOptions.CPUTopology == nil || !topologyOptions.CPUTopology.IsValid() {
+		return
+	}
+
+	nodeAllocation := c.getOrCreateNodeAllocation(nodeName)
+	nodeAllocation.lock.Lock()
+	defer nodeAllocation.lock.Unlock()
+
+	nodeAllocation.update(allocation, topologyOptions.CPUTopology)
+}
+
+func (c *resourceManager) Release(nodeName string, podUID types.UID) {
+	nodeAllocation := c.getOrCreateNodeAllocation(nodeName)
+	nodeAllocation.lock.Lock()
+	defer nodeAllocation.lock.Unlock()
+	nodeAllocation.release(podUID)
+}
+
+func (c *resourceManager) GetAllocatedCPUSet(nodeName string, podUID types.UID) (cpuset.CPUSet, bool) {
+	nodeAllocation := c.getOrCreateNodeAllocation(nodeName)
+	nodeAllocation.lock.RLock()
+	defer nodeAllocation.lock.RUnlock()
+
+	return nodeAllocation.getCPUs(podUID)
+}
+
+func (c *resourceManager) getNUMAAllocateStrategy(node *corev1.Node) schedulingconfig.NUMAAllocateStrategy {
+	numaAllocateStrategy := c.numaAllocateStrategy
+	if val := schedulingconfig.NUMAAllocateStrategy(node.Labels[extension.LabelNodeNUMAAllocateStrategy]); val != "" {
+		numaAllocateStrategy = val
+	}
+	return numaAllocateStrategy
+}
+
+func (c *resourceManager) GetAvailableCPUs(nodeName string, preferredCPUs cpuset.CPUSet) (availableCPUs cpuset.CPUSet, allocated CPUDetails, err error) {
+	topologyOptions := c.topologyManager.GetTopologyOptions(nodeName)
+	if topologyOptions.CPUTopology == nil {
+		return cpuset.NewCPUSet(), nil, errors.New(ErrNotFoundCPUTopology)
+	}
+	if !topologyOptions.CPUTopology.IsValid() {
+		return cpuset.NewCPUSet(), nil, fmt.Errorf("cpuTopology is invalid")
+	}
+
+	allocation := c.getOrCreateNodeAllocation(nodeName)
+	allocation.lock.RLock()
+	defer allocation.lock.RUnlock()
+	availableCPUs, allocated = allocation.getAvailableCPUs(topologyOptions.CPUTopology, topologyOptions.MaxRefCount, topologyOptions.ReservedCPUs, preferredCPUs)
+	return availableCPUs, allocated, nil
+}
+
+func (c *resourceManager) GetNodeAllocation(nodeName string) *NodeAllocation {
+	return c.getOrCreateNodeAllocation(nodeName)
+}
+
+func (c *resourceManager) getAvailableNUMANodeResources(nodeName string) (totalAvailable, totalAllocated map[int]corev1.ResourceList, err error) {
+	topologyOptions := c.topologyManager.GetTopologyOptions(nodeName)
+	nodeAllocation := c.getOrCreateNodeAllocation(nodeName)
+	nodeAllocation.lock.RLock()
+	defer nodeAllocation.lock.RUnlock()
+	totalAvailable, totalAllocated = nodeAllocation.getAvailableNUMANodeResources(topologyOptions)
+	return totalAvailable, totalAllocated, nil
+}
+
+func generateCPUSetHints(topology *CPUTopology, availableCPUs cpuset.CPUSet, reusableCPUs cpuset.CPUSet, request int) []topologymanager.NUMATopologyHint {
+	// Initialize minAffinitySize to include all NUMA Nodes.
+	numaNodes := topology.CPUDetails.NUMANodes().ToSlice()
+	minAffinitySize := len(numaNodes)
+
+	// Iterate through all combinations of numa nodes bitmask and build hints from them.
+	var hints []topologymanager.NUMATopologyHint
+	bitmask.IterateBitMasks(numaNodes, func(mask bitmask.BitMask) {
+		// First, update minAffinitySize for the current request size.
+		cpusInMask := topology.CPUDetails.CPUsInNUMANodes(mask.GetBits()...).Size()
+		if cpusInMask >= request && mask.Count() < minAffinitySize {
+			minAffinitySize = mask.Count()
+		}
+
+		// Then check to see if we have enough CPUs available on the current
+		// numa node bitmask to satisfy the CPU request.
+		numMatching := 0
+		for _, c := range reusableCPUs.ToSlice() {
+			// Disregard this mask if its NUMANode isn't part of it.
+			if !mask.IsSet(topology.CPUDetails[c].NodeID) {
+				return
+			}
+			numMatching++
+		}
+
+		// Finally, check to see if enough available CPUs remain on the current
+		// NUMA node combination to satisfy the CPU request.
+		for _, c := range availableCPUs.ToSlice() {
+			if mask.IsSet(topology.CPUDetails[c].NodeID) {
+				numMatching++
+			}
+		}
+
+		// If they don't, then move onto the next combination.
+		if numMatching < request {
+			return
+		}
+
+		// Otherwise, create a new hint from the numa node bitmask and add it to the
+		// list of hints.  We set all hint preferences to 'false' on the first
+		// pass through.
+		hints = append(hints, topologymanager.NUMATopologyHint{
+			NUMANodeAffinity: mask,
+			Preferred:        false,
+		})
+	})
+
+	// Loop back through all hints and update the 'Preferred' field based on
+	// counting the number of bits sets in the affinity mask and comparing it
+	// to the minAffinitySize. Only those with an equal number of bits set (and
+	// with a minimal set of numa nodes) will be considered preferred.
+	for i := range hints {
+		if hints[i].NUMANodeAffinity.Count() == minAffinitySize {
+			hints[i].Preferred = true
+		}
+	}
+
+	return hints
+}
+
+func generateResourceHints(numaNodes []int, podRequests corev1.ResourceList, totalAvailable map[int]corev1.ResourceList) map[string][]topologymanager.NUMATopologyHint {
+	// Initialize minAffinitySize to include all NUMA Cells.
+	minAffinitySize := len(numaNodes)
+
+	hints := map[string][]topologymanager.NUMATopologyHint{}
+	bitmask.IterateBitMasks(numaNodes, func(mask bitmask.BitMask) {
+		maskBits := mask.GetBits()
+
+		available := make(corev1.ResourceList)
+		for _, nodeID := range maskBits {
+			available = quotav1.Add(available, totalAvailable[nodeID])
+		}
+		if satisfied, _ := quotav1.LessThanOrEqual(podRequests, available); !satisfied {
+			return
+		}
+
+		// set the minimum amount of NUMA nodes that can satisfy the container resources requests
+		if mask.Count() < minAffinitySize {
+			minAffinitySize = mask.Count()
+		}
+
+		// add the node mask as topology hint for all memory types
+		for resourceName := range podRequests {
+			if _, ok := hints[string(resourceName)]; !ok {
+				hints[string(resourceName)] = []topologymanager.NUMATopologyHint{}
+			}
+			hints[string(resourceName)] = append(hints[string(resourceName)], topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: mask,
+				Preferred:        false,
+			})
+		}
+	})
+
+	// update hints preferred according to multiNUMAGroups, in case when it wasn't provided, the default
+	// behavior to prefer the minimal amount of NUMA nodes will be used
+	for resourceName := range podRequests {
+		for i, hint := range hints[string(resourceName)] {
+			hints[string(resourceName)][i].Preferred = len(hint.NUMANodeAffinity.GetBits()) == minAffinitySize
+		}
+	}
+
+	return hints
+}
diff --git a/pkg/scheduler/plugins/nodenumaresource/topology_eventhandler.go b/pkg/scheduler/plugins/nodenumaresource/topology_eventhandler.go
index 15126acb..231265e7 100644
--- a/pkg/scheduler/plugins/nodenumaresource/topology_eventhandler.go
+++ b/pkg/scheduler/plugins/nodenumaresource/topology_eventhandler.go
@@ -24,19 +24,25 @@ import (
 	nrtinformers "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/generated/informers/externalversions"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
 	"k8s.io/kubernetes/pkg/scheduler/framework"
 
-	"github.com/koordinator-sh/koordinator/apis/extension"
 	frameworkexthelper "github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/helper"
-	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
 )
 
 type nodeResourceTopologyEventHandler struct {
-	topologyManager CPUTopologyManager
+	topologyManager TopologyOptionsManager
 }
 
-func registerNodeResourceTopologyEventHandler(handle framework.Handle, topologyManager CPUTopologyManager) error {
+func registerNodeResourceTopologyEventHandler(informerFactory nrtinformers.SharedInformerFactory, topologyManager TopologyOptionsManager) error {
+	nodeResTopologyInformer := informerFactory.Topology().V1alpha1().NodeResourceTopologies().Informer()
+	eventHandler := &nodeResourceTopologyEventHandler{
+		topologyManager: topologyManager,
+	}
+	frameworkexthelper.ForceSyncFromInformer(context.TODO().Done(), informerFactory, nodeResTopologyInformer, eventHandler)
+	return nil
+}
+
+func initNRTInformerFactory(handle framework.Handle) (nrtinformers.SharedInformerFactory, error) {
 	nrtClient, ok := handle.(nrtclientset.Interface)
 	if !ok {
 		kubeConfig := *handle.KubeConfig()
@@ -45,17 +51,12 @@ func registerNodeResourceTopologyEventHandler(handle framework.Handle, topologyM
 		var err error
 		nrtClient, err = nrtclientset.NewForConfig(&kubeConfig)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
 	nodeResTopologyInformerFactory := nrtinformers.NewSharedInformerFactoryWithOptions(nrtClient, 0)
-	nodeResTopologyInformer := nodeResTopologyInformerFactory.Topology().V1alpha1().NodeResourceTopologies().Informer()
-	eventHandler := &nodeResourceTopologyEventHandler{
-		topologyManager: topologyManager,
-	}
-	frameworkexthelper.ForceSyncFromInformer(context.TODO().Done(), nodeResTopologyInformerFactory, nodeResTopologyInformer, eventHandler)
-	return nil
+	return nodeResTopologyInformerFactory, nil
 }
 
 func (m *nodeResourceTopologyEventHandler) OnAdd(obj interface{}) {
@@ -101,84 +102,12 @@ func (m *nodeResourceTopologyEventHandler) OnDelete(obj interface{}) {
 }
 
 func (m *nodeResourceTopologyEventHandler) updateNodeResourceTopology(oldNodeResTopology, newNodeResTopology *nrtv1alpha1.NodeResourceTopology) {
-	podCPUAllocs, err := extension.GetPodCPUAllocs(newNodeResTopology.Annotations)
-	if err != nil {
-		klog.Errorf("Failed to GetPodCPUAllocs from new NodeResourceTopology %s, err: %v", newNodeResTopology.Name, err)
-	}
-
-	kubeletPolicy, err := extension.GetKubeletCPUManagerPolicy(newNodeResTopology.Annotations)
-	if err != nil {
-		klog.Errorf("Failed to GetKubeletCPUManagerPolicy from NodeResourceTopology %s, err: %v", newNodeResTopology.Name, err)
-	}
-	var kubeletReservedCPUs cpuset.CPUSet
-	if kubeletPolicy != nil {
-		kubeletReservedCPUs, err = cpuset.Parse(kubeletPolicy.ReservedCPUs)
-		if err != nil {
-			klog.Errorf("Failed to Parse kubelet reserved CPUs %s, err: %v", kubeletPolicy.ReservedCPUs, err)
-		}
-	}
-
-	// remove cpus reserved by node.annotation.
-	reservedCPUsString, _ := extension.GetReservedCPUs(newNodeResTopology.Annotations)
-	nodeReservationReservedCPUs, err := cpuset.Parse(reservedCPUsString)
-	if err != nil {
-		klog.Errorf("Failed to parse nodeResourceResource reservedCPUs, name: %v, err: %v", newNodeResTopology.Name, err)
-	}
-	reportedCPUTopology, err := extension.GetCPUTopology(newNodeResTopology.Annotations)
-	if err != nil {
-		klog.Errorf("Failed to GetCPUTopology, name: %s, err: %v", newNodeResTopology.Name, err)
-	}
-
-	// reservedCPUs = cpus(all) - cpus(guaranteed) - cpus(kubeletReserved) - cpus(nodeReservationReserved) - cpus(systemQOSReserved)
-	cpuTopology := convertCPUTopology(reportedCPUTopology)
-	reservedCPUs := m.getPodAllocsCPUSet(podCPUAllocs)
-	reservedCPUs = reservedCPUs.Union(kubeletReservedCPUs)
-	reservedCPUs = reservedCPUs.Union(nodeReservationReservedCPUs)
-	systemQOSResource, err := extension.GetSystemQOSResource(newNodeResTopology.Annotations)
-	if err != nil {
-		klog.Errorf("Failed to GetSystemQOSResource, name: %v, err: %v", newNodeResTopology.Name, err)
-	} else if systemQOSResource != nil && systemQOSResource.IsCPUSetExclusive() {
-		cpus, err := cpuset.Parse(systemQOSResource.CPUSet)
-		if err != nil {
-			klog.Errorf("Failed to parse systemQOSResource.CPUSet, name: %s, err: %v", newNodeResTopology.Name, err)
-		} else {
-			reservedCPUs = reservedCPUs.Union(cpus)
-		}
-	}
+	topologyOpts := NewTopologyOptions(newNodeResTopology)
 
 	nodeName := newNodeResTopology.Name
-	m.topologyManager.UpdateCPUTopologyOptions(nodeName, func(options *CPUTopologyOptions) {
-		*options = CPUTopologyOptions{
-			CPUTopology:  cpuTopology,
-			ReservedCPUs: reservedCPUs,
-			Policy:       kubeletPolicy,
-			MaxRefCount:  options.MaxRefCount,
-		}
+	m.topologyManager.UpdateTopologyOptions(nodeName, func(options *TopologyOptions) {
+		// Give other plugins a chance to customize a different MaxRefCount
+		topologyOpts.MaxRefCount = options.MaxRefCount
+		*options = topologyOpts
 	})
 }
-
-func (m *nodeResourceTopologyEventHandler) getPodAllocsCPUSet(podCPUAllocs extension.PodCPUAllocs) cpuset.CPUSet {
-	if len(podCPUAllocs) == 0 {
-		return cpuset.CPUSet{}
-	}
-	builder := cpuset.NewCPUSetBuilder()
-	for _, v := range podCPUAllocs {
-		if !v.ManagedByKubelet || v.UID == "" || v.CPUSet == "" {
-			continue
-		}
-		cpuset, err := cpuset.Parse(v.CPUSet)
-		if err != nil || cpuset.IsEmpty() {
-			continue
-		}
-		builder.Add(cpuset.ToSliceNoSort()...)
-	}
-	return builder.Result()
-}
-
-func convertCPUTopology(reportedCPUTopology *extension.CPUTopology) *CPUTopology {
-	builder := NewCPUTopologyBuilder()
-	for _, info := range reportedCPUTopology.Detail {
-		builder.AddCPUInfo(int(info.Socket), int(info.Node), int(info.Core), int(info.ID))
-	}
-	return builder.Result()
-}
diff --git a/pkg/scheduler/plugins/nodenumaresource/topology_hint.go b/pkg/scheduler/plugins/nodenumaresource/topology_hint.go
new file mode 100644
index 00000000..fc8d8d8d
--- /dev/null
+++ b/pkg/scheduler/plugins/nodenumaresource/topology_hint.go
@@ -0,0 +1,102 @@
+/*
+Copyright 2022 The Koordinator 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.
+*/
+
+package nodenumaresource
+
+import (
+	"context"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/kubernetes/pkg/scheduler/framework"
+
+	apiext "github.com/koordinator-sh/koordinator/apis/extension"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
+)
+
+func (p *Plugin) FilterByNUMANode(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string, policyType apiext.NUMATopologyPolicy) *framework.Status {
+	if policyType == apiext.NUMATopologyPolicyNone {
+		return nil
+	}
+	topologyOptions := p.topologyOptionsManager.GetTopologyOptions(nodeName)
+	numaNodes := topologyOptions.getNUMANodes()
+	if len(numaNodes) == 0 {
+		return framework.NewStatus(framework.UnschedulableAndUnresolvable, "node(s) missing NUMA resources")
+	}
+	return p.handle.(frameworkext.FrameworkExtender).RunNUMATopologyManagerAllocate(ctx, cycleState, pod, nodeName, numaNodes, policyType, false)
+}
+
+func (p *Plugin) ReserveByNUMANode(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string, policyType apiext.NUMATopologyPolicy) *framework.Status {
+	if policyType == apiext.NUMATopologyPolicyNone {
+		return nil
+	}
+	topologyOptions := p.topologyOptionsManager.GetTopologyOptions(nodeName)
+	numaNodes := topologyOptions.getNUMANodes()
+	if len(numaNodes) == 0 {
+		return framework.NewStatus(framework.UnschedulableAndUnresolvable, "node(s) missing NUMA resources")
+	}
+	return p.handle.(frameworkext.FrameworkExtender).RunNUMATopologyManagerAllocate(ctx, cycleState, pod, nodeName, numaNodes, policyType, true)
+}
+
+func (p *Plugin) GetPodTopologyHints(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) (map[string][]topologymanager.NUMATopologyHint, *framework.Status) {
+	state, status := getPreFilterState(cycleState)
+	if !status.IsSuccess() {
+		return nil, status
+	}
+	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
+	if err != nil {
+		return nil, framework.AsStatus(err)
+	}
+	node := nodeInfo.Node()
+
+	resourceOptions, err := p.getResourceOptions(cycleState, state, node, pod, topologymanager.NUMATopologyHint{})
+	if err != nil {
+		return nil, framework.AsStatus(err)
+	}
+	hints, err := p.resourceManager.GetTopologyHints(node, pod, resourceOptions)
+	if err != nil {
+		return nil, framework.NewStatus(framework.Unschedulable, "node(s) Insufficient NUMA Node resources")
+	}
+	return hints, nil
+}
+
+func (p *Plugin) Allocate(ctx context.Context, cycleState *framework.CycleState, affinity topologymanager.NUMATopologyHint, pod *corev1.Pod, nodeName string, assume bool) *framework.Status {
+	state, status := getPreFilterState(cycleState)
+	if !status.IsSuccess() {
+		return status
+	}
+
+	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
+	if err != nil {
+		return framework.AsStatus(err)
+	}
+	node := nodeInfo.Node()
+
+	resourceOptions, err := p.getResourceOptions(cycleState, state, node, pod, affinity)
+	if err != nil {
+		return framework.AsStatus(err)
+	}
+	result, err := p.resourceManager.Allocate(node, pod, resourceOptions)
+	if err != nil {
+		return framework.AsStatus(err)
+	}
+	if assume {
+		p.resourceManager.Update(nodeName, result)
+		state.allocation = result
+		state.preferredCPUBindPolicy = resourceOptions.cpuBindPolicy
+	}
+	return nil
+}
diff --git a/pkg/scheduler/plugins/nodenumaresource/topology_options.go b/pkg/scheduler/plugins/nodenumaresource/topology_options.go
new file mode 100644
index 00000000..725daa6e
--- /dev/null
+++ b/pkg/scheduler/plugins/nodenumaresource/topology_options.go
@@ -0,0 +1,229 @@
+/*
+Copyright 2022 The Koordinator 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.
+*/
+
+package nodenumaresource
+
+import (
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+
+	nrtv1alpha1 "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/klog/v2"
+
+	"github.com/koordinator-sh/koordinator/apis/extension"
+	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
+)
+
+// TopologyOptionsManager manages the System Topology and resource assignments options.
+type TopologyOptionsManager interface {
+	GetTopologyOptions(nodeName string) TopologyOptions
+	UpdateTopologyOptions(nodeName string, updateFn func(options *TopologyOptions))
+	Delete(nodeName string)
+}
+
+type TopologyOptions struct {
+	CPUTopology        *CPUTopology                       `json:"cpuTopology"`
+	ReservedCPUs       cpuset.CPUSet                      `json:"reservedCPUs"`
+	MaxRefCount        int                                `json:"maxRefCount"`
+	Policy             *extension.KubeletCPUManagerPolicy `json:"policy,omitempty"`
+	NUMATopologyPolicy extension.NUMATopologyPolicy       `json:"numaTopologyPolicy"`
+	NUMANodeResources  []NUMANodeResource                 `json:"numaNodeResources"`
+}
+
+type NUMANodeResource struct {
+	Node      int                 `json:"node"`
+	Resources corev1.ResourceList `json:"resources,omitempty"`
+}
+
+type topologyManager struct {
+	lock            sync.Mutex
+	topologyOptions map[string]TopologyOptions
+}
+
+func NewTopologyOptionsManager() TopologyOptionsManager {
+	manager := &topologyManager{
+		topologyOptions: map[string]TopologyOptions{},
+	}
+	return manager
+}
+
+func (m *topologyManager) GetTopologyOptions(nodeName string) TopologyOptions {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+	return m.topologyOptions[nodeName]
+}
+
+func (m *topologyManager) UpdateTopologyOptions(nodeName string, updateFn func(options *TopologyOptions)) {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+	options := m.topologyOptions[nodeName]
+	updateFn(&options)
+	if options.MaxRefCount == 0 {
+		options.MaxRefCount = 1
+	}
+	m.topologyOptions[nodeName] = options
+}
+
+func (m *topologyManager) Delete(nodeName string) {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+	delete(m.topologyOptions, nodeName)
+}
+
+func NewTopologyOptions(nrt *nrtv1alpha1.NodeResourceTopology) TopologyOptions {
+	podCPUAllocs, err := extension.GetPodCPUAllocs(nrt.Annotations)
+	if err != nil {
+		klog.Errorf("Failed to GetPodCPUAllocs from new NodeResourceTopology %s, err: %v", nrt.Name, err)
+	}
+
+	kubeletPolicy, err := extension.GetKubeletCPUManagerPolicy(nrt.Annotations)
+	if err != nil {
+		klog.Errorf("Failed to GetKubeletCPUManagerPolicy from NodeResourceTopology %s, err: %v", nrt.Name, err)
+	}
+	var kubeletReservedCPUs cpuset.CPUSet
+	if kubeletPolicy != nil {
+		kubeletReservedCPUs, err = cpuset.Parse(kubeletPolicy.ReservedCPUs)
+		if err != nil {
+			klog.Errorf("Failed to Parse kubelet reserved CPUs %s, err: %v", kubeletPolicy.ReservedCPUs, err)
+		}
+	}
+
+	// remove cpus reserved by node.annotation.
+	reservedCPUsString, _ := extension.GetReservedCPUs(nrt.Annotations)
+	nodeReservationReservedCPUs, err := cpuset.Parse(reservedCPUsString)
+	if err != nil {
+		klog.Errorf("Failed to parse nodeResourceResource reservedCPUs, name: %v, err: %v", nrt.Name, err)
+	}
+	reportedCPUTopology, err := extension.GetCPUTopology(nrt.Annotations)
+	if err != nil {
+		klog.Errorf("Failed to GetCPUTopology, name: %s, err: %v", nrt.Name, err)
+	}
+
+	// reservedCPUs = cpus(all) - cpus(guaranteed) - cpus(kubeletReserved) - cpus(nodeReservationReserved) - cpus(systemQOSReserved)
+	cpuTopology := convertCPUTopology(reportedCPUTopology)
+	reservedCPUs := getPodAllocsCPUSet(podCPUAllocs)
+	reservedCPUs = reservedCPUs.Union(kubeletReservedCPUs)
+	reservedCPUs = reservedCPUs.Union(nodeReservationReservedCPUs)
+	systemQOSResource, err := extension.GetSystemQOSResource(nrt.Annotations)
+	if err != nil {
+		klog.Errorf("Failed to GetSystemQOSResource, name: %v, err: %v", nrt.Name, err)
+	} else if systemQOSResource != nil && systemQOSResource.IsCPUSetExclusive() {
+		cpus, err := cpuset.Parse(systemQOSResource.CPUSet)
+		if err != nil {
+			klog.Errorf("Failed to parse systemQOSResource.CPUSet, name: %s, err: %v", nrt.Name, err)
+		} else {
+			reservedCPUs = reservedCPUs.Union(cpus)
+		}
+	}
+
+	policy := convertToNUMATopologyPolicy(nrt)
+	numaNodeResources := extractNUMANodeResources(nrt)
+
+	return TopologyOptions{
+		CPUTopology:        cpuTopology,
+		ReservedCPUs:       reservedCPUs,
+		Policy:             kubeletPolicy,
+		MaxRefCount:        1,
+		NUMATopologyPolicy: policy,
+		NUMANodeResources:  numaNodeResources,
+	}
+}
+
+func getPodAllocsCPUSet(podCPUAllocs extension.PodCPUAllocs) cpuset.CPUSet {
+	if len(podCPUAllocs) == 0 {
+		return cpuset.CPUSet{}
+	}
+	builder := cpuset.NewCPUSetBuilder()
+	for _, v := range podCPUAllocs {
+		if !v.ManagedByKubelet || v.UID == "" || v.CPUSet == "" {
+			continue
+		}
+		cpuset, err := cpuset.Parse(v.CPUSet)
+		if err != nil || cpuset.IsEmpty() {
+			continue
+		}
+		builder.Add(cpuset.ToSliceNoSort()...)
+	}
+	return builder.Result()
+}
+
+func convertCPUTopology(reportedCPUTopology *extension.CPUTopology) *CPUTopology {
+	builder := NewCPUTopologyBuilder()
+	for _, info := range reportedCPUTopology.Detail {
+		builder.AddCPUInfo(int(info.Socket), int(info.Node), int(info.Core), int(info.ID))
+	}
+	return builder.Result()
+}
+
+func extractNUMANodeResources(nrt *nrtv1alpha1.NodeResourceTopology) []NUMANodeResource {
+	numaNodeResources := make([]NUMANodeResource, 0, len(nrt.Zones))
+	for i := range nrt.Zones {
+		zone := &nrt.Zones[i]
+		if zone.Type != "Node" {
+			continue
+		}
+		parts := strings.Split(zone.Name, "node-")
+		if len(parts) != 2 {
+			continue
+		}
+		nodeID, err := strconv.Atoi(parts[1])
+		if err != nil {
+			klog.ErrorS(err, "Failed to parse zone name of NodeResourceTopology", "node", nrt.Name, "zoneName", zone.Name)
+			continue
+		}
+		resources := make(corev1.ResourceList)
+		for _, res := range zone.Resources {
+			resName := corev1.ResourceName(res.Name)
+			resources[resName] = res.Allocatable
+		}
+		numaNodeResources = append(numaNodeResources, NUMANodeResource{
+			Node:      nodeID,
+			Resources: resources,
+		})
+	}
+	sort.Slice(numaNodeResources, func(i, j int) bool {
+		return numaNodeResources[i].Node < numaNodeResources[j].Node
+	})
+	return numaNodeResources
+}
+
+func convertToNUMATopologyPolicy(nrt *nrtv1alpha1.NodeResourceTopology) extension.NUMATopologyPolicy {
+	for _, policy := range nrt.TopologyPolicies {
+		switch nrtv1alpha1.TopologyManagerPolicy(policy) {
+		case nrtv1alpha1.BestEffort:
+			return extension.NUMATopologyPolicyBestEffort
+		case nrtv1alpha1.Restricted:
+			return extension.NUMATopologyPolicyRestricted
+		case nrtv1alpha1.SingleNUMANodePodLevel:
+			return extension.NUMATopologyPolicySingleNUMANode
+		}
+	}
+	return extension.NUMATopologyPolicyNone
+}
+
+func (opts *TopologyOptions) getNUMANodes() []int {
+	if len(opts.NUMANodeResources) == 0 {
+		return nil
+	}
+	nodes := make([]int, 0, len(opts.NUMANodeResources))
+	for _, v := range opts.NUMANodeResources {
+		nodes = append(nodes, v.Node)
+	}
+	return nodes
+}
diff --git a/pkg/slo-controller/noderesource/plugins/batchresource/plugin.go b/pkg/slo-controller/noderesource/plugins/batchresource/plugin.go
index b9fafea4..9289767b 100644
--- a/pkg/slo-controller/noderesource/plugins/batchresource/plugin.go
+++ b/pkg/slo-controller/noderesource/plugins/batchresource/plugin.go
@@ -81,6 +81,11 @@ func (p *Plugin) Reset(node *corev1.Node, message string) []framework.ResourceIt
 
 // Calculate calculates Batch resources using the formula below:
 // Node.Total - Node.Reserved - System.Used - Pod(High-Priority).Used, System.Used = Node.Used - Pod(All).Used.
+// As node and podList are the nearly latest state at time T1, the resourceMetrics are the node metric and pod
+// metrics collected and snapshot at time T0 (T0 < T1). There can be gaps between the states of T0 and T1.
+// We firstly calculate an infimum of the batch allocatable at time T0.
+// `BatchAllocatable0 = NodeAllocatable * ratio - SystemUsed0 - Pod(HP and in Pods1).Used0` - Pod(not in Pods1).Used0.
+// Then we minus the sum requests of the pods newly scheduled but have not been reported metrics to give a safe result.
 func (p *Plugin) Calculate(strategy *configuration.ColocationStrategy, node *corev1.Node, podList *corev1.PodList,
 	resourceMetrics *framework.ResourceMetrics) ([]framework.ResourceItem, error) {
 	if strategy == nil || node == nil || podList == nil || resourceMetrics == nil || resourceMetrics.NodeMetric == nil {
@@ -96,15 +101,18 @@ func (p *Plugin) Calculate(strategy *configuration.ColocationStrategy, node *cor
 
 	// compute the requests and usages according to the pods' priority classes.
 	// HP means High-Priority (i.e. not Batch or Free) pods
-	// pod(HP).Used = pod(All).Used - pod(Batch/Free).Used
-	podAllUsed := util.NewZeroResourceList()
 	podHPRequest := util.NewZeroResourceList()
 	podHPUsed := util.NewZeroResourceList()
+	// podAllUsed is the sum usage of all pods reported in NodeMetric.
+	// podKnownUsed is the sum usage of pods which are both reported in NodeMetric and shown in current pod list.
+	podAllUsed := util.NewZeroResourceList()
+	podKnownUsed := util.NewZeroResourceList()
 
 	nodeMetric := resourceMetrics.NodeMetric
 	podMetricMap := make(map[string]*slov1alpha1.PodMetricInfo)
 	for _, podMetric := range nodeMetric.Status.PodsMetric {
 		podMetricMap[util.GetPodMetricKey(podMetric)] = podMetric
+		podAllUsed = quotav1.Add(podAllUsed, getPodMetricUsage(podMetric))
 	}
 
 	for i := range podList.Items {
@@ -113,36 +121,39 @@ func (p *Plugin) Calculate(strategy *configuration.ColocationStrategy, node *cor
 			continue
 		}
 
+		// check if the pod has metrics
+		podKey := util.GetPodKey(pod)
+		podMetric, hasMetric := podMetricMap[podKey]
+		if hasMetric {
+			podKnownUsed = quotav1.Add(podKnownUsed, getPodMetricUsage(podMetric))
+		}
+
+		// count the high-priority usage
 		priorityClass := extension.GetPodPriorityClassWithDefault(pod)
 		podRequest := util.GetPodRequest(pod, corev1.ResourceCPU, corev1.ResourceMemory)
 		isPodHighPriority := priorityClass != extension.PriorityBatch && priorityClass != extension.PriorityFree
-		if isPodHighPriority {
-			podHPRequest = quotav1.Add(podHPRequest, podRequest)
-		}
-		podKey := util.GetPodKey(pod)
-		podMetric, ok := podMetricMap[podKey]
-		if !ok {
-			if isPodHighPriority {
-				podHPUsed = quotav1.Add(podHPUsed, podRequest)
-			}
-			podAllUsed = quotav1.Add(podAllUsed, podRequest)
+		if !isPodHighPriority {
 			continue
 		}
-
-		if isPodHighPriority {
+		podHPRequest = quotav1.Add(podHPRequest, podRequest)
+		if hasMetric {
 			podHPUsed = quotav1.Add(podHPUsed, getPodMetricUsage(podMetric))
+		} else {
+			podHPUsed = quotav1.Add(podHPUsed, podRequest)
 		}
-		podAllUsed = quotav1.Add(podAllUsed, getPodMetricUsage(podMetric))
 	}
 
+	// For the pods reported with metrics but not shown in the current list, count them into the HP used.
+	podUnknownPriorityUsed := quotav1.Subtract(podAllUsed, podKnownUsed)
+	podHPUsed = quotav1.Add(podHPUsed, podUnknownPriorityUsed)
+	klog.V(6).InfoS("batch resource got unknown priority pods used", "node", node.Name,
+		"cpu", podUnknownPriorityUsed.Cpu().String(), "memory", podUnknownPriorityUsed.Memory().String())
+
 	nodeAllocatable := getNodeAllocatable(node)
 	nodeReservation := getNodeReservation(strategy, node)
 
-	// System.Used = Node.Used - Pod(All).Used
-	nodeUsage := getNodeMetricUsage(nodeMetric.Status.NodeMetric)
-	systemUsed := quotav1.Max(quotav1.Subtract(nodeUsage, podAllUsed), util.NewZeroResourceList())
-
-	// System.Used = max(System.Used, Node.Anno.Reserved)
+	// System.Used = max(Node.Used - Pod(All).Used, Node.Anno.Reserved)
+	systemUsed := getResourceListForCPUAndMemory(nodeMetric.Status.NodeMetric.SystemUsage.ResourceList)
 	nodeAnnoReserved := util.GetNodeReservationFromAnnotation(node.Annotations)
 	systemUsed = quotav1.Max(systemUsed, nodeAnnoReserved)
 
@@ -240,27 +251,12 @@ func prepareNodeForResource(node *corev1.Node, nr *framework.NodeResource, name
 
 // getPodMetricUsage gets pod usage from the PodMetricInfo
 func getPodMetricUsage(info *slov1alpha1.PodMetricInfo) corev1.ResourceList {
-	cpuQuant := info.PodUsage.ResourceList[corev1.ResourceCPU]
-	cpuUsageQuant := resource.NewMilliQuantity(cpuQuant.MilliValue(), cpuQuant.Format)
-	memQuant := info.PodUsage.ResourceList[corev1.ResourceMemory]
-	memUsageQuant := resource.NewQuantity(memQuant.Value(), memQuant.Format)
-	return corev1.ResourceList{corev1.ResourceCPU: *cpuUsageQuant, corev1.ResourceMemory: *memUsageQuant}
-}
-
-// getNodeMetricUsage gets node usage from the NodeMetricInfo
-func getNodeMetricUsage(info *slov1alpha1.NodeMetricInfo) corev1.ResourceList {
-	cpuQ := info.NodeUsage.ResourceList[corev1.ResourceCPU]
-	cpuUsageQ := resource.NewMilliQuantity(cpuQ.MilliValue(), cpuQ.Format)
-	memQ := info.NodeUsage.ResourceList[corev1.ResourceMemory]
-	memUsageQ := resource.NewQuantity(memQ.Value(), memQ.Format)
-	return corev1.ResourceList{corev1.ResourceCPU: *cpuUsageQ, corev1.ResourceMemory: *memUsageQ}
+	return getResourceListForCPUAndMemory(info.PodUsage.ResourceList)
 }
 
 // getNodeAllocatable gets node allocatable and filters out non-CPU and non-Mem resources
 func getNodeAllocatable(node *corev1.Node) corev1.ResourceList {
-	result := node.Status.Allocatable.DeepCopy()
-	result = quotav1.Mask(result, []corev1.ResourceName{corev1.ResourceCPU, corev1.ResourceMemory})
-	return result
+	return getResourceListForCPUAndMemory(node.Status.Allocatable)
 }
 
 // getNodeReservation gets node-level safe-guarding reservation with the node's allocatable
@@ -275,6 +271,10 @@ func getNodeReservation(strategy *configuration.ColocationStrategy, node *corev1
 	}
 }
 
+func getResourceListForCPUAndMemory(rl corev1.ResourceList) corev1.ResourceList {
+	return quotav1.Mask(rl, []corev1.ResourceName{corev1.ResourceCPU, corev1.ResourceMemory})
+}
+
 // getReserveRatio returns resource reserved ratio
 func getReserveRatio(reclaimThreshold int64) float64 {
 	return float64(100-reclaimThreshold) / 100.0
diff --git a/pkg/slo-controller/noderesource/resource_calculator.go b/pkg/slo-controller/noderesource/resource_calculator.go
index 61159a8f..f9f49788 100644
--- a/pkg/slo-controller/noderesource/resource_calculator.go
+++ b/pkg/slo-controller/noderesource/resource_calculator.go
@@ -59,12 +59,12 @@ func (r *NodeResourceReconciler) resetNodeResource(node *corev1.Node, message st
 func (r *NodeResourceReconciler) calculateNodeResource(node *corev1.Node,
 	nodeMetric *slov1alpha1.NodeMetric, podList *corev1.PodList) *framework.NodeResource {
 	nr := framework.NewNodeResource()
-	metrics := &framework.ResourceMetrics{
+	resourceMetrics := &framework.ResourceMetrics{
 		NodeMetric: nodeMetric,
 	}
 
 	strategy := sloconfig.GetNodeColocationStrategy(r.cfgCache.GetCfgCopy(), node)
-	framework.RunResourceCalculateExtenders(nr, strategy, node, podList, metrics)
+	framework.RunResourceCalculateExtenders(nr, strategy, node, podList, resourceMetrics)
 
 	return nr
 }
diff --git a/pkg/webhook/elasticquota/quota_topology.go b/pkg/webhook/elasticquota/quota_topology.go
index 43cf5f24..382d3177 100644
--- a/pkg/webhook/elasticquota/quota_topology.go
+++ b/pkg/webhook/elasticquota/quota_topology.go
@@ -153,7 +153,7 @@ func (qt *quotaTopology) fillQuotaDefaultInformation(quota *v1alpha1.ElasticQuot
 		quota.Annotations = make(map[string]string)
 	}
 
-	if parentName, exist := quota.Labels[extension.LabelQuotaParent]; !exist || len(parentName) == 0 {
+	if parentName, exist := quota.Labels[extension.LabelQuotaParent]; (!exist || len(parentName) == 0) && quota.Name != extension.RootQuotaName {
 		quota.Labels[extension.LabelQuotaParent] = extension.RootQuotaName
 		klog.V(5).Infof("fill quota %v parent as root", quota.Name)
 	}
diff --git a/pkg/webhook/elasticquota/quota_topology_check.go b/pkg/webhook/elasticquota/quota_topology_check.go
index ce33e1d8..991f847c 100644
--- a/pkg/webhook/elasticquota/quota_topology_check.go
+++ b/pkg/webhook/elasticquota/quota_topology_check.go
@@ -127,6 +127,9 @@ func (qt *quotaTopology) checkParentQuotaInfo(quotaName, parentName string) erro
 }
 
 func (qt *quotaTopology) checkSubAndParentGroupMaxQuotaKeySame(quotaInfo *QuotaInfo) error {
+	if quotaInfo.Name == extension.RootQuotaName {
+		return nil
+	}
 	if quotaInfo.ParentName != extension.RootQuotaName {
 		parentInfo := qt.quotaInfoMap[quotaInfo.ParentName]
 		if !checkQuotaKeySame(parentInfo.CalculateInfo.Max, quotaInfo.CalculateInfo.Max) {



Test patch:
diff --git a/apis/extension/cpu_normalization_test.go b/apis/extension/cpu_normalization_test.go
new file mode 100644
index 00000000..4ac4be2f
--- /dev/null
+++ b/apis/extension/cpu_normalization_test.go
@@ -0,0 +1,317 @@
+/*
+Copyright 2022 The Koordinator 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.
+*/
+
+package extension
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestGetCPUNormalizationRatio(t *testing.T) {
+	tests := []struct {
+		name    string
+		arg     *corev1.Node
+		want    float64
+		wantErr bool
+	}{
+		{
+			name:    "node has no annotation",
+			arg:     &corev1.Node{},
+			want:    -1,
+			wantErr: false,
+		},
+		{
+			name: "node has no cpu-normalization annotation",
+			arg: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						"xxx": "yyy",
+					},
+				},
+			},
+			want:    -1,
+			wantErr: false,
+		},
+		{
+			name: "node has invalid cpu-normalization ratio format",
+			arg: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						"xxx":                           "yyy",
+						AnnotationCPUNormalizationRatio: "invalidValue",
+					},
+				},
+			},
+			want:    -1,
+			wantErr: true,
+		},
+		{
+			name: "node has valid cpu-normalization ratio",
+			arg: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						"xxx":                           "yyy",
+						AnnotationCPUNormalizationRatio: "1.20",
+					},
+				},
+			},
+			want:    1.2,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, gotErr := GetCPUNormalizationRatio(tt.arg)
+			assert.Equal(t, tt.want, got)
+			assert.Equal(t, tt.wantErr, gotErr != nil)
+		})
+	}
+}
+
+func TestSetCPUNormalizationRatio(t *testing.T) {
+	type args struct {
+		node  *corev1.Node
+		ratio float64
+	}
+	tests := []struct {
+		name      string
+		args      args
+		want      bool
+		wantField *corev1.Node
+	}{
+		{
+			name: "set ratio for a node without annotation",
+			args: args{
+				node:  &corev1.Node{},
+				ratio: 1.2,
+			},
+			want: true,
+			wantField: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						AnnotationCPUNormalizationRatio: "1.20",
+					},
+				},
+			},
+		},
+		{
+			name: "set ratio for a node has old ratio",
+			args: args{
+				node: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							AnnotationCPUNormalizationRatio: "1.10",
+						},
+					},
+				},
+				ratio: 1.15,
+			},
+			want: true,
+			wantField: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						AnnotationCPUNormalizationRatio: "1.15",
+					},
+				},
+			},
+		},
+		{
+			name: "keep ratio precision as 2 for the label value",
+			args: args{
+				node: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							AnnotationCPUNormalizationRatio: "1.00",
+						},
+					},
+				},
+				ratio: 1.125,
+			},
+			want: true,
+			wantField: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						AnnotationCPUNormalizationRatio: "1.12",
+					},
+				},
+			},
+		},
+		{
+			name: "keep the value unchanged",
+			args: args{
+				node: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							AnnotationCPUNormalizationRatio: "1.20",
+						},
+					},
+				},
+				ratio: 1.2,
+			},
+			want: false,
+			wantField: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						AnnotationCPUNormalizationRatio: "1.20",
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := SetCPUNormalizationRatio(tt.args.node, tt.args.ratio)
+			assert.Equal(t, tt.want, got)
+			assert.Equal(t, tt.wantField, tt.args.node)
+		})
+	}
+}
+
+func TestGetCPUBasicInfo(t *testing.T) {
+	tests := []struct {
+		name    string
+		arg     map[string]string
+		want    *CPUBasicInfo
+		wantErr bool
+	}{
+		{
+			name:    "nil annotation",
+			arg:     nil,
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "missing cpu basic info annotation",
+			arg: map[string]string{
+				"aaa": "bbb",
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "parse cpu basic info failed",
+			arg: map[string]string{
+				AnnotationCPUBasicInfo: "invalidValue",
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "get cpu basic info successfully",
+			arg: map[string]string{
+				AnnotationCPUBasicInfo: `{
+    "cpuModel": "CPU A 2.50GHz",
+    "hyperThreadEnabled": true,
+    "turboEnabled": false,
+    "vendorID": "unknown"
+}`,
+			},
+			want: &CPUBasicInfo{
+				CPUModel:           "CPU A 2.50GHz",
+				HyperThreadEnabled: true,
+				TurboEnabled:       false,
+				VendorID:           "unknown",
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, gotErr := GetCPUBasicInfo(tt.arg)
+			assert.Equal(t, tt.want, got)
+			assert.Equal(t, tt.wantErr, gotErr != nil)
+		})
+	}
+}
+
+func TestSetCPUBasicInfo(t *testing.T) {
+	testCPUBasicInfo := &CPUBasicInfo{
+		CPUModel:           "CPU A 2.50GHz",
+		HyperThreadEnabled: true,
+		TurboEnabled:       false,
+		VendorID:           "unknown",
+	}
+	testAnnotationBytes, err := json.Marshal(testCPUBasicInfo)
+	assert.NoError(t, err)
+	testCPUBasicInfo1 := &CPUBasicInfo{
+		CPUModel:           "CPU A 2.50GHz",
+		HyperThreadEnabled: true,
+		TurboEnabled:       true,
+		VendorID:           "unknown",
+	}
+	testAnnotationBytes1, err := json.Marshal(testCPUBasicInfo1)
+	assert.NoError(t, err)
+	type args struct {
+		annotations map[string]string
+		info        *CPUBasicInfo
+	}
+	tests := []struct {
+		name      string
+		args      args
+		want      bool
+		wantField map[string]string
+	}{
+		{
+			name: "annotation not change",
+			args: args{
+				annotations: map[string]string{
+					AnnotationCPUBasicInfo: string(testAnnotationBytes),
+				},
+				info: testCPUBasicInfo,
+			},
+			want: false,
+			wantField: map[string]string{
+				AnnotationCPUBasicInfo: string(testAnnotationBytes),
+			},
+		},
+		{
+			name: "annotation is new",
+			args: args{
+				annotations: map[string]string{},
+				info:        testCPUBasicInfo,
+			},
+			want: true,
+			wantField: map[string]string{
+				AnnotationCPUBasicInfo: string(testAnnotationBytes),
+			},
+		},
+		{
+			name: "annotation changes",
+			args: args{
+				annotations: map[string]string{
+					AnnotationCPUBasicInfo: string(testAnnotationBytes),
+				},
+				info: testCPUBasicInfo1,
+			},
+			want: true,
+			wantField: map[string]string{
+				AnnotationCPUBasicInfo: string(testAnnotationBytes1),
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := SetCPUBasicInfo(tt.args.annotations, tt.args.info)
+			assert.Equal(t, tt.want, got)
+			assert.Equal(t, tt.wantField, tt.args.annotations)
+		})
+	}
+}
diff --git a/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile_test.go b/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile_test.go
index fabddd74..2b30b0ad 100644
--- a/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile_test.go
+++ b/pkg/koordlet/qosmanager/plugins/resctrl/resctrl_reconcile_test.go
@@ -726,7 +726,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 	type args struct {
 		group        string
 		l3Num        int
-		basicCPUInfo koordletutil.CPUBasicInfo
+		basicCPUInfo extension.CPUBasicInfo
 		qosStrategy  *slov1alpha1.ResourceQOSStrategy
 	}
 	type field struct {
@@ -752,7 +752,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        LSResctrlGroup,
 				l3Num:        2,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					LSClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -772,7 +772,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        LSResctrlGroup,
 				l3Num:        2,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					BEClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -791,7 +791,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        LSResctrlGroup,
 				l3Num:        2,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					LSClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -813,7 +813,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        BEResctrlGroup,
 				l3Num:        4,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					BEClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -836,7 +836,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        BEResctrlGroup,
 				l3Num:        4,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					BEClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -855,7 +855,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        BEResctrlGroup,
 				l3Num:        2,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					LSClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -885,7 +885,7 @@ func TestResctrlReconcile_calculateAndApplyCatMbPolicyForGroup(t *testing.T) {
 			args: args{
 				group:        BEResctrlGroup,
 				l3Num:        1,
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				qosStrategy: &slov1alpha1.ResourceQOSStrategy{
 					LSClass: &slov1alpha1.ResourceQOS{
 						ResctrlQOS: &slov1alpha1.ResctrlQOSCfg{
@@ -1096,7 +1096,7 @@ func TestResctrlReconcile_reconcileCatResctrlPolicy(t *testing.T) {
 
 		metricCache := mock_metriccache.NewMockMetricCache(ctrl)
 		metricCache.EXPECT().Get(metriccache.NodeCPUInfoKey).Return(&metriccache.NodeCPUInfo{
-			BasicInfo: koordletutil.CPUBasicInfo{CatL3CbmMask: "7ff"},
+			BasicInfo: extension.CPUBasicInfo{CatL3CbmMask: "7ff"},
 			TotalInfo: koordletutil.CPUTotalInfo{L3ToCPU: map[int32][]koordletutil.ProcessorInfo{0: {}, 1: {}}},
 		}, true).Times(3)
 		opt := &framework.Options{
@@ -1133,19 +1133,19 @@ func TestResctrlReconcile_reconcileCatResctrlPolicy(t *testing.T) {
 
 		// log error for invalid l3 number
 		metricCache.EXPECT().Get(metriccache.NodeCPUInfoKey).Return(&metriccache.NodeCPUInfo{
-			BasicInfo: koordletutil.CPUBasicInfo{CatL3CbmMask: "7ff"},
+			BasicInfo: extension.CPUBasicInfo{CatL3CbmMask: "7ff"},
 			TotalInfo: koordletutil.CPUTotalInfo{},
 		}, true).Times(1)
 		r.reconcileCatResctrlPolicy(nodeSLO.Spec.ResourceQOSStrategy)
 
 		// log error for invalid l3 cbm
 		metricCache.EXPECT().Get(metriccache.NodeCPUInfoKey).Return(&metriccache.NodeCPUInfo{
-			BasicInfo: koordletutil.CPUBasicInfo{CatL3CbmMask: "invalid"},
+			BasicInfo: extension.CPUBasicInfo{CatL3CbmMask: "invalid"},
 			TotalInfo: koordletutil.CPUTotalInfo{L3ToCPU: map[int32][]koordletutil.ProcessorInfo{0: {}, 1: {}}},
 		}, true).Times(1)
 		r.reconcileCatResctrlPolicy(nodeSLO.Spec.ResourceQOSStrategy)
 		metricCache.EXPECT().Get(metriccache.NodeCPUInfoKey).Return(&metriccache.NodeCPUInfo{
-			BasicInfo: koordletutil.CPUBasicInfo{CatL3CbmMask: ""},
+			BasicInfo: extension.CPUBasicInfo{CatL3CbmMask: ""},
 			TotalInfo: koordletutil.CPUTotalInfo{L3ToCPU: map[int32][]koordletutil.ProcessorInfo{0: {}, 1: {}}},
 		}, true).Times(1)
 		r.reconcileCatResctrlPolicy(nodeSLO.Spec.ResourceQOSStrategy)
@@ -1312,7 +1312,7 @@ func TestResctrlReconcile_reconcile(t *testing.T) {
 		CgroupDir: "p0",
 	}
 	testingNodeCPUInfo := &metriccache.NodeCPUInfo{
-		BasicInfo: koordletutil.CPUBasicInfo{CatL3CbmMask: "7ff"},
+		BasicInfo: extension.CPUBasicInfo{CatL3CbmMask: "7ff"},
 		TotalInfo: koordletutil.CPUTotalInfo{L3ToCPU: map[int32][]koordletutil.ProcessorInfo{0: {}, 1: {}}},
 	}
 
@@ -1371,7 +1371,7 @@ func TestResctrlReconcile_reconcile(t *testing.T) {
 func Test_calculateMbaPercentForGroup(t *testing.T) {
 	type args struct {
 		group        string
-		basicCPUInfo koordletutil.CPUBasicInfo
+		basicCPUInfo extension.CPUBasicInfo
 		mbPercent    *int64
 	}
 	tests := []struct {
@@ -1383,7 +1383,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent not config",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 			},
 			want: "",
 		},
@@ -1391,7 +1391,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent value is invalid,not between (0,100]",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				mbPercent:    pointer.Int64(0),
 			},
 			want: "",
@@ -1400,7 +1400,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent value is invalid,not between (0,100]",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				mbPercent:    pointer.Int64(101),
 			},
 			want: "",
@@ -1409,7 +1409,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent value is invalid, not multiple of 10",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				mbPercent:    pointer.Int64(85),
 			},
 			want: "90",
@@ -1418,7 +1418,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent value is valid on intel",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: "GenuineIntel"},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: "GenuineIntel"},
 				mbPercent:    pointer.Int64(80),
 			},
 			want: "80",
@@ -1427,7 +1427,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent value is valid on amd",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
 				mbPercent:    pointer.Int64(80),
 			},
 			want: strconv.FormatInt(int64(0.8*AMDCCDMaxMBGbps), 10),
@@ -1436,7 +1436,7 @@ func Test_calculateMbaPercentForGroup(t *testing.T) {
 			name: "mbPercent value is unlimited on amd",
 			args: args{
 				group:        "BE",
-				basicCPUInfo: koordletutil.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
+				basicCPUInfo: extension.CPUBasicInfo{VendorID: system.AMD_VENDOR_ID},
 				mbPercent:    pointer.Int64(100),
 			},
 			want: AMDCCDUnlimitedMB,
diff --git a/pkg/scheduler/plugins/deviceshare/plugin_test.go b/pkg/scheduler/plugins/deviceshare/plugin_test.go
index ebd403b8..2a583140 100644
--- a/pkg/scheduler/plugins/deviceshare/plugin_test.go
+++ b/pkg/scheduler/plugins/deviceshare/plugin_test.go
@@ -718,7 +718,7 @@ func Test_Plugin_Filter(t *testing.T) {
 			pod:             &corev1.Pod{},
 			nodeDeviceCache: newNodeDeviceCache(),
 			nodeInfo:        testNodeInfo,
-			want:            framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrMissingDevice),
+			want:            nil,
 		},
 		{
 			name: "insufficient device resource 1",
@@ -1595,7 +1595,7 @@ func Test_Plugin_Reserve(t *testing.T) {
 				nodeName: "test-node",
 			},
 			wants: wants{
-				status: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrMissingDevice),
+				status: nil,
 			},
 		},
 		{
diff --git a/pkg/scheduler/plugins/elasticquota/core/group_quota_manager_test.go b/pkg/scheduler/plugins/elasticquota/core/group_quota_manager_test.go
index a0f6ebdc..912e90b5 100644
--- a/pkg/scheduler/plugins/elasticquota/core/group_quota_manager_test.go
+++ b/pkg/scheduler/plugins/elasticquota/core/group_quota_manager_test.go
@@ -54,7 +54,7 @@ func TestGroupQuotaManager_QuotaAdd(t *testing.T) {
 	}
 
 	assert.Equal(t, 6, len(gqm.runtimeQuotaCalculatorMap))
-	assert.Equal(t, 7, len(gqm.quotaInfoMap))
+	assert.Equal(t, 8, len(gqm.quotaInfoMap))
 	assert.Equal(t, 2, len(gqm.resourceKeys))
 }
 
@@ -143,7 +143,7 @@ func TestGroupQuotaManager_UpdateQuota(t *testing.T) {
 	gqm := NewGroupQuotaManagerForTest()
 	quota := CreateQuota("test1", "test-parent", 64, 100*GigaByte, 50, 80*GigaByte, true, false)
 	gqm.UpdateQuota(quota, false)
-	assert.Equal(t, len(gqm.quotaInfoMap), 3)
+	assert.Equal(t, len(gqm.quotaInfoMap), 4)
 }
 
 func TestGroupQuotaManager_UpdateQuotaInternalAndRequest(t *testing.T) {
@@ -180,7 +180,7 @@ func TestGroupQuotaManager_DeleteOneGroup(t *testing.T) {
 	quota2 := AddQuotaToManager(t, gqm, "test2", extension.RootQuotaName, 96, 160*GigaByte, 80, 80*GigaByte, true, false)
 	quota3 := AddQuotaToManager(t, gqm, "test3", extension.RootQuotaName, 96, 160*GigaByte, 40, 40*GigaByte, true, false)
 	assert.Equal(t, 4, len(gqm.runtimeQuotaCalculatorMap))
-	assert.Equal(t, 5, len(gqm.quotaInfoMap))
+	assert.Equal(t, 6, len(gqm.quotaInfoMap))
 
 	err := gqm.UpdateQuota(quota1, true)
 	assert.Nil(t, err)
@@ -198,13 +198,13 @@ func TestGroupQuotaManager_DeleteOneGroup(t *testing.T) {
 	assert.True(t, quotaInfo == nil)
 
 	assert.Equal(t, 1, len(gqm.runtimeQuotaCalculatorMap))
-	assert.Equal(t, 2, len(gqm.quotaInfoMap))
+	assert.Equal(t, 3, len(gqm.quotaInfoMap))
 
 	AddQuotaToManager(t, gqm, "youku", extension.RootQuotaName, 96, 160*GigaByte, 70, 70*GigaByte, true, false)
 	quotaInfo = gqm.GetQuotaInfoByName("youku")
 	assert.NotNil(t, quotaInfo)
 	assert.Equal(t, 2, len(gqm.runtimeQuotaCalculatorMap))
-	assert.Equal(t, 3, len(gqm.quotaInfoMap))
+	assert.Equal(t, 4, len(gqm.quotaInfoMap))
 }
 
 func TestGroupQuotaManager_UpdateQuotaDeltaRequest(t *testing.T) {
@@ -325,7 +325,7 @@ func TestGroupQuotaManager_MultiQuotaAdd(t *testing.T) {
 	AddQuotaToManager(t, gqm, "test2-sub2-1", "test2-sub2", 96, 160*GigaByte, 0, 0*GigaByte, true, false)
 
 	assert.Equal(t, 9, len(gqm.runtimeQuotaCalculatorMap))
-	assert.Equal(t, 10, len(gqm.quotaInfoMap))
+	assert.Equal(t, 11, len(gqm.quotaInfoMap))
 	assert.Equal(t, 2, len(gqm.resourceKeys))
 	assert.Equal(t, 2, len(gqm.runtimeQuotaCalculatorMap[extension.RootQuotaName].quotaTree[v1.ResourceCPU].quotaNodes))
 	assert.Equal(t, 2, len(gqm.runtimeQuotaCalculatorMap["test2"].quotaTree[v1.ResourceCPU].quotaNodes))
@@ -1088,8 +1088,9 @@ func NewGroupQuotaManagerForTest() *GroupQuotaManager {
 		scaleMinQuotaManager:                    NewScaleMinQuotaManager(),
 		quotaTopoNodeMap:                        make(map[string]*QuotaTopoNode),
 	}
-	quotaManager.quotaInfoMap[extension.SystemQuotaName] = NewQuotaInfo(false, true, extension.SystemQuotaName, "")
-	quotaManager.quotaInfoMap[extension.DefaultQuotaName] = NewQuotaInfo(false, true, extension.DefaultQuotaName, "")
+	quotaManager.quotaInfoMap[extension.SystemQuotaName] = NewQuotaInfo(false, true, extension.SystemQuotaName, extension.RootQuotaName)
+	quotaManager.quotaInfoMap[extension.DefaultQuotaName] = NewQuotaInfo(false, true, extension.DefaultQuotaName, extension.RootQuotaName)
+	quotaManager.quotaInfoMap[extension.RootQuotaName] = NewQuotaInfo(true, false, extension.RootQuotaName, "")
 	quotaManager.runtimeQuotaCalculatorMap[extension.RootQuotaName] = NewRuntimeQuotaCalculator(extension.RootQuotaName)
 	return quotaManager
 }
@@ -1255,7 +1256,7 @@ func TestGroupQuotaManager_OnPodUpdateAfterReserve(t *testing.T) {
 }
 
 func TestNewGroupQuotaManager(t *testing.T) {
-	gqm := NewGroupQuotaManager(createResourceList(100, 100), createResourceList(300, 300))
+	gqm := NewGroupQuotaManager(createResourceList(100, 100), createResourceList(300, 300), nil)
 	assert.Equal(t, createResourceList(100, 100), gqm.GetQuotaInfoByName(extension.SystemQuotaName).getMax())
 	assert.Equal(t, createResourceList(300, 300), gqm.GetQuotaInfoByName(extension.DefaultQuotaName).getMax())
 	assert.True(t, gqm.scaleMinQuotaEnabled)
@@ -1305,3 +1306,121 @@ func TestGroupQuotaManager_IsParent(t *testing.T) {
 	assert.False(t, qi2Info.IsParent)
 
 }
+
+func TestGroupQuotaManager_UpdateRootQuotaUsed(t *testing.T) {
+	expectedTotalUsed := createResourceList(0, 0)
+	gqm := NewGroupQuotaManagerForTest()
+	gqm.UpdateClusterTotalResource(createResourceList(1000, 1000))
+
+	sysUsed := createResourceList(10, 30)
+	expectedTotalUsed = quotav1.Add(expectedTotalUsed, sysUsed)
+	gqm.updateGroupDeltaUsedNoLock(extension.SystemQuotaName, sysUsed)
+	assert.Equal(t, sysUsed, gqm.GetQuotaInfoByName(extension.SystemQuotaName).GetUsed())
+
+	defaultUsed := createResourceList(2, 5)
+	expectedTotalUsed = quotav1.Add(expectedTotalUsed, defaultUsed)
+	gqm.updateGroupDeltaUsedNoLock(extension.DefaultQuotaName, defaultUsed)
+	assert.Equal(t, defaultUsed, gqm.GetQuotaInfoByName(extension.DefaultQuotaName).GetUsed())
+
+	//case1: no quota, root quota used
+	rootQuotaUsed := gqm.getQuotaInfoByNameNoLock(extension.RootQuotaName).GetUsed()
+	sysAndDefaultUsed := quotav1.Add(sysUsed, defaultUsed)
+	assert.Equal(t, rootQuotaUsed, sysAndDefaultUsed)
+
+	//case2: just pod no quota, root quota used
+	pod1 := schetesting.MakePod().Name("1").Obj()
+	pod1.Spec.Containers = []v1.Container{
+		{
+			Resources: v1.ResourceRequirements{
+				Requests: createResourceList(10, 10),
+			},
+		},
+	}
+	pod1Used := pod1.Spec.Containers[0].Resources.Requests
+	expectedTotalUsed = quotav1.Add(expectedTotalUsed, pod1Used)
+	gqm.updatePodCacheNoLock(extension.DefaultQuotaName, pod1, true)
+	assert.Equal(t, 1, len(gqm.GetQuotaInfoByName(extension.DefaultQuotaName).GetPodCache()))
+	gqm.updatePodIsAssignedNoLock(extension.DefaultQuotaName, pod1, true)
+	assert.True(t, gqm.getPodIsAssignedNoLock(extension.DefaultQuotaName, pod1))
+	gqm.updatePodUsedNoLock(extension.DefaultQuotaName, nil, pod1)
+	assert.Equal(t, expectedTotalUsed, gqm.GetQuotaInfoByName(extension.RootQuotaName).GetUsed())
+
+	// case3: when build quota tree, root quota used
+	qi1 := createQuota("1", extension.RootQuotaName, 20, 20, 10, 10)
+	qi2 := createQuota("2", extension.RootQuotaName, 30, 30, 8, 6)
+	qi3 := createQuota("3", "1", 30, 20, 8, 5)
+	gqm.UpdateQuota(qi1, false)
+	gqm.UpdateQuota(qi2, false)
+	gqm.UpdateQuota(qi3, false)
+	gqm.updateGroupDeltaUsedNoLock("2", createResourceList(5, 5))
+	gqm.updateGroupDeltaUsedNoLock("3", createResourceList(7, 5))
+
+	expectedTotalUsed = quotav1.Add(expectedTotalUsed, createResourceList(5, 5))
+	expectedTotalUsed = quotav1.Add(expectedTotalUsed, createResourceList(7, 5))
+
+	rootQuotaUsed = gqm.GetQuotaInfoByName(extension.RootQuotaName).GetUsed()
+	assert.Equal(t, expectedTotalUsed, rootQuotaUsed)
+}
+
+func TestGroupQuotaManager_UpdateRootQuotaRequest(t *testing.T) {
+	expectedTotalRequest := createResourceList(0, 0)
+	gqm := NewGroupQuotaManagerForTest()
+	gqm.UpdateClusterTotalResource(createResourceList(1000, 1000))
+
+	sysRequest := createResourceList(10, 30)
+	expectedTotalRequest = quotav1.Add(expectedTotalRequest, sysRequest)
+	gqm.updateGroupDeltaRequestNoLock(extension.SystemQuotaName, sysRequest)
+	assert.Equal(t, sysRequest, gqm.GetQuotaInfoByName(extension.SystemQuotaName).GetRequest())
+
+	defaultRequest := createResourceList(2, 5)
+	expectedTotalRequest = quotav1.Add(expectedTotalRequest, defaultRequest)
+	gqm.updateGroupDeltaRequestNoLock(extension.DefaultQuotaName, defaultRequest)
+	assert.Equal(t, defaultRequest, gqm.GetQuotaInfoByName(extension.DefaultQuotaName).GetRequest())
+
+	//case1: no other quota, root quota request
+	rootQuotaRequest := gqm.getQuotaInfoByNameNoLock(extension.RootQuotaName).GetRequest()
+	sysAndDefaultRequest := quotav1.Add(sysRequest, defaultRequest)
+	assert.Equal(t, rootQuotaRequest, sysAndDefaultRequest)
+
+	//case2: just pod no quota, root quota request
+	pod1 := schetesting.MakePod().Name("1").Obj()
+	pod1.Spec.Containers = []v1.Container{
+		{
+			Resources: v1.ResourceRequirements{
+				Requests: createResourceList(10, 10),
+			},
+		},
+	}
+	pod1Request := pod1.Spec.Containers[0].Resources.Requests
+	expectedTotalRequest = quotav1.Add(expectedTotalRequest, pod1Request)
+	gqm.updatePodCacheNoLock(extension.DefaultQuotaName, pod1, true)
+	assert.Equal(t, 1, len(gqm.GetQuotaInfoByName(extension.DefaultQuotaName).GetPodCache()))
+	gqm.updatePodIsAssignedNoLock(extension.DefaultQuotaName, pod1, true)
+	assert.True(t, gqm.getPodIsAssignedNoLock(extension.DefaultQuotaName, pod1))
+	gqm.updatePodRequestNoLock(extension.DefaultQuotaName, nil, pod1)
+	assert.Equal(t, expectedTotalRequest, gqm.GetQuotaInfoByName(extension.RootQuotaName).GetRequest())
+
+	// case 3. when build quota tree, root quota request
+	qi1 := createQuota("1", extension.RootQuotaName, 20, 20, 10, 10)
+	qi2 := createQuota("2", extension.RootQuotaName, 30, 30, 8, 6)
+	qi3 := createQuota("3", "1", 30, 20, 8, 5)
+	gqm.UpdateQuota(qi1, false)
+	gqm.UpdateQuota(qi2, false)
+	gqm.UpdateQuota(qi3, false)
+	gqm.updateGroupDeltaRequestNoLock("2", createResourceList(5, 5))
+	gqm.updateGroupDeltaRequestNoLock("3", createResourceList(7, 5))
+
+	expectedTotalRequest = quotav1.Add(expectedTotalRequest, createResourceList(5, 5))
+	expectedTotalRequest = quotav1.Add(expectedTotalRequest, createResourceList(7, 5))
+
+	rootQuotaRequest = gqm.GetQuotaInfoByName(extension.RootQuotaName).GetRequest()
+	assert.Equal(t, expectedTotalRequest, rootQuotaRequest)
+}
+
+func TestGroupQuotaManager_RootQuotaRuntime(t *testing.T) {
+	gqm := NewGroupQuotaManagerForTest()
+	gqm.UpdateClusterTotalResource(createResourceList(1000, 1000))
+
+	// case: test root quota runtime
+	assert.Equal(t, gqm.RefreshRuntime(extension.RootQuotaName), gqm.totalResourceExceptSystemAndDefaultUsed.DeepCopy())
+}
diff --git a/pkg/scheduler/plugins/elasticquota/plugin_test.go b/pkg/scheduler/plugins/elasticquota/plugin_test.go
index ebddcc26..658e7f1f 100644
--- a/pkg/scheduler/plugins/elasticquota/plugin_test.go
+++ b/pkg/scheduler/plugins/elasticquota/plugin_test.go
@@ -510,7 +510,7 @@ func TestPlugin_OnNodeUpdate(t *testing.T) {
 			totalRes: createResourceList(200, 2000),
 		},
 		{
-			name: "node not exist",
+			name: "node not exist. we should add node",
 			nodes: []*corev1.Node{
 				{
 					ObjectMeta: metav1.ObjectMeta{
@@ -529,7 +529,7 @@ func TestPlugin_OnNodeUpdate(t *testing.T) {
 					},
 				},
 			},
-			totalRes: createResourceList(300, 3000),
+			totalRes: createResourceList(400, 4000),
 		},
 	}
 
@@ -549,6 +549,32 @@ func TestPlugin_OnNodeUpdate(t *testing.T) {
 	}
 }
 
+func TestPlugin_ResyncNodes(t *testing.T) {
+
+	suit := newPluginTestSuit(t, nil)
+	p, _ := suit.proxyNew(suit.elasticQuotaArgs, suit.Handle)
+	plugin := p.(*Plugin)
+
+	// add node
+	nodes := []*corev1.Node{defaultCreateNodeWithResourceVersion("1"), defaultCreateNodeWithResourceVersion("2"),
+		defaultCreateNodeWithResourceVersion("3")}
+	for _, node := range nodes {
+		plugin.handle.ClientSet().CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{})
+	}
+	time.Sleep(100 * time.Millisecond)
+	assert.Equal(t, plugin.groupQuotaManager.GetClusterTotalResource(), createResourceList(300, 3000))
+
+	// delete node in cache
+	plugin.OnNodeDelete(nodes[1])
+	time.Sleep(100 * time.Millisecond)
+	assert.Equal(t, plugin.groupQuotaManager.GetClusterTotalResource(), createResourceList(200, 2000))
+
+	// resync nodes
+	plugin.ResyncNodes()
+	time.Sleep(100 * time.Millisecond)
+	assert.Equal(t, plugin.groupQuotaManager.GetClusterTotalResource(), createResourceList(300, 3000))
+}
+
 func defaultCreateNodeWithResourceVersion(nodeName string) *corev1.Node {
 	node := defaultCreateNode(nodeName)
 	node.ResourceVersion = "3"
@@ -1280,7 +1306,7 @@ func TestPlugin_Recover(t *testing.T) {
 	assert.Equal(t, pl.groupQuotaManager.GetQuotaInfoByName("test1").GetRequest(), createResourceList(40, 40))
 	assert.Equal(t, pl.groupQuotaManager.GetQuotaInfoByName("test1").GetUsed(), createResourceList(40, 40))
 	assert.True(t, quotav1.IsZero(pl.groupQuotaManager.GetQuotaInfoByName(extension.DefaultQuotaName).GetRequest()))
-	assert.Equal(t, len(pl.groupQuotaManager.GetAllQuotaNames()), 4)
+	assert.Equal(t, len(pl.groupQuotaManager.GetAllQuotaNames()), 5)
 }
 
 func TestPlugin_migrateDefaultQuotaGroupsPod(t *testing.T) {
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator_test.go b/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator_test.go
index 6fe1656d..9678e4ca 100644
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator_test.go
+++ b/pkg/scheduler/plugins/nodenumaresource/cpu_accumulator_test.go
@@ -564,7 +564,7 @@ func TestTakeCPUsWithMaxRefCount(t *testing.T) {
 		cpuTopology.CPUDetails[v.CPUID] = v
 	}
 
-	allocationState := newCPUAllocation("test-node-1")
+	allocationState := NewNodeAllocation("test-node-1")
 	assert.NotNil(t, allocationState)
 
 	// first pod request 4 CPUs
@@ -605,7 +605,7 @@ func TestTakeCPUsSortByRefCount(t *testing.T) {
 		cpuTopology.CPUDetails[v.CPUID] = v
 	}
 
-	allocationState := newCPUAllocation("test-node-1")
+	allocationState := NewNodeAllocation("test-node-1")
 	assert.NotNil(t, allocationState)
 
 	// first pod request 16 CPUs
@@ -762,9 +762,13 @@ func TestTakePreferredCPUs(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, []int{0, 2}, result.ToSlice())
 
-	result, err = takePreferredCPUs(topology, 1, cpus, cpuset.NewCPUSet(), nil, 2, schedulingconfig.CPUBindPolicySpreadByPCPUs, schedulingconfig.CPUExclusivePolicyNone, schedulingconfig.NUMAMostAllocated)
+	result, err = takePreferredCPUs(topology, 1, cpus, cpuset.NewCPUSet(0, 2), nil, 2, schedulingconfig.CPUBindPolicySpreadByPCPUs, schedulingconfig.CPUExclusivePolicyNone, schedulingconfig.NUMAMostAllocated)
 	assert.NoError(t, err)
-	assert.Empty(t, result.ToSlice())
+	assert.Equal(t, []int{0, 2}, result.ToSlice())
+
+	result, err = takePreferredCPUs(topology, 1, cpus.Difference(result), cpuset.NewCPUSet(), nil, 2, schedulingconfig.CPUBindPolicySpreadByPCPUs, schedulingconfig.CPUExclusivePolicyNone, schedulingconfig.NUMAMostAllocated)
+	assert.NoError(t, err)
+	assert.Equal(t, []int{1, 3}, result.ToSlice())
 
 	preferredCPUs := cpuset.NewCPUSet(11, 13, 15, 17)
 	result, err = takePreferredCPUs(topology, 1, cpus, preferredCPUs, nil, 2, schedulingconfig.CPUBindPolicySpreadByPCPUs, schedulingconfig.CPUExclusivePolicyNone, schedulingconfig.NUMAMostAllocated)
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_allocation_test.go b/pkg/scheduler/plugins/nodenumaresource/node_allocation_test.go
similarity index 88%
rename from pkg/scheduler/plugins/nodenumaresource/cpu_allocation_test.go
rename to pkg/scheduler/plugins/nodenumaresource/node_allocation_test.go
index cd7c7dca..b4be2825 100644
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_allocation_test.go
+++ b/pkg/scheduler/plugins/nodenumaresource/node_allocation_test.go
@@ -27,21 +27,25 @@ import (
 	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
 )
 
-func TestNodeAllocationStateAddCPUs(t *testing.T) {
+func TestNodeAllocationAddCPUs(t *testing.T) {
 	cpuTopology := buildCPUTopologyForTest(2, 1, 4, 2)
 	for _, v := range cpuTopology.CPUDetails {
 		v.CoreID = v.SocketID<<16 | v.CoreID
 		cpuTopology.CPUDetails[v.CPUID] = v
 	}
 
-	allocationState := newCPUAllocation("test-node-1")
+	allocationState := NewNodeAllocation("test-node-1")
 	assert.NotNil(t, allocationState)
 	podUID := uuid.NewUUID()
 	allocationState.addCPUs(cpuTopology, podUID, cpuset.MustParse("1-4"), schedulingconfig.CPUExclusivePolicyPCPULevel)
 
 	cpus := cpuset.MustParse("1-4")
-	expectAllocatedPods := map[types.UID]cpuset.CPUSet{
-		podUID: cpus,
+	expectAllocatedPods := map[types.UID]PodAllocation{
+		podUID: {
+			UID:                podUID,
+			CPUSet:             cpus,
+			CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyPCPULevel,
+		},
 	}
 	expectAllocatedCPUs := CPUDetails{}
 	for _, cpuID := range cpus.ToSliceNoSort() {
@@ -71,7 +75,11 @@ func TestNodeAllocationStateAddCPUs(t *testing.T) {
 	anotherPodUID := uuid.NewUUID()
 	allocationState.addCPUs(cpuTopology, anotherPodUID, cpuset.MustParse("2-5"), schedulingconfig.CPUExclusivePolicyPCPULevel)
 	anotherCPUSet := cpuset.MustParse("2-5")
-	expectAllocatedPods[anotherPodUID] = anotherCPUSet
+	expectAllocatedPods[anotherPodUID] = PodAllocation{
+		UID:                anotherPodUID,
+		CPUSet:             anotherCPUSet,
+		CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyPCPULevel,
+	}
 	for _, cpuID := range anotherCPUSet.ToSliceNoSort() {
 		cpuInfo, ok := expectAllocatedCPUs[cpuID]
 		if !ok {
@@ -92,14 +100,14 @@ func TestNodeAllocationStateReleaseCPUs(t *testing.T) {
 		cpuTopology.CPUDetails[v.CPUID] = v
 	}
 
-	allocationState := newCPUAllocation("test-node-1")
+	allocationState := NewNodeAllocation("test-node-1")
 	assert.NotNil(t, allocationState)
 	podUID := uuid.NewUUID()
 	allocationState.addCPUs(cpuTopology, podUID, cpuset.MustParse("1-4"), schedulingconfig.CPUExclusivePolicyPCPULevel)
 
-	allocationState.releaseCPUs(podUID)
+	allocationState.release(podUID)
 
-	expectAllocatedPods := map[types.UID]cpuset.CPUSet{}
+	expectAllocatedPods := map[types.UID]PodAllocation{}
 	expectAllocatedCPUs := CPUDetails{}
 	assert.Equal(t, expectAllocatedPods, allocationState.allocatedPods)
 	assert.Equal(t, expectAllocatedCPUs, allocationState.allocatedCPUs)
@@ -115,7 +123,7 @@ func Test_cpuAllocation_getAvailableCPUs(t *testing.T) {
 		cpuTopology.CPUDetails[v.CPUID] = v
 	}
 
-	allocationState := newCPUAllocation("test-node-1")
+	allocationState := NewNodeAllocation("test-node-1")
 	assert.NotNil(t, allocationState)
 	podUID := uuid.NewUUID()
 	allocationState.addCPUs(cpuTopology, podUID, cpuset.MustParse("1-4"), schedulingconfig.CPUExclusivePolicyPCPULevel)
@@ -131,7 +139,7 @@ func Test_cpuAllocation_getAvailableCPUs(t *testing.T) {
 	expectAvailableCPUs = cpuset.MustParse("0-1,5-15")
 	assert.Equal(t, expectAvailableCPUs, availableCPUs)
 
-	allocationState.releaseCPUs(podUID)
+	allocationState.release(podUID)
 	availableCPUs, _ = allocationState.getAvailableCPUs(cpuTopology, 1, cpuset.NewCPUSet(), cpuset.NewCPUSet())
 	expectAvailableCPUs = cpuset.MustParse("0-1,6-15")
 	assert.Equal(t, expectAvailableCPUs, availableCPUs)
@@ -144,7 +152,7 @@ func Test_cpuAllocation_getAvailableCPUs_with_preferred_cpus(t *testing.T) {
 		cpuTopology.CPUDetails[v.CPUID] = v
 	}
 
-	allocationState := newCPUAllocation("test-node-1")
+	allocationState := NewNodeAllocation("test-node-1")
 	assert.NotNil(t, allocationState)
 	podUID := uuid.NewUUID()
 	allocationState.addCPUs(cpuTopology, podUID, cpuset.MustParse("0-4"), schedulingconfig.CPUExclusivePolicyPCPULevel)
diff --git a/pkg/scheduler/plugins/nodenumaresource/plugin_service_test.go b/pkg/scheduler/plugins/nodenumaresource/plugin_service_test.go
index 688d78f2..052ae439 100644
--- a/pkg/scheduler/plugins/nodenumaresource/plugin_service_test.go
+++ b/pkg/scheduler/plugins/nodenumaresource/plugin_service_test.go
@@ -24,6 +24,8 @@ import (
 
 	"github.com/gin-gonic/gin"
 	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/uuid"
 
 	"github.com/koordinator-sh/koordinator/apis/extension"
@@ -31,48 +33,23 @@ import (
 	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
 )
 
-func TestEndpointsQueryCPUTopologyOptions(t *testing.T) {
-	suit := newPluginTestSuit(t, nil)
-	plugin, err := suit.proxyNew(suit.nodeNUMAResourceArgs, suit.Handle)
-	assert.NoError(t, err)
-	assert.NotNil(t, plugin)
-	p := plugin.(*Plugin)
-
-	expectedCPUTopologyOptions := CPUTopologyOptions{
-		CPUTopology:  buildCPUTopologyForTest(2, 1, 4, 2),
-		ReservedCPUs: cpuset.MustParse("0-1"),
-		MaxRefCount:  1,
-		Policy: &extension.KubeletCPUManagerPolicy{
-			Policy: extension.KubeletCPUManagerPolicyStatic,
-			Options: map[string]string{
-				extension.KubeletCPUManagerPolicyFullPCPUsOnlyOption: "true",
+func TestEndpointsQueryNode(t *testing.T) {
+	suit := newPluginTestSuit(t, []*corev1.Node{
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "test-node-1",
 			},
 		},
-	}
-	p.topologyManager.UpdateCPUTopologyOptions("test-node-1", func(options *CPUTopologyOptions) {
-		*options = expectedCPUTopologyOptions
 	})
-
-	engine := gin.Default()
-	p.RegisterEndpoints(engine.Group("/"))
-	w := httptest.NewRecorder()
-	req, _ := http.NewRequest("GET", "/cpuTopologyOptions/test-node-1", nil)
-	engine.ServeHTTP(w, req)
-	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
-	cpuTopologyOptions := &CPUTopologyOptions{}
-	err = json.NewDecoder(w.Result().Body).Decode(cpuTopologyOptions)
-	assert.NoError(t, err)
-	assert.Equal(t, &expectedCPUTopologyOptions, cpuTopologyOptions)
-}
-
-func TestEndpointsQueryAvailableCPUsOptions(t *testing.T) {
-	suit := newPluginTestSuit(t, nil)
 	plugin, err := suit.proxyNew(suit.nodeNUMAResourceArgs, suit.Handle)
 	assert.NoError(t, err)
 	assert.NotNil(t, plugin)
 	p := plugin.(*Plugin)
+	_ = p.handle.SharedInformerFactory().Core().V1().Nodes().Informer()
+	p.handle.SharedInformerFactory().Start(nil)
+	p.handle.SharedInformerFactory().WaitForCacheSync(nil)
 
-	cpuTopologyOptions := CPUTopologyOptions{
+	topologyOptions := TopologyOptions{
 		CPUTopology:  buildCPUTopologyForTest(2, 1, 4, 2),
 		ReservedCPUs: cpuset.MustParse("0-1"),
 		MaxRefCount:  1,
@@ -83,31 +60,45 @@ func TestEndpointsQueryAvailableCPUsOptions(t *testing.T) {
 			},
 		},
 	}
-	p.topologyManager.UpdateCPUTopologyOptions("test-node-1", func(options *CPUTopologyOptions) {
-		*options = cpuTopologyOptions
+	p.topologyOptionsManager.UpdateTopologyOptions("test-node-1", func(options *TopologyOptions) {
+		*options = topologyOptions
 	})
 
-	p.cpuManager.UpdateAllocatedCPUSet("test-node-1", uuid.NewUUID(), cpuset.MustParse("0,2,4,6"), schedulingconfig.CPUExclusivePolicyNone)
+	podUID := uuid.NewUUID()
+	p.resourceManager.Update("test-node-1", &PodAllocation{
+		UID:                podUID,
+		CPUSet:             cpuset.MustParse("0,2,4,6"),
+		CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyNone,
+	})
 
 	engine := gin.Default()
 	p.RegisterEndpoints(engine.Group("/"))
 	w := httptest.NewRecorder()
-	req, _ := http.NewRequest("GET", "/availableCPUs/test-node-1", nil)
+	req, _ := http.NewRequest("GET", "/nodes/test-node-1", nil)
 	engine.ServeHTTP(w, req)
 	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
-	response := &AvailableCPUsResponse{}
+	response := &NodeResponse{}
 	err = json.NewDecoder(w.Result().Body).Decode(response)
 	assert.NoError(t, err)
 
-	expectedResponse := &AvailableCPUsResponse{
-		AvailableCPUs: cpuset.MustParse("3,5,7-15"),
-		Allocated:     CPUDetails{},
+	expectedResponse := &NodeResponse{
+		Name:            "test-node-1",
+		TopologyOptions: topologyOptions,
+		AvailableCPUs:   cpuset.MustParse("3,5,7-15"),
+		AllocatedCPUs:   CPUDetails{},
+		AllocatedPods: []PodAllocation{
+			{
+				UID:                podUID,
+				CPUSet:             cpuset.MustParse("0,2,4,6"),
+				CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyNone,
+			},
+		},
 	}
 	for _, v := range []int{0, 2, 4, 6} {
-		cpuInfo := cpuTopologyOptions.CPUTopology.CPUDetails[v]
+		cpuInfo := topologyOptions.CPUTopology.CPUDetails[v]
 		cpuInfo.RefCount++
 		cpuInfo.ExclusivePolicy = schedulingconfig.CPUExclusivePolicyNone
-		expectedResponse.Allocated[v] = cpuInfo
+		expectedResponse.AllocatedCPUs[v] = cpuInfo
 	}
 	assert.Equal(t, expectedResponse, response)
 }
diff --git a/pkg/scheduler/plugins/nodenumaresource/plugin_test.go b/pkg/scheduler/plugins/nodenumaresource/plugin_test.go
index 015cb9f1..4a890cc0 100644
--- a/pkg/scheduler/plugins/nodenumaresource/plugin_test.go
+++ b/pkg/scheduler/plugins/nodenumaresource/plugin_test.go
@@ -150,6 +150,10 @@ func newPluginTestSuit(t *testing.T, nodes []*corev1.Node) *pluginTestSuit {
 	}
 
 	cs := kubefake.NewSimpleClientset()
+	for _, v := range nodes {
+		_, err := cs.CoreV1().Nodes().Create(context.TODO(), v, metav1.CreateOptions{})
+		assert.NoError(t, err)
+	}
 	informerFactory := informers.NewSharedInformerFactory(cs, 0)
 	snapshot := newTestSharedLister(nil, nodes)
 	fh, err := schedulertesting.NewFramework(
@@ -221,7 +225,10 @@ func TestPlugin_PreFilter(t *testing.T) {
 				},
 			},
 			wantState: &preFilterState{
-				skip:                   false,
+				requestCPUBind: true,
+				requests: corev1.ResourceList{
+					corev1.ResourceCPU: resource.MustParse("4"),
+				},
 				resourceSpec:           &extension.ResourceSpec{PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicyFullPCPUs,
 				numCPUsNeeded:          4,
@@ -253,7 +260,10 @@ func TestPlugin_PreFilter(t *testing.T) {
 				},
 			},
 			wantState: &preFilterState{
-				skip:                   false,
+				requestCPUBind: true,
+				requests: corev1.ResourceList{
+					corev1.ResourceCPU: resource.MustParse("4"),
+				},
 				resourceSpec:           &extension.ResourceSpec{PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicyFullPCPUs,
 				numCPUsNeeded:          4,
@@ -282,7 +292,10 @@ func TestPlugin_PreFilter(t *testing.T) {
 				},
 			},
 			wantState: &preFilterState{
-				skip:                   false,
+				requestCPUBind: true,
+				requests: corev1.ResourceList{
+					corev1.ResourceCPU: resource.MustParse("4"),
+				},
 				resourceSpec:           &extension.ResourceSpec{PreferredCPUBindPolicy: extension.CPUBindPolicyDefault},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicyFullPCPUs,
 				numCPUsNeeded:          4,
@@ -292,7 +305,8 @@ func TestPlugin_PreFilter(t *testing.T) {
 			name: "skip cpu share pod",
 			pod:  &corev1.Pod{},
 			wantState: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
+				requests:       corev1.ResourceList{},
 			},
 		},
 		{
@@ -321,7 +335,10 @@ func TestPlugin_PreFilter(t *testing.T) {
 				},
 			},
 			wantState: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
+				requests: corev1.ResourceList{
+					corev1.ResourceCPU: resource.MustParse("4"),
+				},
 			},
 		},
 		{
@@ -340,7 +357,8 @@ func TestPlugin_PreFilter(t *testing.T) {
 				},
 			},
 			wantState: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
+				requests:       corev1.ResourceList{},
 			},
 		},
 		{
@@ -383,10 +401,23 @@ func TestPlugin_PreFilter(t *testing.T) {
 				},
 				Spec: corev1.PodSpec{
 					Priority: pointer.Int32(extension.PriorityProdValueMax),
+					Containers: []corev1.Container{
+						{
+							Name: "container-1",
+							Resources: corev1.ResourceRequirements{
+								Requests: corev1.ResourceList{
+									corev1.ResourceCPU: resource.MustParse("4.5"),
+								},
+							},
+						},
+					},
 				},
 			},
 			wantState: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
+				requests: corev1.ResourceList{
+					corev1.ResourceCPU: resource.MustParse("4.5"),
+				},
 			},
 		},
 	}
@@ -421,7 +452,7 @@ func TestPlugin_Filter(t *testing.T) {
 		cpuTopology     *CPUTopology
 		state           *preFilterState
 		pod             *corev1.Pod
-		allocationState *cpuAllocation
+		allocationState *NodeAllocation
 		want            *framework.Status
 	}{
 		{
@@ -432,7 +463,7 @@ func TestPlugin_Filter(t *testing.T) {
 		{
 			name: "error with missing CPUTopology",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			pod:  &corev1.Pod{},
 			want: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrNotFoundCPUTopology),
@@ -440,27 +471,27 @@ func TestPlugin_Filter(t *testing.T) {
 		{
 			name: "error with invalid cpu topology",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			cpuTopology:     &CPUTopology{},
-			allocationState: newCPUAllocation("test-node-1"),
+			allocationState: NewNodeAllocation("test-node-1"),
 			pod:             &corev1.Pod{},
 			want:            framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrInvalidCPUTopology),
 		},
 		{
 			name: "succeed with valid cpu topology",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			cpuTopology:     buildCPUTopologyForTest(2, 1, 4, 2),
-			allocationState: newCPUAllocation("test-node-1"),
+			allocationState: NewNodeAllocation("test-node-1"),
 			pod:             &corev1.Pod{},
 			want:            nil,
 		},
 		{
 			name: "succeed with skip",
 			state: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
 			},
 			pod:  &corev1.Pod{},
 			want: nil,
@@ -471,13 +502,13 @@ func TestPlugin_Filter(t *testing.T) {
 				extension.LabelNodeCPUBindPolicy: string(extension.NodeCPUBindPolicyFullPCPUsOnly),
 			},
 			state: &preFilterState{
-				skip:                   false,
+				requestCPUBind:         true,
 				resourceSpec:           &extension.ResourceSpec{},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicyFullPCPUs,
 				numCPUsNeeded:          5,
 			},
 			cpuTopology:     buildCPUTopologyForTest(2, 1, 4, 2),
-			allocationState: newCPUAllocation("test-node-1"),
+			allocationState: NewNodeAllocation("test-node-1"),
 			pod:             &corev1.Pod{},
 			want:            framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrSMTAlignmentError),
 		},
@@ -487,26 +518,26 @@ func TestPlugin_Filter(t *testing.T) {
 				extension.LabelNodeCPUBindPolicy: string(extension.NodeCPUBindPolicyFullPCPUsOnly),
 			},
 			state: &preFilterState{
-				skip:                   false,
+				requestCPUBind:         true,
 				resourceSpec:           &extension.ResourceSpec{},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicySpreadByPCPUs,
 				numCPUsNeeded:          4,
 			},
 			cpuTopology:     buildCPUTopologyForTest(2, 1, 4, 2),
-			allocationState: newCPUAllocation("test-node-1"),
+			allocationState: NewNodeAllocation("test-node-1"),
 			pod:             &corev1.Pod{},
 			want:            framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrRequiredFullPCPUsPolicy),
 		},
 		{
 			name: "verify Kubelet FullPCPUsOnly with SMTAlignmentError",
 			state: &preFilterState{
-				skip:                   false,
+				requestCPUBind:         true,
 				resourceSpec:           &extension.ResourceSpec{},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicyFullPCPUs,
 				numCPUsNeeded:          5,
 			},
 			cpuTopology:     buildCPUTopologyForTest(2, 1, 4, 2),
-			allocationState: newCPUAllocation("test-node-1"),
+			allocationState: NewNodeAllocation("test-node-1"),
 			kubeletPolicy: &extension.KubeletCPUManagerPolicy{
 				Policy: extension.KubeletCPUManagerPolicyStatic,
 				Options: map[string]string{
@@ -519,13 +550,13 @@ func TestPlugin_Filter(t *testing.T) {
 		{
 			name: "verify Kubelet FullPCPUsOnly with RequiredFullPCPUsPolicy",
 			state: &preFilterState{
-				skip:                   false,
+				requestCPUBind:         true,
 				resourceSpec:           &extension.ResourceSpec{},
 				preferredCPUBindPolicy: schedulingconfig.CPUBindPolicySpreadByPCPUs,
 				numCPUsNeeded:          4,
 			},
 			cpuTopology:     buildCPUTopologyForTest(2, 1, 4, 2),
-			allocationState: newCPUAllocation("test-node-1"),
+			allocationState: NewNodeAllocation("test-node-1"),
 			kubeletPolicy: &extension.KubeletCPUManagerPolicy{
 				Policy: extension.KubeletCPUManagerPolicyStatic,
 				Options: map[string]string{
@@ -563,15 +594,15 @@ func TestPlugin_Filter(t *testing.T) {
 
 			plg := p.(*Plugin)
 			if tt.allocationState != nil {
-				topologyOptions := CPUTopologyOptions{
+				topologyOptions := TopologyOptions{
 					CPUTopology: tt.cpuTopology,
 					Policy:      tt.kubeletPolicy,
 				}
-				plg.topologyManager.UpdateCPUTopologyOptions(tt.allocationState.nodeName, func(options *CPUTopologyOptions) {
+				plg.topologyOptionsManager.UpdateTopologyOptions(tt.allocationState.nodeName, func(options *TopologyOptions) {
 					*options = topologyOptions
 				})
 
-				cpuManager := plg.cpuManager.(*cpuManagerImpl)
+				cpuManager := plg.resourceManager.(*resourceManager)
 				cpuManager.allocationStates[tt.allocationState.nodeName] = tt.allocationState
 			}
 
@@ -611,7 +642,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "error with missing allocationState",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			pod:       &corev1.Pod{},
 			want:      nil,
@@ -620,7 +651,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "error with invalid cpu topology",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			cpuTopology: &CPUTopology{},
 			pod:         &corev1.Pod{},
@@ -630,7 +661,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "succeed with skip",
 			state: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
 			},
 			pod:       &corev1.Pod{},
 			want:      nil,
@@ -639,7 +670,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "score with full empty node FullPCPUs",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -654,7 +685,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "score with satisfied node FullPCPUs",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -669,7 +700,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "score with full empty node SpreadByPCPUs",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicySpreadByPCPUs,
 				},
@@ -684,7 +715,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "score with exceed socket FullPCPUs",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -699,7 +730,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "score with satisfied socket FullPCPUs",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -714,7 +745,7 @@ func TestPlugin_Score(t *testing.T) {
 		{
 			name: "score with full empty socket SpreadByPCPUs",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicySpreadByPCPUs,
 				},
@@ -732,7 +763,7 @@ func TestPlugin_Score(t *testing.T) {
 				extension.LabelNodeNUMAAllocateStrategy: string(extension.NodeNUMAAllocateStrategyLeastAllocated),
 			},
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicySpreadByPCPUs,
 				},
@@ -750,7 +781,7 @@ func TestPlugin_Score(t *testing.T) {
 				extension.LabelNodeCPUBindPolicy: string(extension.NodeCPUBindPolicyFullPCPUsOnly),
 			},
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicySpreadByPCPUs,
 				},
@@ -790,14 +821,14 @@ func TestPlugin_Score(t *testing.T) {
 			assert.Nil(t, err)
 
 			plg := p.(*Plugin)
-			allocateState := newCPUAllocation("test-node-1")
+			allocateState := NewNodeAllocation("test-node-1")
 			if tt.cpuTopology != nil {
-				plg.topologyManager.UpdateCPUTopologyOptions(allocateState.nodeName, func(options *CPUTopologyOptions) {
+				plg.topologyOptionsManager.UpdateTopologyOptions(allocateState.nodeName, func(options *TopologyOptions) {
 					options.CPUTopology = tt.cpuTopology
 				})
 			}
 
-			cpuManager := plg.cpuManager.(*cpuManagerImpl)
+			cpuManager := plg.resourceManager.(*resourceManager)
 			cpuManager.allocationStates[allocateState.nodeName] = allocateState
 
 			suit.start()
@@ -844,7 +875,7 @@ func TestPlugin_Reserve(t *testing.T) {
 		{
 			name: "error with missing allocationState",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			pod:  &corev1.Pod{},
 			want: framework.NewStatus(framework.Error, ErrNotFoundCPUTopology),
@@ -852,7 +883,7 @@ func TestPlugin_Reserve(t *testing.T) {
 		{
 			name: "error with invalid cpu topology",
 			state: &preFilterState{
-				skip: false,
+				requestCPUBind: true,
 			},
 			cpuTopology: &CPUTopology{},
 			pod:         &corev1.Pod{},
@@ -861,7 +892,7 @@ func TestPlugin_Reserve(t *testing.T) {
 		{
 			name: "succeed with skip",
 			state: &preFilterState{
-				skip: true,
+				requestCPUBind: false,
 			},
 			pod:  &corev1.Pod{},
 			want: nil,
@@ -869,8 +900,8 @@ func TestPlugin_Reserve(t *testing.T) {
 		{
 			name: "succeed with valid cpu topology",
 			state: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 4,
+				requestCPUBind: true,
+				numCPUsNeeded:  4,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -887,8 +918,8 @@ func TestPlugin_Reserve(t *testing.T) {
 				extension.LabelNodeCPUBindPolicy: string(extension.NodeCPUBindPolicySpreadByPCPUs),
 			},
 			state: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 4,
+				requestCPUBind: true,
+				numCPUsNeeded:  4,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -899,8 +930,8 @@ func TestPlugin_Reserve(t *testing.T) {
 			want:        nil,
 			wantCPUSet:  cpuset.NewCPUSet(0, 2, 4, 6),
 			wantState: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 4,
+				requestCPUBind: true,
+				numCPUsNeeded:  4,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -910,8 +941,8 @@ func TestPlugin_Reserve(t *testing.T) {
 		{
 			name: "error with big request cpu",
 			state: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 24,
+				requestCPUBind: true,
+				numCPUsNeeded:  24,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -926,8 +957,8 @@ func TestPlugin_Reserve(t *testing.T) {
 				extension.LabelNodeNUMAAllocateStrategy: string(extension.NodeNUMAAllocateStrategyLeastAllocated),
 			},
 			state: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 4,
+				requestCPUBind: true,
+				numCPUsNeeded:  4,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -945,8 +976,8 @@ func TestPlugin_Reserve(t *testing.T) {
 				extension.LabelNodeNUMAAllocateStrategy: string(extension.NodeNUMAAllocateStrategyMostAllocated),
 			},
 			state: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 4,
+				requestCPUBind: true,
+				numCPUsNeeded:  4,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -961,8 +992,8 @@ func TestPlugin_Reserve(t *testing.T) {
 		{
 			name: "succeed allocate from reservation reserved cpus",
 			state: &preFilterState{
-				skip:          false,
-				numCPUsNeeded: 4,
+				requestCPUBind: true,
+				numCPUsNeeded:  4,
 				resourceSpec: &extension.ResourceSpec{
 					PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 				},
@@ -1003,23 +1034,23 @@ func TestPlugin_Reserve(t *testing.T) {
 			assert.Nil(t, err)
 
 			plg := p.(*Plugin)
-			allocationState := newCPUAllocation("test-node-1")
+			nodeAllocation := NewNodeAllocation("test-node-1")
 			if tt.cpuTopology != nil {
-				plg.topologyManager.UpdateCPUTopologyOptions(allocationState.nodeName, func(options *CPUTopologyOptions) {
+				plg.topologyOptionsManager.UpdateTopologyOptions(nodeAllocation.nodeName, func(options *TopologyOptions) {
 					options.CPUTopology = tt.cpuTopology
 				})
 				if len(tt.allocatedCPUs) > 0 {
-					allocationState.addCPUs(tt.cpuTopology, uuid.NewUUID(), cpuset.NewCPUSet(tt.allocatedCPUs...), schedulingconfig.CPUExclusivePolicyNone)
+					nodeAllocation.addCPUs(tt.cpuTopology, uuid.NewUUID(), cpuset.NewCPUSet(tt.allocatedCPUs...), schedulingconfig.CPUExclusivePolicyNone)
 				}
 				if len(tt.reservedCPUs) > 0 {
 					for reservationUID, cpus := range tt.reservedCPUs {
-						allocationState.addCPUs(tt.cpuTopology, reservationUID, cpus, schedulingconfig.CPUExclusivePolicyNone)
+						nodeAllocation.addCPUs(tt.cpuTopology, reservationUID, cpus, schedulingconfig.CPUExclusivePolicyNone)
 					}
 				}
 			}
 
-			cpuManager := plg.cpuManager.(*cpuManagerImpl)
-			cpuManager.allocationStates[allocationState.nodeName] = allocationState
+			cpuManager := plg.resourceManager.(*resourceManager)
+			cpuManager.allocationStates[nodeAllocation.nodeName] = nodeAllocation
 
 			suit.start()
 
@@ -1056,19 +1087,25 @@ func TestPlugin_Reserve(t *testing.T) {
 			if !tt.want.IsSuccess() {
 				return
 			}
-			assert.True(t, tt.wantCPUSet.Equals(tt.state.allocatedCPUs))
+			var gotCPUs cpuset.CPUSet
+			if tt.state.allocation != nil {
+				gotCPUs = tt.state.allocation.CPUSet
+			}
+			assert.True(t, tt.wantCPUSet.Equals(gotCPUs))
 		})
 	}
 }
 
 func TestPlugin_Unreserve(t *testing.T) {
 	state := &preFilterState{
-		skip:          false,
-		numCPUsNeeded: 24,
+		requestCPUBind: true,
+		numCPUsNeeded:  24,
 		resourceSpec: &extension.ResourceSpec{
 			PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 		},
-		allocatedCPUs: cpuset.NewCPUSet(0, 1, 2, 3),
+		allocation: &PodAllocation{
+			CPUSet: cpuset.NewCPUSet(0, 1, 2, 3),
+		},
 	}
 	cpuTopology := buildCPUTopologyForTest(2, 1, 4, 2)
 
@@ -1080,20 +1117,24 @@ func TestPlugin_Unreserve(t *testing.T) {
 
 	cycleState := framework.NewCycleState()
 	cycleState.Write(stateKey, state)
-	topologyManager := NewCPUTopologyManager()
-	topologyManager.UpdateCPUTopologyOptions("test-node-1", func(options *CPUTopologyOptions) {
+	topologyOptionsManager := NewTopologyOptionsManager()
+	topologyOptionsManager.UpdateTopologyOptions("test-node-1", func(options *TopologyOptions) {
 		options.CPUTopology = cpuTopology
 	})
 	plg := &Plugin{
-		cpuManager: &cpuManagerImpl{
-			topologyManager:  topologyManager,
-			allocationStates: map[string]*cpuAllocation{},
+		resourceManager: &resourceManager{
+			topologyManager:  topologyOptionsManager,
+			allocationStates: map[string]*NodeAllocation{},
 		},
 	}
-	plg.cpuManager.UpdateAllocatedCPUSet("test-node-1", pod.UID, state.allocatedCPUs, schedulingconfig.CPUExclusivePolicyNone)
+	plg.resourceManager.Update("test-node-1", &PodAllocation{
+		UID:                pod.UID,
+		CPUSet:             state.allocation.CPUSet,
+		CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyNone,
+	})
 	plg.Unreserve(context.TODO(), cycleState, pod, "test-node-1")
 
-	availableCPUs, allocated, err := plg.cpuManager.GetAvailableCPUs("test-node-1")
+	availableCPUs, allocated, err := plg.resourceManager.GetAvailableCPUs("test-node-1", cpuset.CPUSet{})
 	assert.NoError(t, err)
 	assert.Empty(t, allocated)
 	assert.Equal(t, cpuTopology.CPUDetails.CPUs().ToSlice(), availableCPUs.ToSlice())
@@ -1121,12 +1162,14 @@ func TestPlugin_PreBind(t *testing.T) {
 	plg := p.(*Plugin)
 
 	state := &preFilterState{
-		skip:          false,
-		numCPUsNeeded: 4,
+		requestCPUBind: true,
+		numCPUsNeeded:  4,
 		resourceSpec: &extension.ResourceSpec{
 			PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 		},
-		allocatedCPUs: cpuset.NewCPUSet(0, 1, 2, 3),
+		allocation: &PodAllocation{
+			CPUSet: cpuset.NewCPUSet(0, 1, 2, 3),
+		},
 	}
 	cycleState := framework.NewCycleState()
 	cycleState.Write(stateKey, state)
@@ -1164,13 +1207,15 @@ func TestPlugin_PreBindWithCPUBindPolicyNone(t *testing.T) {
 	plg := p.(*Plugin)
 
 	state := &preFilterState{
-		skip:          false,
-		numCPUsNeeded: 4,
+		requestCPUBind: true,
+		numCPUsNeeded:  4,
 		resourceSpec: &extension.ResourceSpec{
 			PreferredCPUBindPolicy: extension.CPUBindPolicyDefault,
 		},
 		preferredCPUBindPolicy: schedulingconfig.CPUBindPolicyFullPCPUs,
-		allocatedCPUs:          cpuset.NewCPUSet(0, 1, 2, 3),
+		allocation: &PodAllocation{
+			CPUSet: cpuset.NewCPUSet(0, 1, 2, 3),
+		},
 	}
 	cycleState := framework.NewCycleState()
 	cycleState.Write(stateKey, state)
@@ -1214,12 +1259,14 @@ func TestPlugin_PreBindReservation(t *testing.T) {
 	plg := p.(*Plugin)
 
 	state := &preFilterState{
-		skip:          false,
-		numCPUsNeeded: 4,
+		requestCPUBind: true,
+		numCPUsNeeded:  4,
 		resourceSpec: &extension.ResourceSpec{
 			PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 		},
-		allocatedCPUs: cpuset.NewCPUSet(0, 1, 2, 3),
+		allocation: &PodAllocation{
+			CPUSet: cpuset.NewCPUSet(0, 1, 2, 3),
+		},
 	}
 	cycleState := framework.NewCycleState()
 	cycleState.Write(stateKey, state)
@@ -1243,8 +1290,8 @@ func TestRestoreReservation(t *testing.T) {
 	pl := p.(*Plugin)
 	cycleState := framework.NewCycleState()
 	state := &preFilterState{
-		skip:          false,
-		numCPUsNeeded: 4,
+		requestCPUBind: true,
+		numCPUsNeeded:  4,
 		resourceSpec: &extension.ResourceSpec{
 			PreferredCPUBindPolicy: extension.CPUBindPolicyFullPCPUs,
 		},
@@ -1263,11 +1310,15 @@ func TestRestoreReservation(t *testing.T) {
 			NodeName: "test-node",
 		},
 	}
-	pl.topologyManager.UpdateCPUTopologyOptions("test-node", func(options *CPUTopologyOptions) {
+	pl.topologyOptionsManager.UpdateTopologyOptions("test-node", func(options *TopologyOptions) {
 		options.CPUTopology = buildCPUTopologyForTest(1, 2, 8, 2)
 		options.MaxRefCount = 1
 	})
-	pl.cpuManager.UpdateAllocatedCPUSet("test-node", reservation.UID, cpuset.NewCPUSet(6, 7, 8, 9), schedulingconfig.CPUExclusivePolicyNone)
+	pl.resourceManager.Update("test-node", &PodAllocation{
+		UID:                reservation.UID,
+		CPUSet:             cpuset.NewCPUSet(6, 7, 8, 9),
+		CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyNone,
+	})
 
 	podA := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
@@ -1289,9 +1340,16 @@ func TestRestoreReservation(t *testing.T) {
 			NodeName: "test-node",
 		},
 	}
-	pl.cpuManager.UpdateAllocatedCPUSet("test-node", podA.UID, cpuset.NewCPUSet(6, 7), schedulingconfig.CPUExclusivePolicyNone)
-	pl.cpuManager.UpdateAllocatedCPUSet("test-node", podB.UID, cpuset.NewCPUSet(8, 9), schedulingconfig.CPUExclusivePolicyNone)
-
+	pl.resourceManager.Update("test-node", &PodAllocation{
+		UID:                podA.UID,
+		CPUSet:             cpuset.NewCPUSet(6, 7),
+		CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyNone,
+	})
+	pl.resourceManager.Update("test-node", &PodAllocation{
+		UID:                podB.UID,
+		CPUSet:             cpuset.NewCPUSet(8, 9),
+		CPUExclusivePolicy: schedulingconfig.CPUExclusivePolicyNone,
+	})
 	node := &corev1.Node{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: "test-node",
diff --git a/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler_test.go b/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler_test.go
index bc589d62..cd19fa63 100644
--- a/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler_test.go
+++ b/pkg/scheduler/plugins/nodenumaresource/pod_eventhandler_test.go
@@ -103,22 +103,22 @@ func TestPodEventHandler(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			cpuTopology := buildCPUTopologyForTest(2, 2, 4, 2)
-			topologyManager := NewCPUTopologyManager()
-			topologyManager.UpdateCPUTopologyOptions("test-node-1", func(options *CPUTopologyOptions) {
+			topologyOptionsManager := NewTopologyOptionsManager()
+			topologyOptionsManager.UpdateTopologyOptions("test-node-1", func(options *TopologyOptions) {
 				options.CPUTopology = cpuTopology
 			})
-			cpuManager := &cpuManagerImpl{
-				topologyManager:  topologyManager,
-				allocationStates: map[string]*cpuAllocation{},
+			resourceManager := &resourceManager{
+				topologyManager:  topologyOptionsManager,
+				allocationStates: map[string]*NodeAllocation{},
 			}
 			handler := &podEventHandler{
-				cpuManager: cpuManager,
+				resourceManager: resourceManager,
 			}
 			handler.OnAdd(tt.pod)
 			handler.OnUpdate(tt.pod, tt.pod)
 
-			allocation := cpuManager.getOrCreateAllocation("test-node-1")
-			_, ok := allocation.allocatedPods[tt.pod.UID]
+			nodeAllocation := resourceManager.getOrCreateNodeAllocation("test-node-1")
+			_, ok := nodeAllocation.allocatedPods[tt.pod.UID]
 			if tt.wantAdd && !ok {
 				t.Errorf("expect add the Pod but not found")
 			} else if !tt.wantAdd && ok {
@@ -126,7 +126,7 @@ func TestPodEventHandler(t *testing.T) {
 			}
 
 			cpusetBuilder := cpuset.NewCPUSetBuilder()
-			for _, v := range allocation.allocatedCPUs {
+			for _, v := range nodeAllocation.allocatedCPUs {
 				cpusetBuilder.Add(v.CPUID)
 			}
 			cpuset := cpusetBuilder.Result()
@@ -137,7 +137,8 @@ func TestPodEventHandler(t *testing.T) {
 			} else if !tt.want.Equals(cpuset) {
 				t.Errorf("expect cpuset equal, but failed, expect: %v, got: %v", tt.want, cpuset)
 			}
-			cpuset = allocation.allocatedPods[tt.pod.UID]
+			allocation := nodeAllocation.allocatedPods[tt.pod.UID]
+			cpuset = allocation.CPUSet
 			if tt.want.IsEmpty() && !cpuset.IsEmpty() {
 				t.Errorf("expect empty cpuset but got")
 			} else if !tt.want.IsEmpty() && cpuset.IsEmpty() {
@@ -147,8 +148,8 @@ func TestPodEventHandler(t *testing.T) {
 			}
 
 			handler.OnDelete(tt.pod)
-			assert.Empty(t, allocation.allocatedPods)
-			assert.Empty(t, allocation.allocatedCPUs)
+			assert.Empty(t, nodeAllocation.allocatedPods)
+			assert.Empty(t, nodeAllocation.allocatedCPUs)
 		})
 	}
 
diff --git a/pkg/scheduler/plugins/nodenumaresource/cpu_topology_manager_test.go b/pkg/scheduler/plugins/nodenumaresource/topology_options_test.go
similarity index 81%
rename from pkg/scheduler/plugins/nodenumaresource/cpu_topology_manager_test.go
rename to pkg/scheduler/plugins/nodenumaresource/topology_options_test.go
index b5297d47..a6fcf37f 100644
--- a/pkg/scheduler/plugins/nodenumaresource/cpu_topology_manager_test.go
+++ b/pkg/scheduler/plugins/nodenumaresource/topology_options_test.go
@@ -31,7 +31,7 @@ import (
 	"github.com/koordinator-sh/koordinator/pkg/util/cpuset"
 )
 
-func TestCPUTopologyManager(t *testing.T) {
+func TestTopologyOptionsManager(t *testing.T) {
 	suit := newPluginTestSuit(t, nil)
 
 	expectCPUTopology := buildCPUTopologyForTest(2, 1, 4, 2)
@@ -99,46 +99,48 @@ func TestCPUTopologyManager(t *testing.T) {
 	_, err = suit.NRTClientset.TopologyV1alpha1().NodeResourceTopologies().Create(context.TODO(), topology, metav1.CreateOptions{})
 	assert.NoError(t, err)
 
-	topologyManager := NewCPUTopologyManager()
-	assert.NotNil(t, topologyManager)
+	topologyOptionsManager := NewTopologyOptionsManager()
+	assert.NotNil(t, topologyOptionsManager)
 
 	extendHandle := &frameworkHandleExtender{
 		ExtendedHandle: suit.Extender,
 		Clientset:      suit.NRTClientset,
 	}
-	err = registerNodeResourceTopologyEventHandler(extendHandle, topologyManager)
+	nrtInformerFactory, err := initNRTInformerFactory(extendHandle)
+	assert.NoError(t, err)
+	err = registerNodeResourceTopologyEventHandler(nrtInformerFactory, topologyOptionsManager)
 	assert.NoError(t, err)
 
 	suit.start()
 
-	cpuTopologyOptions := topologyManager.GetCPUTopologyOptions(nodeName)
-	assert.NotNil(t, cpuTopologyOptions.CPUTopology)
+	topologyOptions := topologyOptionsManager.GetTopologyOptions(nodeName)
+	assert.NotNil(t, topologyOptions.CPUTopology)
 	for k, v := range expectCPUTopology.CPUDetails {
 		v.CoreID = v.SocketID<<16 | v.CoreID
 		expectCPUTopology.CPUDetails[k] = v
 	}
-	assert.Equal(t, expectCPUTopology, cpuTopologyOptions.CPUTopology)
+	assert.Equal(t, expectCPUTopology, topologyOptions.CPUTopology)
 
-	policy := cpuTopologyOptions.Policy
+	policy := topologyOptions.Policy
 	assert.NotNil(t, policy)
 	assert.Equal(t, expectPolicy, policy)
 
-	assert.Equal(t, 1, cpuTopologyOptions.MaxRefCount)
+	assert.Equal(t, 1, topologyOptions.MaxRefCount)
 
 	expectReservedCPUs := cpuset.MustParse("0-7")
-	assert.Equal(t, expectReservedCPUs, cpuTopologyOptions.ReservedCPUs)
+	assert.Equal(t, expectReservedCPUs, topologyOptions.ReservedCPUs)
 
 	delete(topology.Annotations, extension.AnnotationNodeCPUAllocs)
 	_, err = suit.NRTClientset.TopologyV1alpha1().NodeResourceTopologies().Update(context.TODO(), topology, metav1.UpdateOptions{})
 	assert.NoError(t, err)
 
 	time.Sleep(100 * time.Millisecond)
-	cpuTopologyOptions = topologyManager.GetCPUTopologyOptions(nodeName)
-	assert.Equal(t, "0-1,4-7", cpuTopologyOptions.ReservedCPUs.String())
+	topologyOptions = topologyOptionsManager.GetTopologyOptions(nodeName)
+	assert.Equal(t, "0-1,4-7", topologyOptions.ReservedCPUs.String())
 
 	err = suit.NRTClientset.TopologyV1alpha1().NodeResourceTopologies().Delete(context.TODO(), topology.Name, metav1.DeleteOptions{})
 	assert.NoError(t, err)
 	time.Sleep(100 * time.Millisecond)
-	cpuTopologyOptions = topologyManager.GetCPUTopologyOptions(nodeName)
-	assert.Equal(t, CPUTopologyOptions{}, cpuTopologyOptions)
+	topologyOptions = topologyOptionsManager.GetTopologyOptions(nodeName)
+	assert.Equal(t, TopologyOptions{}, topologyOptions)
 }
diff --git a/pkg/slo-controller/noderesource/plugins/batchresource/plugin_test.go b/pkg/slo-controller/noderesource/plugins/batchresource/plugin_test.go
index ea6a13bf..f1ab2d77 100644
--- a/pkg/slo-controller/noderesource/plugins/batchresource/plugin_test.go
+++ b/pkg/slo-controller/noderesource/plugins/batchresource/plugin_test.go
@@ -146,6 +146,9 @@ func getTestResourceMetrics() *framework.ResourceMetrics {
 					NodeUsage: slov1alpha1.ResourceMap{
 						ResourceList: makeResourceList("50", "55G"),
 					},
+					SystemUsage: slov1alpha1.ResourceMap{
+						ResourceList: makeResourceList("7", "12G"),
+					},
 				},
 				PodsMetric: []*slov1alpha1.PodMetricInfo{
 					genPodMetric("test", "podA", "11", "11G"),
@@ -847,6 +850,12 @@ func TestPluginCalculate(t *testing.T) {
 								NodeUsage: slov1alpha1.ResourceMap{
 									ResourceList: makeResourceList("50", "55G"),
 								},
+								SystemUsage: slov1alpha1.ResourceMap{
+									ResourceList: corev1.ResourceList{
+										corev1.ResourceCPU:    resource.MustParse("7"),
+										corev1.ResourceMemory: resource.MustParse("12G"),
+									},
+								},
 							},
 							PodsMetric: []*slov1alpha1.PodMetricInfo{
 								genPodMetric("test", "podProd", "5", "5G"),
@@ -872,6 +881,105 @@ func TestPluginCalculate(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "calculate with pods terminated",
+			args: args{
+				strategy: &configuration.ColocationStrategy{
+					Enable:                        pointer.Bool(true),
+					CPUReclaimThresholdPercent:    pointer.Int64(65),
+					MemoryReclaimThresholdPercent: pointer.Int64(65),
+					DegradeTimeMinutes:            pointer.Int64(15),
+					UpdateTimeThresholdSeconds:    pointer.Int64(300),
+					ResourceDiffThreshold:         pointer.Float64(0.1),
+				},
+				node: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-node1",
+					},
+					Status: makeNodeStat("100", "120G"),
+				},
+				podList: &corev1.PodList{
+					Items: []corev1.Pod{
+						{
+							ObjectMeta: metav1.ObjectMeta{
+								Name:      "podProd",
+								Namespace: "test",
+								Labels: map[string]string{
+									extension.LabelPodQoS: string(extension.QoSLS),
+								},
+							},
+							Spec: corev1.PodSpec{
+								NodeName: "test-node1",
+								Containers: []corev1.Container{
+									{
+										Resources: makeResourceReq("10", "10G"),
+									},
+								},
+								// regarded as Prod by default
+							},
+							Status: corev1.PodStatus{
+								Phase: corev1.PodRunning,
+							},
+						},
+						{
+							ObjectMeta: metav1.ObjectMeta{
+								Name:      "podProd1",
+								Namespace: "test",
+								// missing qos label
+							},
+							Spec: corev1.PodSpec{
+								NodeName: "test-node1",
+								Containers: []corev1.Container{
+									{
+										Resources: makeResourceReq("10", "10G"),
+									},
+								},
+								PriorityClassName: string(extension.PriorityProd),
+								Priority:          pointer.Int32(extension.PriorityProdValueMax),
+							},
+							Status: corev1.PodStatus{
+								Phase: corev1.PodRunning,
+							},
+						},
+					},
+				},
+				resourceMetrics: &framework.ResourceMetrics{
+					NodeMetric: &slov1alpha1.NodeMetric{
+						Status: slov1alpha1.NodeMetricStatus{
+							UpdateTime: &metav1.Time{Time: time.Now()},
+							NodeMetric: &slov1alpha1.NodeMetricInfo{
+								NodeUsage: slov1alpha1.ResourceMap{
+									ResourceList: makeResourceList("25", "30G"),
+								},
+								SystemUsage: slov1alpha1.ResourceMap{
+									ResourceList: corev1.ResourceList{
+										corev1.ResourceCPU:    resource.MustParse("5"),
+										corev1.ResourceMemory: resource.MustParse("10G"),
+									},
+								},
+							},
+							PodsMetric: []*slov1alpha1.PodMetricInfo{
+								genPodMetric("test", "podProd", "5", "5G"),
+								genPodMetric("test", "podProd2", "10", "10G"),
+							},
+						},
+					},
+				},
+			},
+			want: []framework.ResourceItem{
+				{
+					Name:     extension.BatchCPU,
+					Quantity: resource.NewQuantity(35000, resource.DecimalSI),
+					Message:  "batchAllocatable[CPU(Milli-Core)]:35000 = nodeAllocatable:100000 - nodeReservation:35000 - systemUsage:5000 - podHPUsed:25000",
+				},
+				{
+					Name:     extension.BatchMemory,
+					Quantity: resource.NewScaledQuantity(43, 9),
+					Message:  "batchAllocatable[Mem(GB)]:43 = nodeAllocatable:120 - nodeReservation:42 - systemUsage:10 - podHPUsed:25",
+				},
+			},
+			wantErr: false,
+		},
 	}
 
 	for _, tt := range tests {
@@ -1046,9 +1154,9 @@ func Test_getPodMetricUsage(t *testing.T) {
 	}
 }
 
-func Test_getNodeMetricUsage(t *testing.T) {
+func Test_getResourceListForCPUAndMemory(t *testing.T) {
 	type args struct {
-		info *slov1alpha1.NodeMetricInfo
+		rl corev1.ResourceList
 	}
 	tests := []struct {
 		name string
@@ -1058,14 +1166,10 @@ func Test_getNodeMetricUsage(t *testing.T) {
 		{
 			name: "get correct scaled resource quantity",
 			args: args{
-				info: &slov1alpha1.NodeMetricInfo{
-					NodeUsage: slov1alpha1.ResourceMap{
-						ResourceList: corev1.ResourceList{
-							corev1.ResourceCPU:    resource.MustParse("40"),
-							corev1.ResourceMemory: resource.MustParse("80Gi"),
-							"unknown_resource":    resource.MustParse("10"),
-						},
-					},
+				rl: corev1.ResourceList{
+					corev1.ResourceCPU:    resource.MustParse("40"),
+					corev1.ResourceMemory: resource.MustParse("80Gi"),
+					"unknown_resource":    resource.MustParse("10"),
 				},
 			},
 			want: makeResourceList("40", "80Gi"),
@@ -1073,7 +1177,7 @@ func Test_getNodeMetricUsage(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := getNodeMetricUsage(tt.args.info)
+			got := getResourceListForCPUAndMemory(tt.args.rl)
 			testingCorrectResourceList(t, &tt.want, &got)
 		})
 	}
diff --git a/pkg/slo-controller/noderesource/resource_calculator_test.go b/pkg/slo-controller/noderesource/resource_calculator_test.go
index 2af6928d..1fa42ad3 100644
--- a/pkg/slo-controller/noderesource/resource_calculator_test.go
+++ b/pkg/slo-controller/noderesource/resource_calculator_test.go
@@ -338,6 +338,12 @@ func Test_calculateNodeResource(t *testing.T) {
 									corev1.ResourceMemory: resource.MustParse("55G"),
 								},
 							},
+							SystemUsage: slov1alpha1.ResourceMap{
+								ResourceList: corev1.ResourceList{
+									corev1.ResourceCPU:    resource.MustParse("7"),
+									corev1.ResourceMemory: resource.MustParse("12G"),
+								},
+							},
 						},
 						PodsMetric: []*slov1alpha1.PodMetricInfo{
 							{
@@ -542,6 +548,12 @@ func Test_calculateNodeResource(t *testing.T) {
 									corev1.ResourceMemory: resource.MustParse("55G"),
 								},
 							},
+							SystemUsage: slov1alpha1.ResourceMap{
+								ResourceList: corev1.ResourceList{
+									corev1.ResourceCPU:    resource.MustParse("7"),
+									corev1.ResourceMemory: resource.MustParse("12G"),
+								},
+							},
 						},
 						PodsMetric: []*slov1alpha1.PodMetricInfo{
 							{
@@ -746,6 +758,12 @@ func Test_calculateNodeResource(t *testing.T) {
 									corev1.ResourceMemory: resource.MustParse("55G"),
 								},
 							},
+							SystemUsage: slov1alpha1.ResourceMap{
+								ResourceList: corev1.ResourceList{
+									corev1.ResourceCPU:    resource.MustParse("7"),
+									corev1.ResourceMemory: resource.MustParse("12G"),
+								},
+							},
 						},
 						PodsMetric: []*slov1alpha1.PodMetricInfo{
 							{
@@ -947,6 +965,12 @@ func Test_calculateNodeResource(t *testing.T) {
 									corev1.ResourceMemory: resource.MustParse("55G"),
 								},
 							},
+							SystemUsage: slov1alpha1.ResourceMap{
+								ResourceList: corev1.ResourceList{
+									corev1.ResourceCPU:    resource.MustParse("7"),
+									corev1.ResourceMemory: resource.MustParse("12G"),
+								},
+							},
 						},
 						PodsMetric: []*slov1alpha1.PodMetricInfo{
 							{
diff --git a/test/e2e/scheduling/helper.go b/test/e2e/scheduling/helper.go
index 7735cd89..638642eb 100644
--- a/test/e2e/scheduling/helper.go
+++ b/test/e2e/scheduling/helper.go
@@ -22,6 +22,8 @@ import (
 	"fmt"
 	"time"
 
+	nrtv1alpha1 "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha1"
+	nrtclientset "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/generated/clientset/versioned"
 	"github.com/onsi/ginkgo"
 	"github.com/onsi/gomega"
 	appsv1 "k8s.io/api/apps/v1"
@@ -238,3 +240,25 @@ func expectPodBoundReservation(clientSet clientset.Interface, koordinatorClientS
 		UID:  reservation.UID,
 	}), "pod is not using the expected reservation")
 }
+
+func getSuitableNodeResourceTopology(client nrtclientset.Interface, expectNumNodes int) *nrtv1alpha1.NodeResourceTopology {
+	nrtList, err := client.TopologyV1alpha1().NodeResourceTopologies().List(context.TODO(), metav1.ListOptions{})
+	framework.ExpectNoError(err, "unable to list NRT")
+	var gotNRT *nrtv1alpha1.NodeResourceTopology
+	for i := range nrtList.Items {
+		nrt := &nrtList.Items[i]
+		numNodes := 0
+		for _, zone := range nrt.Zones {
+			if zone.Type == "Node" {
+				numNodes++
+			}
+		}
+		if numNodes != expectNumNodes {
+			continue
+		}
+		gotNRT = nrt
+		break
+	}
+	gomega.Expect(gotNRT).Should(gomega.Not(gomega.BeNil()))
+	return gotNRT
+}
diff --git a/test/e2e/scheduling/nodenumaresource.go b/test/e2e/scheduling/nodenumaresource.go
index 2a4c74bc..7e08e2ad 100644
--- a/test/e2e/scheduling/nodenumaresource.go
+++ b/test/e2e/scheduling/nodenumaresource.go
@@ -19,13 +19,20 @@ package scheduling
 import (
 	"context"
 	"encoding/json"
+	"fmt"
+	"strings"
 	"time"
 
+	nrtclientset "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/generated/clientset/versioned"
 	"github.com/onsi/ginkgo"
 	"github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/apimachinery/pkg/util/sets"
+	quotav1 "k8s.io/apiserver/pkg/quota/v1"
 	k8spodutil "k8s.io/kubernetes/pkg/api/v1/pod"
 	"k8s.io/utils/pointer"
 
@@ -358,4 +365,492 @@ var _ = SIGDescribe("NodeNUMAResource", func() {
 			}
 		})
 	})
+
+	ginkgo.Context("NUMA Topology Policy", func() {
+		var targetNodeName string
+		var nrtClient nrtclientset.Interface
+
+		ginkgo.BeforeEach(func() {
+			targetNodeName = ""
+			config := f.ClientConfig()
+			var err error
+			nrtClient, err = nrtclientset.NewForConfig(config)
+			framework.ExpectNoError(err, "unable to create NRT ClientSet")
+		})
+
+		ginkgo.AfterEach(func() {
+			if targetNodeName != "" {
+				framework.RemoveLabelOffNode(f.ClientSet, targetNodeName, extension.LabelNUMATopologyPolicy)
+			}
+		})
+
+		// SingleNUMANode with 2 NUMA Nodes
+		framework.ConformanceIt("SingleNUMANode with 2 NUMA Nodes", func() {
+			nrt := getSuitableNodeResourceTopology(nrtClient, 2)
+			node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nrt.Name, metav1.GetOptions{})
+			framework.ExpectNoError(err, "unable to get node")
+
+			ginkgo.By(fmt.Sprintf("Label node %s with %s=%s", node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicySingleNUMANode)))
+			framework.AddOrUpdateLabelOnNode(f.ClientSet, node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicySingleNUMANode))
+			targetNodeName = node.Name
+
+			ginkgo.By("Create two pods allocate 56% resources of Node, and every Pod allocates 28% resources per NUMA Node")
+			cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
+			memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
+			percent := intstr.FromString("28%")
+			cpu, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests := corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			rsConfig := pauseRSConfig{
+				Replicas: int32(2),
+				PodConfig: pausePodConfig{
+					Name:      "request-resource-with-single-numa-pod",
+					Namespace: f.Namespace.Name,
+					Labels: map[string]string{
+						"test-app": "true",
+					},
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				},
+			}
+			runPauseRS(f, rsConfig)
+
+			ginkgo.By("Check the two pods allocated in two NUMA Nodes")
+			podList, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{})
+			framework.ExpectNoError(err)
+			nodes := sets.NewInt()
+			for i := range podList.Items {
+				pod := &podList.Items[i]
+				ginkgo.By(fmt.Sprintf("pod %q, resourceStatus: %s", pod.Name, pod.Annotations[extension.AnnotationResourceStatus]))
+				resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+				framework.ExpectNoError(err, "invalid resourceStatus")
+				gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(1))
+				r := equality.Semantic.DeepEqual(resourceStatus.NUMANodeResources[0].Resources, requests)
+				gomega.Expect(r).Should(gomega.Equal(true))
+				gomega.Expect(false).Should(gomega.Equal(nodes.Has(int(resourceStatus.NUMANodeResources[0].Node))))
+				nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+			}
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+
+			ginkgo.By("Create the third Pod allocates 30% resources of Node, expect it failed to schedule")
+			percent = intstr.FromString("30%")
+			cpu, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests = corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			pod := createPausePod(f, pausePodConfig{
+				Name:      "must-be-failed-pod",
+				Namespace: f.Namespace.Name,
+				Resources: &corev1.ResourceRequirements{
+					Limits:   requests,
+					Requests: requests,
+				},
+				SchedulerName: "koord-scheduler",
+				NodeName:      node.Name,
+			})
+			ginkgo.By("Wait for Pod schedule failed")
+			framework.ExpectNoError(e2epod.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "wait for pod schedule failed", 60*time.Second, func(pod *corev1.Pod) (bool, error) {
+				_, scheduledCondition := k8spodutil.GetPodCondition(&pod.Status, corev1.PodScheduled)
+				if scheduledCondition != nil && scheduledCondition.Status == corev1.ConditionFalse &&
+					strings.Contains(scheduledCondition.Message, "NUMA Topology affinity") {
+					return true, nil
+				}
+				return false, nil
+			}))
+		})
+
+		// SingleNUMANode with 2 NUMA Nodes with LSR
+		framework.ConformanceIt("SingleNUMANode with 2 NUMA Nodes with LSR", func() {
+			nrt := getSuitableNodeResourceTopology(nrtClient, 2)
+			node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nrt.Name, metav1.GetOptions{})
+			framework.ExpectNoError(err, "unable to get node")
+
+			ginkgo.By(fmt.Sprintf("Label node %s with %s=%s", node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicySingleNUMANode)))
+			framework.AddOrUpdateLabelOnNode(f.ClientSet, node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicySingleNUMANode))
+			targetNodeName = node.Name
+
+			ginkgo.By("Create two pods allocate 56% resources of Node, and every Pod allocates 28% resources per NUMA Node")
+			cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
+			memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
+			percent := intstr.FromString("28%")
+			cpu, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			cpu -= cpu % 1000
+			memory, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests := corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			rsConfig := pauseRSConfig{
+				Replicas: int32(2),
+				PodConfig: pausePodConfig{
+					Name:      "request-resource-with-single-numa-pod",
+					Namespace: f.Namespace.Name,
+					Labels: map[string]string{
+						"test-app":            "true",
+						extension.LabelPodQoS: string(extension.QoSLSR),
+					},
+					PriorityClassName: string(extension.PriorityProd),
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				},
+			}
+			runPauseRS(f, rsConfig)
+
+			ginkgo.By("Check the two pods allocated in two NUMA Nodes")
+			podList, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{})
+			framework.ExpectNoError(err)
+			nodes := sets.NewInt()
+			for i := range podList.Items {
+				pod := &podList.Items[i]
+				ginkgo.By(fmt.Sprintf("pod %q, resourceStatus: %s", pod.Name, pod.Annotations[extension.AnnotationResourceStatus]))
+				resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+				framework.ExpectNoError(err, "invalid resourceStatus")
+				gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(1))
+				r := equality.Semantic.DeepEqual(resourceStatus.NUMANodeResources[0].Resources, requests)
+				gomega.Expect(r).Should(gomega.Equal(true))
+				gomega.Expect(false).Should(gomega.Equal(nodes.Has(int(resourceStatus.NUMANodeResources[0].Node))))
+				nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+				gomega.Expect(resourceStatus.CPUSet).Should(gomega.Not(gomega.BeEmpty()))
+			}
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+		})
+
+		// SingleNUMANode with 2 NUMA Nodes - resources crossover two NUMA Nodes
+		framework.ConformanceIt("SingleNUMANode with 2 NUMA Nodes - resources crossover two NUMA Nodes", func() {
+			nrt := getSuitableNodeResourceTopology(nrtClient, 2)
+			node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nrt.Name, metav1.GetOptions{})
+			framework.ExpectNoError(err, "unable to get node")
+
+			ginkgo.By(fmt.Sprintf("Label node %s with %s=%s", node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicySingleNUMANode)))
+			framework.AddOrUpdateLabelOnNode(f.ClientSet, node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicySingleNUMANode))
+			targetNodeName = node.Name
+
+			ginkgo.By("Create two pods allocate 40% cpu of one NUMA Node, and 40% memory of one NUMA Node")
+			cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
+			memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
+			percent := intstr.FromString("40%")
+			cpu, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			for i := 0; i < 2; i++ {
+				requests := corev1.ResourceList{
+					corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+				}
+				runPausePod(f, pausePodConfig{
+					Name:      fmt.Sprintf("request-memory-with-single-numa-pod-%d", i),
+					Namespace: f.Namespace.Name,
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				})
+			}
+			for i := 0; i < 2; i++ {
+				requests := corev1.ResourceList{
+					corev1.ResourceCPU: *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				}
+				runPausePod(f, pausePodConfig{
+					Name:      fmt.Sprintf("request-cpu-with-single-numa-pod-%d", i),
+					Namespace: f.Namespace.Name,
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				})
+			}
+			framework.ExpectNoError(e2epod.DeletePodWithWaitByName(f.ClientSet, "request-memory-with-single-numa-pod-0", f.Namespace.Name))
+			framework.ExpectNoError(e2epod.DeletePodWithWaitByName(f.ClientSet, "request-cpu-with-single-numa-pod-1", f.Namespace.Name))
+
+			ginkgo.By("Create the third Pod allocates 40% resources of Node, expect it failed to schedule")
+			percent = intstr.FromString("40%")
+			cpu, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests := corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			pod := createPausePod(f, pausePodConfig{
+				Name:      "must-be-failed-pod",
+				Namespace: f.Namespace.Name,
+				Resources: &corev1.ResourceRequirements{
+					Limits:   requests,
+					Requests: requests,
+				},
+				SchedulerName: "koord-scheduler",
+				NodeName:      node.Name,
+			})
+			ginkgo.By("Wait for Pod schedule failed")
+			framework.ExpectNoError(e2epod.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "wait for pod schedule failed", 60*time.Second, func(pod *corev1.Pod) (bool, error) {
+				_, scheduledCondition := k8spodutil.GetPodCondition(&pod.Status, corev1.PodScheduled)
+				if scheduledCondition != nil && scheduledCondition.Status == corev1.ConditionFalse &&
+					strings.Contains(scheduledCondition.Message, "NUMA Topology affinity") {
+					return true, nil
+				}
+				return false, nil
+			}))
+		})
+
+		// Restricted with 2 NUMA Nodes
+		framework.ConformanceIt("Restricted with 2 NUMA Nodes", func() {
+			nrt := getSuitableNodeResourceTopology(nrtClient, 2)
+			node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nrt.Name, metav1.GetOptions{})
+			framework.ExpectNoError(err, "unable to get node")
+
+			ginkgo.By(fmt.Sprintf("Label node %s with %s=%s", node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicyRestricted)))
+			framework.AddOrUpdateLabelOnNode(f.ClientSet, node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicyRestricted))
+			targetNodeName = node.Name
+
+			ginkgo.By("Create two pods allocate 60% resources of Node, and every Pod allocates 30% resources per NUMA Node")
+			cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
+			memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
+			percent := intstr.FromString("28%")
+			cpu, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests := corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			rsConfig := pauseRSConfig{
+				Replicas: int32(2),
+				PodConfig: pausePodConfig{
+					Name:      "request-resource-with-single-numa-pod",
+					Namespace: f.Namespace.Name,
+					Labels: map[string]string{
+						"test-app": "true",
+					},
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				},
+			}
+			runPauseRS(f, rsConfig)
+
+			ginkgo.By("Check the two pods allocated in two NUMA Nodes")
+			podList, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{})
+			framework.ExpectNoError(err)
+			nodes := sets.NewInt()
+			for i := range podList.Items {
+				pod := &podList.Items[i]
+				resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+				framework.ExpectNoError(err, "invalid resourceStatus")
+				gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(1))
+				r := equality.Semantic.DeepEqual(resourceStatus.NUMANodeResources[0].Resources, requests)
+				gomega.Expect(r).Should(gomega.Equal(true))
+				gomega.Expect(false).Should(gomega.Equal(nodes.Has(int(resourceStatus.NUMANodeResources[0].Node))))
+				nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+			}
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+
+			ginkgo.By("Create the third Pod allocates 40% resources of Node, expect it scheduled")
+			percent = intstr.FromString("30%")
+			cpu, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests = corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			pod := runPausePod(f, pausePodConfig{
+				Name:      "must-be-succeeded-pod",
+				Namespace: f.Namespace.Name,
+				Resources: &corev1.ResourceRequirements{
+					Limits:   requests,
+					Requests: requests,
+				},
+				SchedulerName: "koord-scheduler",
+				NodeName:      node.Name,
+			})
+			nodes = sets.NewInt()
+			resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+			framework.ExpectNoError(err, "invalid resourceStatus")
+			gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(2))
+			allocated := quotav1.Add(resourceStatus.NUMANodeResources[0].Resources, resourceStatus.NUMANodeResources[1].Resources)
+			r := equality.Semantic.DeepEqual(allocated, requests)
+			gomega.Expect(r).Should(gomega.Equal(true))
+			nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+			nodes.Insert(int(resourceStatus.NUMANodeResources[1].Node))
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+		})
+
+		// Restricted with 2 NUMA Nodes - resources crossover two NUMA Nodes
+		framework.ConformanceIt("Restricted with 2 NUMA Nodes - resources crossover two NUMA Nodes", func() {
+			nrt := getSuitableNodeResourceTopology(nrtClient, 2)
+			node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nrt.Name, metav1.GetOptions{})
+			framework.ExpectNoError(err, "unable to get node")
+
+			ginkgo.By(fmt.Sprintf("Label node %s with %s=%s", node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicyRestricted)))
+			framework.AddOrUpdateLabelOnNode(f.ClientSet, node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicyRestricted))
+			targetNodeName = node.Name
+
+			ginkgo.By("Create two pods allocate 40% cpu of one NUMA Node, and 40% memory of one NUMA Node")
+			cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
+			memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
+			percent := intstr.FromString("40%")
+			cpu, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			for i := 0; i < 2; i++ {
+				requests := corev1.ResourceList{
+					corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+				}
+				runPausePod(f, pausePodConfig{
+					Name:      fmt.Sprintf("request-memory-with-single-numa-pod-%d", i),
+					Namespace: f.Namespace.Name,
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				})
+			}
+			for i := 0; i < 2; i++ {
+				requests := corev1.ResourceList{
+					corev1.ResourceCPU: *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				}
+				runPausePod(f, pausePodConfig{
+					Name:      fmt.Sprintf("request-cpu-with-single-numa-pod-%d", i),
+					Namespace: f.Namespace.Name,
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				})
+			}
+			framework.ExpectNoError(e2epod.DeletePodWithWaitByName(f.ClientSet, "request-memory-with-single-numa-pod-0", f.Namespace.Name))
+			framework.ExpectNoError(e2epod.DeletePodWithWaitByName(f.ClientSet, "request-cpu-with-single-numa-pod-1", f.Namespace.Name))
+
+			ginkgo.By("Create the third Pod allocates 40% resources of Node, expect it scheduled")
+			percent = intstr.FromString("40%")
+			cpu, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests := corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			pod := runPausePod(f, pausePodConfig{
+				Name:      "must-be-failed-pod",
+				Namespace: f.Namespace.Name,
+				Resources: &corev1.ResourceRequirements{
+					Limits:   requests,
+					Requests: requests,
+				},
+				SchedulerName: "koord-scheduler",
+				NodeName:      node.Name,
+			})
+			nodes := sets.NewInt()
+			resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+			framework.ExpectNoError(err, "invalid resourceStatus")
+			gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(2))
+			allocated := quotav1.Add(resourceStatus.NUMANodeResources[0].Resources, resourceStatus.NUMANodeResources[1].Resources)
+			r := equality.Semantic.DeepEqual(allocated, requests)
+			gomega.Expect(r).Should(gomega.Equal(true))
+			nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+			nodes.Insert(int(resourceStatus.NUMANodeResources[1].Node))
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+		})
+
+		// BestEffort with 2 NUMA Nodes
+		framework.ConformanceIt("BestEffort with 2 NUMA Nodes", func() {
+			nrt := getSuitableNodeResourceTopology(nrtClient, 2)
+			node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nrt.Name, metav1.GetOptions{})
+			framework.ExpectNoError(err, "unable to get node")
+
+			ginkgo.By(fmt.Sprintf("Label node %s with %s=%s", node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicyBestEffort)))
+			framework.AddOrUpdateLabelOnNode(f.ClientSet, node.Name, extension.LabelNUMATopologyPolicy, string(extension.NUMATopologyPolicyBestEffort))
+			targetNodeName = node.Name
+
+			ginkgo.By("Create two pods allocate 60% resources of Node, and every Pod allocates 30% resources per NUMA Node")
+			cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
+			memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
+			percent := intstr.FromString("28%")
+			cpu, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ := intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests := corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			rsConfig := pauseRSConfig{
+				Replicas: int32(2),
+				PodConfig: pausePodConfig{
+					Name:      "request-resource-with-single-numa-pod",
+					Namespace: f.Namespace.Name,
+					Labels: map[string]string{
+						"test-app": "true",
+					},
+					Resources: &corev1.ResourceRequirements{
+						Limits:   requests,
+						Requests: requests,
+					},
+					SchedulerName: "koord-scheduler",
+					NodeName:      node.Name,
+				},
+			}
+			runPauseRS(f, rsConfig)
+
+			ginkgo.By("Check the two pods allocated in two NUMA Nodes")
+			podList, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{})
+			framework.ExpectNoError(err)
+			nodes := sets.NewInt()
+			for i := range podList.Items {
+				pod := &podList.Items[i]
+				resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+				framework.ExpectNoError(err, "invalid resourceStatus")
+				gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(1))
+				r := equality.Semantic.DeepEqual(resourceStatus.NUMANodeResources[0].Resources, requests)
+				gomega.Expect(r).Should(gomega.Equal(true))
+				gomega.Expect(false).Should(gomega.Equal(nodes.Has(int(resourceStatus.NUMANodeResources[0].Node))))
+				nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+			}
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+
+			ginkgo.By("Create the third Pod allocates 40% resources of Node, expect it failed to schedule")
+			percent = intstr.FromString("30%")
+			cpu, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(cpuQuantity.MilliValue()), false)
+			memory, _ = intstr.GetScaledValueFromIntOrPercent(&percent, int(memoryQuantity.Value()), false)
+			requests = corev1.ResourceList{
+				corev1.ResourceCPU:    *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI),
+				corev1.ResourceMemory: *resource.NewQuantity(int64(memory), resource.DecimalSI),
+			}
+			pod := runPausePod(f, pausePodConfig{
+				Name:      "must-be-succeeded-pod",
+				Namespace: f.Namespace.Name,
+				Resources: &corev1.ResourceRequirements{
+					Limits:   requests,
+					Requests: requests,
+				},
+				SchedulerName: "koord-scheduler",
+				NodeName:      node.Name,
+			})
+			nodes = sets.NewInt()
+			resourceStatus, err := extension.GetResourceStatus(pod.Annotations)
+			framework.ExpectNoError(err, "invalid resourceStatus")
+			gomega.Expect(len(resourceStatus.NUMANodeResources)).Should(gomega.Equal(2))
+			allocated := quotav1.Add(resourceStatus.NUMANodeResources[0].Resources, resourceStatus.NUMANodeResources[1].Resources)
+			r := equality.Semantic.DeepEqual(allocated, requests)
+			gomega.Expect(r).Should(gomega.Equal(true))
+			nodes.Insert(int(resourceStatus.NUMANodeResources[0].Node))
+			nodes.Insert(int(resourceStatus.NUMANodeResources[1].Node))
+			gomega.Expect(nodes.Len()).Should(gomega.Equal(2))
+		})
+	})
 })
diff --git a/test/e2e/scheduling/reservation.go b/test/e2e/scheduling/reservation.go
index 4248861e..ba9dbdd2 100644
--- a/test/e2e/scheduling/reservation.go
+++ b/test/e2e/scheduling/reservation.go
@@ -902,6 +902,7 @@ var _ = SIGDescribe("Reservation", func() {
 			reservation, err := manifest.ReservationFromManifest("scheduling/simple-reservation.yaml")
 			framework.ExpectNoError(err, "unable to load reservation")
 
+			reservation.Spec.Template.Namespace = f.Namespace.Name
 			reservation.Spec.Template.Labels = map[string]string{
 				"e2e-reservation-interpodaffinity": "true",
 			}
@@ -932,6 +933,7 @@ var _ = SIGDescribe("Reservation", func() {
 				Name: string(uuid.NewUUID()),
 				Labels: map[string]string{
 					"e2e-reservation-interpodaffinity": "true",
+					"app":                              "e2e-test-reservation",
 				},
 				Affinity:      reservation.Spec.Template.Spec.Affinity,
 				NodeName:      reservation.Status.NodeName,
@@ -944,6 +946,53 @@ var _ = SIGDescribe("Reservation", func() {
 			gomega.Expect(p.Spec.NodeName).Should(gomega.Equal(reservation.Status.NodeName))
 		})
 
+		framework.ConformanceIt("validates PodAntiAffinity with reservation - Pod failed to schedule", func() {
+			ginkgo.By("Create reservation")
+			reservation, err := manifest.ReservationFromManifest("scheduling/simple-reservation.yaml")
+			framework.ExpectNoError(err, "unable to load reservation")
+
+			reservation.Spec.Template.Namespace = f.Namespace.Name
+			reservation.Spec.Template.Labels = map[string]string{
+				"e2e-reservation-interpodaffinity": "true",
+			}
+
+			reservation.Spec.Template.Spec.Affinity = &corev1.Affinity{
+				PodAntiAffinity: &corev1.PodAntiAffinity{
+					RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
+						{
+							LabelSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{
+									"e2e-reservation-interpodaffinity": "true",
+								},
+							},
+							TopologyKey: corev1.LabelHostname,
+						},
+					},
+				},
+			}
+
+			_, err = f.KoordinatorClientSet.SchedulingV1alpha1().Reservations().Create(context.TODO(), reservation, metav1.CreateOptions{})
+			framework.ExpectNoError(err, "unable to create reservation")
+
+			ginkgo.By("Wait for reservation scheduled")
+			reservation = waitingForReservationScheduled(f.KoordinatorClientSet, reservation)
+
+			ginkgo.By("Create pod and wait for scheduled")
+			pod := createPausePod(f, pausePodConfig{
+				Name: string(uuid.NewUUID()),
+				Labels: map[string]string{
+					"e2e-reservation-interpodaffinity": "true",
+				},
+				Affinity:      reservation.Spec.Template.Spec.Affinity,
+				NodeName:      reservation.Status.NodeName,
+				SchedulerName: reservation.Spec.Template.Spec.SchedulerName,
+			})
+			framework.ExpectNoError(e2epod.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "wait for pod schedule failed", 60*time.Second, func(pod *corev1.Pod) (bool, error) {
+				_, scheduledCondition := k8spodutil.GetPodCondition(&pod.Status, corev1.PodScheduled)
+				return scheduledCondition != nil && scheduledCondition.Status == corev1.ConditionFalse, nil
+			}))
+		})
+
 		ginkgo.Context("PodTopologySpread Filtering With Reservation", func() {
 			var nodeNames []string
 			topologyKey := "kubernetes.io/e2e-pts-filter"