Repository: koordinator-sh/koordinator

Issue:
[BUG] The noderesourcefitplus plugin does not implement the LocalStorageCapacityIsolation FeatureGate.​
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!-->

**What happened**:

​​After enabling the noderesourcefitplus plugin, the koord-scheduler panics when a Pod configures ephemeral-storage resources. The error log is as follows:​
```sh
I0715 08:21:32.313315       1 schedule_one.go:93] "Attempting to schedule pod" pod="default/spark-8ovsebcy-driver" 
E0715 08:21:32.314272       1 runtime.go:79] Observed a panic: &errors.errorString{s:"feature \"LocalStorageCapacityIsolation\" is not registered in FeatureGate \"k8s.io/apiserver/pkg/util/feature/feature_gate.go:28\""} (feature "LocalStorageCapacityIsolation" is not registered in FeatureGate "k8s.io/apiserver/pkg/util/feature/feature_gate.go:28") 
goroutine 967 [running]: 
k8s.io/apimachinery/pkg/util/runtime.logPanic({0x23581e0, 0xc000f8d920}) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go:75 +0x85 
k8s.io/apimachinery/pkg/util/runtime.HandleCrash({0x0, 0x0, 0xc002464000?}) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go:49 +0x6b 
panic({0x23581e0?, 0xc000f8d920?}) 
	/home/dji/.gvm/gos/go1.23.3/src/runtime/panic.go:785 +0x132 
k8s.io/component-base/featuregate.(*featureGate).Enabled(0xc0001f55c0, {0x27a87f8, 0x1d}) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/component-base/featuregate/feature_gate.go:317 +0x1a5 
github.com/koordinator-sh/koordinator/pkg/scheduler/plugins/noderesourcefitplus.GetNonzeroRequestForResource({0x278f5c4, 0x11}, 0xc0012b2530) 
	/data/code/koordinator/main/koordinator/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go:184 +0x4f4 
github.com/koordinator-sh/koordinator/pkg/scheduler/plugins/noderesourcefitplus.calculatePodResourceRequest(0xc0014bdb08, {0x278f5c4, 0x11}) 
	/data/code/koordinator/main/koordinator/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go:144 +0x76 
github.com/koordinator-sh/koordinator/pkg/scheduler/plugins/noderesourcefitplus.calculateResourceAllocatableRequest(0xc003428b40, 0xc00310b680?, {0x278f5c4, 0x11}) 
	/data/code/koordinator/main/koordinator/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go:115 +0x38 
github.com/koordinator-sh/koordinator/pkg/scheduler/plugins/noderesourcefitplus.(*ResourceAllocationPriority).getResourceScore(0xc00205fc68, 0xc00088e660, {0xc0022b84c0, 0x3, 0x0?}, 0xc0014bdb08, 0xc003428b40, {0xc000e20fd8, 0x11}) 
	/data/code/koordinator/main/koordinator/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go:92 +0xcf 
github.com/koordinator-sh/koordinator/pkg/scheduler/plugins/noderesourcefitplus.(*Plugin).Score(0xc001cbb7b8, {0x0?, 0x7fb4d7762428?}, 0xc002283640, 0xc0014bdb08, {0xc000e20fd8, 0x11}) 
	/data/code/koordinator/main/koordinator/pkg/scheduler/plugins/noderesourcefitplus/node_resources_fit_plus.go:92 +0x35e 
k8s.io/kubernetes/pkg/scheduler/framework/runtime.(*instrumentedScorePlugin).Score(0xc0031332a0, {0x2b71bb0, 0xc002292460}, 0xc002283640, 0xc0014bdb08, {0xc000e20fd8, 0x11}) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/kubernetes/pkg/scheduler/framework/runtime/instrumented_plugins.go:82 +0x75 
k8s.io/kubernetes/pkg/scheduler/framework/runtime.(*frameworkImpl).runScorePlugin(0x2b77b58?, {0x2b71bb0?, 0xc002292460?}, {0x2b60098?, 0xc0031332a0?}, 0x0?, 0x0?, {0xc000e20fd8?, 0x0?}) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/kubernetes/pkg/scheduler/framework/runtime/framework.go:1126 +0x2d2 
k8s.io/kubernetes/pkg/scheduler/framework/runtime.(*frameworkImpl).RunScorePlugins.func2(0x1) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/kubernetes/pkg/scheduler/framework/runtime/framework.go:1055 +0x2f1 
k8s.io/kubernetes/pkg/scheduler/framework/parallelize.Parallelizer.Until.func1(0x1) 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/kubernetes/pkg/scheduler/framework/parallelize/parallelism.go:60 +0x46 
k8s.io/client-go/util/workqueue.ParallelizeUntil.func1() 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/client-go/util/workqueue/parallelizer.go:90 +0xf3 
created by k8s.io/client-go/util/workqueue.ParallelizeUntil in goroutine 715 
	/data/code/koordinator/main/koordinator/vendor/k8s.io/client-go/util/workqueue/parallelizer.go:76 +0x1fb
```

**What you expected to happen**:

**How to reproduce it (as minimally and precisely as possible)**:

**Anything else we need to know?**:

Analysis: 
In the following code, it is assumed by default that the LocalStorageCapacityIsolation FeatureGate exists. However, in Kubernetes v1.25 and later versions, the LocalStorageCapacityIsolation FeatureGate has been removed. Thus, this code will panic when running on Kubernetes v1.25+.​
```go
func GetNonzeroRequestForResource(resource v1.ResourceName, requests *v1.ResourceList) int64 {
	switch resource {
	case v1.ResourceCPU:
		// Override if un-set, but not if explicitly set to zero
		if _, found := (*requests)[v1.ResourceCPU]; !found {
			return schedutil.DefaultMilliCPURequest
		}
		return requests.Cpu().MilliValue()
	case v1.ResourceMemory:
		// Override if un-set, but not if explicitly set to zero
		if _, found := (*requests)[v1.ResourceMemory]; !found {
			return schedutil.DefaultMemoryRequest
		}
		return requests.Memory().Value()
	case v1.ResourceEphemeralStorage:
		// if the local storage capacity isolation feature gate is disabled, pods request 0 disk.
		if !utilfeature.DefaultFeatureGate.Enabled("LocalStorageCapacityIsolation") {
			return 0
		}

		quantity, found := (*requests)[v1.ResourceEphemeralStorage]
		if !found {
			return 0
		}
		return quantity.Value()
	default:
		if schedutil.IsScalarResourceName(resource) {
			quantity, found := (*requests)[resource]
			if !found {
				return 0
			}
			return quantity.Value()
		}
	}
	return 0
}
```

