Skip to content

Keep separate precompiled drivers when kernel names match - #3125

Open
SebTardif wants to merge 3 commits into
NVIDIA:mainfrom
SebTardif:fix/clusterpolicy-precompiled-daemonset-name
Open

SebTardif wants to merge 3 commits into
NVIDIA:mainfrom
SebTardif:fix/clusterpolicy-precompiled-daemonset-name

Conversation

@SebTardif

@SebTardif SebTardif commented Oct 26, 2026 •

Copy link
Copy Markdown

Description

driver.usePrecompiled=true on a ClusterPolicy builds the driver DaemonSet name with getSanitizedKernelVersion. That helper strips x86_64, aarch64, and _64k. The node selector and the precompiled image tag still use the raw feature.node.kubernetes.io/kernel-version.full.

Two kernels that differ only by architecture or page size share one name. DaemonSet updates that object, and the last loop iteration wins. The other kernel's nodes get no driver. Map iteration order is not stable, so the surviving selector can change on the next reconcile.

This pull covers both paths.

On a ClusterPolicy, driver.nvidiaDriverCRD.enabled defaults to false, so driver.usePrecompiled=true builds these DaemonSets. The name keeps the architecture and page-size tokens. _ is rewritten to .. A kernel that contains _ also gets a short hash of the raw string, so x86_64 and x86.64 do not share a name. Kernels with no _, such as 5.4.0-generic, keep the previous name. After the new objects are created, a stale precompiled DaemonSet in the operator namespace is deleted. A DaemonSet in another namespace is left alone. One failed delete does not skip the rest.

