Repository: kubernetes-sigs/kueue (2026-01-15T04:28:06Z)

Issue:
Support DRA Extended Resources (KEP-5004) in Kueue
 **What would you like to be added**:
When a DeviceClass has `spec.extendedResourceName` set ([KEP-5004](https://github.com/kubernetes/enhancements/issues/5004), Alpha in K8s 1.34), the kube-scheduler automatically converts extended resource requests (e.g., `nvidia.com/gpu: 1`) into ResourceClaims. Kueue needs to handle this to avoid double counting. The workload's pod spec has the extended resource request, and the scheduler also creates a ResourceClaim. Without proper handling, Kueue charges quota for both.

  **Why is this needed**:
Extended resources are the bridge between the legacy device plugin model (`resources.requests: nvidia.com/gpu: 1`) and DRA. As vendors migrate their drivers from device plugins to DRA, existing workloads using extended resource syntax should continue working with Kueue quota management. Without this, workloads using extended resources with DRA-backed DeviceClasses would either be double-counted or not counted at all.

 **This enhancement requires the following artifacts**:

  - [x] Design doc — https://github.com/kubernetes-sigs/kueue/pull/8734
  - [x] Implementation — https://github.com/kubernetes-sigs/kueue/pull/8597
  - [ ] Docs update

  Part of #8243



Discussion:
/assign
/kind feature
/reopen
for documentations @sohankunkerkar 
@tenzen-y: Reopened this issue.

<details>

In response to [this](https://github.com/kubernetes-sigs/kueue/issues/9868#issuecomment-4104951082):

>/reopen
>for documentations @sohankunkerkar 


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>



Patch:
diff --git a/charts/kueue/templates/rbac/role.yaml b/charts/kueue/templates/rbac/role.yaml
index 525f26636bc..c751b970627 100644
--- a/charts/kueue/templates/rbac/role.yaml
+++ b/charts/kueue/templates/rbac/role.yaml
@@ -364,6 +364,7 @@ rules:
   - apiGroups:
       - resource.k8s.io
     resources:
+      - deviceclasses
       - resourceclaims
       - resourceclaimtemplates
     verbs:
diff --git a/cmd/kueue/main.go b/cmd/kueue/main.go
index 3b9bde8a393..7de89bf06bc 100644
--- a/cmd/kueue/main.go
+++ b/cmd/kueue/main.go
@@ -28,6 +28,7 @@ import (
 
 	zaplog "go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
+	resourceapi "k8s.io/api/resource/v1"
 	schedulingv1 "k8s.io/api/scheduling/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -101,6 +102,7 @@ var (
 func init() {
 	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
 	utilruntime.Must(schedulingv1.AddToScheme(scheme))
+	utilruntime.Must(resourceapi.AddToScheme(scheme))
 
 	utilruntime.Must(kueue.AddToScheme(scheme))
 	utilruntime.Must(kueuev1beta1.AddToScheme(scheme))
diff --git a/config/components/rbac/role.yaml b/config/components/rbac/role.yaml
index acb243359be..75536f2e777 100644
--- a/config/components/rbac/role.yaml
+++ b/config/components/rbac/role.yaml
@@ -345,6 +345,7 @@ rules:
 - apiGroups:
   - resource.k8s.io
   resources:
+  - deviceclasses
   - resourceclaims
   - resourceclaimtemplates
   verbs:
diff --git a/pkg/cache/queue/cluster_queue.go b/pkg/cache/queue/cluster_queue.go
index 8e1b4ba8baf..af4ad73a89a 100644
--- a/pkg/cache/queue/cluster_queue.go
+++ b/pkg/cache/queue/cluster_queue.go
@@ -279,7 +279,8 @@ func (c *ClusterQueue) PushOrUpdate(wInfo *workload.Info) {
 		specChangedSinceEval := oldInfo.LastEvaluatedGeneration != 0 &&
 			wInfo.Obj.Generation != oldInfo.LastEvaluatedGeneration
 
-		// Update in place if the workload didn't change to potentially become admissible.
+		// Update in place if the workload didn't change to potentially become admissible,
+		// unless Eviction/Requeued status changed which can affect queue order.
 		if !specChangedSinceEval &&
 			equality.Semantic.DeepEqual(oldInfo.Obj.Spec, wInfo.Obj.Spec) &&
 			!priorityBoostAnnotationChanged(oldInfo, wInfo) &&
@@ -288,7 +289,8 @@ func (c *ClusterQueue) PushOrUpdate(wInfo *workload.Info) {
 				apimeta.FindStatusCondition(wInfo.Obj.Status.Conditions, kueue.WorkloadEvicted)) &&
 			equality.Semantic.DeepEqual(apimeta.FindStatusCondition(oldInfo.Obj.Status.Conditions, kueue.WorkloadRequeued),
 				apimeta.FindStatusCondition(wInfo.Obj.Status.Conditions, kueue.WorkloadRequeued)) &&
-			workload.HasClosedPreemptionGate(oldInfo.Obj) == workload.HasClosedPreemptionGate(wInfo.Obj) {
+			workload.HasClosedPreemptionGate(oldInfo.Obj) == workload.HasClosedPreemptionGate(wInfo.Obj) &&
+			!draRequestsChanged(oldInfo, wInfo) {
 			c.inadmissibleWorkloads.insert(key, wInfo)
 			return
 		}
@@ -315,6 +317,16 @@ func priorityBoostAnnotationChanged(oldInfo, newInfo *workload.Info) bool {
 	return oldInfo.Obj.Annotations[controllerconstants.PriorityBoostAnnotationKey] != newInfo.Obj.Annotations[controllerconstants.PriorityBoostAnnotationKey]
 }
 
+// draRequestsChanged returns true if DRA preprocessing changed TotalRequests.
+// DRA extended resources are resolved in Reconcile, which can modify TotalRequests
+// without changing the workload Spec.
+func draRequestsChanged(oldInfo, newInfo *workload.Info) bool {
+	if !features.Enabled(features.DynamicResourceAllocation) {
+		return false
+	}
+	return !equality.Semantic.DeepEqual(oldInfo.TotalRequests, newInfo.TotalRequests)
+}
+
 func (c *ClusterQueue) RebuildLocalQueue(lqName string) {
 	c.rwm.Lock()
 	defer c.rwm.Unlock()
diff --git a/pkg/cache/queue/manager.go b/pkg/cache/queue/manager.go
index 325cf19f141..6b764cc27b1 100644
--- a/pkg/cache/queue/manager.go
+++ b/pkg/cache/queue/manager.go
@@ -36,6 +36,7 @@ import (
 	"sigs.k8s.io/kueue/pkg/cache/hierarchy"
 	queueafs "sigs.k8s.io/kueue/pkg/cache/queue/afs"
 	utilindexer "sigs.k8s.io/kueue/pkg/controller/core/indexer"
+	"sigs.k8s.io/kueue/pkg/dra"
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/metrics"
 	afs "sigs.k8s.io/kueue/pkg/util/admissionfairsharing"
@@ -413,7 +414,7 @@ func (m *Manager) AddLocalQueue(ctx context.Context, q *kueue.LocalQueue) error
 		}
 
 		log := ctrl.LoggerFrom(ctx).WithValues("workload", klog.KObj(&w))
-		if workload.NeedsDRAReconcile(&w) {
+		if dra.NeedsDRAReconcile(&w) {
 			if m.draReconcileChannel != nil {
 				m.draReconcileChannel <- event.TypedGenericEvent[*kueue.Workload]{Object: &w}
 				log.V(4).Info("Sent DRA workload to reconcile channel due to LocalQueue creation")
diff --git a/pkg/config/validation.go b/pkg/config/validation.go
index 5d5fcaeed8e..daccec2f53a 100644
--- a/pkg/config/validation.go
+++ b/pkg/config/validation.go
@@ -525,6 +525,12 @@ func ValidateFeatureGates(featureGateCLI string, featureGateMap map[string]bool)
 		}
 	}
 
+	if features.Enabled(features.DRAExtendedResources) {
+		if !features.Enabled(features.DynamicResourceAllocation) {
+			allErrs = append(allErrs, field.Invalid(featureGatesPath, "DRAExtendedResources", "DRAExtendedResources requires DynamicResourceAllocation to be enabled"))
+		}
+	}
+
 	return allErrs
 }
 
diff --git a/pkg/controller/core/indexer/indexer.go b/pkg/controller/core/indexer/indexer.go
index 8b308493416..9a9bd2a7e5b 100644
--- a/pkg/controller/core/indexer/indexer.go
+++ b/pkg/controller/core/indexer/indexer.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 
 	corev1 "k8s.io/api/core/v1"
+	resourceapi "k8s.io/api/resource/v1"
 	apimeta "k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -33,15 +34,16 @@ import (
 )
 
 const (
-	WorkloadQueueKey           = "spec.queueName"
-	WorkloadClusterQueueKey    = "status.admission.clusterQueue"
-	QueueClusterQueueKey       = "spec.clusterQueue"
-	LimitRangeHasContainerType = "spec.hasContainerType"
-	WorkloadQuotaReservedKey   = "status.quotaReserved"
-	WorkloadRuntimeClassKey    = "spec.runtimeClass"
-	OwnerReferenceUID          = "metadata.ownerReferences.uid"
-	WorkloadAdmissionCheckKey  = "status.admissionChecks"
-	WorkloadPriorityClassKey   = "spec.priorityClassRef"
+	WorkloadQueueKey                     = "spec.queueName"
+	WorkloadClusterQueueKey              = "status.admission.clusterQueue"
+	QueueClusterQueueKey                 = "spec.clusterQueue"
+	LimitRangeHasContainerType           = "spec.hasContainerType"
+	WorkloadQuotaReservedKey             = "status.quotaReserved"
+	WorkloadRuntimeClassKey              = "spec.runtimeClass"
+	OwnerReferenceUID                    = "metadata.ownerReferences.uid"
+	WorkloadAdmissionCheckKey            = "status.admissionChecks"
+	WorkloadPriorityClassKey             = "spec.priorityClassRef"
+	DeviceClassExtendedResourceNameIndex = "spec.extendedResourceName"
 	// WorkloadSliceNameKey is an index for pods by their workload slice name annotation.
 	// Used to find pods belonging to an elastic workload slice chain.
 	WorkloadSliceNameKey = "metadata.workloadSliceName"
@@ -212,6 +214,18 @@ func IndexWorkloadPriorityClass(obj client.Object) []string {
 	return []string{wl.Spec.PriorityClassRef.Name}
 }
 
+// IndexDeviceClassExtendedResourceName indexes DeviceClasses by spec.extendedResourceName.
+func IndexDeviceClassExtendedResourceName(obj client.Object) []string {
+	dc, ok := obj.(*resourceapi.DeviceClass)
+	if !ok {
+		return nil
+	}
+	if dc.Spec.ExtendedResourceName == nil || *dc.Spec.ExtendedResourceName == "" {
+		return nil
+	}
+	return []string{*dc.Spec.ExtendedResourceName}
+}
+
 // Setup sets the index with the given fields for core apis.
 func Setup(ctx context.Context, indexer client.FieldIndexer) error {
 	if err := indexer.IndexField(ctx, &kueue.Workload{}, WorkloadQueueKey, IndexWorkloadQueue); err != nil {
@@ -253,5 +267,11 @@ func Setup(ctx context.Context, indexer client.FieldIndexer) error {
 			return fmt.Errorf("setting index on ownerReferences.uid for Pod: %w", err)
 		}
 	}
+	// Index DeviceClasses by extendedResourceName for fast lookup during extended resource translation.
+	if features.Enabled(features.DRAExtendedResources) {
+		if err := indexer.IndexField(ctx, &resourceapi.DeviceClass{}, DeviceClassExtendedResourceNameIndex, IndexDeviceClassExtendedResourceName); err != nil {
+			return fmt.Errorf("setting index on extendedResourceName for DeviceClass: %w", err)
+		}
+	}
 	return nil
 }
diff --git a/pkg/controller/core/workload_controller.go b/pkg/controller/core/workload_controller.go
index 14960aed488..f452dd1b4b9 100644
--- a/pkg/controller/core/workload_controller.go
+++ b/pkg/controller/core/workload_controller.go
@@ -33,6 +33,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/validation/field"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
@@ -185,6 +186,7 @@ func (r *WorkloadReconciler) logger() logr.Logger {
 // +kubebuilder:rbac:groups=node.k8s.io,resources=runtimeclasses,verbs=get;list;watch
 // +kubebuilder:rbac:groups=resource.k8s.io,resources=resourceclaims,verbs=get;list;watch
 // +kubebuilder:rbac:groups=resource.k8s.io,resources=resourceclaimtemplates,verbs=get;list;watch
+// +kubebuilder:rbac:groups=resource.k8s.io,resources=deviceclasses,verbs=get;list;watch
 
 func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 	log := ctrl.LoggerFrom(ctx)
@@ -253,8 +255,7 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		})
 		return reconcile.Result{}, client.IgnoreNotFound(err)
 	}
-	if features.Enabled(features.DynamicResourceAllocation) && workload.Status(&wl) == workload.StatusPending &&
-		workload.HasDRA(&wl) {
+	if workload.Status(&wl) == workload.StatusPending && dra.NeedsDRAReconcile(&wl) {
 		workload.AdjustResources(ctx, r.client, &wl)
 		if workload.HasResourceClaim(&wl) {
 			log.V(3).Info("Workload is inadmissible because it uses resource claims which is not supported")
@@ -272,6 +273,8 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		}
 
 		log.V(3).Info("Processing DRA resources for workload")
+
+		// Process ResourceClaimTemplates (existing DRA path)
 		draResources, fieldErrs := dra.GetResourceRequestsForResourceClaimTemplates(ctx, r.client, &wl)
 		if len(fieldErrs) > 0 {
 			err := fieldErrs.ToAggregate()
@@ -289,6 +292,44 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 			return ctrl.Result{}, err
 		}
 
+		// Process Extended Resources backed by DRA (new path)
+		var extendedResources map[kueue.PodSetReference]corev1.ResourceList
+		var replacedExtendedResources map[kueue.PodSetReference]sets.Set[corev1.ResourceName]
+		if features.Enabled(features.DRAExtendedResources) {
+			var extFieldErrs field.ErrorList
+			extendedResources, replacedExtendedResources, extFieldErrs = dra.ResolveExtendedResourceQuota(ctx, r.client, &wl)
+			if len(extFieldErrs) > 0 {
+				err := extFieldErrs.ToAggregate()
+				log.Error(err, "Failed to process DRA extended resources for workload")
+				updateErr := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+					updated := workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, err.Error(), r.clock.Now())
+					if updated && workload.SetRequeuedCondition(wl, kueue.WorkloadInadmissible, err.Error(), false) {
+						updated = true
+					}
+					return updated, nil
+				})
+				if updateErr != nil {
+					return ctrl.Result{}, fmt.Errorf("failed to update workload status for DRA extended resources error: %w", updateErr)
+				}
+				return ctrl.Result{}, err
+			}
+		}
+
+		// Merge extended resources into draResources.
+		// When a DeviceClass appears in both paths, the extended resources path uses
+		// the deviceClassMappings logical name as the quota key, unifying quota accounting.
+		for podSetName, resources := range extendedResources {
+			if existing, ok := draResources[podSetName]; ok {
+				// Merge resources for the same podset
+				draResources[podSetName] = resource.MergeResourceListKeepSum(existing, resources)
+			} else {
+				if draResources == nil {
+					draResources = make(map[kueue.PodSetReference]corev1.ResourceList)
+				}
+				draResources[podSetName] = resources
+			}
+		}
+
 		quotaReservedCond := apimeta.FindStatusCondition(wl.Status.Conditions, kueue.WorkloadQuotaReserved)
 		requeuedCond := apimeta.FindStatusCondition(wl.Status.Conditions, kueue.WorkloadRequeued)
 
@@ -307,8 +348,8 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		}
 
 		var queueOptions []workload.InfoOption
-		if len(draResources) > 0 {
-			queueOptions = append(queueOptions, workload.WithPreprocessedDRAResources(draResources))
+		if len(draResources) > 0 || len(replacedExtendedResources) > 0 {
+			queueOptions = append(queueOptions, workload.WithPreprocessedDRAResources(draResources, replacedExtendedResources))
 		}
 
 		if workload.IsAdmissible(&wl) {
@@ -966,7 +1007,7 @@ func (r *WorkloadReconciler) Create(e event.TypedCreateEvent[*kueue.Workload]) b
 	wlCopy := e.Object.DeepCopy()
 	workload.AdjustResources(ctx, r.client, wlCopy)
 
-	if workload.NeedsDRAReconcile(e.Object) {
+	if dra.NeedsDRAReconcile(e.Object) {
 		log.V(2).Info("Skipping DRA workload in Create event - will be handled in Reconcile")
 		return true
 	}
@@ -1055,11 +1096,10 @@ func (r *WorkloadReconciler) Update(e event.TypedUpdateEvent[*kueue.Workload]) b
 			}
 		})
 	case prevStatus == workload.StatusPending && status == workload.StatusPending:
-		if workload.NeedsDRAReconcile(e.ObjectNew) {
+		if dra.NeedsDRAReconcile(e.ObjectNew) {
 			log.V(2).Info("Skipping queue update for DRA workload - handled in Reconcile")
 		} else {
-			err := r.queues.UpdateWorkload(log, wlCopy)
-			if err != nil {
+			if err := r.queues.UpdateWorkload(log, wlCopy); err != nil {
 				log.V(2).Info("ignored an error for now", "error", err)
 			}
 		}
@@ -1092,8 +1132,7 @@ func (r *WorkloadReconciler) Update(e event.TypedUpdateEvent[*kueue.Workload]) b
 			// AddOrUpdateWorkload is only called once. When moving it to the main
 			// reconciler, we would execute it on every run, which might mess up the state.
 			if immediate {
-				// Skip queue operations for DRA workloads - they are handled in Reconcile loop
-				if features.Enabled(features.DynamicResourceAllocation) && workload.HasDRA(e.ObjectNew) {
+				if dra.NeedsDRAReconcile(e.ObjectNew) {
 					log.V(2).Info("Skipping immediate requeue for DRA workload - handled in Reconcile")
 				} else {
 					if err := r.queues.AddOrUpdateWorkloadWithoutLock(log, wlCopy); err != nil {
@@ -1299,7 +1338,7 @@ func (h *resourceUpdatesHandler) queueReconcileForPending(ctx context.Context, q
 		log.V(5).Info("Queue reconcile for")
 		workload.AdjustResources(ctrl.LoggerInto(ctx, log), h.r.client, wlCopy)
 
-		if features.Enabled(features.DynamicResourceAllocation) && workload.HasDRA(wlCopy) {
+		if dra.NeedsDRAReconcile(wlCopy) {
 			req := reconcile.Request{
 				NamespacedName: types.NamespacedName{
 					Name:      wlCopy.Name,
diff --git a/pkg/dra/extended_resources.go b/pkg/dra/extended_resources.go
new file mode 100644
index 00000000000..3c52ac71457
--- /dev/null
+++ b/pkg/dra/extended_resources.go
@@ -0,0 +1,194 @@
+/*
+Copyright The Kubernetes 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 dra
+
+import (
+	"context"
+	"fmt"
+
+	corev1 "k8s.io/api/core/v1"
+	resourceapi "k8s.io/api/resource/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/validation/field"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta2"
+	"sigs.k8s.io/kueue/pkg/features"
+	utilresource "sigs.k8s.io/kueue/pkg/util/resource"
+	"sigs.k8s.io/kueue/pkg/workload"
+)
+
+// NeedsDRAReconcile returns true if the workload needs DRA processing in Reconcile.
+// Fast in-memory check with no API calls. Uses a broad format-based filter
+// (IsExtendedResourceName) which may over-trigger for non-DRA extended resources,
+// but this only costs one extra Reconcile that finds no DeviceClass and queues normally.
+//
+// Note: there is no DeviceClass watcher. If a DeviceClass is created after a workload
+// was marked inadmissible, requeuing depends on the next QueueInadmissibleWorkloads event.
+func NeedsDRAReconcile(wl *kueue.Workload) bool {
+	if !features.Enabled(features.DynamicResourceAllocation) {
+		return false
+	}
+	if workload.HasDRA(wl) {
+		return true
+	}
+	if !features.Enabled(features.DRAExtendedResources) {
+		return false
+	}
+	for i := range wl.Spec.PodSets {
+		ps := &wl.Spec.PodSets[i]
+		for _, containers := range [][]corev1.Container{ps.Template.Spec.InitContainers, ps.Template.Spec.Containers} {
+			for _, c := range containers {
+				for name, qty := range c.Resources.Requests {
+					if !qty.IsZero() && utilresource.IsExtendedResourceName(name) {
+						return true
+					}
+				}
+			}
+		}
+	}
+	return false
+}
+
+// resolveContainerExtendedResources converts DRA-backed extended resources in a
+// container's requests into logical quota keys. For each extended resource, it looks
+// up DeviceClasses by spec.extendedResourceName. If a DeviceClass is found in
+// deviceClassMappings, the mapped name is used as quota key; otherwise the
+// extendedResourceName itself is used. Returns the converted resources and the set
+// of original resource names that were replaced (for double-count prevention).
+func resolveContainerExtendedResources(
+	ctx context.Context,
+	cl client.Client,
+	container corev1.Container,
+	containerPath *field.Path,
+) (corev1.ResourceList, sets.Set[corev1.ResourceName], field.ErrorList) {
+	log := ctrl.LoggerFrom(ctx)
+	result := corev1.ResourceList{}
+	replaced := sets.New[corev1.ResourceName]()
+	var errs field.ErrorList
+
+	for resourceName, quantity := range container.Resources.Requests {
+		if quantity.IsZero() || !utilresource.IsExtendedResourceName(resourceName) {
+			continue
+		}
+
+		log.V(4).Info("Checking extended resource for DRA backing", "resource", resourceName, "quantity", quantity.String())
+
+		var dcList resourceapi.DeviceClassList
+		if err := cl.List(ctx, &dcList, client.MatchingFields{
+			"spec.extendedResourceName": string(resourceName),
+		}); err != nil {
+			errs = append(errs, field.InternalError(
+				containerPath.Child("resources", "requests", string(resourceName)),
+				fmt.Errorf("failed to list DeviceClasses for extended resource %q: %w", resourceName, err),
+			))
+			continue
+		}
+
+		if len(dcList.Items) == 0 {
+			log.V(4).Info("No DeviceClass found, not a DRA-backed extended resource", "resource", resourceName)
+			continue
+		}
+
+		qty, ok := quantity.AsInt64()
+		if !ok {
+			errs = append(errs, field.Invalid(
+				containerPath.Child("resources", "requests", string(resourceName)),
+				quantity.String(),
+				"extended resource quantity must be an integer",
+			))
+			continue
+		}
+
+		// Determine the quota key. If the DeviceClass is also in deviceClassMappings,
+		// use the mapped logical name to unify quota with the ResourceClaimTemplate path.
+		// Otherwise, use the extendedResourceName directly.
+		quotaKey := resourceName
+		for _, dc := range dcList.Items {
+			if logicalName, found := Mapper().lookup(corev1.ResourceName(dc.Name)); found {
+				quotaKey = logicalName
+				break
+			}
+		}
+
+		log.V(4).Info("Resolved extended resource to DRA quota key",
+			"resource", resourceName, "quotaKey", quotaKey, "quantity", qty,
+			"deviceClass", dcList.Items[0].Name)
+
+		replaced.Insert(resourceName)
+		result = utilresource.MergeResourceListKeepSum(result, corev1.ResourceList{
+			quotaKey: *resource.NewQuantity(qty, resource.DecimalSI),
+		})
+	}
+	return result, replaced, errs
+}
+
+// ResolveExtendedResourceQuota converts extended resource requests across all PodSets
+// into DRA logical quota resources. Per PodSet, init containers are aggregated with
+// max (sequential) and regular containers with sum (concurrent), then combined with max.
+func ResolveExtendedResourceQuota(ctx context.Context, cl client.Client, wl *kueue.Workload) (
+	map[kueue.PodSetReference]corev1.ResourceList,
+	map[kueue.PodSetReference]sets.Set[corev1.ResourceName],
+	field.ErrorList,
+) {
+	if cl == nil {
+		return nil, nil, nil
+	}
+
+	log := ctrl.LoggerFrom(ctx)
+	perPodSet := make(map[kueue.PodSetReference]corev1.ResourceList)
+	replacedExtendedResources := make(map[kueue.PodSetReference]sets.Set[corev1.ResourceName])
+	var allErrs field.ErrorList
+
+	for i := range wl.Spec.PodSets {
+		ps := &wl.Spec.PodSets[i]
+		replaced := sets.New[corev1.ResourceName]()
+		podSetPath := field.NewPath("spec", "podSets").Index(i).Child("template", "spec")
+
+		// Closure captures outer `replaced` set to accumulate across init and regular containers.
+		resolveContainers := func(containers []corev1.Container, pathSegment string, merge func(a, b corev1.ResourceList) corev1.ResourceList) corev1.ResourceList {
+			var result corev1.ResourceList
+			for j, container := range containers {
+				containerPath := podSetPath.Child(pathSegment).Index(j)
+				res, containerReplaced, errs := resolveContainerExtendedResources(ctx, cl, container, containerPath)
+				allErrs = append(allErrs, errs...)
+				replaced = replaced.Union(containerReplaced)
+				result = merge(result, res)
+			}
+			return result
+		}
+
+		maxInitResources := resolveContainers(ps.Template.Spec.InitContainers, "initContainers", utilresource.MergeResourceListKeepMax)
+		sumRegularResources := resolveContainers(ps.Template.Spec.Containers, "containers", utilresource.MergeResourceListKeepSum)
+
+		aggregated := utilresource.MergeResourceListKeepMax(maxInitResources, sumRegularResources)
+		if len(aggregated) > 0 {
+			log.V(4).Info("Resolved extended resources for PodSet", "podSet", ps.Name, "resources", aggregated)
+			perPodSet[ps.Name] = aggregated
+		}
+		if replaced.Len() > 0 {
+			replacedExtendedResources[ps.Name] = replaced
+		}
+	}
+
+	if len(allErrs) > 0 {
+		return nil, nil, allErrs
+	}
+	return perPodSet, replacedExtendedResources, nil
+}
diff --git a/pkg/dra/mapper.go b/pkg/dra/mapper.go
index 0167aa680ab..dac7bff3434 100644
--- a/pkg/dra/mapper.go
+++ b/pkg/dra/mapper.go
@@ -40,7 +40,7 @@ func newDRAResourceMapper() *ResourceMapper {
 	}
 }
 
-// Returns (logicalResourceName, true) on success or ("", false) when the device class is not mapped.
+// lookup returns (logicalResourceName, true) on success or ("", false) when the device class is not mapped.
 func (m *ResourceMapper) lookup(deviceClass corev1.ResourceName) (corev1.ResourceName, bool) {
 	logicalResource, found := m.deviceClassToResource[deviceClass]
 	return logicalResource, found
diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go
index 8229ebf944f..6f653d7e703 100644
--- a/pkg/features/kube_features.go
+++ b/pkg/features/kube_features.go
@@ -174,9 +174,16 @@ const (
 	// owner: @alaypatel07
 	// kep: https://github.com/kubernetes-sigs/kueue/tree/main/keps/2941-DRA
 	//
-	// Enable quota accounting for Dynamic Resource Allocation (DRA) devies in workloads
+	// Enable quota accounting for Dynamic Resource Allocation (DRA) devices in workloads
 	DynamicResourceAllocation featuregate.Feature = "DynamicResourceAllocation"
 
+	// owner: @sohankunkerkar
+	// kep: https://github.com/kubernetes-sigs/kueue/tree/main/keps/2941-DRA
+	//
+	// Enable extended resources support for DRA. Allows workloads to request DRA devices
+	// via standard resources.requests using DeviceClass extendedResourceName.
+	DRAExtendedResources featuregate.Feature = "DRAExtendedResources"
+
 	// owner: @khrm
 	// kep: https://github.com/kubernetes-sigs/kueue/tree/main/keps/2349-multikueue-external-custom-job-support
 	//
@@ -432,6 +439,9 @@ var defaultVersionedFeatureGates = map[featuregate.Feature]featuregate.Versioned
 	DynamicResourceAllocation: {
 		{Version: version.MustParse("0.14"), Default: false, PreRelease: featuregate.Alpha},
 	},
+	DRAExtendedResources: {
+		{Version: version.MustParse("0.17"), Default: false, PreRelease: featuregate.Alpha},
+	},
 	MultiKueueAdaptersForCustomJobs: {
 		{Version: version.MustParse("0.14"), Default: false, PreRelease: featuregate.Alpha},
 		{Version: version.MustParse("0.15"), Default: true, PreRelease: featuregate.Beta},
diff --git a/pkg/util/resource/resource.go b/pkg/util/resource/resource.go
index 343b2a2f5c0..11e46f0b9d0 100644
--- a/pkg/util/resource/resource.go
+++ b/pkg/util/resource/resource.go
@@ -17,6 +17,8 @@ limitations under the License.
 package resource
 
 import (
+	"strings"
+
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 )
@@ -111,3 +113,23 @@ func IsZero(rl corev1.ResourceList) bool {
 
 	return true
 }
+
+// IsExtendedResourceName returns true if the resource name is an extended resource.
+// An extended resource is a fully-qualified resource name with a domain prefix
+// that is not in the kubernetes.io namespace and is not a standard resource.
+// This matches the upstream logic in k8s.io/kubernetes/pkg/apis/core/helper.
+func IsExtendedResourceName(name corev1.ResourceName) bool {
+	if isNativeResource(name) || isHugePageResourceName(name) {
+		return false
+	}
+	return strings.Contains(string(name), "/")
+}
+
+func isNativeResource(name corev1.ResourceName) bool {
+	return !strings.Contains(string(name), "/") ||
+		strings.HasPrefix(string(name), corev1.ResourceDefaultNamespacePrefix)
+}
+
+func isHugePageResourceName(name corev1.ResourceName) bool {
+	return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
+}
diff --git a/pkg/workload/workload.go b/pkg/workload/workload.go
index 07b55bf2ea9..a59a316f72c 100644
--- a/pkg/workload/workload.go
+++ b/pkg/workload/workload.go
@@ -110,9 +110,9 @@ type AssignmentClusterQueueState struct {
 	ClusterQueueGeneration int64
 }
 
-// dra holds DRA-specific configuration for workload.Info construction.
 type dra struct {
-	preprocessedDRAResources map[kueue.PodSetReference]corev1.ResourceList
+	preprocessedDRAResources  map[kueue.PodSetReference]corev1.ResourceList
+	replacedExtendedResources map[kueue.PodSetReference]sets.Set[corev1.ResourceName]
 }
 
 type InfoOptions struct {
@@ -139,11 +139,15 @@ func WithResourceTransformations(transforms []config.ResourceTransformation) Inf
 	}
 }
 
-// WithPreprocessedDRAResources creates an InfoOption that provides preprocessed DRA resources.
-func WithPreprocessedDRAResources(draResources map[kueue.PodSetReference]corev1.ResourceList) InfoOption {
+// WithPreprocessedDRAResources provides DRA resources to add and extended resources to remove.
+func WithPreprocessedDRAResources(
+	draResources map[kueue.PodSetReference]corev1.ResourceList,
+	replacedExtendedResources map[kueue.PodSetReference]sets.Set[corev1.ResourceName],
+) InfoOption {
 	return func(o *InfoOptions) {
 		o.dra = dra{
-			preprocessedDRAResources: draResources,
+			preprocessedDRAResources:  draResources,
+			replacedExtendedResources: replacedExtendedResources,
 		}
 	}
 }
@@ -589,6 +593,13 @@ func totalRequestsFromPodSets(wl *kueue.Workload, info *InfoOptions) []PodSetRes
 		effectiveRequests = applyResourceTransformations(effectiveRequests, info.resourceTransformations)
 		setRes.Requests = resources.NewRequests(effectiveRequests)
 		if features.Enabled(features.DynamicResourceAllocation) && info.preprocessedDRAResources != nil {
+			// First, remove extended resources that were converted to DRA logical resources
+			if replacedRes, exists := info.replacedExtendedResources[ps.Name]; exists {
+				for extRes := range replacedRes {
+					delete(setRes.Requests, extRes)
+				}
+			}
+			// Then, add the DRA logical resources
 			if draRes, exists := info.preprocessedDRAResources[ps.Name]; exists {
 				for resName, quantity := range draRes {
 					if setRes.Requests == nil {
@@ -1332,17 +1343,6 @@ func HasDRA(w *kueue.Workload) bool {
 	return HasResourceClaim(w) || HasResourceClaimTemplates(w)
 }
 
-// NeedsDRAReconcile returns true if the workload has DRA resources that require
-// processing in the Reconcile loop rather than being queued directly from event
-// handlers. DRA workloads need Reconcile-based processing for proper error
-// handling (e.g. setting Inadmissible conditions on the workload).
-func NeedsDRAReconcile(wl *kueue.Workload) bool {
-	if !features.Enabled(features.DynamicResourceAllocation) {
-		return false
-	}
-	return HasDRA(wl)
-}
-
 // HasResourceClaimTemplates returns true if the workload has ResourceClaimTemplates.
 func HasResourceClaimTemplates(w *kueue.Workload) bool {
 	for _, ps := range w.Spec.PodSets {
diff --git a/site/data/featuregates/versioned_feature_list.yaml b/site/data/featuregates/versioned_feature_list.yaml
index 2a9bf5beb97..a764a4768a5 100644
--- a/site/data/featuregates/versioned_feature_list.yaml
+++ b/site/data/featuregates/versioned_feature_list.yaml
@@ -29,6 +29,12 @@
     lockToDefault: false
     preRelease: Alpha
     version: "0.17"
+- name: DRAExtendedResources
+  versionedSpecs:
+  - default: false
+    lockToDefault: false
+    preRelease: Alpha
+    version: "0.17"
 - name: DynamicResourceAllocation
   versionedSpecs:
   - default: false


Test patch:
diff --git a/hack/testing/e2e-common.sh b/hack/testing/e2e-common.sh
index 432e085c7de..832d5feb9fb 100644
--- a/hack/testing/e2e-common.sh
+++ b/hack/testing/e2e-common.sh
@@ -281,6 +281,8 @@ function patch_kind_config_for_dra {
     cp "$1" "$patched_config"
 
     $YQ -i '.featureGates.DynamicResourceAllocation = true' "$patched_config"
+    # Enable Extended Resources (alpha feature in k8s 1.35)
+    $YQ -i '.featureGates.DRAExtendedResource = true' "$patched_config"
     $YQ -i '.containerdConfigPatches += ["[plugins.\"io.containerd.grpc.v1.cri\"]\n  enable_cdi = true"]' "$patched_config"
     $YQ -i '(.nodes[] | select(.role == "control-plane")).kubeadmConfigPatches[0] = "kind: ClusterConfiguration
 apiVersion: kubeadm.k8s.io/v1beta3
diff --git a/pkg/config/validation_test.go b/pkg/config/validation_test.go
index 9c45f5bd5fa..8779bd2914a 100644
--- a/pkg/config/validation_test.go
+++ b/pkg/config/validation_test.go
@@ -1072,6 +1072,19 @@ func TestValidateFeatureGates(t *testing.T) {
 				},
 			},
 		},
+		"DRAExtendedResources requires DynamicResourceAllocation": {
+			setupFeatureGates: map[featuregate.Feature]bool{
+				features.DRAExtendedResources:      true,
+				features.DynamicResourceAllocation: false,
+			},
+			wantErr: field.ErrorList{
+				&field.Error{
+					Type:   field.ErrorTypeInvalid,
+					Field:  "featureGates",
+					Detail: "DRAExtendedResources requires DynamicResourceAllocation to be enabled",
+				},
+			},
+		},
 	}
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
diff --git a/pkg/dra/extended_resources_test.go b/pkg/dra/extended_resources_test.go
new file mode 100644
index 00000000000..fe6349f0d73
--- /dev/null
+++ b/pkg/dra/extended_resources_test.go
@@ -0,0 +1,519 @@
+/*
+Copyright The Kubernetes 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 dra
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	corev1 "k8s.io/api/core/v1"
+	resourceapi "k8s.io/api/resource/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	configapi "sigs.k8s.io/kueue/apis/config/v1beta2"
+	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta2"
+	utilresource "sigs.k8s.io/kueue/pkg/util/resource"
+)
+
+func newFakeClient(deviceClasses ...*resourceapi.DeviceClass) client.Client {
+	scheme := runtime.NewScheme()
+	_ = resourceapi.AddToScheme(scheme)
+
+	builder := fake.NewClientBuilder().
+		WithScheme(scheme).
+		WithIndex(&resourceapi.DeviceClass{}, "spec.extendedResourceName",
+			func(obj client.Object) []string {
+				dc := obj.(*resourceapi.DeviceClass)
+				if dc.Spec.ExtendedResourceName == nil || *dc.Spec.ExtendedResourceName == "" {
+					return nil
+				}
+				return []string{*dc.Spec.ExtendedResourceName}
+			})
+
+	for _, dc := range deviceClasses {
+		builder = builder.WithObjects(dc)
+	}
+	return builder.Build()
+}
+
+func TestIsExtendedResourceName(t *testing.T) {
+	tests := []struct {
+		name     string
+		resource corev1.ResourceName
+		want     bool
+	}{
+		{
+			name:     "cpu is not extended resource",
+			resource: corev1.ResourceCPU,
+			want:     false,
+		},
+		{
+			name:     "memory is not extended resource",
+			resource: corev1.ResourceMemory,
+			want:     false,
+		},
+		{
+			name:     "ephemeral-storage is not extended resource",
+			resource: corev1.ResourceEphemeralStorage,
+			want:     false,
+		},
+		{
+			name:     "nvidia.com/gpu is extended resource",
+			resource: "nvidia.com/gpu",
+			want:     true,
+		},
+		{
+			name:     "example.com/gpu is extended resource",
+			resource: "example.com/gpu",
+			want:     true,
+		},
+		{
+			name:     "unqualified name is not extended resource",
+			resource: "custom-resource",
+			want:     false,
+		},
+		{
+			name:     "hugepages is not extended resource",
+			resource: "hugepages-2Mi",
+			want:     false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := utilresource.IsExtendedResourceName(tt.resource)
+			if got != tt.want {
+				t.Errorf("utilresource.IsExtendedResourceName(%s) = %v, want %v", tt.resource, got, tt.want)
+			}
+		})
+	}
+}
+
+func TestResolveExtendedResourceQuota(t *testing.T) {
+	gpuDeviceClass := &resourceapi.DeviceClass{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "gpu.nvidia.com",
+		},
+		Spec: resourceapi.DeviceClassSpec{
+			ExtendedResourceName: ptr.To("example.com/gpu"),
+		},
+	}
+
+	migDeviceClass := &resourceapi.DeviceClass{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "mig.nvidia.com",
+		},
+		Spec: resourceapi.DeviceClassSpec{
+			ExtendedResourceName: ptr.To("nvidia.com/mig-1g.10gb"),
+		},
+	}
+
+	plainDeviceClass := &resourceapi.DeviceClass{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "plain.nvidia.com",
+		},
+		Spec: resourceapi.DeviceClassSpec{},
+	}
+
+	tests := []struct {
+		name           string
+		workload       *kueue.Workload
+		deviceClasses  []*resourceapi.DeviceClass
+		mapperMappings []configapi.DeviceClassMapping
+		want           map[kueue.PodSetReference]corev1.ResourceList
+		wantReplaced   map[kueue.PodSetReference]sets.Set[corev1.ResourceName]
+		wantErr        bool
+		wantErrCount   int
+	}{
+		{
+			name: "workload with extended resource backed by DRA",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											corev1.ResourceCPU: resource.MustParse("1"),
+											"example.com/gpu":  resource.MustParse("2"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			want: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"example.com/gpu": resource.MustParse("2"),
+				},
+			},
+			wantReplaced: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu"),
+			},
+		},
+		{
+			name: "workload with multiple extended resources",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											"example.com/gpu":        resource.MustParse("1"),
+											"nvidia.com/mig-1g.10gb": resource.MustParse("2"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass, migDeviceClass},
+			want: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"example.com/gpu":        resource.MustParse("1"),
+					"nvidia.com/mig-1g.10gb": resource.MustParse("2"),
+				},
+			},
+			wantReplaced: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu", "nvidia.com/mig-1g.10gb"),
+			},
+		},
+		{
+			name: "workload with extended resource not backed by DRA (no matching DeviceClass)",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											"other.vendor.io/resource": resource.MustParse("1"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			want:          nil,
+		},
+		{
+			name: "workload with no extended resources",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											corev1.ResourceCPU:    resource.MustParse("1"),
+											corev1.ResourceMemory: resource.MustParse("1Gi"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			want:          nil,
+		},
+		{
+			name: "workload with DeviceClass that has no extendedResourceName",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											"some.other/resource": resource.MustParse("1"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{plainDeviceClass},
+			want:          nil,
+		},
+		{
+			name: "workload with multiple containers",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{
+									{
+										Name:  "c1",
+										Image: "pause",
+										Resources: corev1.ResourceRequirements{
+											Requests: corev1.ResourceList{
+												"example.com/gpu": resource.MustParse("1"),
+											},
+										},
+									},
+									{
+										Name:  "c2",
+										Image: "pause",
+										Resources: corev1.ResourceRequirements{
+											Requests: corev1.ResourceList{
+												"example.com/gpu": resource.MustParse("2"),
+											},
+										},
+									},
+								},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			want: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"example.com/gpu": resource.MustParse("3"),
+				},
+			},
+			wantReplaced: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu"),
+			},
+		},
+		{
+			name: "init containers use max, regular containers use sum",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								InitContainers: []corev1.Container{
+									{
+										Name:  "init1",
+										Image: "pause",
+										Resources: corev1.ResourceRequirements{
+											Requests: corev1.ResourceList{
+												"example.com/gpu": resource.MustParse("5"),
+											},
+										},
+									},
+									{
+										Name:  "init2",
+										Image: "pause",
+										Resources: corev1.ResourceRequirements{
+											Requests: corev1.ResourceList{
+												"example.com/gpu": resource.MustParse("3"),
+											},
+										},
+									},
+								},
+								Containers: []corev1.Container{
+									{
+										Name:  "c1",
+										Image: "pause",
+										Resources: corev1.ResourceRequirements{
+											Requests: corev1.ResourceList{
+												"example.com/gpu": resource.MustParse("1"),
+											},
+										},
+									},
+									{
+										Name:  "c2",
+										Image: "pause",
+										Resources: corev1.ResourceRequirements{
+											Requests: corev1.ResourceList{
+												"example.com/gpu": resource.MustParse("2"),
+											},
+										},
+									},
+								},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			want: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"example.com/gpu": resource.MustParse("5"),
+				},
+			},
+			wantReplaced: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu"),
+			},
+		},
+		{
+			name: "workload with non-integer extended resource quantity",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											"example.com/gpu": resource.MustParse("500m"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			wantErr:       true,
+			wantErrCount:  1,
+		},
+		{
+			name: "extended resource uses deviceClassMappings logical name when DeviceClass is mapped",
+			workload: &kueue.Workload{
+				ObjectMeta: metav1.ObjectMeta{Name: "wl", Namespace: "ns1"},
+				Spec: kueue.WorkloadSpec{
+					PodSets: []kueue.PodSet{{
+						Name:  "main",
+						Count: 1,
+						Template: corev1.PodTemplateSpec{
+							Spec: corev1.PodSpec{
+								Containers: []corev1.Container{{
+									Name:  "c",
+									Image: "pause",
+									Resources: corev1.ResourceRequirements{
+										Requests: corev1.ResourceList{
+											"example.com/gpu": resource.MustParse("1"),
+										},
+									},
+								}},
+							},
+						},
+					}},
+				},
+			},
+			deviceClasses: []*resourceapi.DeviceClass{gpuDeviceClass},
+			mapperMappings: []configapi.DeviceClassMapping{
+				{
+					Name:             "gpu-claims",
+					DeviceClassNames: []corev1.ResourceName{"gpu.nvidia.com"},
+				},
+			},
+			want: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"gpu-claims": resource.MustParse("1"),
+				},
+			},
+			wantReplaced: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.mapperMappings != nil {
+				_ = CreateMapperFromConfiguration(tt.mapperMappings)
+				t.Cleanup(func() { _ = CreateMapperFromConfiguration(nil) })
+			}
+
+			cl := newFakeClient(tt.deviceClasses...)
+
+			got, gotReplaced, errs := ResolveExtendedResourceQuota(t.Context(), cl, tt.workload)
+
+			if tt.wantErr {
+				if len(errs) == 0 {
+					t.Errorf("ResolveExtendedResourceQuota() expected error, got none")
+				}
+				if tt.wantErrCount > 0 && len(errs) != tt.wantErrCount {
+					t.Errorf("ResolveExtendedResourceQuota() expected %d errors, got %d: %v", tt.wantErrCount, len(errs), errs)
+				}
+				return
+			}
+
+			if len(errs) > 0 {
+				t.Errorf("ResolveExtendedResourceQuota() unexpected errors: %v", errs)
+				return
+			}
+
+			opts := []cmp.Option{
+				cmpopts.EquateEmpty(),
+			}
+			if diff := cmp.Diff(tt.want, got, opts...); diff != "" {
+				t.Errorf("ResolveExtendedResourceQuota() resources mismatch (-want +got):\n%s", diff)
+			}
+			if diff := cmp.Diff(tt.wantReplaced, gotReplaced, opts...); diff != "" {
+				t.Errorf("ResolveExtendedResourceQuota() replacedExtendedResources mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
diff --git a/pkg/util/resource/resource_test.go b/pkg/util/resource/resource_test.go
index f0afaa13196..242a15d3300 100644
--- a/pkg/util/resource/resource_test.go
+++ b/pkg/util/resource/resource_test.go
@@ -236,3 +236,64 @@ func TestQuantityToFloat(t *testing.T) {
 		})
 	}
 }
+
+func TestIsExtendedResourceName(t *testing.T) {
+	cases := map[string]struct {
+		name corev1.ResourceName
+		want bool
+	}{
+		"cpu": {
+			name: corev1.ResourceCPU,
+			want: false,
+		},
+		"memory": {
+			name: corev1.ResourceMemory,
+			want: false,
+		},
+		"ephemeral-storage": {
+			name: corev1.ResourceEphemeralStorage,
+			want: false,
+		},
+		"hugepages-2Mi": {
+			name: corev1.ResourceName(corev1.ResourceHugePagesPrefix + "2Mi"),
+			want: false,
+		},
+		"extended resource with domain": {
+			name: "example.com/gpu",
+			want: true,
+		},
+		"nvidia gpu": {
+			name: "nvidia.com/gpu",
+			want: true,
+		},
+		"extended resource with subdomain": {
+			name: "gpu.resource.nvidia.com/mig-1g.5gb",
+			want: true,
+		},
+		"empty string": {
+			name: "",
+			want: false,
+		},
+		"simple name without slash": {
+			name: "custom-resource",
+			want: false,
+		},
+		"kubernetes.io namespace": {
+			name: "kubernetes.io/foo",
+			want: false,
+		},
+		"requests prefix": {
+			name: "requests.cpu",
+			want: false,
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			got := IsExtendedResourceName(tc.name)
+			if got != tc.want {
+				t.Errorf("IsExtendedResourceName(%q) = %v, want %v", tc.name, got, tc.want)
+			}
+		})
+	}
+}
diff --git a/pkg/util/testingjobs/job/wrappers.go b/pkg/util/testingjobs/job/wrappers.go
index 6188f18a6ce..d7ca0790cf1 100644
--- a/pkg/util/testingjobs/job/wrappers.go
+++ b/pkg/util/testingjobs/job/wrappers.go
@@ -238,6 +238,12 @@ func (j *JobWrapper) Containers(containers ...corev1.Container) *JobWrapper {
 	return j
 }
 
+// InitContainers sets the init containers for the pod template.
+func (j *JobWrapper) InitContainers(containers ...corev1.Container) *JobWrapper {
+	j.Spec.Template.Spec.InitContainers = containers
+	return j
+}
+
 // UID updates the uid of the job.
 func (j *JobWrapper) UID(uid string) *JobWrapper {
 	j.ObjectMeta.UID = types.UID(uid)
diff --git a/pkg/workload/workload_test.go b/pkg/workload/workload_test.go
index 2e6423c0ea1..cf36819529b 100644
--- a/pkg/workload/workload_test.go
+++ b/pkg/workload/workload_test.go
@@ -1675,7 +1675,88 @@ func TestWithPreprocessedDRAResources(t *testing.T) {
 
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
-			info := NewInfo(&tc.workload, WithPreprocessedDRAResources(tc.draResources))
+			info := NewInfo(&tc.workload, WithPreprocessedDRAResources(tc.draResources, nil))
+
+			if diff := cmp.Diff(tc.wantInfo.TotalRequests, info.TotalRequests); diff != "" {
+				t.Errorf("Unexpected TotalRequests (-want,+got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestWithPreprocessedDRAResourcesReplacesExtendedResources(t *testing.T) {
+	features.SetFeatureGateDuringTest(t, features.DynamicResourceAllocation, true)
+
+	cases := map[string]struct {
+		workload                  kueue.Workload
+		draResources              map[kueue.PodSetReference]corev1.ResourceList
+		replacedExtendedResources map[kueue.PodSetReference]sets.Set[corev1.ResourceName]
+		wantInfo                  Info
+	}{
+		"extended resource replaced with DRA resource": {
+			workload: *utiltestingapi.MakeWorkload("test-wl", "default").
+				PodSets(*utiltestingapi.MakePodSet("main", 1).
+					Request(corev1.ResourceCPU, "100m").
+					Request("example.com/gpu", "1").
+					Obj()).
+				Obj(),
+			draResources: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"gpu": resource.MustParse("1"),
+				},
+			},
+			replacedExtendedResources: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu"),
+			},
+			wantInfo: Info{
+				TotalRequests: []PodSetResources{
+					{
+						Name:  "main",
+						Count: 1,
+						Requests: resources.Requests{
+							corev1.ResourceCPU: 100,
+							"gpu":              1,
+						},
+					},
+				},
+			},
+		},
+		"multiple extended resources replaced": {
+			workload: *utiltestingapi.MakeWorkload("test-wl", "default").
+				PodSets(*utiltestingapi.MakePodSet("main", 2).
+					Request(corev1.ResourceCPU, "100m").
+					Request("example.com/gpu", "2").
+					Request("example.com/tpu", "1").
+					Obj()).
+				Obj(),
+			draResources: map[kueue.PodSetReference]corev1.ResourceList{
+				"main": {
+					"gpu": resource.MustParse("2"),
+					"tpu": resource.MustParse("1"),
+				},
+			},
+			replacedExtendedResources: map[kueue.PodSetReference]sets.Set[corev1.ResourceName]{
+				"main": sets.New[corev1.ResourceName]("example.com/gpu", "example.com/tpu"),
+			},
+			wantInfo: Info{
+				TotalRequests: []PodSetResources{
+					{
+						Name:  "main",
+						Count: 2,
+						Requests: resources.Requests{
+							corev1.ResourceCPU: 200,
+							"gpu":              4,
+							"tpu":              2,
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			info := NewInfo(&tc.workload, WithPreprocessedDRAResources(tc.draResources, tc.replacedExtendedResources))
 
 			if diff := cmp.Diff(tc.wantInfo.TotalRequests, info.TotalRequests); diff != "" {
 				t.Errorf("Unexpected TotalRequests (-want,+got):\n%s", diff)
diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml
index 2a9bf5beb97..a764a4768a5 100644
--- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml
+++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml
@@ -29,6 +29,12 @@
     lockToDefault: false
     preRelease: Alpha
     version: "0.17"
+- name: DRAExtendedResources
+  versionedSpecs:
+  - default: false
+    lockToDefault: false
+    preRelease: Alpha
+    version: "0.17"
 - name: DynamicResourceAllocation
   versionedSpecs:
   - default: false
diff --git a/test/e2e/config/dra/controller_manager_config.yaml b/test/e2e/config/dra/controller_manager_config.yaml
index 9426e23a69d..7d43f63a940 100644
--- a/test/e2e/config/dra/controller_manager_config.yaml
+++ b/test/e2e/config/dra/controller_manager_config.yaml
@@ -21,6 +21,7 @@ integrations:
   - "pod"
 featureGates:
   DynamicResourceAllocation: true
+  DRAExtendedResources: true
 resources:
   deviceClassMappings:
   - name: gpu
diff --git a/test/e2e/dra/dra_test.go b/test/e2e/dra/dra_test.go
index 3c8874049c5..f247f7b6484 100644
--- a/test/e2e/dra/dra_test.go
+++ b/test/e2e/dra/dra_test.go
@@ -21,7 +21,9 @@ import (
 	"github.com/onsi/gomega"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
+	resourceapi "k8s.io/api/resource/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -323,4 +325,278 @@ var _ = ginkgo.Describe("DRA", func() {
 			util.ExpectJobToBeCompleted(ctx, k8sClient, job)
 		})
 	})
+
+	ginkgo.When("Creating Jobs with Extended Resources backed by DRA", func() {
+		var (
+			resourceFlavor      *kueue.ResourceFlavor
+			clusterQueue        *kueue.ClusterQueue
+			localQueue          *kueue.LocalQueue
+			extendedResDevClass *resourceapi.DeviceClass
+		)
+
+		const (
+			// Extended resource name that maps to DRA DeviceClass
+			extendedResourceName = "example.com/gpu"
+			// DeviceClass name with extendedResourceName set
+			extendedResDevClassName = "gpu-extended.example.com"
+		)
+
+		ginkgo.BeforeEach(func() {
+			// Create a DeviceClass with extendedResourceName that uses the same driver
+			// as dra-example-driver but exposes GPUs as extended resources
+			extendedResDevClass = &resourceapi.DeviceClass{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: extendedResDevClassName,
+				},
+				Spec: resourceapi.DeviceClassSpec{
+					// Use the same selector as the default dra-example-driver DeviceClass
+					Selectors: []resourceapi.DeviceSelector{
+						{
+							CEL: &resourceapi.CELDeviceSelector{
+								Expression: "device.driver == '" + draconsts.DriverName + "'",
+							},
+						},
+					},
+					// This is the key field for Extended Resources backed by DRA
+					ExtendedResourceName: ptr.To(extendedResourceName),
+				},
+			}
+			util.MustCreate(ctx, k8sClient, extendedResDevClass)
+
+			resourceFlavor = utiltestingapi.MakeResourceFlavor("ext-res-dra-flavor-" + ns.Name).Obj()
+			util.MustCreate(ctx, k8sClient, resourceFlavor)
+
+			// ClusterQueue with quota for both paths:
+			// - "example.com/gpu" for extended resources (auto-discovered, no deviceClassMappings)
+			// - "gpu" for ResourceClaimTemplates (via deviceClassMappings)
+			clusterQueue = utiltestingapi.MakeClusterQueue("ext-res-dra-cq-" + ns.Name).
+				ResourceGroup(
+					*utiltestingapi.MakeFlavorQuotas(resourceFlavor.Name).
+						Resource(corev1.ResourceCPU, "4").
+						Resource(corev1.ResourceName(extendedResourceName), "4").
+						Resource("gpu", "4").
+						Obj()).
+				Obj()
+			util.CreateClusterQueuesAndWaitForActive(ctx, k8sClient, clusterQueue)
+
+			localQueue = utiltestingapi.MakeLocalQueue("ext-res-dra-lq", ns.Name).
+				ClusterQueue(clusterQueue.Name).Obj()
+			util.CreateLocalQueuesAndWaitForActive(ctx, k8sClient, localQueue)
+		})
+
+		ginkgo.AfterEach(func() {
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, resourceFlavor, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, extendedResDevClass, true)
+		})
+
+		ginkgo.It("Should admit and run a job using extended resource backed by DRA", func() {
+			ginkgo.By("Creating Job with extended resource request (no ResourceClaimTemplate)")
+			job := testingjob.MakeJob("ext-res-dra-job", ns.Name).
+				Queue(kueue.LocalQueueName(localQueue.Name)).
+				Request(corev1.ResourceCPU, "100m").
+				// Extended resources require both requests AND limits
+				RequestAndLimit(corev1.ResourceName(extendedResourceName), "1").
+				Image(util.GetAgnHostImage(), util.BehaviorExitFast).
+				Obj()
+			util.MustCreate(ctx, k8sClient, job)
+
+			wlLookupKey := types.NamespacedName{
+				Name:      workloadjob.GetWorkloadNameForJob(job.Name, job.UID),
+				Namespace: ns.Name,
+			}
+
+			ginkgo.By("Verifying workload is created and has quota reservation with correct DRA resource usage")
+			createdWorkload := &kueue.Workload{}
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(createdWorkload)).To(gomega.BeTrue())
+				g.Expect(createdWorkload.Status.Admission).NotTo(gomega.BeNil())
+				g.Expect(createdWorkload.Status.Admission.PodSetAssignments).To(gomega.HaveLen(1))
+
+				// Verify DRA resource usage uses extendedResourceName directly as quota key
+				assignment := createdWorkload.Status.Admission.PodSetAssignments[0]
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName(extendedResourceName)))
+				g.Expect(assignment.ResourceUsage[corev1.ResourceName(extendedResourceName)]).To(gomega.Equal(resource.MustParse("1")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying job is unsuspended")
+			util.ExpectJobUnsuspended(ctx, k8sClient, client.ObjectKeyFromObject(job))
+
+			ginkgo.By("Verifying job completes successfully")
+			util.ExpectJobToBeCompleted(ctx, k8sClient, job)
+
+			ginkgo.By("Verifying workload finished successfully")
+			util.ExpectWorkloadToFinish(ctx, k8sClient, wlLookupKey)
+		})
+
+		ginkgo.It("Should keep job suspended when extended resource DRA quota is exceeded", func() {
+			ginkgo.By("Creating Job with extended resource request exceeding quota")
+			job := testingjob.MakeJob("large-ext-res-dra-job", ns.Name).
+				Queue(kueue.LocalQueueName(localQueue.Name)).
+				Request(corev1.ResourceCPU, "100m").
+				// Extended resources require both requests AND limits
+				RequestAndLimit(corev1.ResourceName(extendedResourceName), "10"). // Exceeds quota of 4
+				Image(util.GetAgnHostImage(), util.BehaviorWaitForDeletion).
+				Obj()
+			util.MustCreate(ctx, k8sClient, job)
+
+			wlLookupKey := types.NamespacedName{
+				Name:      workloadjob.GetWorkloadNameForJob(job.Name, job.UID),
+				Namespace: ns.Name,
+			}
+
+			ginkgo.By("Waiting for workload to be created")
+			createdWorkload := &kueue.Workload{}
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying job remains suspended")
+			createdJob := &batchv1.Job{}
+			gomega.Consistently(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(job), createdJob)).To(gomega.Succeed())
+				g.Expect(createdJob.Spec.Suspend).To(gomega.Equal(ptr.To(true)))
+			}, util.ConsistentDuration, util.ShortInterval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying workload does not get admitted")
+			gomega.Consistently(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
+				g.Expect(workload.IsAdmitted(createdWorkload)).To(gomega.BeFalse())
+				g.Expect(workload.HasQuotaReservation(createdWorkload)).To(gomega.BeFalse())
+			}, util.ConsistentDuration, util.ShortInterval).Should(gomega.Succeed())
+		})
+
+		ginkgo.It("Should correctly mix ResourceClaimTemplate and Extended Resource jobs", func() {
+			ginkgo.By("Creating ResourceClaimTemplate for first job")
+			rct := utiltesting.MakeResourceClaimTemplate("mixed-gpu-template", ns.Name).
+				DeviceRequest("gpu-request", draconsts.DriverName, 2).
+				Obj()
+			util.MustCreate(ctx, k8sClient, rct)
+
+			ginkgo.By("Creating first job using ResourceClaimTemplate (2 GPUs)")
+			job1 := testingjob.MakeJob("mixed-rct-job", ns.Name).
+				Queue(kueue.LocalQueueName(localQueue.Name)).
+				Request(corev1.ResourceCPU, "100m").
+				Image(util.GetAgnHostImage(), util.BehaviorExitFast).
+				ResourceClaimTemplate("gpu", "mixed-gpu-template").
+				Obj()
+			util.MustCreate(ctx, k8sClient, job1)
+
+			ginkgo.By("Creating second job using extended resource (2 GPUs)")
+			job2 := testingjob.MakeJob("mixed-ext-res-job", ns.Name).
+				Queue(kueue.LocalQueueName(localQueue.Name)).
+				Request(corev1.ResourceCPU, "100m").
+				RequestAndLimit(corev1.ResourceName(extendedResourceName), "2").
+				Image(util.GetAgnHostImage(), util.BehaviorExitFast).
+				Obj()
+			util.MustCreate(ctx, k8sClient, job2)
+
+			ginkgo.By("Verifying RCT job is admitted with gpu resource usage")
+			wlLookupKey1 := types.NamespacedName{
+				Name:      workloadjob.GetWorkloadNameForJob(job1.Name, job1.UID),
+				Namespace: ns.Name,
+			}
+			createdWorkload1 := &kueue.Workload{}
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey1, createdWorkload1)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(createdWorkload1)).To(gomega.BeTrue())
+				g.Expect(createdWorkload1.Status.Admission).NotTo(gomega.BeNil())
+				assignment := createdWorkload1.Status.Admission.PodSetAssignments[0]
+				g.Expect(assignment.ResourceUsage["gpu"]).To(gomega.Equal(resource.MustParse("2")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying extended resource job is admitted with extendedResourceName usage")
+			wlLookupKey2 := types.NamespacedName{
+				Name:      workloadjob.GetWorkloadNameForJob(job2.Name, job2.UID),
+				Namespace: ns.Name,
+			}
+			createdWorkload2 := &kueue.Workload{}
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey2, createdWorkload2)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(createdWorkload2)).To(gomega.BeTrue())
+				g.Expect(createdWorkload2.Status.Admission).NotTo(gomega.BeNil())
+				assignment := createdWorkload2.Status.Admission.PodSetAssignments[0]
+				g.Expect(assignment.ResourceUsage[corev1.ResourceName(extendedResourceName)]).To(gomega.Equal(resource.MustParse("2")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying both jobs complete successfully")
+			util.ExpectJobToBeCompleted(ctx, k8sClient, job1)
+			util.ExpectJobToBeCompleted(ctx, k8sClient, job2)
+		})
+
+		ginkgo.It("Should track ResourceClaimTemplate and Extended Resource requests separately", func() {
+			ginkgo.By("Creating ResourceClaimTemplate requesting 2 GPUs")
+			rct := utiltesting.MakeResourceClaimTemplate("both-gpu-template", ns.Name).
+				DeviceRequest("gpu-request", draconsts.DriverName, 2).
+				Obj()
+			util.MustCreate(ctx, k8sClient, rct)
+
+			ginkgo.By("Creating job with ResourceClaimTemplate (2 GPUs) and extended resource (1 GPU)")
+			job := testingjob.MakeJob("both-dra-methods-job", ns.Name).
+				Queue(kueue.LocalQueueName(localQueue.Name)).
+				Request(corev1.ResourceCPU, "100m").
+				RequestAndLimit(corev1.ResourceName(extendedResourceName), "1").
+				ResourceClaimTemplate("gpu-claim", "both-gpu-template").
+				Image(util.GetAgnHostImage(), util.BehaviorExitFast).
+				Obj()
+			util.MustCreate(ctx, k8sClient, job)
+
+			wlLookupKey := types.NamespacedName{
+				Name:      workloadjob.GetWorkloadNameForJob(job.Name, job.UID),
+				Namespace: ns.Name,
+			}
+
+			ginkgo.By("Verifying workload is admitted with separate quota for each path")
+			createdWorkload := &kueue.Workload{}
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(createdWorkload)).To(gomega.BeTrue())
+				g.Expect(createdWorkload.Status.Admission).NotTo(gomega.BeNil())
+
+				assignment := createdWorkload.Status.Admission.PodSetAssignments[0]
+				// RCT path uses "gpu" (from deviceClassMappings)
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName("gpu")))
+				g.Expect(assignment.ResourceUsage["gpu"]).To(gomega.Equal(resource.MustParse("2")))
+				// Extended resources path uses extendedResourceName directly
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName(extendedResourceName)))
+				g.Expect(assignment.ResourceUsage[corev1.ResourceName(extendedResourceName)]).To(gomega.Equal(resource.MustParse("1")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying job completes successfully")
+			util.ExpectJobToBeCompleted(ctx, k8sClient, job)
+		})
+
+		ginkgo.It("Should not double-count extended resource when translated to DRA logical resource", func() {
+			ginkgo.By("Creating Job with extended resource only (no ResourceClaimTemplate)")
+			job := testingjob.MakeJob("no-double-count-job", ns.Name).
+				Queue(kueue.LocalQueueName(localQueue.Name)).
+				Request(corev1.ResourceCPU, "100m").
+				RequestAndLimit(corev1.ResourceName(extendedResourceName), "1").
+				Image(util.GetAgnHostImage(), util.BehaviorExitFast).
+				Obj()
+			util.MustCreate(ctx, k8sClient, job)
+
+			wlLookupKey := types.NamespacedName{
+				Name:      workloadjob.GetWorkloadNameForJob(job.Name, job.UID),
+				Namespace: ns.Name,
+			}
+
+			ginkgo.By("Verifying workload is admitted with exactly 1 GPU (not double-counted)")
+			createdWorkload := &kueue.Workload{}
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(createdWorkload)).To(gomega.BeTrue())
+				g.Expect(createdWorkload.Status.Admission).NotTo(gomega.BeNil())
+
+				assignment := createdWorkload.Status.Admission.PodSetAssignments[0]
+				// Should have the extendedResourceName as quota key, counted exactly once
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName(extendedResourceName)))
+				g.Expect(assignment.ResourceUsage[corev1.ResourceName(extendedResourceName)]).To(gomega.Equal(resource.MustParse("1")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			ginkgo.By("Verifying job completes successfully")
+			util.ExpectJobToBeCompleted(ctx, k8sClient, job)
+		})
+	})
 })
diff --git a/test/integration/singlecluster/controller/dra/dra_test.go b/test/integration/singlecluster/controller/dra/dra_test.go
index 769fece62f1..b6064ea0832 100644
--- a/test/integration/singlecluster/controller/dra/dra_test.go
+++ b/test/integration/singlecluster/controller/dra/dra_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/onsi/ginkgo/v2"
 	"github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
+	resourceapi "k8s.io/api/resource/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/ptr"
@@ -29,6 +30,7 @@ import (
 
 	config "sigs.k8s.io/kueue/apis/config/v1beta2"
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta2"
+	"sigs.k8s.io/kueue/pkg/features"
 	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
 	utiltestingapi "sigs.k8s.io/kueue/pkg/util/testing/v1beta2"
 	"sigs.k8s.io/kueue/pkg/workload"
@@ -750,4 +752,298 @@ var _ = ginkgo.Describe("DRA Integration", ginkgo.Ordered, ginkgo.ContinueOnFail
 			gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
 		})
 	})
+
+	ginkgo.When("Extended Resources backed by DRA", func() {
+		var (
+			ns             *corev1.Namespace
+			resourceFlavor *kueue.ResourceFlavor
+			clusterQueue   *kueue.ClusterQueue
+			localQueue     *kueue.LocalQueue
+			deviceClass    *resourceapi.DeviceClass
+		)
+
+		const extendedResourceName = "example.com/gpu"
+
+		ginkgo.BeforeAll(func() {
+			features.SetFeatureGateDuringTest(ginkgo.GinkgoTB(), features.DRAExtendedResources, true)
+			fwk.StopManager(ctx)
+			fwk.StartManager(ctx, cfg, managerSetup(nil))
+		})
+
+		ginkgo.AfterAll(func() {
+			fwk.StopManager(ctx)
+			fwk.StartManager(ctx, cfg, managerSetup(nil))
+		})
+
+		ginkgo.BeforeEach(func() {
+			ns = &corev1.Namespace{
+				ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "dra-ext-",
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
+
+			deviceClass = &resourceapi.DeviceClass{
+				ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "gpu-ext-",
+				},
+				Spec: resourceapi.DeviceClassSpec{
+					ExtendedResourceName: ptr.To(extendedResourceName),
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, deviceClass)).To(gomega.Succeed())
+
+			resourceFlavor = utiltestingapi.MakeResourceFlavor("").Obj()
+			resourceFlavor.GenerateName = "rf-ext-"
+			gomega.Expect(k8sClient.Create(ctx, resourceFlavor)).To(gomega.Succeed())
+
+			clusterQueue = &kueue.ClusterQueue{
+				ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "ext-cq-",
+				},
+				Spec: kueue.ClusterQueueSpec{
+					NamespaceSelector: &metav1.LabelSelector{},
+					ResourceGroups: []kueue.ResourceGroup{
+						{
+							CoveredResources: []corev1.ResourceName{corev1.ResourceName(extendedResourceName)},
+							Flavors: []kueue.FlavorQuotas{
+								{
+									Name: kueue.ResourceFlavorReference(resourceFlavor.Name),
+									Resources: []kueue.ResourceQuota{
+										{Name: corev1.ResourceName(extendedResourceName), NominalQuota: resource.MustParse("4")},
+									},
+								},
+							},
+						},
+					},
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, clusterQueue)).To(gomega.Succeed())
+			util.ExpectClusterQueuesToBeActive(ctx, k8sClient, clusterQueue)
+
+			localQueue = utiltestingapi.MakeLocalQueue("ext-lq", ns.Name).
+				ClusterQueue(clusterQueue.Name).Obj()
+			gomega.Expect(k8sClient.Create(ctx, localQueue)).To(gomega.Succeed())
+		})
+
+		ginkgo.AfterEach(func() {
+			gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, resourceFlavor, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, deviceClass, true)
+		})
+
+		ginkgo.It("Should admit workload with DRA-backed extended resource", func() {
+			ginkgo.By("Creating a workload requesting DRA-backed extended resource")
+			wl := utiltestingapi.MakeWorkload("ext-res-wl", ns.Name).
+				Queue("ext-lq").
+				Request(corev1.ResourceName(extendedResourceName), "2").
+				Obj()
+			gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
+
+			ginkgo.By("Verifying workload is admitted with extendedResourceName as quota key")
+			gomega.Eventually(func(g gomega.Gomega) {
+				var updatedWl kueue.Workload
+				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWl)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(&updatedWl)).To(gomega.BeTrue())
+				g.Expect(updatedWl.Status.Admission).NotTo(gomega.BeNil())
+				g.Expect(updatedWl.Status.Admission.PodSetAssignments).To(gomega.HaveLen(1))
+
+				assignment := updatedWl.Status.Admission.PodSetAssignments[0]
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName(extendedResourceName)))
+				g.Expect(assignment.ResourceUsage[corev1.ResourceName(extendedResourceName)]).To(gomega.Equal(resource.MustParse("2")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+		})
+	})
+
+	ginkgo.When("Extended Resources with unified quota via deviceClassMappings", func() {
+		var (
+			ns             *corev1.Namespace
+			resourceFlavor *kueue.ResourceFlavor
+			clusterQueue   *kueue.ClusterQueue
+			localQueue     *kueue.LocalQueue
+			deviceClass    *resourceapi.DeviceClass
+		)
+
+		const (
+			extendedResourceName = "example.com/gpu"
+			logicalName          = "gpu-claims"
+		)
+
+		ginkgo.BeforeAll(func() {
+			features.SetFeatureGateDuringTest(ginkgo.GinkgoTB(), features.DynamicResourceAllocation, true)
+			features.SetFeatureGateDuringTest(ginkgo.GinkgoTB(), features.DRAExtendedResources, true)
+
+			deviceClass = &resourceapi.DeviceClass{
+				ObjectMeta: metav1.ObjectMeta{Name: "gpu.example.com"},
+				Spec: resourceapi.DeviceClassSpec{
+					ExtendedResourceName: ptr.To(extendedResourceName),
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, deviceClass)).To(gomega.Succeed())
+
+			fwk.StopManager(ctx)
+			fwk.StartManager(ctx, cfg, managerSetup(func(c *config.Configuration) {
+				c.Resources.DeviceClassMappings = append(c.Resources.DeviceClassMappings, config.DeviceClassMapping{
+					Name:             logicalName,
+					DeviceClassNames: []corev1.ResourceName{"gpu.example.com"},
+				})
+			}))
+		})
+
+		ginkgo.AfterAll(func() {
+			fwk.StopManager(ctx)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, deviceClass, true)
+			fwk.StartManager(ctx, cfg, managerSetup(nil))
+		})
+
+		ginkgo.BeforeEach(func() {
+			ns = &corev1.Namespace{
+				ObjectMeta: metav1.ObjectMeta{GenerateName: "dra-unified-"},
+			}
+			gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
+
+			resourceFlavor = utiltestingapi.MakeResourceFlavor("").Obj()
+			resourceFlavor.GenerateName = "rf-unified-"
+			gomega.Expect(k8sClient.Create(ctx, resourceFlavor)).To(gomega.Succeed())
+
+			clusterQueue = &kueue.ClusterQueue{
+				ObjectMeta: metav1.ObjectMeta{GenerateName: "unified-cq-"},
+				Spec: kueue.ClusterQueueSpec{
+					NamespaceSelector: &metav1.LabelSelector{},
+					ResourceGroups: []kueue.ResourceGroup{{
+						CoveredResources: []corev1.ResourceName{logicalName},
+						Flavors: []kueue.FlavorQuotas{{
+							Name: kueue.ResourceFlavorReference(resourceFlavor.Name),
+							Resources: []kueue.ResourceQuota{{
+								Name:         logicalName,
+								NominalQuota: resource.MustParse("8"),
+							}},
+						}},
+					}},
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, clusterQueue)).To(gomega.Succeed())
+			util.ExpectClusterQueuesToBeActive(ctx, k8sClient, clusterQueue)
+
+			localQueue = utiltestingapi.MakeLocalQueue("unified-lq", ns.Name).
+				ClusterQueue(clusterQueue.Name).Obj()
+			gomega.Expect(k8sClient.Create(ctx, localQueue)).To(gomega.Succeed())
+		})
+
+		ginkgo.AfterEach(func() {
+			gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, resourceFlavor, true)
+		})
+
+		ginkgo.It("Should use deviceClassMappings logical name as quota key", func() {
+			wl := utiltestingapi.MakeWorkload("unified-wl", ns.Name).
+				Queue("unified-lq").
+				Request(corev1.ResourceName(extendedResourceName), "1").
+				Obj()
+			gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
+
+			gomega.Eventually(func(g gomega.Gomega) {
+				var updatedWl kueue.Workload
+				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWl)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(&updatedWl)).To(gomega.BeTrue())
+				g.Expect(updatedWl.Status.Admission).NotTo(gomega.BeNil())
+				g.Expect(updatedWl.Status.Admission.PodSetAssignments).To(gomega.HaveLen(1))
+
+				assignment := updatedWl.Status.Admission.PodSetAssignments[0]
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName(logicalName)))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+		})
+	})
+
+	ginkgo.When("DRAExtendedResources feature gate disabled", func() {
+		var (
+			ns             *corev1.Namespace
+			resourceFlavor *kueue.ResourceFlavor
+			clusterQueue   *kueue.ClusterQueue
+			localQueue     *kueue.LocalQueue
+		)
+
+		const extendedResourceName = "example.com/gpu"
+
+		ginkgo.BeforeAll(func() {
+			features.SetFeatureGateDuringTest(ginkgo.GinkgoTB(), features.DynamicResourceAllocation, true)
+			features.SetFeatureGateDuringTest(ginkgo.GinkgoTB(), features.DRAExtendedResources, false)
+			fwk.StopManager(ctx)
+			fwk.StartManager(ctx, cfg, managerSetup(nil))
+		})
+
+		ginkgo.AfterAll(func() {
+			fwk.StopManager(ctx)
+			fwk.StartManager(ctx, cfg, managerSetup(nil))
+		})
+
+		ginkgo.BeforeEach(func() {
+			ns = &corev1.Namespace{
+				ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "dra-gate-off-",
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
+
+			resourceFlavor = utiltestingapi.MakeResourceFlavor("").Obj()
+			resourceFlavor.GenerateName = "rf-gate-off-"
+			gomega.Expect(k8sClient.Create(ctx, resourceFlavor)).To(gomega.Succeed())
+
+			clusterQueue = &kueue.ClusterQueue{
+				ObjectMeta: metav1.ObjectMeta{
+					GenerateName: "gate-off-cq-",
+				},
+				Spec: kueue.ClusterQueueSpec{
+					NamespaceSelector: &metav1.LabelSelector{},
+					ResourceGroups: []kueue.ResourceGroup{
+						{
+							CoveredResources: []corev1.ResourceName{extendedResourceName},
+							Flavors: []kueue.FlavorQuotas{
+								{
+									Name: kueue.ResourceFlavorReference(resourceFlavor.Name),
+									Resources: []kueue.ResourceQuota{
+										{Name: extendedResourceName, NominalQuota: resource.MustParse("4")},
+									},
+								},
+							},
+						},
+					},
+				},
+			}
+			gomega.Expect(k8sClient.Create(ctx, clusterQueue)).To(gomega.Succeed())
+			util.ExpectClusterQueuesToBeActive(ctx, k8sClient, clusterQueue)
+
+			localQueue = utiltestingapi.MakeLocalQueue("gate-off-lq", ns.Name).
+				ClusterQueue(clusterQueue.Name).Obj()
+			gomega.Expect(k8sClient.Create(ctx, localQueue)).To(gomega.Succeed())
+		})
+
+		ginkgo.AfterEach(func() {
+			gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, resourceFlavor, true)
+		})
+
+		ginkgo.It("Should treat extended resources as normal resources when gate is off", func() {
+			wl := utiltestingapi.MakeWorkload("gate-off-wl", ns.Name).
+				Queue("gate-off-lq").
+				Request(corev1.ResourceName(extendedResourceName), "2").
+				Obj()
+			gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
+
+			gomega.Eventually(func(g gomega.Gomega) {
+				var updatedWl kueue.Workload
+				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWl)).To(gomega.Succeed())
+				g.Expect(workload.HasQuotaReservation(&updatedWl)).To(gomega.BeTrue())
+				g.Expect(updatedWl.Status.Admission).NotTo(gomega.BeNil())
+				g.Expect(updatedWl.Status.Admission.PodSetAssignments).To(gomega.HaveLen(1))
+
+				assignment := updatedWl.Status.Admission.PodSetAssignments[0]
+				g.Expect(assignment.ResourceUsage).To(gomega.HaveKey(corev1.ResourceName(extendedResourceName)))
+				g.Expect(assignment.ResourceUsage[corev1.ResourceName(extendedResourceName)]).To(gomega.Equal(resource.MustParse("2")))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+		})
+	})
 })
diff --git a/test/integration/singlecluster/controller/dra/suite_test.go b/test/integration/singlecluster/controller/dra/suite_test.go
index ba865a4ea45..9aa2b7b2df6 100644
--- a/test/integration/singlecluster/controller/dra/suite_test.go
+++ b/test/integration/singlecluster/controller/dra/suite_test.go
@@ -63,6 +63,7 @@ var _ = ginkgo.BeforeSuite(func() {
 		WebhookPath: util.WebhookPath,
 		APIServerFeatureGates: []string{
 			"DynamicResourceAllocation=true",
+			"DRAExtendedResource=true",
 		},
 		APIServerRuntimeConfig: []string{
 			"resource.k8s.io/v1beta2=true",