**Environment**:
- App version: v1.6.0
- Kubernetes version (use `kubectl version`): 1.28
- Install details (e.g. helm install args):
- Node environment (for koordlet/runtime-proxy issue):
  - Containerd/Docker version:
  - OS version:
  - Kernal version:
  - Cgroup driver: cgroupfs/systemd
- Others:



Hints from issue comments:
@ZiMengSheng: @LY-today PTAL@LY-today: > [@LY-today](https://github.com/LY-today) PTAL

get@LY-today: @qinfustu 
Hi, thank you for your suggestions. If you have any optimization ideas, please send me a PR and I will review it.@qinfustu: @LY-today 
Okay, I'll open a PR.

Solution patch:
diff --git a/cmd/koord-manager/main.go b/cmd/koord-manager/main.go
index 176438c7..0beeeff0 100644
--- a/cmd/koord-manager/main.go
+++ b/cmd/koord-manager/main.go
@@ -48,6 +48,7 @@ import (
 	kmmetrics "github.com/koordinator-sh/koordinator/pkg/util/metrics/koordmanager"
 	"github.com/koordinator-sh/koordinator/pkg/util/sloconfig"
 	"github.com/koordinator-sh/koordinator/pkg/webhook"
+	podvalidating "github.com/koordinator-sh/koordinator/pkg/webhook/pod/validating"
 	// +kubebuilder:scaffold:imports
 )
 
@@ -81,6 +82,7 @@ func main() {
 	opts := options.NewOptions()
 	opts.InitFlags(flag.CommandLine)
 	sloconfig.InitFlags(flag.CommandLine)
+	podvalidating.InitFlags(flag.CommandLine)
 	utilfeature.DefaultMutableFeatureGate.AddFlag(pflag.CommandLine)
 	klog.InitFlags(nil)
 	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
diff --git a/pkg/features/features.go b/pkg/features/features.go
index 7491309c..682c6ff1 100644
--- a/pkg/features/features.go
+++ b/pkg/features/features.go
@@ -85,6 +85,9 @@ const (
 	// EnableQuotaAdmission enables quota admission.
 	EnableQuotaAdmission featuregate.Feature = "EnableQuotaAdmission"
 
+	// EnablePodEnhancedValidator enables enhanced validator for pods with configurable rules.
+	EnablePodEnhancedValidator featuregate.Feature = "EnablePodEnhancedValidator"
+
 	// Enable sync GPU shared resource from Device CRD
 	EnableSyncGPUSharedResource featuregate.Feature = "EnableSyncGPUSharedResource"
 
@@ -120,6 +123,7 @@ var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
 	ColocationProfileController:            {Default: false, PreRelease: featuregate.Alpha},
 	ValidatePodDeviceResource:              {Default: false, PreRelease: featuregate.Alpha},
 	DevicePluginAdaption:                   {Default: false, PreRelease: featuregate.Alpha},
+	EnablePodEnhancedValidator:             {Default: false, PreRelease: featuregate.Alpha},
 }
 
 const (
diff --git a/pkg/scheduler/frameworkext/framework_extender.go b/pkg/scheduler/frameworkext/framework_extender.go
index 7ea52818..556b72df 100644
--- a/pkg/scheduler/frameworkext/framework_extender.go
+++ b/pkg/scheduler/frameworkext/framework_extender.go
@@ -23,7 +23,6 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	k8sfeature "k8s.io/apiserver/pkg/util/feature"
 	listerscorev1 "k8s.io/client-go/listers/core/v1"
 	"k8s.io/klog/v2"
 	schedconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
@@ -34,7 +33,6 @@ import (
 	koordinatorclientset "github.com/koordinator-sh/koordinator/pkg/client/clientset/versioned"
 	koordinatorinformers "github.com/koordinator-sh/koordinator/pkg/client/informers/externalversions"
 	listerschedulingv1alpha1 "github.com/koordinator-sh/koordinator/pkg/client/listers/scheduling/v1alpha1"
-	"github.com/koordinator-sh/koordinator/pkg/features"
 	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/schedulingphase"
 	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
 	reservationutil "github.com/koordinator-sh/koordinator/pkg/util/reservation"
@@ -544,11 +542,6 @@ func (ext *frameworkExtenderImpl) GetNUMATopologyHintProvider() []topologymanage
 }
 
 func (ext *frameworkExtenderImpl) RunReservePluginsReserve(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) *framework.Status {
-	if k8sfeature.DefaultFeatureGate.Enabled(features.ResizePod) {
-		if isPodAssumed(cycleState) {
-			return nil
-		}
-	}
 	schedulingphase.RecordPhase(cycleState, schedulingphase.Reserve)
 	defer func() { schedulingphase.RecordPhase(cycleState, "") }()
 	status := ext.Framework.RunReservePluginsReserve(ctx, cycleState, pod, nodeName)
@@ -570,20 +563,3 @@ func (ext *frameworkExtenderImpl) RunResizePod(ctx context.Context, cycleState *
 	}
 	return nil
 }
-
-const podAssumedStateKey = "koordinator.sh/assumed"
-
-type assumedState struct{}
-
-func (s *assumedState) Clone() framework.StateData {
-	return s
-}
-
-func markPodAssumed(cycleState *framework.CycleState) {
-	cycleState.Write(podAssumedStateKey, &assumedState{})
-}
-
-func isPodAssumed(cycleState *framework.CycleState) bool {
-	assumed, _ := cycleState.Read(podAssumedStateKey)
-	return assumed != nil
-}
diff --git a/pkg/scheduler/frameworkext/framework_extender_factory.go b/pkg/scheduler/frameworkext/framework_extender_factory.go
index df813539..ba6e9dee 100644
--- a/pkg/scheduler/frameworkext/framework_extender_factory.go
+++ b/pkg/scheduler/frameworkext/framework_extender_factory.go
@@ -279,17 +279,12 @@ func (f *FrameworkExtenderFactory) scheduleOne(ctx context.Context, fwk framewor
 
 	if k8sfeature.DefaultFeatureGate.Enabled(features.ResizePod) {
 		// NOTE(joseph): We can modify the Pod because we have cloned the Pod in the NextPod function.
-		pod.Spec.NodeName = scheduleResult.SuggestedHost
-		status := fwk.RunReservePluginsReserve(ctx, cycleState, pod, scheduleResult.SuggestedHost)
-		if !status.IsSuccess() {
-			fwk.RunReservePluginsUnreserve(ctx, cycleState, pod, scheduleResult.SuggestedHost)
-			return scheduleResult, status.AsError()
-		}
-		markPodAssumed(cycleState)
-
+		// Make sure to modify the Pod only related to AssumePod, and do not modify the plugins' cache, since
+		// when the assume failure would not do Unreserve the plugins' cache.
+		// Other resizing logic about the plugins' cache can be convergent to its Reserve phase.
 		extender, ok := fwk.(*frameworkExtenderImpl)
 		if ok {
-			status = extender.RunResizePod(ctx, cycleState, pod, scheduleResult.SuggestedHost)
+			status := extender.RunResizePod(ctx, cycleState, pod, scheduleResult.SuggestedHost)
 			if !status.IsSuccess() {
 				fwk.RunReservePluginsUnreserve(ctx, cycleState, pod, scheduleResult.SuggestedHost)
 				return scheduleResult, status.AsError()
diff --git a/pkg/scheduler/frameworkext/interface.go b/pkg/scheduler/frameworkext/interface.go
index 0f056d2b..5e2742fe 100644
--- a/pkg/scheduler/frameworkext/interface.go
+++ b/pkg/scheduler/frameworkext/interface.go
@@ -199,7 +199,7 @@ type ReservationScoreExtensions interface {
 	NormalizeReservationScore(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, scores ReservationScoreList) *framework.Status
 }
 
-// ResizePodPlugin is an interface that resize the pod resource spec after reserve.
+// ResizePodPlugin is an interface that resize the pod resource spec before the Assume phase.
 // If you want to use the feature, must enable the feature gate ResizePod=true
 type ResizePodPlugin interface {
 	framework.Plugin
diff --git a/pkg/scheduler/plugins/deviceshare/plugin.go b/pkg/scheduler/plugins/deviceshare/plugin.go
index f0dd5c1d..381d2910 100644
--- a/pkg/scheduler/plugins/deviceshare/plugin.go
+++ b/pkg/scheduler/plugins/deviceshare/plugin.go
@@ -440,6 +440,42 @@ func (p *Plugin) Reserve(ctx context.Context, cycleState *framework.CycleState,
 		return nil
 	}
 
+	if state.allocationResult == nil {
+		status = p.allocate(ctx, cycleState, pod, nodeName)
+		if !status.IsSuccess() {
+			return status
+		}
+	}
+	if state.allocationResult == nil {
+		return nil
+	}
+
+	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(nodeName, false)
+	if nodeDeviceInfo == nil {
+		return nil
+	}
+
+	nodeDeviceInfo.lock.Lock()
+	defer nodeDeviceInfo.lock.Unlock()
+	nodeDeviceInfo.updateCacheUsed(state.allocationResult, pod, true)
+	return nil
+}
+
+func (p *Plugin) allocate(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) *framework.Status {
+	defer func() {
+		// ReservationRestoreState is O(n) complexity of node number of the cluster.
+		// cleanReservationRestoreState clears ReservationRestoreState in the stateData to reduce memory cost before entering
+		// the binding cycle.
+		cleanReservationRestoreState(cycleState)
+	}()
+	state, status := getPreFilterState(cycleState)
+	if !status.IsSuccess() {
+		return status
+	}
+	if state.skip {
+		return nil
+	}
+
 	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
 	if err != nil {
 		return framework.AsStatus(err)
@@ -471,8 +507,8 @@ func (p *Plugin) Reserve(ctx context.Context, cycleState *framework.CycleState,
 	restoreState := reservationRestoreState.getNodeState(nodeName)
 	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[nodeName])
 
-	nodeDeviceInfo.lock.Lock()
-	defer nodeDeviceInfo.lock.Unlock()
+	nodeDeviceInfo.lock.RLock()
+	defer nodeDeviceInfo.lock.RUnlock()
 
 	result, status := p.allocateWithNominatedReservation(
 		allocator, cycleState, state, restoreState, nodeInfo.Node(), pod, preemptible)
@@ -490,7 +526,6 @@ func (p *Plugin) Reserve(ctx context.Context, cycleState *framework.CycleState,
 	if err != nil {
 		return framework.NewStatus(framework.Error, fmt.Sprintf("fillGPUTotalMem failed: %v, node: %v", err, nodeName))
 	}
-	nodeDeviceInfo.updateCacheUsed(result, pod, true)
 	state.allocationResult = result
 	return nil
 }
@@ -528,6 +563,13 @@ func (p *Plugin) ResizePod(ctx context.Context, cycleState *framework.CycleState
 		return nil
 	}
 
+	if state.allocationResult == nil {
+		status = p.allocate(ctx, cycleState, pod, nodeName)
+		if !status.IsSuccess() {
+			return status
+		}
+	}
+
 	var allocated corev1.ResourceList
 	for _, allocations := range state.allocationResult {
 		for _, v := range allocations {
diff --git a/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go b/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go
index e340d049..c3da5d99 100644
--- a/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go
+++ b/pkg/scheduler/plugins/noderesourcefitplus/node_resource_fit_plus_utils.go
@@ -92,8 +92,7 @@ func (r *ResourceAllocationPriority) getResourceScore(args *config.NodeResources
 		allocatable[resourceName], requested[resourceName] = calculateResourceAllocatableRequest(nodeInfo, pod, resourceName)
 	}
 
-	var score int64
-	score = r.scorer(nodeName, args, requested, allocatable)
+	score := r.scorer(nodeName, args, requested, allocatable)
 
 	return score
 }
@@ -180,11 +179,6 @@ func GetNonzeroRequestForResource(resource v1.ResourceName, requests *v1.Resourc
 		}
 		return requests.Memory().Value()
 	case v1.ResourceEphemeralStorage:
-		// if the local storage capacity isolation feature gate is disabled, pods request 0 disk.
-		if !utilfeature.DefaultFeatureGate.Enabled("LocalStorageCapacityIsolation") {
-			return 0
-		}
-
 		quantity, found := (*requests)[v1.ResourceEphemeralStorage]
 		if !found {
 			return 0
diff --git a/pkg/webhook/pod/validating/pod_enhanced_validator.go b/pkg/webhook/pod/validating/pod_enhanced_validator.go
new file mode 100644
index 00000000..7fd0178c
--- /dev/null
+++ b/pkg/webhook/pod/validating/pod_enhanced_validator.go
@@ -0,0 +1,311 @@
+/*
+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 validating
+
+import (
+	"context"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"reflect"
+	"strings"
+	"sync"
+	"time"
+
+	admissionv1 "k8s.io/api/admission/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"github.com/koordinator-sh/koordinator/pkg/features"
+	"github.com/koordinator-sh/koordinator/pkg/util"
+	utilfeature "github.com/koordinator-sh/koordinator/pkg/util/feature"
+)
+
+const (
+	ConfigKeyEnable = "enable"
+	ConfigKeyRules  = "rules"
+
+	// DefaultConfReconcileInterval is the default reconcile interval for config
+	DefaultConfReconcileInterval = 5 * time.Minute
+)
+
+var (
+	// PodEnhancedValidatorConfigNamespace defines the namespace for the PodEnhancedValidator configuration.
+	PodEnhancedValidatorConfigNamespace = "koordinator-system"
+	// PodEnhancedValidatorConfigName defines the name for the PodEnhancedValidator configuration.
+	PodEnhancedValidatorConfigName = "pod-enhanced-validator-config"
+	// PodEnhancedValidatorReconcileInterval defines the reconcile interval for the PodEnhancedValidator configuration.
+	PodEnhancedValidatorReconcileInterval = DefaultConfReconcileInterval
+
+	DefaultPodEnhancedValidatorConf = &PodEnhancedValidatorConfig{
+		Enable: false,
+		Rules:  []ValidationRule{},
+	}
+)
+
+func (h *PodValidatingHandler) podEnhancedValidate(ctx context.Context, req admission.Request) (string, error) {
+	if !utilfeature.DefaultFeatureGate.Enabled(features.EnablePodEnhancedValidator) {
+		return "", nil
+	}
+
+	newPod := &corev1.Pod{}
+	switch req.Operation {
+	case admissionv1.Create, admissionv1.Update:
+		if err := h.Decoder.DecodeRaw(req.Object, newPod); err != nil {
+			return "", err
+		}
+	default:
+		return "", nil
+	}
+
+	reason, err := h.PodEnhancedValidator.ValidatePod(newPod)
+	if err != nil {
+		return reason, err
+	}
+	return "", nil
+}
+
+func InitFlags(fs *flag.FlagSet) {
+	fs.StringVar(&PodEnhancedValidatorConfigNamespace, "pod-enhanced-validator-config-namespace",
+		PodEnhancedValidatorConfigNamespace, "The namespace for the pod-enhanced-validator configuration.")
+	fs.StringVar(&PodEnhancedValidatorConfigName, "pod-enhanced-validator-config-name",
+		PodEnhancedValidatorConfigName, "The name for the pod-enhanced-validator configuration.")
+	fs.DurationVar(&PodEnhancedValidatorReconcileInterval, "pod-enhanced-validator-reconcile-interval",
+		DefaultConfReconcileInterval, "The reconcile interval for the pod-enhanced-validator configuration.")
+}
+
+// ValidationRule defines a single validation rule
+type ValidationRule struct {
+	// Name is the unique identifier for this rule
+	Name string `json:"name"`
+	// RequiredLabels specifies label keys that must be present on pods
+	RequiredLabels []string `json:"requiredLabels,omitempty"`
+	// NamespaceWhitelist contains namespaces that are exempt from this validation rule
+	NamespaceWhitelist []string `json:"namespaceWhitelist,omitempty"`
+
+	// namespaceWhitelistSet is a cached set for fast namespace lookup
+	// this field is not serialized and is built from NamespaceWhitelist
+	namespaceWhitelistSet sets.Set[string] `json:"-"`
+}
+
+// isNamespaceWhitelisted checks if a namespace is in the whitelist using the cached map
+func (r *ValidationRule) isNamespaceWhitelisted(namespace string) bool {
+	if r.namespaceWhitelistSet == nil {
+		return false
+	}
+	return r.namespaceWhitelistSet.Has(namespace)
+}
+
+// PodEnhancedValidatorConfig defines the configuration for pod enhanced validation
+type PodEnhancedValidatorConfig struct {
+	// Enable controls whether pod enhanced validation is enabled
+	Enable bool `json:"enable"`
+	// Rules contains the list of validation rules to apply
+	Rules []ValidationRule `json:"rules,omitempty"`
+}
+
+// PodEnhancedValidator manages the pod-enhanced-validator configuration with hot reload support
+type PodEnhancedValidator struct {
+	client          client.Client
+	config          *PodEnhancedValidatorConfig
+	configName      string
+	configNamespace string
+
+	startOnce sync.Once
+	sync.RWMutex
+}
+
+func NewPodEnhancedValidator(client client.Client) *PodEnhancedValidator {
+	return &PodEnhancedValidator{
+		client:          client,
+		configName:      PodEnhancedValidatorConfigName,
+		configNamespace: PodEnhancedValidatorConfigNamespace,
+		config:          DefaultPodEnhancedValidatorConf,
+	}
+}
+
+// ensureConfigSynced ensures the config synchronization is started (lazy loading)
+func (m *PodEnhancedValidator) ensureConfigSynced() {
+	m.startOnce.Do(func() {
+		klog.V(4).Infof("Starting PodEnhancedValidator with lazy initialization")
+
+		// sync configuration immediately
+		if err := m.syncConfig(); err != nil {
+			klog.Errorf("Failed to sync config during initialization: %v", err)
+		}
+
+		// start periodic configuration synchronization
+		go wait.Until(func() {
+			if err := m.syncConfig(); err != nil {
+				klog.Errorf("Failed to sync config for pod-enhanced-validator: %v", err)
+			}
+		}, PodEnhancedValidatorReconcileInterval, wait.NeverStop)
+	})
+}
+
+// syncConfig synchronizes the configuration from the ConfigMap
+func (m *PodEnhancedValidator) syncConfig() error {
+	cm := &corev1.ConfigMap{}
+	key := types.NamespacedName{
+		Namespace: m.configNamespace,
+		Name:      m.configName,
+	}
+
+	err := m.client.Get(context.Background(), key, cm)
+	if err != nil {
+		if errors.IsNotFound(err) {
+			// ConfigMap not found, reset to default config
+			m.updateConfig(DefaultPodEnhancedValidatorConf)
+			klog.Info("pod-enhanced-validator configmap not found, reset to default config")
+			return nil
+		}
+		return err
+	}
+
+	m.handleConfigMapUpdate(cm)
+	return nil
+}
+
+// handleConfigMapUpdate handles configmap update events
+func (m *PodEnhancedValidator) handleConfigMapUpdate(cm *corev1.ConfigMap) {
+	config, err := m.parseConfig(cm)
+	if err != nil {
+		klog.Errorf("failed to parse config for pod-enhanced-validator, err=%v", err)
+		return
+	}
+
+	m.updateConfig(config)
+}
+
+func (m *PodEnhancedValidator) updateConfig(config *PodEnhancedValidatorConfig) {
+	m.Lock()
+	defer m.Unlock()
+
+	// check if the config has changed
+	if reflect.DeepEqual(m.config, config) {
+		klog.V(4).Infof("skip updating unchanged config for pod-enhanced-validator")
+		return
+	}
+
+	m.config = config
+
+	klog.Infof("updated pod-enhanced-validator config with %d rules: enable=%v",
+		len(config.Rules), config.Enable)
+}
+
+// parseConfig parses configmap data
+func (m *PodEnhancedValidator) parseConfig(cm *corev1.ConfigMap) (*PodEnhancedValidatorConfig, error) {
+	var config PodEnhancedValidatorConfig
+
+	// parse enable field with case-insensitive comparison
+	if enableData, ok := cm.Data[ConfigKeyEnable]; ok {
+		enableData = strings.ToLower(strings.TrimSpace(enableData))
+		config.Enable = enableData == "true"
+	}
+
+	// parse rules field
+	if rulesData, ok := cm.Data[ConfigKeyRules]; ok {
+		var rules []ValidationRule
+		if err := json.Unmarshal([]byte(rulesData), &rules); err != nil {
+			return nil, fmt.Errorf("failed to unmarshal validation rules: %w", err)
+		}
+		config.Rules = rules
+	} else {
+		config.Rules = []ValidationRule{} // default to empty rules if not specified
+	}
+
+	// build namespace whitelist sets for all validation rules
+	m.buildNamespaceWhitelistSet(&config)
+
+	return &config, nil
+}
+
+// buildNamespaceWhitelistSet builds namespace whitelist sets for all validation rules
+func (m *PodEnhancedValidator) buildNamespaceWhitelistSet(config *PodEnhancedValidatorConfig) {
+	for i := range config.Rules {
+		rule := &config.Rules[i]
+
+		// Build namespace whitelist set
+		if len(rule.NamespaceWhitelist) > 0 {
+			rule.namespaceWhitelistSet = sets.New[string]()
+			for _, ns := range rule.NamespaceWhitelist {
+				if ns != "" {
+					rule.namespaceWhitelistSet.Insert(ns)
+				}
+			}
+		}
+	}
+}
+
+// GetConfig returns the current configuration
+func (m *PodEnhancedValidator) GetConfig() *PodEnhancedValidatorConfig {
+	// lazy loading: start only when actually needed
+	m.ensureConfigSynced()
+
+	m.RLock()
+	defer m.RUnlock()
+	return m.config
+}
+
+// ValidatePod validates a pod against all configured rules
+func (m *PodEnhancedValidator) ValidatePod(pod *corev1.Pod) (string, error) {
+	config := m.GetConfig()
+	if !config.Enable {
+		return "", nil
+	}
+	for _, rule := range config.Rules {
+		// check if namespace is whitelisted for this rule
+		if rule.isNamespaceWhitelisted(pod.Namespace) {
+			if klog.V(4).Enabled() {
+				klog.Infof("pod %s is in whitelisted namespace for rule %s", util.GetPodKey(pod), rule.Name)
+			}
+			continue
+		}
+
+		// validate required labels
+		if err := m.validateRequiredLabels(pod, rule); err != nil {
+			return fmt.Sprintf("validation rule '%s' failed: %v", rule.Name, err), err
+		}
+	}
+	return "", nil
+}
+
+// validateRequiredLabels validates that all required labels are present
+func (m *PodEnhancedValidator) validateRequiredLabels(pod *corev1.Pod, rule ValidationRule) error {
+	var missingLabels []string
+
+	for _, key := range rule.RequiredLabels {
+		if _, exists := pod.Labels[key]; !exists {
+			missingLabels = append(missingLabels, key)
+		}
+	}
+
+	if len(missingLabels) > 0 {
+		if len(missingLabels) == 1 {
+			return fmt.Errorf("required label '%s' is missing", missingLabels[0])
+		}
+		return fmt.Errorf("required labels are missing: %v", missingLabels)
+	}
+
+	return nil
+}
diff --git a/pkg/webhook/pod/validating/validating_handler.go b/pkg/webhook/pod/validating/validating_handler.go
index 772a376f..12d82a1a 100644
--- a/pkg/webhook/pod/validating/validating_handler.go
+++ b/pkg/webhook/pod/validating/validating_handler.go
@@ -37,6 +37,7 @@ const (
 	ClusterColocationProfile = "ClusterColocationProfile"
 	EvaluateQuota            = "EvaluateQuota"
 	DeviceResource           = "DeviceResource"
+	EnhancedValidation       = "EnhancedValidation"
 )
 
 // PodValidatingHandler handles Pod
@@ -48,6 +49,9 @@ type PodValidatingHandler struct {
 
 	// QuotaEvaluator evaluate pod quota usage
 	QuotaEvaluator quotaevaluate.Evaluator
+
+	// PodEnhancedValidator manages pod enhanced validation configuration
+	PodEnhancedValidator *PodEnhancedValidator
 }
 
 var _ admission.Handler = &PodValidatingHandler{}
@@ -118,6 +122,14 @@ func (h *PodValidatingHandler) validatingPodFn(ctx context.Context, req admissio
 		return false, reason, err
 	}
 
+	start = time.Now()
+	reason, err = h.podEnhancedValidate(ctx, req)
+	metrics.RecordWebhookDurationMilliseconds(metrics.ValidatingWebhook,
+		metrics.Pod, string(req.Operation), err, EnhancedValidation, time.Since(start).Seconds())
+	if err != nil {
+		return false, reason, err
+	}
+
 	return
 }
 
diff --git a/pkg/webhook/pod/validating/webhooks.go b/pkg/webhook/pod/validating/webhooks.go
index c2edb150..d969502a 100644
--- a/pkg/webhook/pod/validating/webhooks.go
+++ b/pkg/webhook/pod/validating/webhooks.go
@@ -52,6 +52,7 @@ func (b *podValidateBuilder) Build() admission.Handler {
 	}
 	quotaAccessor := quotaevaluate.NewQuotaAccessor(h.Client)
 	h.QuotaEvaluator = quotaevaluate.NewQuotaEvaluator(quotaAccessor, 16, make(chan struct{}))
+	h.PodEnhancedValidator = NewPodEnhancedValidator(b.mgr.GetClient())
 
 	return h
 }



Test patch:
diff --git a/pkg/scheduler/plugins/noderesourcefitplus/node_resources_fit_plus_test.go b/pkg/scheduler/plugins/noderesourcefitplus/node_resources_fit_plus_test.go
index 119b7091..267c3168 100644
--- a/pkg/scheduler/plugins/noderesourcefitplus/node_resources_fit_plus_test.go
+++ b/pkg/scheduler/plugins/noderesourcefitplus/node_resources_fit_plus_test.go
@@ -22,7 +22,6 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
-	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	apiruntime "k8s.io/apimachinery/pkg/runtime"
@@ -108,7 +107,7 @@ func NodeResourcesPluginFactoryProxy(factoryFn frameworkruntime.PluginFactory, p
 func TestPlugin_Score(t *testing.T) {
 
 	var v1beta2args v1beta3.NodeResourcesFitPlusArgs
-	v1beta2args.Resources = map[v1.ResourceName]v1beta3.ResourcesType{
+	v1beta2args.Resources = map[corev1.ResourceName]v1beta3.ResourcesType{
 		"nvidia.com/gpu": {Type: k8sConfig.MostAllocated, Weight: 2},
 		"cpu":            {Type: k8sConfig.LeastAllocated, Weight: 1},
 		"memory":         {Type: k8sConfig.LeastAllocated, Weight: 1},
@@ -131,14 +130,16 @@ func TestPlugin_Score(t *testing.T) {
 			},
 			Status: corev1.NodeStatus{
 				Allocatable: corev1.ResourceList{
-					corev1.ResourceCPU:    resource.MustParse("96"),
-					corev1.ResourceMemory: resource.MustParse("512Gi"),
-					"nvidia.com/gpu":      resource.MustParse("8"),
+					corev1.ResourceCPU:              resource.MustParse("96"),
+					corev1.ResourceMemory:           resource.MustParse("512Gi"),
+					"nvidia.com/gpu":                resource.MustParse("8"),
+					corev1.ResourceEphemeralStorage: resource.MustParse("100Gi"),
 				},
 				Capacity: corev1.ResourceList{
-					corev1.ResourceCPU:    resource.MustParse("96"),
-					corev1.ResourceMemory: resource.MustParse("512Gi"),
-					"nvidia.com/gpu":      resource.MustParse("8"),
+					corev1.ResourceCPU:              resource.MustParse("96"),
+					corev1.ResourceMemory:           resource.MustParse("512Gi"),
+					"nvidia.com/gpu":                resource.MustParse("8"),
+					corev1.ResourceEphemeralStorage: resource.MustParse("100Gi"),
 				},
 			},
 		},
@@ -148,16 +149,18 @@ func TestPlugin_Score(t *testing.T) {
 			},
 			Status: corev1.NodeStatus{
 				Allocatable: corev1.ResourceList{
-					corev1.ResourceCPU:    resource.MustParse("96"),
-					corev1.ResourceMemory: resource.MustParse("512Gi"),
-					"nvidia.com/gpu":      resource.MustParse("8"),
-					"xx.xx/xx":            resource.MustParse("8"),
+					corev1.ResourceCPU:              resource.MustParse("96"),
+					corev1.ResourceMemory:           resource.MustParse("512Gi"),
+					"nvidia.com/gpu":                resource.MustParse("8"),
+					"xx.xx/xx":                      resource.MustParse("8"),
+					corev1.ResourceEphemeralStorage: resource.MustParse("100Gi"),
 				},
 				Capacity: corev1.ResourceList{
-					corev1.ResourceCPU:    resource.MustParse("96"),
-					corev1.ResourceMemory: resource.MustParse("512Gi"),
-					"nvidia.com/gpu":      resource.MustParse("8"),
-					"xx.xx/xx":            resource.MustParse("8"),
+					corev1.ResourceCPU:              resource.MustParse("96"),
+					corev1.ResourceMemory:           resource.MustParse("512Gi"),
+					"nvidia.com/gpu":                resource.MustParse("8"),
+					"xx.xx/xx":                      resource.MustParse("8"),
+					corev1.ResourceEphemeralStorage: resource.MustParse("100Gi"),
 				},
 			},
 		},
@@ -237,14 +240,16 @@ func TestPlugin_Score(t *testing.T) {
 					Name: "test-container",
 					Resources: corev1.ResourceRequirements{
 						Limits: corev1.ResourceList{
-							corev1.ResourceCPU:    resource.MustParse("16"),
-							corev1.ResourceMemory: resource.MustParse("32Gi"),
-							"nvidia.com/gpu":      resource.MustParse("2"),
+							corev1.ResourceCPU:              resource.MustParse("16"),
+							corev1.ResourceMemory:           resource.MustParse("32Gi"),
+							"nvidia.com/gpu":                resource.MustParse("2"),
+							corev1.ResourceEphemeralStorage: resource.MustParse("10Gi"),
 						},
 						Requests: corev1.ResourceList{
-							corev1.ResourceCPU:    resource.MustParse("16"),
-							corev1.ResourceMemory: resource.MustParse("32Gi"),
-							"nvidia.com/gpu":      resource.MustParse("2"),
+							corev1.ResourceCPU:              resource.MustParse("16"),
+							corev1.ResourceMemory:           resource.MustParse("32Gi"),
+							"nvidia.com/gpu":                resource.MustParse("2"),
+							corev1.ResourceEphemeralStorage: resource.MustParse("10Gi"),
 						},
 					},
 				},
diff --git a/pkg/webhook/pod/validating/pod_enhanced_validator_test.go b/pkg/webhook/pod/validating/pod_enhanced_validator_test.go
new file mode 100644
index 00000000..3d614292
--- /dev/null
+++ b/pkg/webhook/pod/validating/pod_enhanced_validator_test.go
@@ -0,0 +1,380 @@
+/*
+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 validating
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	admissionv1 "k8s.io/api/admission/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"github.com/koordinator-sh/koordinator/pkg/features"
+	"github.com/koordinator-sh/koordinator/pkg/util"
+	utilfeature "github.com/koordinator-sh/koordinator/pkg/util/feature"
+	"github.com/koordinator-sh/koordinator/pkg/webhook/elasticquota"
+)
+
+func TestPodEnhancedValidate(t *testing.T) {
+	quotaLabelRequiredCM := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      PodEnhancedValidatorConfigName,
+			Namespace: PodEnhancedValidatorConfigNamespace,
+		},
+		Data: map[string]string{
+			ConfigKeyEnable: "true",
+			ConfigKeyRules: `[
+						{
+							"name": "quota-label-required",
+							"requiredLabels": ["quota.scheduling.koordinator.sh/name"],
+							"namespaceWhitelist": ["kube-system", "koordinator-system"]
+						}
+					]`,
+		},
+	}
+	multipleLabelsRequiredCM := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      PodEnhancedValidatorConfigName,
+			Namespace: PodEnhancedValidatorConfigNamespace,
+		},
+		Data: map[string]string{
+			ConfigKeyEnable: "true",
+			ConfigKeyRules: `[
+						{
+							"name": "multiple-labels-required",
+							"requiredLabels": ["product", "app"]
+						}
+					]`,
+		},
+	}
+	testCases := []struct {
+		name        string
+		operation   admissionv1.Operation
+		newPod      *corev1.Pod
+		configMap   *corev1.ConfigMap
+		wantAllowed bool
+		wantReason  string
+		wantErr     bool
+	}{
+		{
+			name:        "no config map: should allow all pods",
+			operation:   admissionv1.Create,
+			newPod:      elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap:   nil,
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "validation disabled: should allow all pods",
+			operation: admissionv1.Create,
+			newPod:    elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      PodEnhancedValidatorConfigName,
+					Namespace: PodEnhancedValidatorConfigNamespace,
+				},
+				Data: map[string]string{
+					ConfigKeyEnable: "false",
+					ConfigKeyRules: `[
+						{
+							"name": "quota-label-required",
+							"requiredLabels": ["quota.scheduling.koordinator.sh/name"]
+						}
+					]`,
+				},
+			},
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "empty rules: should allow all pods",
+			operation: admissionv1.Create,
+			newPod:    elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      PodEnhancedValidatorConfigName,
+					Namespace: PodEnhancedValidatorConfigNamespace,
+				},
+				Data: map[string]string{
+					ConfigKeyEnable: "true",
+					ConfigKeyRules:  `[]`,
+				},
+			},
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "quota-label-required: create pod with quota label",
+			operation: admissionv1.Create,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("quota.scheduling.koordinator.sh/name", "quota1").Obj(),
+			configMap:   quotaLabelRequiredCM,
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "quota-label-required: update pod with quota label",
+			operation: admissionv1.Update,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("quota.scheduling.koordinator.sh/name", "quota1").Obj(),
+			configMap:   quotaLabelRequiredCM,
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:        "quota-label-required: pod without quota label",
+			operation:   admissionv1.Create,
+			newPod:      elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap:   quotaLabelRequiredCM,
+			wantAllowed: false,
+			wantReason: "validation rule 'quota-label-required' failed: " +
+				"required label 'quota.scheduling.koordinator.sh/name' is missing",
+			wantErr: true,
+		},
+		{
+			name:        "quota-label-required: update pod without quota label",
+			operation:   admissionv1.Update,
+			newPod:      elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap:   quotaLabelRequiredCM,
+			wantAllowed: false,
+			wantReason: "validation rule 'quota-label-required' failed: " +
+				"required label 'quota.scheduling.koordinator.sh/name' is missing",
+			wantErr: true,
+		},
+		{
+			name:        "quota-label-required: pod in whitelisted namespace",
+			operation:   admissionv1.Create,
+			newPod:      elasticquota.MakePod("kube-system", "pod1").Obj(),
+			configMap:   quotaLabelRequiredCM,
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:        "quota-label-required: pod in another whitelisted namespace",
+			operation:   admissionv1.Create,
+			newPod:      elasticquota.MakePod("koordinator-system", "pod1").Obj(),
+			configMap:   quotaLabelRequiredCM,
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "multiple-labels-required: pod with required labels",
+			operation: admissionv1.Create,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("product", "product1").
+				Label("app", "app1").Obj(),
+			configMap:   multipleLabelsRequiredCM,
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "multiple-labels-required: pod missing app label",
+			operation: admissionv1.Create,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("product", "product1").Obj(),
+			configMap:   multipleLabelsRequiredCM,
+			wantAllowed: false,
+			wantReason:  "validation rule 'multiple-labels-required' failed: required label 'app' is missing",
+			wantErr:     true,
+		},
+		{
+			name:      "multiple-labels-required: pod missing product label",
+			operation: admissionv1.Create,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("app", "app1").Obj(),
+			configMap:   multipleLabelsRequiredCM,
+			wantAllowed: false,
+			wantReason:  "validation rule 'multiple-labels-required' failed: required label 'product' is missing",
+			wantErr:     true,
+		},
+		{
+			name:        "multiple-labels-required: pod without required labels",
+			operation:   admissionv1.Create,
+			newPod:      elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap:   multipleLabelsRequiredCM,
+			wantAllowed: false,
+			wantReason:  "validation rule 'multiple-labels-required' failed: required labels are missing: [product app]",
+			wantErr:     true,
+		},
+		{
+			name:      "multiple rules: all rules pass",
+			operation: admissionv1.Create,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("quota.scheduling.koordinator.sh/name", "quota1").
+				Label("app", "myapp").Obj(),
+			configMap: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      PodEnhancedValidatorConfigName,
+					Namespace: PodEnhancedValidatorConfigNamespace,
+				},
+				Data: map[string]string{
+					ConfigKeyEnable: "true",
+					ConfigKeyRules: `[
+						{
+							"name": "quota-label-required",
+							"requiredLabels": ["quota.scheduling.koordinator.sh/name"]
+						},
+						{
+							"name": "app-label-required",
+							"requiredLabels": ["app"]
+						}
+					]`,
+				},
+			},
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "multiple rules: first rule fails",
+			operation: admissionv1.Create,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("app", "myapp").Obj(),
+			configMap: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      PodEnhancedValidatorConfigName,
+					Namespace: PodEnhancedValidatorConfigNamespace,
+				},
+				Data: map[string]string{
+					ConfigKeyEnable: "true",
+					ConfigKeyRules: `[
+						{
+							"name": "quota-label-required",
+							"requiredLabels": ["quota.scheduling.koordinator.sh/name"]
+						},
+						{
+							"name": "app-label-required",
+							"requiredLabels": ["app"]
+						}
+					]`,
+				},
+			},
+			wantAllowed: false,
+			wantReason: "validation rule 'quota-label-required' failed: " +
+				"required label 'quota.scheduling.koordinator.sh/name' is missing",
+			wantErr: true,
+		},
+		{
+			name:      "update operation: should validate",
+			operation: admissionv1.Update,
+			newPod: elasticquota.MakePod("ns1", "pod1").
+				Label("quota.scheduling.koordinator.sh/name", "quota1").Obj(),
+			configMap: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      PodEnhancedValidatorConfigName,
+					Namespace: PodEnhancedValidatorConfigNamespace,
+				},
+				Data: map[string]string{
+					ConfigKeyEnable: "true",
+					ConfigKeyRules: `[
+						{
+							"name": "quota-label-required",
+							"requiredLabels": ["quota.scheduling.koordinator.sh/name"]
+						}
+					]`,
+				},
+			},
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+		{
+			name:      "delete operation: should allow",
+			operation: admissionv1.Delete,
+			newPod:    elasticquota.MakePod("ns1", "pod1").Obj(),
+			configMap: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      PodEnhancedValidatorConfigName,
+					Namespace: PodEnhancedValidatorConfigNamespace,
+				},
+				Data: map[string]string{
+					ConfigKeyEnable: "true",
+					ConfigKeyRules: `[
+						{
+							"name": "quota-label-required",
+							"requiredLabels": ["quota.scheduling.koordinator.sh/name"]
+						}
+					]`,
+				},
+			},
+			wantAllowed: true,
+			wantReason:  "",
+			wantErr:     false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			defer utilfeature.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate,
+				features.EnablePodEnhancedValidator, true)()
+			scheme := runtime.NewScheme()
+			_ = clientgoscheme.AddToScheme(scheme)
+			decoder := admission.NewDecoder(scheme)
+			var fakeClient client.Client
+			if tc.configMap != nil {
+				fakeClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tc.configMap).Build()
+			} else {
+				fakeClient = fake.NewClientBuilder().WithScheme(scheme).Build()
+			}
+			h := &PodValidatingHandler{
+				Client:  fakeClient,
+				Decoder: decoder,
+			}
+			// create a validator to build the whitelist set
+			h.PodEnhancedValidator = NewPodEnhancedValidator(fakeClient)
+
+			var objRawExt, oldObjRawExt runtime.RawExtension
+			if tc.newPod != nil {
+				objRawExt = runtime.RawExtension{
+					Raw: []byte(util.DumpJSON(tc.newPod)),
+				}
+			}
+
+			req := newAdmissionRequest(tc.operation, objRawExt, oldObjRawExt, "pods")
+			gotReason, err := h.podEnhancedValidate(context.TODO(), admission.Request{AdmissionRequest: req})
+			if tc.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			gotAllowed := err == nil
+			if gotAllowed != tc.wantAllowed {
+				t.Errorf("podEnhancedValidate gotAllowed = %v, want %v", gotAllowed, tc.wantAllowed)
+			}
+			if gotReason != tc.wantReason {
+				t.Errorf("podEnhancedValidate:\n"+
+					"gotReason = %v,\n"+
+					"want = %v", gotReason, tc.wantReason)
+			}
+		})
+	}
+}
diff --git a/pkg/webhook/pod/validating/validating_handler_test.go b/pkg/webhook/pod/validating/validating_handler_test.go
index d8fed0e1..91c4636c 100644
--- a/pkg/webhook/pod/validating/validating_handler_test.go
+++ b/pkg/webhook/pod/validating/validating_handler_test.go
@@ -25,6 +25,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	clientcache "k8s.io/client-go/tools/cache"
 	sigcache "sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
@@ -39,7 +40,12 @@ import (
 )
 
 func makeTestHandler() *PodValidatingHandler {
-	client := fake.NewClientBuilder().Build()
+	// Create an independent scheme to avoid race conditions
+	scheme := runtime.NewScheme()
+	_ = v1alpha1.AddToScheme(scheme)
+	_ = clientgoscheme.AddToScheme(scheme)
+
+	client := fake.NewClientBuilder().WithScheme(scheme).Build()
 	sche := client.Scheme()
 	sche.AddKnownTypes(schema.GroupVersion{
 		Group:   "scheduling.sigs.k8s.io",