feat: --odf — single-node OpenShift Data Foundation - #19
Merged
raghavendra-talur merged 18 commits intoAug 25, 2026
Conversation
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Adds an idempotent CreateDataDisk to interfaces.VMManager for the ODF data disk: libvirt reuses an existing pool volume by vol-path before creating a new qcow2 one, vfkit reuses an existing state-dir file before truncating a new sparse raw one. Both Delete implementations clean up the disk by name (config.ODFVolName). Fakes mirror ImportDisk. Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Add ODF data disk attachment in the create-master-vms stage, immediately after the bake-images block so device ordering is deterministic. The disk is writable and exclusive, allowing OpenShift Data Foundation to use it as the backing device for Ceph. Sparse on both backends (APFS clonefile on macOS, qcow2 on Linux) so disk-space preflight doesn't count it. Test verifies the master VM carries a writable data disk after the bake store, that CreateDataDisk was called once with correct parameters, and that plain clusters (without --odf) have no ODF disk. Assisted-by: Claude Code/claude-haiku-4-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Add the ODFSpec/ODFInstaller contract for the four ODF install phases (InstallOperators, SetupLVM, EnableSingleNode, CreateStorageCluster), wire it into Deps, and add a recording fake (pattern: fake Installer) plus a simulate-trace section, so later stage/provider work has a compilable seam to build against. Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Pure renderer functions for the --odf storage stack (LVMS + ODF/Rook-Ceph on a single node): operator install manifests, LVMCluster, the Immediate- binding StorageClass, CephCSI Driver CRs with the hardware-validated resource floors, the monitoring-config trim, the trimmed StorageCluster (with the three ODF 4.22 single-node deltas from the spike: count:3/ replica:1, non-empty placement+preparePlacement no-op TSCs, and the easyshift Immediate SC), and the SINGLE_NODE merge patch. config.OLMChannelForVersion lives in config/paths.go (not providers/odf) since the create stage that bumps MasterCPUs/MasterRAM for --odf cannot import providers. Assisted-by: Claude Code/claude-sonnet-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
…ators odfOGName and odfSubName were declared but never referenced; the second half of RenderOperators hardcoded "openshift-storage-operator-group" and "odf-operator" as literals instead of interpolating them, unlike the LVMS half which correctly parameterizes lvmsOGName/lvmsSubName. Wire both constants into the Sprintf args so the two operator names are single-sourced, matching the LVMS half. Assisted-by: Claude Code/claude-sonnet-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Implements interfaces.ODFInstaller: drives the render.go manifests against a running cluster via `oc`, all through the CommandRunner so --simulate traces every invocation and tests assert exact calls. odf.New(cmd) wires an Installer around two shared helpers: apply() writes rendered content to <WorkDir>/odf/<file> (CommandRunner has no stdin, so files are both the transport and a debugging artifact) then `oc --kubeconfig ... apply -f`; pollUntil() polls a probe every 10s until it reports true or a phase-scoped timeout fires, retrying transient probe errors (API flaps) rather than failing on the first one. Timeouts are package vars, not consts, so tests shrink them instead of waiting out the real durations. InstallOperators applies the LVMS/ODF Subscriptions and waits for both CSVs Succeeded (found by name-prefix scan, since CSV names are version-decorated) plus the StorageCluster CRD. SetupLVM applies the LVMCluster, waits for status.state Ready, then applies the Immediate StorageClass. EnableSingleNode looks up the ocs-operator Subscription by spec.name (its metadata.name is catalog-decorated), patches it to SINGLE_NODE, waits for the deployment restart to report the env value and Available=True, labels the node, then applies both CephCSI Driver CRs and the monitoring trim. CreateStorageCluster applies the StorageCluster, waits for status.phase Ready, then waits for both ceph StorageClasses to exist (ocs-client-operator creates them asynchronously, so Ready alone isn't completion). Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Review finding: the SINGLE_NODE env lookup in EnableSingleNode used a
nested {range}{range}...{end}{end} jsonpath that was never run against
real oc. Switch to the exact single-line form validated live on the
2026-08-25 spike cluster:
{.spec.template.spec.containers[0].env[?(@.name=="SINGLE_NODE")].value}
ocs-operator's deployment is single-container, so containers[0] is
safe. No test fixture change needed — the fake matches on the
"SINGLE_NODE" substring, which still appears in the new jsonpath.
Assisted-by: Claude Code/claude-fable-5
Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Add the install-odf stage, which drives the four ODF phases (InstallOperators, SetupLVM, EnableSingleNode, CreateStorageCluster) against the converged cluster via interfaces.ODFInstaller, deriving its spec (device path, OLM channel, PVC size) from the cluster config. No-op unless the cluster opted in with --odf. Wire installodf.New(d.ODF) into buildStages after merge-kubeconfig and before finalize, and wire providers/odf.New(cmd) into the shared production deps used by both Linux and Darwin. Assisted-by: Claude Code/claude-sonnet-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-sonnet-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Fixes a review finding on docs/dev/odf.md: the replica-1 capacity note pointed at a "redundancy caveat below" that only exists in docs/user/usage.md. Reworded in place instead of cross-linking a fragile anchor. Also carries the spec's 20 GB+ VM -> 28 GB+ host scaling corollary into the resource-floors section. Assisted-by: Claude Code/claude-sonnet-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
Assisted-by: Claude Code/claude-fable-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.com>
--odf-disk below 12 GB made DataPVCSizeGi (ODFDiskGB/3 - 3) zero or negative, failing ~40 minutes into the install instead of at validation; reject it in validateNew. Also: keep pollUntil's last probe error in the timeout message instead of discarding it, correct a comment claiming the ODF data disk uses APFS clonefile on macOS (it's a truncate-sparse raw file), and use the existing KubeconfigPath() helper instead of hand-building the path. Assisted-by: Claude Code/claude-sonnet-5 Signed-off-by: Raghavendra Talur <raghavendra.talur@gmail.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.
Summary
easyshift create -n foo --odfnow produces an SNO cluster with a working OpenShift Data Foundation stack: RBD + CephFS StorageClasses backed by a dedicated per-cluster data disk, on both the libvirt and vfkit backends. Distilled from dfmicro's single-node ODF recipe (credit: theSINGLE_NODE=trueswitch, Immediate-binding StorageClass, and trimmed StorageCluster were worked out there), translated to real OCP and hardware-validated on 4.22.--odf,--odf-disk(default 100 GB sparse, ≥12 GB floor; usable Ceph capacity ≈ a third),--master-cpus.--odfauto-raises the master to the validated floors (8 vCPU / 19456 MiB) with a log line, never an error.VMManager.CreateDataDisk, sparse qcow2 in the libvirt pool / sparse raw in the vfkit state dir) attached after the bake store, so the guest device path is deterministic (/dev/vdb, or/dev/vdcwith--bake-images). The LVM Storage operator builds the VG/thin pool in-node from anLVMCluster; an Immediate-binding StorageClass avoids the WaitForFirstConsumer mon/OSD PVC deadlock.install-odfstage (after merge-kubeconfig): four idempotent, wait-bounded phases via a newproviders/odfpackage — operators (LVMS + ODF via OLM, waiting on the dependentocs-operatorCSV + StorageCluster CRD), LVM + StorageClass, single-node enablement (SINGLE_NODE=truepatched onto the subscription found byspec.name, node label, CephCSIDriverfloors for all controller containers, a cluster-monitoring trim), and the trimmed StorageCluster (waitsReadyand both StorageClasses). Rollback is a deliberate no-op (rationale in the spec/docs).count: 3, replica: 1(areplica: 3re-raisesgetMinimumNodespast the SINGLE_NODE relaxation); empty placements are ignored and render an invalid empty-topologyKey TSC in SINGLE_NODE mode, so the device set carries non-empty no-op TSCs inplacement+preparePlacement; the Driver CRs must floor every controller container (plugin alone defaults to 250Mi) and be applied before the StorageCluster to avoid a csi-operator scheduling deadlock.docs/dev/odf.mdinternals,--odfusage docs, design + spike + validation record indocs/superpowers/specs/2026-08-25-odf-single-node-design.md, ROADMAP follow-ups (day-2odf remove, host-RAM-aware--master-ramcap, sidecar lifetime fix).Test plan
make checkgreen throughout (unit tests for renderers value-by-value, installer polling/timeout semantics, stage ordering pinned against fakes, app-level--odfwiring,--simulatetraces the four phases).install-odfcompleted in ~13 min, StorageClusterReady+ CephHEALTH_OK+ 3 OSDs + both StorageClasses; RBD PVC Bound in 21 s and a pod wrote/read a file; fulleasyshift stop/startcycle returned toReady/HEALTH_OK(device paths stable across reboots).🤖 Generated with Claude Code