Skip to content

fix(satellite): repair a crashed replica's activity log instead of re-initialising it - #220

Open
Andrei Kvapil (kvaps) wants to merge 3 commits into
fix/csi-clone-and-restore-idempotencyfrom
fix/unclean-al-apply
Open

Andrei Kvapil (kvaps) wants to merge 3 commits into
fix/csi-clone-and-restore-idempotencyfrom
fix/unclean-al-apply

Conversation

@kvaps

@kvaps Andrei Kvapil (kvaps) commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

A replica that goes down while Primary and writing leaves its activity log open, and drbdmeta answers the metadata probe with "please apply-al first". The satellite read that as "no metadata" and ran create-md --force, which destroys the replica's generation identifiers and dirty bitmap. If it held the only up-to-date copy, that copy came back as something to sync into. It treated a crash the same as a foreign superblock because it had no per-volume record to tell them apart.

The record is the set of GIs this replica's volume was seen to carry. The observer writes every GI it reads into a per-resource history file in the satellite's state directory. That file outlives the status field, which is dropped whenever the volume leaves a data-bearing disk state, and survives a restart; the GI in the status counts too. It is fsynced before it replaces the old one, and deleting the resource drops it.

  • A dirty log on a volume with no record is initialised as before: a clone, a snapshot receive, a volume added to a live resource, a replica created again.
  • One with a record is repaired with drbdadm apply-al. Then some UUID in its metadata, the current one, any peer slot's bitmap UUID or a history UUID, has to be one it was seen with, compared without the Primary bit. A Primary that lost a peer keeps its old current UUID only in that peer's bitmap slot, which get-gi never shows, and after a resync the GI sits in the history block. So the check reads dump-md, in both the v09 and the v08 layout. If none matches, the superblock is not this replica's and it gets metadata of its own after all.
  • A failed apply-al or metadata read is an error rather than a re-initialisation.
  • External metadata takes the same check: dump-md finds the meta disk from the .res.

Both metadata paths (ensureMetadata and ensurePerVolumeMetadata) take the same repair. A volume with no record falls back to the old behaviour, so this only ever narrows when metadata is destroyed. A failed repair surfaces in the satellite log and as a requeue; there is no Event for it yet. The check holds the whole dump-md output, bitmap included, in memory once per repair.

Stacked on #190.

Fixes #184

Testing

  • New tests drive both paths: a GI known from the status or from the history file, found only in a peer's bitmap slot, only in the history block or with the Primary bit flipped, is repaired with no create-md; metadata that does not name it is initialised, internal or external; a failing apply-al or metadata read is an error with no create-md; the history survives a restart, keeps the newest 32 GIs per volume and goes with the resource; the observer records GIs only for data-bearing volumes, and the one the manager runs feeds the reconciler. The parser fixtures follow the layout print_dump_gi in drbd-utils prints. Reverting the block parsing, the Primary-bit comparison, the history lookup, the fail-closed read, the external-metadata check, the cap, the delete cleanup, the repeat cache or the observer wiring reddens the matching test.
  • go test ./...: green apart from the known pkg/rest shutdown flake.
  • golangci-lint 2.11.4 (the CI pin) over the whole tree: clean.
  • Not run against real DRBD. The e2e the issue asks for (crash a writing Primary, bring the satellite back) needs a stand, and I had none for this round.

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fdffeb72-fbae-4705-b578-95a1f2d0d8ea

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

…-initialising it

A replica that goes down while Primary and writing leaves its activity
log open, and drbdmeta answers the metadata probe with "please apply-al
first". The satellite read that as "no metadata" and ran create-md
--force, which destroys the replica's generation identifiers and dirty
bitmap; if it held the only up-to-date copy, that copy came back as
something to sync into. It treated a crash the same as a foreign
superblock, because it had no per-volume record to tell them apart.

The record is the set of GIs this replica's volume was seen to carry.
The observer writes every GI it reads into a per-resource history file
in the satellite's state directory, which outlives the status field
(that one is dropped whenever the volume leaves a data-bearing state)
and a restart; the GI in the status counts too. A dirty log on a volume
with no record is initialised as before: a clone, a receive, a volume
added to a live resource. One with a record is repaired with apply-al,
and some UUID then in its metadata, current, a peer slot's bitmap UUID
or history, has to be one it was seen with, compared without the
Primary bit; otherwise the superblock is not this replica's and it gets
metadata of its own. A Primary that lost a peer keeps its old current
UUID only in that peer's bitmap slot, which get-gi never shows, so the
check reads dump-md. A failed apply-al or metadata read is an error
rather than a re-initialisation.

Fixes #184

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
… record whole

drbdmeta prints a v09 volume's history UUIDs as a block over several
lines, and v08's as one uuid block. The parser read neither, so a GI
that a resync had moved into the history was never found and the
replica was initialised after all. It now reads both blocks and only
bare UUID statements, so the bitmap and the device UUID stay out.

External metadata takes the same check as internal: dump-md finds the
meta disk from the .res. Deleting a resource now drops its cached last
GIs together with the file, under the lock the record is written
under, so a replica created again records its first GI. The record is
fsynced before it replaces the old one, since it exists for a node
that went down hard. The observer is built by a constructor a test can
drive, which pins the wiring into the reconciler.

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…ap fixture

Remove the temporary file when the rename fails, state the window the
delete lock leaves open, and give the dump fixtures the bitmap header
drbdmeta prints for v09.

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.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