feat: add lblk (non-NVMe block device) support for storage nodes - #437
Merged
Merged
Conversation
boddumanohar
force-pushed
the
feat/lblk-block-device-support
branch
2 times, most recently
from
August 19, 2026 13:08
63b7682 to
d2953d2
Compare
boddumanohar
added a commit
that referenced
this pull request
Aug 19, 2026
…d-disk reuse blkForceFormat (StorageNodeSet fleet-wide + per-node override) threads through two places lblk's --force-format actually matters: - node_configure.py's --force-format flag (marks a partitioned disk eligible at configure time) - the add-node API's force_format param (triggers the actual wipefs at add-node time — configure-time eligibility alone doesn't wipe anything) Found while working through the lblk-e2e-testing.md test plan: PR #437 never wired this at all, so partitioned-disk reuse and the disk+own-partition-conflict test were both unreachable through the CRD. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
boddumanohar
added a commit
that referenced
this pull request
Aug 21, 2026
…d-disk reuse blkForceFormat (StorageNodeSet fleet-wide + per-node override) threads through two places lblk's --force-format actually matters: - node_configure.py's --force-format flag (marks a partitioned disk eligible at configure time) - the add-node API's force_format param (triggers the actual wipefs at add-node time — configure-time eligibility alone doesn't wipe anything) Found while working through the lblk-e2e-testing.md test plan: PR #437 never wired this at all, so partitioned-disk reuse and the disk+own-partition-conflict test were both unreachable through the CRD. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
boddumanohar
force-pushed
the
feat/lblk-block-device-support
branch
from
August 21, 2026 11:50
d551c39 to
6fb2971
Compare
boddumanohar
added a commit
that referenced
this pull request
Aug 25, 2026
…d-disk reuse blkForceFormat (StorageNodeSet fleet-wide + per-node override) threads through two places lblk's --force-format actually matters: - node_configure.py's --force-format flag (marks a partitioned disk eligible at configure time) - the add-node API's force_format param (triggers the actual wipefs at add-node time — configure-time eligibility alone doesn't wipe anything) Found while working through the lblk-e2e-testing.md test plan: PR #437 never wired this at all, so partitioned-disk reuse and the disk+own-partition-conflict test were both unreachable through the CRD. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
boddumanohar
force-pushed
the
feat/lblk-block-device-support
branch
from
August 25, 2026 11:43
e453622 to
5c53534
Compare
noctarius
force-pushed
the
main
branch
2 times, most recently
from
September 9, 2026 10:21
60dceb7 to
fbaabe4
Compare
boddumanohar
force-pushed
the
feat/lblk-block-device-support
branch
2 times, most recently
from
September 11, 2026 14:19
11ccac8 to
3884341
Compare
boddumanohar
marked this pull request as ready for review
September 11, 2026 14:29
geoffrey1330
previously approved these changes
Sep 11, 2026
boddumanohar
force-pushed
the
feat/lblk-block-device-support
branch
from
September 14, 2026 09:20
3884341 to
0b84391
Compare
boddumanohar
added a commit
that referenced
this pull request
Sep 15, 2026
Storage node pods used Kubernetes' default auto-injected ServiceAccount token, which lives roughly an hour. If simplyblock-storage-node-sa was ever deleted and recreated (e.g. during a chart upgrade), every already-running pod kept its stale token until the hour expired, then started failing API calls with 401 — recoverable only by deleting every pod by hand. Pods now mount an explicit ten-minute projected token instead (AutomountServiceAccountToken: false, with a kube-api-access-short projected volume built to match the shape of Kubernetes' own auto-injected one). kubelet refreshes a projected token at ~80% of its expiration, so a ServiceAccount recreation now self-heals within minutes. Found while testing #437's lblk device-mode work on config-israel, but unrelated to lblk itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wires the sbcli lblk device mode through the operator: a cluster-wide
StorageCluster.spec.deviceMode ("nvme" or "lblk") passed to cluster-create,
and fleet and per-node block-device selectors (enableLblk, blkNames,
blkNamesExclude, blkSerials, blkForceFormat, and lblkJournalPercent) on
StorageNodeSet and StorageNode that flow into the per-node ConfigMap and from
there into the DaemonSet init container's node_configure.py invocation
(--lblk, --blk-names, --blk-names-exclude, --blk-serials, --force-format, and
--jm-percent), mirroring the existing PCIe selector wiring.
Storage nodes can then run on cloud block volumes, virtio-blk disks, or other
non-NVMe local disks instead of requiring physical NVMe hardware.
Addresses #436.
Carries the fixes found while testing this on config-israel that are
specific to lblk itself:
- Admission enforces the immutability of blkNames, blkNamesExclude, and
blkSerials, including the unset-to-set transition the backend silently
ignores, and the mutual exclusivity of the lblk and PCIe selectors.
- The config-generator init container mounts /dev and /sys, which device
discovery needs.
- --force-format reaches node_configure.py, so a partitioned disk can be
reused as a whole-disk lblk device.
Two other fixes found during the same testing pass but unrelated to lblk
(a StorageNode deletion race when its parent StorageNodeSet is already gone,
and the storage node ServiceAccount token's expiration) are split out to a
separate PR rather than riding along here.
The backend side of this is on sbcli main: sending device_mode=lblk or the
lblk configure flags to an older control plane fails at cluster-create or
node-configure time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebasing onto main after the StorageCluster v1alpha2 rebuild (#536) surfaced that this branch's own spec.deviceMode field duplicated the deviceClass field that rebuild already shipped (NVMe/LogicalBlock, immutable, defaulted to NVMe), and that v1alpha1 is now a spoke converting into it — a bare deviceMode addition there would have round-tripped through the hub and been silently dropped. Dropping that field and instead mapping the existing spec.deviceClass onto sbcli's cluster-create device_mode wire value in the new storagecluster_controller.go keeps one field for the concept instead of two. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
boddumanohar
force-pushed
the
feat/lblk-block-device-support
branch
from
September 15, 2026 09:29
0b84391 to
92d1d05
Compare
…ster.spec.deviceMode The rebase's deviceClass wiring commit removed the v1alpha1 deviceMode field from the Go type and config/crd/bases, but dist/install.yaml is kustomize output rather than a controller-gen artifact and had to be rebuilt separately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
noctarius
approved these changes
Sep 15, 2026
noctarius
added a commit
that referenced
this pull request
Sep 17, 2026
Brings the VolumeGroupSnapshot work (#504), lblk device support (#437), the client-side compression design (#398), and two openapi.json syncs (#541, #549) onto the branch carrying the CRD redesign. Thirteen files conflicted. The eight generated ones — the deepcopy, the StorageNode CRD in its four copies, the manager role, the chart's webhook template, and install.yaml — were resolved by regenerating rather than by editing, since a generated file has no side to prefer. Three were modify-and-delete, where the lblk work edited the v1alpha1 controllers this branch retired: storagenode_controller.go, the per-node ConfigMap writer, and the StorageNodeSet unit test. The deletions stand. Their replacements under internal/controllers/node are what the operator runs, and restoring a v1alpha1 controller to hold a feature would reinstate the model the redesign removed. cmd/main.go took both import sides. One test came across orphaned. TestBuildStorageNodeSetDaemonSet_ConfigGenerator- MountsDevAndSys guards the /dev and /sys mounts node_configure.py's lblk eligibility check needs, and it was written against the v1alpha1 builder; it is rewritten against BuildStorageNodeDaemonSet, which is the builder this operator runs and which carries those mounts. The assertion is about the container rather than about which kind describes it. lblk arrives half-wired, and this commit does not finish it. What works is the cluster half and the workload half: StorageCluster.spec.deviceClass still resolves to sbcli's device_mode, and the DaemonSet's init script still assembles --lblk, --blk-names, --blk-names-exclude, --blk-serials, --jm-percent, and --force-format. What is missing is the field half. The six selectors exist on v1alpha1's StorageNodeSet and StorageNode, which this operator no longer reads, and have no counterpart on v1alpha2; the per-node ConfigMap writer under internal/controllers/node therefore emits none of the LBLK or BLK_ keys the init script reads, so every flag resolves empty. Selecting devices by lblk is inert on this branch until v1alpha2 carries the fields, which is its own change rather than a conflict resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fixes: #436
Exposes sbcli's
lblkdevice mode as a cluster-level choice plus per-fleet device selectors, so storage nodes can run on generic Linux block devices — cloud block volumes, virtio-blk disks, SAS/SATA disks — instead of requiring NVMe PCIe hardware. Inlblkmode each selected device is wrapped in an SPDK AIO bdev; everything from alceml upward is unchanged.deviceClass(NVMe|LogicalBlock, defaultNVMe) is the cluster-wide choice, mapped onto sbcli'sdevice_mode(nvme|lblk) onPOST /api/v2/clusters/. Immutable — the backend bakes it in at cluster-create and cannot switch a live cluster. This field predates this PR: it shipped onStorageClusterv1alpha2 in feat(operator): StorageCluster moves to v1alpha2, and its operations are rebuilt #536, and this PR wires it through tocluster-createrather than adding a field of its own.enableLblkturns a fleet onto block devices. Immutable, and mutually exclusive with the NVMe selectors.blkNames/blkNamesExclude/blkSerialsselect the devices: by kernel name, by kernel name negatively, or by serial/WWN. At most one may be set; with none set, every eligible whole disk on the node is used.blkSerialsis the one to prefer on cloud and virtualized disks, whose/dev/sdXname is not stable across reboots.blkForceFormatwipes partition tables and filesystem signatures so a partitioned disk becomes eligible as a whole disk. Destructive, and off by default — without it a partitioned disk is simply rejected as ineligible.lblkJournalPercentcarves the journal out of the smallest selected device when that device is a partition rather than a whole disk. Ignored otherwise; the backend's own default is 3.Every field exists on
StorageNodeSet.spec, on its per-nodenodeConfigsentries, and onStorageNode.spec.overrides, exactly like the existing PCIe selectors, and the most specific one wins.How a selection reaches the node
Device selection is not part of the "add storage node" REST API — it happens node-locally in
node_configure.py, which the DaemonSet init container already drives for NVMe. The lblk fields ride the same path rather than introducing a second integration point:node_configure.pyflagenableLblkLBLK--lblkblkNamesBLK_NAMES--blk-namesblkNamesExcludeBLK_NAMES_EXCLUDE--blk-names-excludeblkSerialsBLK_SERIALS--blk-serialsblkForceFormatLBLK_FORCE_FORMAT--force-formatlblkJournalPercentLBLK_JM_PERCENT--jm-percentThe
s-node-api-config-generatorinit container that runsnode_configure.pynow also mounts the host's/devand/sys(already mounted on the main container, for SPDK). NVMe discovery never needed them: it resolves a device to a PCI address purely from/sys/class/nvmeand/sys/bus/pci, sysfs metadata that's visible in any container without a hostPath mount. lblk devices have no such bus to walk — eligibility (mounted, held by LVM/md, partitioned, NUMA node) can only be read off the real block layer:lsblkover the actual device nodes,/sys/block/<name>/holders, and/dev/disk/by-id/*for stable identity. Thatby-idtree in particular only exists because the host's udev populates it; a container's own default/devnever has one, hostPath or not.What admission rejects
blkNames/blkNamesExclude/blkSerialsset on a new objectdeviceClasschanged on a live clusterStorageCluster's+k8s:immutablemarker)The immutability covers the unset-to-set transition too, which matters more than it looks: a fleet commonly starts with no selector at all (auto-select), and
node_configure.pysilently skips regenerating config for an already-provisioned node — so an edit that Kubernetes accepted would read back as though it had taken effect while changing nothing.Scope
This PR is now field plumbing plus the two fixes specific to lblk itself (admission's immutability/mutual-exclusivity rules and the config-generator's
/dev//sysmounts). Two other fixes found during the same config-israel testing pass but unrelated to lblk — a StorageNode deletion race when its parent StorageNodeSet is already gone, and the storage node ServiceAccount token's expiration — are split out to #535 rather than riding along here.Rebase note (2026-09-15)
Rebased onto
mainpast theStorageClusterv1alpha2 rebuild (#536). Thatrebuild had already shipped
spec.deviceClass: NVMe|LogicalBlock— the sameconcept this PR originally added as its own
spec.deviceModeon v1alpha1 —and
v1alpha1is now a spoke converting into the v1alpha2 hub, so adeviceModefield added only there would round-trip through the conversionand be silently dropped. This PR now drops that field and instead wires the
existing
deviceClassthrough tocluster-createin the newstoragecluster_controller.go.StorageNodeSet/StorageNode's lblk fieldsare unaffected: those kinds have not moved to v1alpha2 yet.
🤖 Generated with Claude Code