Skip to content

Ignore already cordoned nodes during driver upgrades - #282

Open
JunAr7112 wants to merge 1 commit into
NVIDIA:mainfrom
JunAr7112:cordon-ownership
Open

JunAr7112 wants to merge 1 commit into
NVIDIA:mainfrom
JunAr7112:cordon-ownership

Conversation

@JunAr7112

@JunAr7112 JunAr7112 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

This PR is part of this feature request. Essentially we want to ignore nodes that are already cordoned (not by any of the GPU operator components) during upgrades from upgrade controller/k8s-driver-manager.

SOLUTION

internal/kubernetes/client.go
WaitUntilNoExternalCordon polls the node until it is safe to rotate. If schedulable you proceed, if unschedulable and GPU-claimed you proceed as upgrade is already in progress, or if unschedulable and no GPU claim keep waiting (admin cordon). API GET failures are retried. The wait ends when the pod context is cancelled, so an admin-cordoned node holds the init container instead of crashlooping.

AcquireNodeCordon is a conflict-retried update that either writes this component’s claim and sets unschedulable=true in one update, or sees an unclaimed cordon and returns acquired=false without changing the node

ReleaseNodeCordon removes only this component’s claim. The node is uncordoned only if the peer GPU claim is also gone. No claim → no-op, so an admin cordon is left in place.

cmd/driver-manager/main.go - Uninstall always starts with waitForExternalCordonToClear, before host-driver checks, operand pause, drain, or rmmod. That wait is required on every path, including auto-upgrade, where cordonNode is never called because the controller already cordoned and restarted this pod. For cordonNode (drain / eviction path only) loops: wait until the cordon is gone or GPU-claimed, then AcquireNodeCordon. If acquire returns false, an admin recoredoned between the wait and the update; wait again rather than rotate under that cordon. For uncordonNode calls ReleaseNodeCordon. It runs on success, the no-driver path, and failure cleanup. If driver-manager never claimed, the node stays unschedulable.

TESTING:
TestAcquireNodeCordon — claims a schedulable node with annotation plus cordon in one update, and refuses to claim an already-cordoned node that GPU Operator does not own.
TestWaitUntilNoExternalCordonReturnsImmediately — wait returns at once if the node is schedulable or already GPU-claimed, so a normal or in-progress upgrade is not blocked.
TestWaitUntilNoExternalCordonReturnsOnceCleared — on an unclaimed cordon, wait blocks until the node is uncordoned or a GPU claim appears.
TestWaitUntilNoExternalCordonStopsWhenContextCancelled — cancelling the client context ends the wait instead of spinning forever.
TestWaitUntilNoExternalCordonRetriesGetFailures — transient GET failures are retried so a brief API error does not fail uninstall.
TestReleaseNodeCordonPreservesPeerClaim — releasing our claim leaves the node cordoned if the peer GPU component still holds it.


Devin Review

@copy-pr-bot

copy-pr-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@JunAr7112
JunAr7112 marked this pull request as ready for review October 7, 2026 07:20
@JunAr7112
JunAr7112 force-pushed the cordon-ownership branch 2 times, most recently from 235d371 to 43fbecd Compare October 7, 2026 20:06
@gengwg

gengwg commented Oct 10, 2026

Copy link
Copy Markdown

Concern: waiting on an external cordon before install means a node cordoned for a suspected hardware fault reboots with no driver, so nvidia-smi, DCGM diag and health checks are unavailable exactly when they are needed for triage. For NVIDIA/gpu-operator#212, the NVIDIA/gpu-operator#258 annotation fixes the uncordon without blocking the install. Could the wait be opt-in?

@JunAr7112 JunAr7112 self-assigned this Nov 11, 2026

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +177 to +180
case "cordon-required", "wait-for-jobs-required", "pod-deletion-required",
"drain-required", "node-maintenance-required", "post-maintenance-required",
"pod-restart-required", "validation-required", "uncordon-required",
"upgrade-failed":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Pending upgrade bypasses administrator cordon

When an administrator cordons a node labeled cordon-required, gpuUpgradeInProgress treats the pending state as an existing GPU cordon. Driver rotation proceeds during administrator maintenance.

Learn more

The upgrade-state label describes progress, not ownership of spec.unschedulable. cordon-required identifies the phase before the upgrade controller applies its cordon. A node already cordoned by an administrator can reach that phase without the controller owning the cordon. WaitUntilNoExternalCordon then returns, allowing uninstall to proceed despite the external cordon.

