Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e84a15d
feat(tier): scaffold bdev_tier module (SPEC-73 M1)
rducom Jun 20, 2026
f8ca01e
feat(tier): bdev_tier core io path + lifecycle + RPC (SPEC-73 M1)
rducom Jun 20, 2026
efbfcd0
ci(spdk): build on module/bdev/tier changes (SPEC-73 M1)
rducom Jun 21, 2026
e0a75b6
feat(tier): native superblock geometry (sb reserve + phys_offset) + m…
rducom Jun 21, 2026
e1cd3c8
feat(tier): on-disk superblock persistence (SPEC-73 M1, INV-T1)
rducom Jun 21, 2026
ce1d31f
fix(tier): forward-declare tier_sb_persist_cb (used before definition)
rducom Jun 21, 2026
3888eff
feat(tier): get_bands returns object {bands:[...]} for clean C# binding
rducom Jun 21, 2026
bedb933
feat(blob): add spdk_blob_get_cluster_lba accessor (SPEC-73 M2a)
rducom Jun 21, 2026
aa676bf
feat(blob): M2 relocate primitives (SPEC-73 M2a+M2b core)
rducom Jun 21, 2026
e29cccd
feat(lvol): bdev_lvol_get_cluster_placement RPC (SPEC-73 M2a)
rducom Jun 21, 2026
da56080
feat(tier): M2b relocate orchestration (SPEC-73)
rducom Jun 21, 2026
1b202db
feat(raid5f): degraded-read fallback on present-member read error (SP…
rducom Jun 21, 2026
9a8d355
feat(raid): nexus logical heat instrumentation (SPEC-73 M5, option A)
rducom Jun 21, 2026
d85cce3
harden(spec-73): tier dup-wwn guard + raid5f double-fault guard + hea…
rducom Jun 21, 2026
fa5210d
fix(tier): single-copy md on single-disk composites (band_b == NULL)
rducom Jun 21, 2026
1f0778f
fix(spec-73 A1): refuse relocate of snapshot-shared clusters (-EBUSY)
rducom Jun 21, 2026
f65d7ed
feat(spec-73 C5): relocate copy CRC32c read-back verify
rducom Jun 21, 2026
e0ac3d0
feat(spec-73 C6): enable snapshot-cluster tiering (relax A1 owned-onl…
rducom Jun 21, 2026
178660c
feat(spec-73 B2/M4): bdev_raid_rebuild_ranges — read-reconstruct-writ…
rducom Jun 21, 2026
4fe5e8e
fix(spec-73 M4): correct patch hunk line count (was truncating bdev_r…
rducom Jun 21, 2026
f5bf22b
fix(spec-73 M4): drop unused 'bdev' var (SPDK -Werror)
rducom Jun 21, 2026
8679e45
feat(spec-73 A2): SB-authoritative assembly RPCs (read_sb + assemble_…
rducom Jun 21, 2026
41a0fe3
fix(spec-73 data-integrity): F1 cluster-aligned geometry, F2 snapshot…
rducom Jun 21, 2026
b989631
fix(spec-73 tier): audit remediation — integrity, hot-remove, md resy…
rducom Jul 4, 2026
50f3f2e
fix(spec-73 cbt): UAF guards, durable rebuild, reset-driven bitmap (D3)
rducom Jul 4, 2026
59883af
fix(spec-73 patches): C1 blob-freeze relocate, C2 locked repair, C-1 …
rducom Jul 4, 2026
9bbcb7f
ci(spec-73): prod-code unit tests + supply-chain pin (W4, TS2, TS3, T1)
rducom Jul 4, 2026
0f18474
feat(spec-73 v2): on-disk format v2, capabilities RPC, batch relocate…
rducom Jul 4, 2026
3326517
fix(spec-73 review): remediate code-review findings across tier, cbt,…
rducom Jul 4, 2026
79de223
test(spec-73): fix LeakSanitizer leak in tier SB unit test (CI T1)
rducom Jul 4, 2026
7046ffa
feat(spec-73): action-plan round — EC geometry (C3), SEC1 audit, life…
rducom Jul 4, 2026
c26718e
fix(spec-73 T1): stub vbdev_tier_sb_fanout_idle in host test
rducom Jul 4, 2026
21e70de
fix(spec-73): PR#13 code-review remediation (R1–R17) + deferred #3/#5
rducom Jul 4, 2026
f8301d3
feat(spec-73): bdev_lvol_remap_clusters batch no-copy remap RPC
rducom Jul 4, 2026
4303d33
perf(spec-73 deferred#4): word-wise cluster claim + resume cursor
rducom Jul 4, 2026
f672f43
fix(cbt): epoch_freeze snapshot-AND-clears the live bitmap (delta sem…
rducom Jul 5, 2026
54d02b2
fix(tier): superblock persist must not require FLUSH support on the base
rducom Jul 5, 2026
41c1ad9
feat(lvol): NOTICE-level shutdown unload observability (patch 0012)
rducom Jul 5, 2026
fc02088
fix(cbt): audit remediation — delta preservation, completion re-mark,…
rducom Jul 6, 2026
3937009
fix(tier): audit remediation — drain safety, thread funneling, flush …
rducom Jul 6, 2026
2c35e58
fix(patches): C1 lost-write drain + H4 ambiguous-commit quarantine (0…
rducom Jul 6, 2026
7ab4822
docs(rpc-contract): document audit-remediation contract changes
rducom Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/spdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ on:
paths:
- "images/spdk/**"
- "module/bdev/cbt/**"
- "module/bdev/tier/**"
- "patches/**"
- ".github/workflows/spdk.yml"
pull_request:
types: [labeled, synchronize]
# TS3: `opened` so lint + unit tests run on EVERY PR from the first push;
# image builds stay gated by the authorize job (label + allowlist).
types: [opened, labeled, synchronize]
paths:
- "images/spdk/**"
- "module/bdev/cbt/**"
- "module/bdev/tier/**"
- "patches/**"
- ".github/workflows/spdk.yml"
workflow_dispatch:
Expand Down Expand Up @@ -74,6 +78,13 @@ jobs:
dockerfile: images/spdk/Dockerfile
config: images/spdk/.hadolint.yaml

# TS2: host-compiled unit tests of the PRODUCTION tier code (mock SPDK
# headers — no SPDK/Docker build involved, runs in seconds). Kept in this
# job to avoid spinning an extra runner; a failure blocks the image build
# via the compute-tag dependency below.
- name: Tier module unit tests (T1, ASAN+UBSAN)
run: make -C module/bdev/tier/test

# ────────────────────────────────────────────────────────────────
# 2. Compute image tag (single source of truth)
# ────────────────────────────────────────────────────────────────
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ module/bdev/cbt/test_cbt_props
module/bdev/cbt/test_cbt_resilience
module/bdev/cbt/test_cbt_postfix
module/bdev/cbt/*.dSYM/
module/bdev/tier/test/test_tier_sb
module/bdev/tier/test/*.dSYM/

# Audit reports & plans (local only)
docs/audits/
126 changes: 126 additions & 0 deletions docs/FORMAT-tier-superblock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# bdev_tier on-disk superblock — format v2

Authoritative description of the `struct tier_superblock` written by `vbdev_tier_sb.c`.
The C structs in `module/bdev/tier/vbdev_tier.h` are the source of truth; this
document explains the layout and the invariants. `SPDK_STATIC_ASSERT`s in the
header fail the build if the layout drifts from what is described here.

## Scope & compatibility

**Clean break — there is NO migration from v1 and none is planned.** The project
predates any public deployment; every environment is redeploy-by-wipe. A v1
superblock (`magic "TIERSB01"`, `version 1`) is deliberately **not** readable by
v2 code (`tier_sb_valid` rejects any `version != 2`). Upgrading = wipe the disks
and re-provision.

## Placement

One superblock reserve lives at the very start of **every** base bdev of a
composite (LBA 0). The reserve is `TIER_SB_RESERVE_BYTES` = 256 KiB, per disk,
outside the composite's usable address space (`phys_offset >= sb_blocks`).

Every copy self-describes the **whole** composite (all bands). There is no SPDK
`examine` path: assembly is driven by the CSI agent, which reads each disk's
superblock (`bdev_tier_read_sb`), picks the highest-seq valid copy across disks,
and replays `bdev_tier_create` + `bdev_tier_assemble_band` at the stored
geometry.

## A/B slots (F-5)

The 256 KiB reserve is split into **two 128 KiB slots**:

```
disk LBA 0 128 KiB 256 KiB
|------------- slot A --------|------------- slot B --------|
seq 0,2,4,... seq 1,3,5,...
```

Generation `seq = N` is written to slot `N % 2` (`tier_sb_slot_for_seq`). A torn
write (crash mid-write) therefore damages **at most one slot**; the other still
holds the previous generation. Readers validate both slots and take the valid
one with the highest `seq` (`tier_sb_select`). This is the standard mdadm/LVM
double-copy technique and closes the v1 single-copy hole (F-5): a mid-write crash
no longer bricks a disk's superblock.

Only the slot for the current generation is written and flushed each update
(128 KiB I/O), not the whole reserve.

## Header layout (256 bytes)

| offset | size | field | notes |
|-------:|-----:|:------|:------|
| 0 | 8 | `magic` | `0x5449455253423032` = "TIERSB02" (LE) |
| 8 | 4 | `version` | `2` |
| 12 | 4 | `crc` | CRC32c over the whole struct with `crc = 0` |
| 16 | 8 | `seq` | monotone generation; highest valid wins |
| 24 | 8 | `created_epoch_sec` | wall clock at serialization (informative) |
| 32 | 16 | `generation_uuid` | composite **instance** id (fencing, F-2) |
| 48 | 64 | `composite_name` | |
| 112 | 8 | `md_num_blocks` | size of the mirrored md region (composite blocks) |
| 120 | 8 | `cluster_blocks` | blobstore cluster grain; **u64 since v2 (F-3)** |
| 128 | 4 | `md_mirror_a` | band slot id of md RAID1 leg A |
| 132 | 4 | `md_mirror_b` | band slot id of md RAID1 leg B |
| 136 | 4 | `num_bands` | |
| 140 | 4 | `this_band_id` | which slot this copy physically sits on (`UINT32_MAX` if unknown) |
| 144 | 4 | `blocklen` | common block size |
| 148 | 4 | `reserved0` | |
| 152 | 104 | `reserved[104]` | zero-filled; future header fields (F-2) |
| 256 | … | `bands[64]` | 64 × 192-byte band descriptors |

Total = 256 + 64 × 192 = **12544 bytes** (fits one 128 KiB slot with room to spare).

## Band descriptor layout (192 bytes)

| offset | size | field |
|-------:|-----:|:------|
| 0 | 4 | `band_id` |
| 4 | 4 | `tier` (enum tier_class) |
| 8 | 4 | `state` (enum tier_band_state) |
| 12 | 4 | `reserved0` |
| 16 | 8 | `lba_start` |
| 24 | 8 | `num_blocks` |
| 32 | 64 | `wwn` |
| 96 | 64 | `serial` |
| 160 | 32 | `reserved[32]` (F-2) |

## Invariants

- **Endianness (F-4)**: little-endian only (amd64/arm64). `tier_sb_valid` rejects
a byte-swapped magic explicitly (a big-endian writer) rather than failing the
CRC silently.
- **Generation uniqueness (M5)**: `seq` is reserved (`t->seq++`) at the entry of
`tier_sb_write_all`, before any I/O. Two concurrent fan-outs can never share a
`seq`; gaps are harmless ("highest seq wins"), duplicates would be fatal.
- **Cross-restart monotonicity (R2)**: `bdev_tier_register` re-reads every band's
on-disk SB and seeds `t->seq` to the highest seq found **before** the first
persist. A fresh in-RAM composite starts at `seq 0` (the CSI replays
`create`+`assemble` without threading seq); without rehydration the first persist
writes `seq 1`, which a pre-restart SB at a high seq out-votes forever, so the
composite would reassemble the STALE geometry. Rehydration makes the first
post-register persist `max_on_disk + 1`, which wins. The generation_uuid still
changes each `create` (F-2), but after rehydration the current instance always
holds the highest seq, so its slot wins per disk.
- **Durability (F-6)**: each written slot is FLUSHed before the generation is
considered committed.
- **DEGRADED exclusion (M5b)**: only `ACTIVE` bands are written; a DEGRADED
disk's stale copy is out-voted by `seq` at reassembly.
- **Fencing (F-2)**: `generation_uuid` is minted once at `bdev_tier_create` and
copied into every superblock. A re-created composite gets a fresh uuid, so
disks left over from a previous instance cannot be silently cross-assembled.
The CSI compares uuids across a candidate disk set before assembling.

## Reserved space

104 header bytes + 32 bytes/band are zero-filled and CRC-covered. New fields are
added by shrinking a `reserved` array (keeping the surrounding offsets fixed) —
which changes the meaning of previously-zero bytes but NOT the struct size, so a
reader that predates the field sees zero (a safe default) and the static asserts
still pass. A field that changes size or reorders existing fields is a v3 break.

## Test coverage

`module/bdev/tier/test/test_tier_sb.c` (host-compiled, ASAN+UBSAN, links the
PRODUCTION `vbdev_tier_sb.c`): ABI offsets/sizes, serialize/validate roundtrip,
CRC/version/byte-swapped-magic rejection, u64 cluster_blocks, A/B slot selection
(highest-seq, torn-slot fallback, short-buffer safety), and a binary golden
header vector that catches a field reorder even when `sizeof` is unchanged.
Loading