Skip to content

fix(nvsnap): disable L2 on a storage driver that qualified nothing - #2100

Open
balajinvda wants to merge 2 commits into
mainfrom
nvsnap/storage-capability-catalog
Open

balajinvda wants to merge 2 commits into
mainfrom
nvsnap/storage-capability-catalog

Conversation

@balajinvda

@balajinvda balajinvda commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Why

An L2 StorageClass whose provisioner matched no storage profile was promoted
anyway, on the default snapshot-clone ReadOnlyMany shape:

Warn("L2 storage profile: no profile for provisioner[/type]; falling back to
default snapshot-clone ROX promoter ...")

That assumes the driver supports volume snapshots and binds ReadOnlyMany across
nodes. It has proven neither. Customers run Weka, VAST and FSS, none of which are
in the built-in table, so the outcome there is either a promote-time failure or,
worse, a claim that binds and reads wrong. As the NVCA storage capability catalog
puts it: an access mode a driver merely accepts is not a qualification.

The reason this survived is a doc comment that read true. ResolveStorageProfile
said a miss meant "caller disables L2". The caller did not; it fell back to ROX.

What changed

resolveL2Promoter returns a distinct errUnqualifiedStorage and the caller
disables L2. An unreadable StorageClass counts as unqualified too, because an
unknown class cannot be a qualified one. The three log lines that announced a
fallback now say L2 is disabled and name the remedy.

The remedy already exists and needs no code: the nvsnap-storage-profiles
ConfigMap overlay is wired in l2_integration.go and overrides the built-in
table, so an operator qualifies Weka, VAST or FSS by adding an entry.

The ResolveStorageProfile comment now records what it used to get wrong,
rather than quietly being corrected.

Customer Release Notes

Not customer visible. nvsnap is not deployed anywhere yet.

Plan Summary

Not applicable.

Usage

To qualify a backend, add an entry to the nvsnap-storage-profiles ConfigMap:

profiles.yaml: |
  csi.weka.io:
    strategy: shared-volume
    volumeHandleTransform: none

Testing

go build, go test ./..., check-gazelle.

Eight tests in l2_integration_unqualified_test.go, all driven through
resolveL2Promoter with a fake client so the StorageClass read, the ConfigMap
read, the parse and the overlay merge are exercised together rather than only
ResolveStorageProfile's pure merge.

Failing closed:

  • an unknown provisioner (csi.weka.io) is reported unqualified
  • an unreadable StorageClass is reported unqualified
  • a malformed ConfigMap leaves the driver unqualified rather than becoming a
    silent qualification

Not over-failing:

  • NVMesh still resolves, so backends that already worked are unaffected
  • an absent ConfigMap still resolves the built-ins, which is the common case

The documented remedy, which had no coverage at all before:

  • a ConfigMap entry qualifies a provisioner the built-ins never heard of
  • a ConfigMap entry outranks the built-in for the same key
  • parameters.type keeps hyperdisk-ml and pd-ssd apart, since conflating them
    would assume cross-node ReadOnlyMany on a driver that has not qualified it

Mutation-checked twice. Reverting the fail-closed return to the old nil turns
the suite red, and ignoring the ConfigMap overlay turns the two overlay cases
red. The first attempt at the second mutation reported zero failures because
dropping the variable stopped the package compiling and the harness counted no
FAIL lines; it was rewritten to keep the build green. A mutation that does not
compile proves nothing.

startL2Backend is not covered. It builds its clients from
rest.InClusterConfig, so testing it needs a seam in agent startup, and after
this change its entire contribution is propagating the resolver's error. The
decision logic is covered above. Worth revisiting if that caller grows logic.

Notes

No override flag, deliberately. One would exist only to preserve behaviour for
operators depending on the old fallback, and with nvsnap undeployed there are
none. A flag added for a migration window nobody needs does not get removed
later, and #1023 is trying to shrink this configuration surface rather than grow
it. If a deployment later turns out to need a window, adding the flag then is
cheap and will be backed by a real case.

This is step 1 of #2099. Step 2 is to stop holding a second opinion about the
same StorageClasses: NVCA publishes qualified access modes in the
nvcf-storage-capabilities ConfigMap, which nvsnap can read at runtime. That is
a read of an existing ConfigMap, not a change to NVCA, and the two Go modules
stay independent.

References

Relates to #2099
Relates to #1023

Related Pull Requests

None

Dependencies

None

Summary by CodeRabbit

  • Bug Fixes
    • L2 snapshot processing is disabled when a storage class cannot be read or does not match a supported storage profile, rather than falling back to a default promoter.
    • Storage classes that match a supported profile continue to use L2 snapshot processing.
  • Configuration
    • Storage profiles can qualify additional storage provisioners, and configured profiles take precedence over built-in profiles.
    • Profiles are matched by provisioner and volume type, so storage types with different capabilities are handled separately.

An L2 StorageClass whose provisioner matched no storage profile was
promoted anyway, on the default snapshot-clone ReadOnlyMany shape. That
assumes the driver supports volume snapshots and binds ReadOnlyMany across
nodes, neither of which it has proven. On Weka, VAST or FSS the result is
either a promote-time failure or, worse, a claim that binds and reads
wrong. As NVCA's storage capability catalog puts it, an access mode a
driver merely accepts is not a qualification.

resolveL2Promoter now returns a distinct errUnqualifiedStorage and the
caller disables L2. An unreadable StorageClass counts as unqualified too,
since an unknown class cannot be a qualified one. The three log lines that
announced a fallback now say L2 is disabled and name the remedy: add the
provisioner to the nvsnap-storage-profiles ConfigMap, which is already
wired and already overrides the built-in table.

There is deliberately no override flag. One would exist solely to preserve
behaviour for operators depending on the old fallback, and nvsnap is not
deployed anywhere yet, so there are none. A flag added for a migration
window nobody needs does not get removed later, and #1023 is trying to
shrink this surface rather than grow it.

Also corrects the doc comment on ResolveStorageProfile, which claimed the
caller disabled L2 on a miss while the caller in fact fell back to ROX.
That gap between the contract and the behaviour is how the assumption
survived; the comment now records it rather than quietly reading true.

Three tests, mutation-checked against a revert to the old fallback: an
unknown provisioner (csi.weka.io) fails closed, an unreadable class fails
closed, and NVMesh still resolves, so backends that already worked are
unaffected.

Relates to #2099

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA/nvcf/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e46f49b2-9dd3-481c-ab7f-1a820027b88a

📥 Commits

Reviewing files that changed from the base of the PR and between 537dd8e and ac75afb.

📒 Files selected for processing (1)
  • src/compute-plane-services/nvsnap/internal/agent/l2_integration_unqualified_test.go

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.


📝 Walkthrough

Walkthrough

Promoter resolution now returns errors for unqualified storage and promoter-construction failures. The L2 backend disables L2 when resolution returns an error. New tests cover built-in and ConfigMap profiles, missing StorageClasses, and volume-type-specific capabilities.

Changes

L2 promoter qualification

Layer / File(s) Summary
Resolve qualified L2 promoters
src/compute-plane-services/nvsnap/internal/agent/l2_integration.go, src/compute-plane-services/nvsnap/internal/checkpointstore/storage_profile.go, src/compute-plane-services/nvsnap/internal/agent/l2_integration_unqualified_test.go, src/compute-plane-services/nvsnap/internal/agent/BUILD.bazel
Resolution returns errors for unreadable StorageClasses, missing profile matches, and promoter-construction failures. Tests cover built-in and ConfigMap profiles, malformed or absent ConfigMaps, and distinct capabilities by volume type. The Bazel test target includes the new test file. The profile-miss comment states that callers must disable L2.
Disable L2 on resolution errors
src/compute-plane-services/nvsnap/internal/agent/l2_integration.go
startL2Backend returns an L2-disabled error when promoter resolution fails. A nil promoter without an error remains a successful resolution result.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to ac75a

Unqualified storage disables L2 without stopping the agent; requests can continue through the L3 fallback. The change is ready to merge after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits format with the required scoped fix type and accurately describes the primary change: disabling L2 when storage qualification fails.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

…mmends

The fail-closed error tells operators to qualify their driver by adding an
entry to the nvsnap-storage-profiles ConfigMap. Nothing tested that path,
which is poor for advice printed in an error. The first three tests only
covered the built-in table.

Five more, all through resolveL2Promoter with a fake client so the
ConfigMap read, the parse and the overlay merge are exercised together
rather than just ResolveStorageProfile's pure merge:

- a ConfigMap entry qualifies a provisioner the built-ins never heard of,
  which is the documented remedy working end to end
- a ConfigMap entry outranks the built-in for the same key, so an operator
  can correct a built-in that is wrong for their cluster
- a malformed ConfigMap leaves the driver unqualified and L2 off, rather
  than becoming a silent qualification
- an absent ConfigMap still resolves the built-ins, the common case
- parameters.type keeps hyperdisk-ml and pd-ssd apart, since conflating
  them would assume cross-node ReadOnlyMany on a driver that has not
  qualified it, which is the assumption this branch exists to remove

Mutation-checked by ignoring the overlay: the two ConfigMap cases turn
red. The first attempt at that mutation reported zero failures because
dropping the variable stopped the package compiling and the harness
counted no FAIL lines, so the run was rewritten to keep it building. A
mutation that does not compile proves nothing.

startL2Backend itself stays uncovered: it builds its clients from
rest.InClusterConfig, so testing it needs a seam in agent startup. After
this change its whole contribution is propagating the resolver's error,
and the decision logic is fully covered here. Worth revisiting if that
caller grows logic.

Relates to #2099

Co-Authored-By: Balaji Ganesan <bganesan@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.

1 participant