Example: An administrator cordons gpu-1; the controller sets gpu-driver-upgrade-state=cordon-required while waiting to cordon it. The manager starts and proceeds rather than waiting for the administrator to uncordon gpu-1.

Recommended fix: Do not accept pre-cordon upgrade phases as evidence of ownership. Use an atomic controller-owned claim when the controller actually cordons the node, and gate both waiting and acquisition on that claim.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +200 to +203
if annotationPresent(annotations, nodeInitialUnschedulableAnnotationKey) ||
annotationPresent(annotations, gpuUpgradeInitialUnschedulableAnnotationKey) ||
annotationPresent(annotations, gpuUpgradeWaitForSafeLoadAnnotationKey) {
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Recorded administrator cordons treated as GPU-owned

When an initial-state annotation says true, gpuCordonClaimed still accepts it as GPU ownership. The value records a pre-existing cordon, so uninstall proceeds on an administrator-cordoned node.

Learn more

The initial-state annotations record the value of spec.unschedulable before a GPU component performed its update. A value of true means the node was cordoned before that update; presence alone cannot identify the current cordon owner. The legacy CordonNode explicitly writes true for an already cordoned node. Treating either key as an unconditional claim bypasses both the initial wait and the acquisition guard.

Example: An administrator cordons gpu-1; an earlier manager writes driver-manager.node-initial-state.unschedulable=true. A new manager sees that annotation and rotates the driver without waiting for the administrator.

Recommended fix: Distinguish an initial-state recording from an active ownership claim. For legacy recordings, require evidence the node was schedulable before the component cordoned it; for controller ownership use a marker written atomically with its cordon.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +317 to +321
unschedulable := gpuPeerOwnsCordon(node, peerClaimAnnotation)
if !unschedulable {
annotations[nodeInitialUnschedulableAnnotationKey] = nil
}
return c.patchNodeSchedulingState(node, unschedulable, annotations)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Shared cordon release restores wrong state

If a peer's cordon predates AcquireNodeCordon and clears first, ReleaseNodeCordon sets the node schedulable. It ignores the recorded initial true, undoing a cordon that existed before this manager acquired it.

Learn more

AcquireNodeCordon records node-initial-state.unschedulable=true when it joins a peer's existing cordon. ReleaseNodeCordon bases the final scheduling state only on whether that peer still has a claim. When the peer drops its claim first, the recorded initial state is still true, yet this function removes it and uncordons the node. This can also override an administrator's original cordon if the peer had cordoned an already unschedulable node.

Example: gpu-1 starts unschedulable under a controller claim. The manager acquires, recording true; the controller drops its claim. The manager releases and makes gpu-1 schedulable, although it was unschedulable before the manager joined.

Recommended fix: Preserve the initial scheduling state separately from peer ownership and restore it when the final claim is removed. Define how to handle peers that preserve their own initial state so the manager does not erase it.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +310 to +311
if !annotationTrue(node.Annotations, claimAnnotation) {
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Legacy driver cordons stay stuck

If a previous manager cordoned the node, ReleaseNodeCordon finds no new ownership claim and does nothing. A restarted manager's no-driver path completes without uncordoning that node.

Learn more

The previous CordonNode wrote only driver-manager.node-initial-state.unschedulable. On restart, the new uninstallDriver can take the no-driver path and call uncordonNode without acquiring a new claim. ReleaseNodeCordon then returns without cleaning up the old cordon. This leaves the node unschedulable after the successful driver rotation.

Example: The old manager cordons gpu-1, writes initial-state false, and its pod restarts during driver rotation. The replacement sees no loaded driver, finishes cleanup, but leaves gpu-1 cordoned because the new claim annotation is absent.

Recommended fix: Migrate legacy initial-state=false recordings into an ownership claim before the no-driver/skip paths, or explicitly release these legacy cordons when their prior state was schedulable; avoid claiming initial-state=true administrator cordons.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +175 to +181
func gpuUpgradeInProgress(labels map[string]string) bool {
switch labels[gpuUpgradeStateLabelKey] {
case "cordon-required", "wait-for-jobs-required", "pod-deletion-required",
"drain-required", "node-maintenance-required", "post-maintenance-required",
"pod-restart-required", "validation-required", "uncordon-required",
"upgrade-failed":
return true

@devin-ai-integration devin-ai-integration Bot Nov 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Pending upgrade bypasses administrator cordon

On an administrator-cordoned node labeled cordon-required, gpuCordonClaimed authorizes rotation before the controller claims the cordon. The manager evicts pods despite the administrator's cordon and can later uncordon that node.

Learn more

An upgrade-state label identifies a stage of work, not the actor that set spec.unschedulable. The state cordon-required can be present before the upgrade controller applies a cordon. WaitUntilNoExternalCordon accepts the label as a GPU claim, and AcquireNodeCordon can then add driver-manager's claim to an administrator's cordon. The upgrade proceeds under the administrator's maintenance cordon; if the label clears before release, driver-manager can also uncordon it.

Example: An administrator cordons gpu-1 while its upgrade label is cordon-required. The driver manager immediately evicts GPU pods rather than waiting for the administrator to uncordon gpu-1.

Recommended fix: Do not infer cordon ownership from an upgrade state alone. Coordinate an explicit controller ownership marker with the upgrade controller, and require a confirmed owned cordon before allowing a pre-cordoned node through the wait or acquire checks.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +200 to +203
if annotationPresent(annotations, nodeInitialUnschedulableAnnotationKey) ||
annotationPresent(annotations, gpuUpgradeInitialUnschedulableAnnotationKey) ||
annotationPresent(annotations, gpuUpgradeWaitForSafeLoadAnnotationKey) {
return true

@devin-ai-integration devin-ai-integration Bot Nov 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Recorded administrator cordon becomes manager-owned

When CordonNode previously recorded true for an administrator-cordoned node, gpuCordonClaimed accepts that recording as ownership. The manager can claim, rotate, and uncordon the administrator's node.

Learn more

The existing CordonNode records the node's prior scheduling state, including true when the node was already cordoned. A recording of true therefore confirms the cordon was present before driver-manager touched the node; it is not a claim of ownership. The new presence check passes that node through WaitUntilNoExternalCordon, and AcquireNodeCordon can claim it. If no peer claim exists at release, ReleaseNodeCordon makes the administrator's node schedulable.

Example: An older driver-manager run records driver-manager.node-initial-state.unschedulable=true on an already cordoned node. A subsequent run sees that annotation, evicts workloads, and releases the administrator's cordon.

Recommended fix: Distinguish a true initial-state recording from ownership. Only an explicit validated owner claim or an initial state of false associated with an active manager cordon can justify proceeding or uncordoning; preserve the original state when releasing.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +344 to +345
if node.Spec.Unschedulable {
return nil

@devin-ai-integration devin-ai-integration Bot Nov 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Reapplied administrator cordon inherits stale claim

If an administrator uncordons then recordons a node between manager runs, ClearStaleCordonClaim retains the old claim. The next run treats that administrator cordon as its own and can uncordon it.

Learn more

A successful AcquireNodeCordon leaves a manager ownership annotation on the Node until release. If the manager stops, an administrator can uncordon and later cordon that node again without changing the annotation. The next startup sees an already-cordoned Node and skips cleanup, although the active cordon is now external. WaitUntilNoExternalCordon and ReleaseNodeCordon then treat the stale marker as ownership and can remove the administrator's cordon.

Example: A failed upgrade leaves gpu-driver-manager.node-cordoned=true. An administrator uncordons gpu-1, then cordons it for maintenance before restarting the driver pod. The manager skips cleanup and later uncordons gpu-1.

Recommended fix: Do not treat an indefinitely persistent claim as evidence of ownership across an unobserved uncordon/recordon cycle. Use a controller-coordinated ownership protocol or an observable cordon generation/lease, and avoid releasing the node when ownership cannot be established.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +311 to +312
if !annotationTrue(node.Annotations, claimAnnotation) {
return nil

@devin-ai-integration devin-ai-integration Bot Nov 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Legacy cordon remains after interrupted upgrade

If an older manager cordoned the node before unloading its driver, the replacement's ReleaseNodeCordon finds no new claim. The no-driver recovery path never acquires one, leaving the node cordoned after installation.

Learn more

Before this change, CordonNode recorded only driver-manager.node-initial-state.unschedulable, not the new ownership annotation. If the driver container restarts after the driver has been unloaded, uninstallDriver takes its no-driver path and later invokes uncordonNode without acquiring the new annotation. The release guard returns without removing the legacy cordon, so scheduling stays disabled.

Example: Version A cordons gpu-1 and records initial state false, then the driver unloads and the pod restarts with version B. Version B completes no-driver cleanup, but its uncordon call does nothing because gpu-driver-manager.node-cordoned is absent.

Recommended fix: Migrate legacy initial-state recordings when safe, especially false on an unschedulable node, before the no-driver release path. Ensure migration does not convert an initial true administrator cordon into a manager-owned cordon.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Signed-off-by: Arjun <agadiyar@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants