Skip to content

fix lvol creation - #2

Merged
alexander-sheredin merged 1 commit into
mainfrom
sfam495
Mar 15, 2024
Merged

fix lvol creation#2
alexander-sheredin merged 1 commit into
mainfrom
sfam495

Conversation

@alexander-sheredin

Copy link
Copy Markdown
Contributor

No description provided.

@Hamdy-khader Hamdy-khader left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve

hamdy-simplyblock added a commit that referenced this pull request Mar 15, 2024
hamdy-simplyblock added a commit that referenced this pull request Mar 15, 2024
hamdy-simplyblock added a commit that referenced this pull request Mar 16, 2024
set version: 1.6.4
hamdy-simplyblock added a commit that referenced this pull request Mar 18, 2024
set version: 1.7.0
hamdy-simplyblock added a commit that referenced this pull request Mar 23, 2024
fix device add bug

set version: 1.8.0
hamdy-simplyblock added a commit that referenced this pull request Mar 29, 2024
set version: 1.9.0
hamdy-simplyblock added a commit that referenced this pull request Mar 29, 2024
set version: 1.9.7
hamdy-simplyblock added a commit that referenced this pull request Apr 4, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 4, 2024
set version: 1.3.8
hamdy-simplyblock added a commit that referenced this pull request Apr 16, 2024
Set version: 2.1.6
hamdy-simplyblock added a commit that referenced this pull request Apr 16, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 18, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 18, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 19, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 21, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 25, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 29, 2024
hamdy-simplyblock added a commit that referenced this pull request Apr 29, 2024
set version: 4.1.1
hamdy-simplyblock pushed a commit that referenced this pull request May 7, 2024
Set version: 1.0.6
hamdy-simplyblock pushed a commit that referenced this pull request May 7, 2024
hamdy-simplyblock added a commit that referenced this pull request May 8, 2024
Add API path to cluster API /cluster/gracefulshutdown/
hamdy-simplyblock added a commit that referenced this pull request May 21, 2024
hamdy-simplyblock added a commit that referenced this pull request May 23, 2024
hamdy-simplyblock added a commit that referenced this pull request May 23, 2024
* Add tasks runner #1

* wip

* add service to swarm

* add service to swarm #2

* q fix

* q fix 2

* q fix 2

* q fix 2

* q fix 2

* q fix 2
schmidt-scaled added a commit that referenced this pull request Jun 6, 2026
…RPC name

Three defects in the JM shared_placement support (654f941) caused
placement-map/journal conflicts and a runaway migration loop:

1. bdev_jm_create hard-coded shared_placement=True (JM_SHARED_PLACEMENT_DEFAULT),
   so every JM created on a node restart came up shared-placement even on a
   cluster that had NOT been migrated -- while distrib (which honors
   cluster.shared_placement) stayed legacy. The mismatch produced a conflict
   on the placement maps when reading journal records. Now bdev_jm_create
   takes a shared_placement arg (default off, omitted when False, mirroring
   bdev_distrib_create) and the three storage_node_ops call sites pass
   cluster.shared_placement -- so a restart reproduces the cluster's actual
   mode and JM/distrib stay consistent.

2. jm_set_shared_placement was called with no name, but the data-plane RPC
   requires one (there is exactly one JM per node, jm_<node_id>); the call
   errored. The client method now makes name mandatory and
   cluster_ops.set_shared_placement passes jm_<node_id>.

3. Because the JM RPC always errored (#2), set_shared_placement returned False,
   the monitor never cleared shared_placement_migration_pending, and it
   re-fired the migration on every tick (the "every few seconds" symptom).
   Fixing #2 lets the one-shot migration complete and clear the flag.

Tests updated to match: JM create emits the flag only when opted in, and
the JM runtime RPC requires a name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled added a commit that referenced this pull request Jun 6, 2026
…RPC name

Three defects in the JM shared_placement support (654f941) caused
placement-map/journal conflicts and a runaway migration loop:

1. bdev_jm_create hard-coded shared_placement=True (JM_SHARED_PLACEMENT_DEFAULT),
   so every JM created on a node restart came up shared-placement even on a
   cluster that had NOT been migrated -- while distrib (which honors
   cluster.shared_placement) stayed legacy. The mismatch produced a conflict
   on the placement maps when reading journal records. Now bdev_jm_create
   takes a shared_placement arg (default off, omitted when False, mirroring
   bdev_distrib_create) and the three storage_node_ops call sites pass
   cluster.shared_placement -- so a restart reproduces the cluster's actual
   mode and JM/distrib stay consistent.

2. jm_set_shared_placement was called with no name, but the data-plane RPC
   requires one (there is exactly one JM per node, jm_<node_id>); the call
   errored. The client method now makes name mandatory and
   cluster_ops.set_shared_placement passes jm_<node_id>.

3. Because the JM RPC always errored (#2), set_shared_placement returned False,
   the monitor never cleared shared_placement_migration_pending, and it
   re-fired the migration on every tick (the "every few seconds" symptom).
   Fixing #2 lets the one-shot migration complete and clear the flag.

Tests updated to match: JM create emits the flag only when opted in, and
the JM runtime RPC requires a name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled added a commit that referenced this pull request Jun 6, 2026
…RPC name

Three defects in the JM shared_placement support (654f941) caused
placement-map/journal conflicts and a runaway migration loop:

1. bdev_jm_create hard-coded shared_placement=True (JM_SHARED_PLACEMENT_DEFAULT),
   so every JM created on a node restart came up shared-placement even on a
   cluster that had NOT been migrated -- while distrib (which honors
   cluster.shared_placement) stayed legacy. The mismatch produced a conflict
   on the placement maps when reading journal records. Now bdev_jm_create
   takes a shared_placement arg (default off, omitted when False, mirroring
   bdev_distrib_create) and the three storage_node_ops call sites pass
   cluster.shared_placement -- so a restart reproduces the cluster's actual
   mode and JM/distrib stay consistent.

2. jm_set_shared_placement was called with no name, but the data-plane RPC
   requires one (there is exactly one JM per node, jm_<node_id>); the call
   errored. The client method now makes name mandatory and
   cluster_ops.set_shared_placement passes jm_<node_id>.

3. Because the JM RPC always errored (#2), set_shared_placement returned False,
   the monitor never cleared shared_placement_migration_pending, and it
   re-fired the migration on every tick (the "every few seconds" symptom).
   Fixing #2 lets the one-shot migration complete and clear the flag.

Tests updated to match: JM create emits the flag only when opted in, and
the JM runtime RPC requires a name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled added a commit that referenced this pull request Jun 6, 2026
…RPC name

Three defects in the JM shared_placement support (654f941) caused
placement-map/journal conflicts and a runaway migration loop:

1. bdev_jm_create hard-coded shared_placement=True (JM_SHARED_PLACEMENT_DEFAULT),
   so every JM created on a node restart came up shared-placement even on a
   cluster that had NOT been migrated -- while distrib (which honors
   cluster.shared_placement) stayed legacy. The mismatch produced a conflict
   on the placement maps when reading journal records. Now bdev_jm_create
   takes a shared_placement arg (default off, omitted when False, mirroring
   bdev_distrib_create) and the three storage_node_ops call sites pass
   cluster.shared_placement -- so a restart reproduces the cluster's actual
   mode and JM/distrib stay consistent.

2. jm_set_shared_placement was called with no name, but the data-plane RPC
   requires one (there is exactly one JM per node, jm_<node_id>); the call
   errored. The client method now makes name mandatory and
   cluster_ops.set_shared_placement passes jm_<node_id>.

3. Because the JM RPC always errored (#2), set_shared_placement returned False,
   the monitor never cleared shared_placement_migration_pending, and it
   re-fired the migration on every tick (the "every few seconds" symptom).
   Fixing #2 lets the one-shot migration complete and clear the flag.

Tests updated to match: JM create emits the flag only when opted in, and
the JM runtime RPC requires a name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled added a commit that referenced this pull request Jul 2, 2026
…ver force-strands devices

Fix #2's sibling guard (skip the global device force when a sibling on the same
home node also reached IO-error quorum) checked sibling quorum at the *instant*
the current device tripped. But the two devices on a node do not trip together:
their events are processed on independent per-node collector threads and cross
quorum a few seconds apart (observed ~2s), and the first to trip clears its own
votes on force. So neither sibling ever observes the other's quorum -- the first
tripper doesn't yet see the second, and after it forces+clears, the second can't
see the first. The guard fired 0x while both siblings were forced globally
UNAVAILABLE, stranding the devices until a full node restart.

Live incident (6-node mixed-churn soak, iodepth 128): iter-4 network_outage_20 on
node b9a6aad8 force-marked both its devices (storage_id 6,7) at 12:15:37/39; they
were never re-admitted, leaving a phantom offline_devices=2 baseline that iter-5's
dual outage stacked on top of -> 5 devices offline -> cluster SUSPENDED.

Fix: remember which devices were quorum-forced (_recent_quorum_forced, stamped
before votes are cleared). Treat a sibling that reached quorum OR was force-marked
within SIBLING_FORCE_EVIDENCE_SEC as node-outage evidence, and re-admit a sibling
that was wrongly forced in the race (only when status=UNAVAILABLE, io_error=False,
not retries_exhausted -- a genuine local fault is left to the restart/removal
path). Now the second sibling to trip recognizes the node outage and undoes the
first force, so offline_devices returns to 0 on node recovery instead of wedging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled added a commit that referenced this pull request Jul 2, 2026
…ver force-strands devices

Fix #2's sibling guard (skip the global device force when a sibling on the same
home node also reached IO-error quorum) checked sibling quorum at the *instant*
the current device tripped. But the two devices on a node do not trip together:
their events are processed on independent per-node collector threads and cross
quorum a few seconds apart (observed ~2s), and the first to trip clears its own
votes on force. So neither sibling ever observes the other's quorum -- the first
tripper doesn't yet see the second, and after it forces+clears, the second can't
see the first. The guard fired 0x while both siblings were forced globally
UNAVAILABLE, stranding the devices until a full node restart.

Live incident (6-node mixed-churn soak, iodepth 128): iter-4 network_outage_20 on
node b9a6aad8 force-marked both its devices (storage_id 6,7) at 12:15:37/39; they
were never re-admitted, leaving a phantom offline_devices=2 baseline that iter-5's
dual outage stacked on top of -> 5 devices offline -> cluster SUSPENDED.

Fix: remember which devices were quorum-forced (_recent_quorum_forced, stamped
before votes are cleared). Treat a sibling that reached quorum OR was force-marked
within SIBLING_FORCE_EVIDENCE_SEC as node-outage evidence, and re-admit a sibling
that was wrongly forced in the race (only when status=UNAVAILABLE, io_error=False,
not retries_exhausted -- a genuine local fault is left to the restart/removal
path). Now the second sibling to trip recognizes the node outage and undoes the
first force, so offline_devices returns to 0 on node recovery instead of wedging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled pushed a commit that referenced this pull request Jul 20, 2026
…oot loop

FD-0 whole-domain reboot (2026-07-20) looped restart->abort->offline and
dropped fio to EIO on the surviving mirror. Root cause: 16-way parallel
FD recovery saturated the Python GIL, inflating the CP-local work between
port-block RPCs so the client-port block on the serving counterpart
exceeded the 6s nvmf ack-timeout and reject-converted the live client;
and the coverage gate required down same-FD siblings' devices, so every
recovering node aborted waiting on its down peers (circular dependency).

Fixes (all unit-tested, tests/unit/test_restart_cpu_fixes.py):
- A: async (queue-based) logging handler (utils.make_async_handler) removes
  the process-global handler-lock + stdout contention under 100+ threads.
- B: bounded connect/reconnect workers (_restart_worker_sem /
  _bounded_thread, RESTART_WORKER_MAX_CONCURRENCY=24) cap GIL-bound fan-out.
- C-safe: _connect_device_thread fast-skips (single attempt, no backoff)
  when the owning peer is not ONLINE — cuts ~6s/device on down peers.
- #1: replace the process-global _recreate_lvstore_gate with a per-LVS
  lock registry; same-LVS recreates serialize, different-LVS run
  concurrently; activation_mode bypasses the lock.
- #2: coverage gate excludes down same-failure-domain siblings from the
  required set only under a sanctioned dead-FD recovery
  (fd_dead_recovery_allowed = FTT floor), breaking the restart loop while
  preserving the 2026-07-16 silent-connect-failure protection.
- Per-LVS port-block-duration print right after unblock in both recreate
  paths.
- collect_logs.py: drop the O(N^2) per-node `sn check` block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
schmidt-scaled pushed a commit that referenced this pull request Jul 21, 2026
Window decomposition (2026-07-21, 3.3s window): ~45 RPCs x ~50ms
CP-observed + 1.46s CP gaps. This removes the movable content:

#1 pre-stage hublvol NVMf subsystems + listeners BEFORE the block:
   StorageNode.prestage_hublvol_subsystem() (subsystem/listener half of
   expose_bdev — no lvstore/bdev dependency; identical parameters incl.
   the disjoint-cntlid requirement). Wired pre-block in both impls
   (leader hublvol, transferhub when persisted, sec_1 shared-NQN
   subsystem; non-leader: own secondary-hublvol subsystem). In-window
   expose reduces to probe+add_ns. Idempotent; failure falls back to
   in-window creation.
#2 coordinator success stamp deferred: externally-managed locks mark
   pending_stamp instead of paying the FDB txn in-window; the impls'
   release helpers stamp post-unblock (cooldown shifts later — strictly
   more conservative).
#3 port deny/allow event emission (FDB+graylog, ~190ms measured
   in-window) deferred to post-unblock/abort flush; block-span logs
   carry the true timing, events stay complete for audit.
#4 examine-idempotency probes (raid/lvstore exists) computed pre-block
   in both impls — they read only snode's fresh SPDK, built pre-block.

Expected: in-window content drops by ~8-10 RPCs + the event write +
the stamp txn per window (~3.3s -> ~2s); reaching <1s additionally
needs the monitor-baseline trim (follow-up #5) since examine +
leadership + attach are the intrinsic in-window floor at the current
~50ms/RPC GIL tax.

7 new tests; full unit suite: 784 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mxsrc pushed a commit to mxsrc/sbcli that referenced this pull request Jul 27, 2026
…in_shutdown self-heal

A concurrent `sn shutdown` of two nodes wedged a node in `in_shutdown`
permanently (incident 2026-06-18). set_node_status committed
in_shutdown->offline via atomic CAS, but a concurrent full-object
write_to_db() (which serializes the WHOLE row) re-wrote a stale
in_shutdown. No offline->in_shutdown event existed — a silent lost
update. The node then deadlocked: the monitor skips in_shutdown nodes,
re-shutdown is rejected, and the peer's concurrent shutdown is rejected.

Root cause: atomic_update only prevents lost updates if EVERY writer of a
key uses it. Any `x=db.get(); x.field=v; x.write_to_db()` on a
concurrently-written row clobbers other fields (notably status). Device
status lives inside the StorageNode row, so every device write during a
shutdown full-writes the node and reverts node.status.

Fix simplyblock#1 — route clobbering writers through db.atomic_update (CAS):
- device_controller: _atomic_device_set helper; device state/io_error/
  retries/JM state/peer remote_devices/recreate/add-from-failed
- health_check_service: node + device health, remote_jm_devices
- health_controller: remote_devices rebuild
- mgmt_node_monitor: MgmtNode status
- storage_node_monitor: cluster is_re_balancing + shared_placement flags
- lvol_monitor: status/health/io_error/deletion_status
- lvol_stat_collector: io_error clear
- cluster_ops: set_shared_placement, add_replication
- backup_controller: backup_source

Fix simplyblock#2 — self-heal: new StorageNode.shutdown_since (stamped in
set_node_status, mirrors down_since); the monitor reconciles a node stuck
in_shutdown past a 120s grace with SPDK confirmed dead back to OFFLINE.
auto_restart_disabled stays set so an intentionally-stopped node is not
auto-restarted. Only in_shutdown needed a backstop — cluster/lvol/device
status self-correct each monitor cycle, and restarting is owned by a
retrying task runner.

test_shared_placement: DB fake updated to model atomic_update faithfully.
Full suite: 1288 passed, 257 skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schmidt-scaled pushed a commit that referenced this pull request Aug 13, 2026
replication_commit took one fire-and-forget internal snapshot, selected a
cutover base that was 1-2 replication intervals old, and enqueued the final
task — which froze immediately, WITHOUT waiting for anything to replicate. The
writes between the last replicated snapshot and the pre-commit snapshot were
covered neither by the final step's delta (top blob only = writes after the
pre-commit snapshot) nor by anything on the target: every cutover silently
lost up to ~2 intervals of data. Invisible to the harness because fio's
sequential sweep rewrites its whole working set and verify_backlog only checks
recent writes.

The cutover now shrinks the delta iteratively before freezing:

  snapshot #1 (at commit) -> wait until replicated AND converted on the target
  -> IMMEDIATELY snapshot #2 (delta = just the wait window) -> wait again ->
  IMMEDIATELY build the target clone on that last replicated snapshot and run
  the freeze + ANA flip.

replication_commit is now thin (validate, shrink snapshot #1, enqueue); the
final-task runner owns the shrink state machine (bounded by
REPL_CUTOVER_SHRINK_TIMEOUT_SEC, waiting does not burn task retries) and the
clone/map-id/replication-record preparation, so the base is always the freshly
replicated shrink snapshot. Old-style tasks with tgt_* params still run
unchanged (skip shrink + prepare).

Harness: case 1 now writes a baseline fio never touches and md5-verifies it
through the cutover volume after a remount — the ONLY assertion that exercises
the replicated snapshot history (fio's own data always arrives via the final
step); target paths are connected inside the cutover wait loop as soon as the
runner creates the target volume, keeping multipath ahead of the ANA flip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RaunakJalan pushed a commit that referenced this pull request Aug 31, 2026
replication_commit took one fire-and-forget internal snapshot, selected a
cutover base that was 1-2 replication intervals old, and enqueued the final
task — which froze immediately, WITHOUT waiting for anything to replicate. The
writes between the last replicated snapshot and the pre-commit snapshot were
covered neither by the final step's delta (top blob only = writes after the
pre-commit snapshot) nor by anything on the target: every cutover silently
lost up to ~2 intervals of data. Invisible to the harness because fio's
sequential sweep rewrites its whole working set and verify_backlog only checks
recent writes.

The cutover now shrinks the delta iteratively before freezing:

  snapshot #1 (at commit) -> wait until replicated AND converted on the target
  -> IMMEDIATELY snapshot #2 (delta = just the wait window) -> wait again ->
  IMMEDIATELY build the target clone on that last replicated snapshot and run
  the freeze + ANA flip.

replication_commit is now thin (validate, shrink snapshot #1, enqueue); the
final-task runner owns the shrink state machine (bounded by
REPL_CUTOVER_SHRINK_TIMEOUT_SEC, waiting does not burn task retries) and the
clone/map-id/replication-record preparation, so the base is always the freshly
replicated shrink snapshot. Old-style tasks with tgt_* params still run
unchanged (skip shrink + prepare).

Harness: case 1 now writes a baseline fio never touches and md5-verifies it
through the cutover volume after a remount — the ONLY assertion that exercises
the replicated snapshot history (fio's own data always arrives via the final
step); target paths are connected inside the cutover wait loop as soon as the
runner creates the target volume, keeping multipath ahead of the ANA flip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.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.

2 participants