NVIDIADriver pools in internal/state/nodepool.go (the change from #3099) key each pool on the raw kernel as well as the sanitized name. Nodes with the same raw kernel still share one pool.

Fixes #3124
Fixes #3098

Checklist

  • No secrets, sensitive information, or unrelated changes
  • Lint checks passing (make lint)
  • Generated assets in-sync (make validate-generated-assets)
  • Go mod artifacts in-sync (make validate-modules)
  • Test cases are added for new code paths

make validate-generated-assets and make validate-modules do not apply. This change does not touch API types or modules.

Testing

Red, before the name change, on the real ClusterPolicyController.step() path:

go test -count=1 -timeout 180s -run TestPrecompiledDriverDaemonSetsStayDistinctWhenKernelsSanitizeTogether ./controllers/

The controller created nvidia-driver-daemonset-5.14.0-427.37.1.el9.4-ubuntu22.04 for 5.14.0-427.37.1.el9_4.x86_64, then logged DaemonSet is different, updating for 5.14.0-427.37.1.el9_4.aarch64 against that same name. One DaemonSet remained, with the arm64 node selector and the arm64 image. The test failed with 1 DaemonSet instead of 2.

Green, after the change:

go test -count=1 -timeout 180s ./controllers/
golangci-lint run --timeout 5m ./controllers/

TestDriver, TestGetSanitizedKernelVersion, and TestDriverPrecompiledLibModulesUbuntu / Suse are in that package run. Those kernels (5.4.0-generic, 5.4.135-generic) still produce two DaemonSets with the previous names.

The node-pool change and the review follow-ups:

go test -count=1 -timeout 120s -run TestGetNodePoolsKeepsPrecompiledKernelsThatSanitizeTogether ./internal/state/
go test -count=1 -timeout 180s -run 'TestKernelSuffixKeepsDotAndUnderscoreKernelsDistinct|TestDeleteUndesiredPrecompiledDaemonsets' ./controllers/
golangci-lint run --timeout 5m ./controllers/ ./internal/state/

This is a controller test with the kernel strings already used by TestGetSanitizedKernelVersion. It is not a live cluster capture. Those strings match a RHEL uname -r, which Node Feature Discovery stores in feature.node.kubernetes.io/kernel-version.full.


Devin Review

The DaemonSet name used getSanitizedKernelVersion, which strips
architecture and page size. The node selector and the image tag keep
the raw kernel, so x86_64 and aarch64 updated one object.

Keep those tokens in the name. After the new objects exist, delete a
precompiled DaemonSet whose name is no longer desired.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif
SebTardif requested a review from a team as a code owner October 26, 2026 20:21
@copy-pr-bot

copy-pr-bot Bot commented Oct 26, 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.

@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 3 potential issues.

Devin Review

Comment thread controllers/object_controls.go Outdated
// those tokens, but the node selector and the image tag keep the raw kernel,
// so kernels that sanitize together still need different objects.
func kernelSuffixForPrecompiledDaemonSet(kernelVersion string) string {
return strings.Trim(strings.ToLower(strings.ReplaceAll(kernelVersion, "_", ".")), ".")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Distinct kernels share one driver DaemonSet

When raw kernels differ by _ versus ., kernelSuffixForPrecompiledDaemonSet can assign both the same name. The last update replaces the first kernel's node selector and image, leaving its nodes without a driver.

Learn more

Precompiled driver DaemonSets use their names as object identities. The new suffix maps both underscores and literal dots to dots, but the node selector and image tag continue to use the raw release. A second raw kernel with the same rewritten suffix therefore updates the first DaemonSet rather than creating another, and map iteration determines which nodes get a driver. The old sanitizer stripped x86_64 but did not strip x86.64, so this particular pair previously had different names.

Example: Nodes with 5.14.0-427.37.1.el9_4.x86_64 and 5.14.0-427.37.1.el9.4.x86.64 produce the same new suffix. Only the last kernel's node selector remains; two DaemonSets were expected.

Recommended fix: Use an injective encoding of raw kernel releases for the DaemonSet name, or append a stable hash of the full raw release. Keep the result within Kubernetes name limits and use the identical name builder for creation and the desired set.

Devin Review


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

Comment thread controllers/object_controls.go Outdated
Comment on lines +4426 to +4428
err := n.client.List(ctx, list, client.MatchingLabels{
precompiledIdentificationLabelKey: precompiledIdentificationLabelValue,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Foreign driver DaemonSets are deleted

When another namespace has an active precompiled driver DaemonSet, deleteUndesiredPrecompiledDaemonsets can delete it despite different ownership. The cluster-wide list checks only its label and name prefix, removing a running driver unrelated to this policy.

Learn more

The cleanup list has no namespace restriction, and the later deletion uses only the precompiled label, driver name prefix, and desired names. The previous cleanup does not delete active DaemonSets, whereas this new pass deletes them regardless of status. An active DaemonSet belonging to another namespace or another owner can therefore lose its running pods during an ordinary reconciliation.

Example: A DaemonSet named nvidia-driver-daemonset-5.14-old-rhel9 in a second namespace has nvidia.com/precompiled=true and serves one node. A ClusterPolicy reconciliation whose kernel map lacks that name deletes it even though the DaemonSet belongs to the second namespace.

Recommended fix: Restrict deletion to DaemonSets in n.operatorNamespace with a controller owner reference identifying n.singleton (UID as well as kind). Make the desired-name comparison only within that owned set.

Devin Review


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

Comment thread controllers/object_controls_test.go Resolved
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Walkthrough

Precompiled driver DaemonSet names now retain architecture and page-size tokens, replace underscores with dots, lowercase the kernel version, and include the node OS. The deployment pass tracks desired names and deletes identified precompiled DaemonSets outside that set only when deployment has no errors and at least one name is desired. Listing failures and deletion failures other than NotFound are returned as errors. A controller test checks that kernels that previously sanitized to the same value produce distinct DaemonSets with matching selectors and images.

Priority: Normal

Severity of issue fixed: Medium

Merge Risk: Moderate · up to 45945

With this change, a precompiled driver DaemonSet can be deleted before its renamed replacement is ready. Nodes could lose their working driver while the replacement is still starting or failing. Gate cleanup on readiness and add a regression test before merging.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between e154719 and 45945b5.

Files selected for processing: 2
  • controllers/object_controls.go
  • controllers/object_controls_test.go

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Comment on lines +2005 to +2007
list := &appsv1.DaemonSetList{}
require.NoError(t, clusterPolicyController.client.List(ctx, list, client.MatchingLabels{"app": "nvidia-driver-daemonset"}))
require.Len(t, list.Items, 2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Stability & Availability · Heavy lift

🔎 Supported by static analysis

Script executed:

set -eu
printf '%s\n' '--- changed files and relevant diff ---'
git diff --stat e15471941743c5588e07fcc5f1415d5815e92b55 45945b54bebc6cc5dc274389f5ab8b9f72f8b7c2
git diff --unified=80 e15471941743c5588e07fcc5f1415d5815e92b55 45945b54bebc6cc5dc274389f5ab8b9f72f8b7c2 -- controllers/object_controls.go controllers/object_controls_test.go controllers || true
printf '%s\n' '--- cleanup and precompiled references ---'
rg -n -F --glob '*.go' -e 'PrecompiledDriverDaemonSets' -e 'precompiled' -e 'desired' -e 'Delete' controllers | head -n 300
printf '%s\n' '--- test names and cleanup assertions ---'
rg -n --glob '*_test.go' -e 'DaemonSet|reconcil|cleanup|Delete|Ready|ready|kernel' controllers/object_controls_test.go controllers || true

Repository: NVIDIA/gpu-operator

Length of output: 41596


Script executed:

set -eu
printf '%s\n' '--- precompiled reconciliation and cleanup ---'
nl -ba controllers/object_controls.go | sed -n '4300,4445p'
printf '%s\n' '--- DaemonSet readiness paths ---'
rg -n -F --glob '*.go' -e 'func DaemonSet' -e 'return gpuv1.NotReady' -e 'overallState' controllers/object_controls.go
nl -ba controllers/object_controls.go | sed -n '3900,4305p'
printf '%s\n' '--- complete added test ---'
nl -ba controllers/object_controls_test.go | sed -n '1935,2030p'
printf '%s\n' '--- precompiled-related tests across controllers ---'
rg -n --glob '*_test.go' -e 'precompiledDriverDaemonsets|PrecompiledDriverDaemonSets|precompiledIdentificationLabelKey|commonDriverDaemonsetName|kernelVersionMap|cleanupStalePrecompiledDaemonsets|deleteUndesiredPrecompiledDaemonsets' controllers || test "$?" -eq 1

Repository: NVIDIA/gpu-operator

Length of output: 32661


Script executed:

set -eu
nl -ba controllers/object_controls.go | sed -n '4740,4810p'
nl -ba controllers/object_controls_test.go | sed -n '640,715p'

Repository: NVIDIA/gpu-operator

Length of output: 6182


Script executed:

set -eu
rg -n -F --glob '*.go' -e 'func (n ClusterPolicyController) cleanupUnusedDriverDaemonSets' -e 'cleanupUnusedDriverDaemonSets(' -e 'podCount' controllers/object_controls.go controllers/*_test.go
nl -ba controllers/object_controls.go | sed -n '4550,4750p'

Repository: NVIDIA/gpu-operator

Length of output: 9249


Keep the active legacy DaemonSet until its replacement is ready.

precompiledDriverDaemonsets deletes non-desired DaemonSets when replacement reconciliation returns gpuv1.NotReady without an error. This can remove the active legacy DaemonSet before the replacement can serve its nodes.

Gate cleanup on overallState == gpuv1.Ready. Add a regression test that checks the legacy DaemonSet remains while the replacement is not ready, then is removed after readiness on a later reconciliation.

Suggested cleanup guard
-	if len(errs) == 0 && len(desired) > 0 {
+	if len(errs) == 0 && overallState == gpuv1.Ready && len(desired) > 0 {

Source: Path instructions

Comment thread controllers/object_controls.go Resolved
@rajatchopra

Copy link
Copy Markdown
Contributor

Please respond to Devin's review comments. And also include #3099 within this PR.

getNodePools keyed each precompiled pool on the sanitized kernel.
That string drops x86_64, aarch64, and _64k, so an x86 node and an
arm node, or a 4k node and a 64k node, became one pool. The selector
and image tag then used only the first node's raw kernel. Key the
pool map on the raw kernel as well, and sort equal names by that
kernel. Nodes that share a raw kernel still share one pool.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
A kernel that uses '_' and one that already uses '.' in that spot
were rewritten to the same DaemonSet name. A kernel that contains '_'
now gets a short hash of the raw string. Kernels with no '_' keep the
previous name.

Cleanup lists only the operator namespace. One failed delete does not
skip the remaining stale DaemonSets.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif SebTardif changed the title Keep a ClusterPolicy precompiled DaemonSet per raw kernel Keep separate precompiled drivers when kernel names match Nov 3, 2026
@SebTardif

Copy link
Copy Markdown
Author

@rajatchopra

Please respond to Devin's review comments. And also include #3099 within this PR.

Done. The node-pool change from #3099 is on this branch, and that pull is closed.

A kernel that uses _ and one that already uses . no longer share a DaemonSet name. Cleanup lists only this operator's namespace, deletes the old arch-stripped name, and continues if one delete fails.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

2 participants