Skip to content

DeviceState.Prepare() has no device-exclusivity check, a second claim can be prepared onto a device another claim still holds #263

Description

@yogeshbendre

What happened?

kubernetes/kubernetes#141471 describes a real race: when a pod using a DRA claim is force-deleted (kubectl delete pod --grace-period=0 --force, or the identical mechanism podgc uses automatically on node loss), the control plane can deallocate and reallocate the claim's device to a new claim before the original pod's container has actually stopped on the node.

Reproduced twice against this driver (v0.4.0):
Once with two manually-created pods and once with a single-replica Deployment where the ReplicaSet controller recreated the replacement entirely on its own.
Both were reproduced from a single force-delete, no exotic setup:

  • Manual-pods variant: 1.52s driver-level double-booking window, ≥0.57s confirmed container-level overlap.
  • Deployment variant (automatic self-healing, no manual intervention): 2.05s driver-level double-booking window, ≥1.61s confirmed container-level overlap. The ReplicaSet controller created the replacement in 79ms, faster than kubelet finished tearing down the original.

In both cases, NodePrepareResources for the new claim succeeded immediately and unconditionally, while the original claim's container was still confirmed Running on the same device. There was no rejection, error, retry, or queuing from the driver.

Where this lives in the code

DeviceState.Prepare()prepareDevices()computeDeviceConfig() (cmd/dra-example-kubeletplugin/state.go, current main as of 2026-09-01) only checks that a requested device exists in s.allocatable, the driver's static set of known devices.
Nothing anywhere in this path cross-checks checkpoint.PreparedClaims for whether a different claim UID already holds that same device before accepting a new Prepare() call for it.
Confirmed this gap is still present after the recent checkpoint-restore fix (#259) — that fix touched the already-restored-from-checkpoint branch only, not this path.

Why this looks like the intended contract

staging/src/k8s.io/dynamic-resource-allocation/kubeletplugin/draplugin.go's godoc (recently extended by kubernetes/kubernetes#141479, merged 2026-08-31) now explicitly documents this exact scenario:

Real-world scenarios in which this can occur: Pod force-deletion... Node loss. In these cases,
the control-plane record can be removed immediately while node-level teardown runs
asynchronously... Because Kubernetes cannot guarantee exclusive device handoff in this scenario,
the DRA driver must enforce idempotence and device exclusivity to prevent concurrent
preparation.

As the reference implementation most third-party driver authors look to for how to implement this contract, this driver currently provides no example of that enforcement.

What did you expect to happen?

Some form of rejection, queuing, or wait when a Prepare() call arrives for a device the driver's own checkpoint still shows as held by a different, not-yet-unprepared claim, rather than silently succeeding for both.

Anything else we need to know?

None

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions