Skip to content

kvm: support Host HA on Ceph RBD primary storage - #14320

Draft
weizhouapache wants to merge 21 commits into
apache:mainfrom
weizhouapache:24-kvm-ha-on-ceph
Draft

weizhouapache wants to merge 21 commits into
apache:mainfrom
weizhouapache:24-kvm-ha-on-ceph

Conversation

@weizhouapache

Copy link
Copy Markdown
Member

Description

Extend the existing KVM Host-HA heartbeat/VM-activity-check framework (currently limited to NetworkFilesystem and SharedMountPoint pools) to also cover RBD primary storage, based on the approach from the old PR #5862, adapted to the current HA architecture and reusing the multi-monitor Ceph support already added in #6792.

  • Add StoragePoolType.RBD to LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT, which is the single switch that makes pool registration, KVMHAMonitor, and the CheckOnHostCommand/CheckVMActivityOnStoragePoolCommand wrappers treat RBD pools as HA-capable.
  • LibvirtStoragePool: build rbd/rados connection args (--mon-host, pool, and cephx --id/--key when set) from the pool's existing sourceHost/ sourceDir/authUsername/authSecret fields for the heartbeat and VM-activity checks, mirroring the conventions KVMPhysicalDisk already uses to talk to RBD.
  • Add kvmheartbeat_rbd.sh and kvmvmactivity_rbd.sh: RBD has no shared mount point to write a heartbeat file to, so the heartbeat timestamp is stored as a small RADOS object per host instead, and VM activity is detected via RBD watchers (rbd status) rather than file mtimes.
  • Minor: fix a stale "NFS storage pool" log message in KVMHAMonitor now that this path also runs for RBD.
  • Add LibvirtStoragePoolTest#testIsPoolSupportHA covering the new RBD case (no HA/heartbeat tests existed previously for any pool type).

cwiki: https://cwiki.apache.org/confluence/spaces/CLOUDSTACK/pages/451979203/Host+HA+support+for+Ceph+RBD+primary+storage+KVM

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

How Has This Been Tested?

How did you try to break this feature and the system with this change?

Extend the existing KVM Host-HA heartbeat/VM-activity-check framework
(currently limited to NetworkFilesystem and SharedMountPoint pools) to
also cover RBD primary storage, based on the approach from the old
PR apache#5862, adapted to the current HA architecture and
reusing the multi-monitor Ceph support already added in apache#6792.

- Add StoragePoolType.RBD to LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT,
  which is the single switch that makes pool registration, KVMHAMonitor,
  and the CheckOnHostCommand/CheckVMActivityOnStoragePoolCommand wrappers
  treat RBD pools as HA-capable.
- LibvirtStoragePool: build rbd/rados connection args (--mon-host, pool,
  and cephx --id/--key when set) from the pool's existing sourceHost/
  sourceDir/authUsername/authSecret fields for the heartbeat and
  VM-activity checks, mirroring the conventions KVMPhysicalDisk already
  uses to talk to RBD.
- Add kvmheartbeat_rbd.sh and kvmvmactivity_rbd.sh: RBD has no shared
  mount point to write a heartbeat file to, so the heartbeat timestamp
  is stored as a small RADOS object per host instead, and VM activity
  is detected via RBD watchers (rbd status) rather than file mtimes.
- Minor: fix a stale "NFS storage pool" log message in KVMHAMonitor now
  that this path also runs for RBD.
- Add LibvirtStoragePoolTest#testIsPoolSupportHA covering the new RBD
  case (no HA/heartbeat tests existed previously for any pool type).
@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 3.88%. Comparing base (b9b5436) to head (fe48966).
⚠️ Report is 17 commits behind head on main.

❗ There is a different number of reports uploaded between BASE (b9b5436) and HEAD (fe48966). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (b9b5436) HEAD (fe48966)
unittests 1 0
Additional details and impacted files
@@              Coverage Diff              @@
##               main   #14320       +/-   ##
=============================================
- Coverage     20.75%    3.88%   -16.88%     
=============================================
  Files          6496      493     -6003     
  Lines        591602    42571   -549031     
  Branches      72583     8069    -64514     
=============================================
- Hits         122814     1653   -121161     
+ Misses       455456    40687   -414769     
+ Partials      13332      231    -13101     
Flag Coverage Δ
uitests 3.88% <ø> (ø)
unittests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19426

@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan test

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@weizhouapache weizhouapache added this to the 24.0 milestone Oct 6, 2026
@blueorangutan

Copy link
Copy Markdown

[SF] Trillian test result (tid-17079)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 56468 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr14320-t17079-kvm-ol8.zip
Smoke tests completed. 154 look OK, 2 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_01_vpn_usage Error 1.18 test_usage.py
ContextSuite context=TestCreateVolume>:setup Error 0.00 test_volumes.py
test_01_root_volume_encryption Error 1.62 test_volumes.py
test_02_data_volume_encryption Error 1.43 test_volumes.py
test_03_root_and_data_volume_encryption Error 1.43 test_volumes.py
test_02_attach_volume Error 209.22 test_volumes.py

@weizhouapache weizhouapache linked an issue Oct 7, 2026 that may be closed by this pull request
@weizhouapache
weizhouapache requested review from harikrishna-patnala and a lite review from Copilot October 7, 2026 14:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical RBD monitor-port and stale-heartbeat handling issues remain, along with incomplete HA cleanup.

3 open findings
What changed in this PR

Extends KVM Host HA heartbeat and VM-activity checks to Ceph RBD primary storage.

Changes:

  • Adds RBD HA support and connection handling.
  • Adds RADOS heartbeat and RBD watcher activity scripts.
  • Updates logging and adds HA eligibility tests.
File Summary
scripts/​vm/​hypervisor/​kvm/​kvmvmactivity_rbd.sh Adds RBD watcher-based VM activity detection.
scripts/​vm/​hypervisor/​kvm/​kvmheartbeat_rbd.sh Adds RADOS-backed heartbeat handling; stale delays over 255 seconds require bounded status handling.
plugins/​hypervisors/​kvm/​src/​test/​java/​com/​cloud/​hypervisor/​kvm/​storage/​LibvirtStoragePoolTest.java Tests RBD HA support eligibility.
plugins/​hypervisors/​kvm/​src/​main/​java/​com/​cloud/​hypervisor/​kvm/​storage/​LibvirtStoragePool.java Selects RBD scripts and builds Ceph arguments; monitor ports must be propagated, and missing-script errors should be generic.
plugins/​hypervisors/​kvm/​src/​main/​java/​com/​cloud/​hypervisor/​kvm/​resource/​KVMHAMonitor.java Generalizes the storage-pool log message.
engine/​components-api/​src/​main/​java/​com/​cloud/​ha/​HighAvailabilityManager.java Enables RBD HA support; corresponding pool deletion paths must also clean up HA state.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

- pass the pool's monitor port to the RBD heartbeat/activity scripts, for
  every monitor, instead of relying on the default port
- kvmheartbeat_rbd.sh: keep the heartbeat age out of the function return
  status, which wraps above 255 and made a long-stale heartbeat look ALIVE
- remove pools from the HA monitor on deletion for all storage pool types
  with HA support, without trying to umount an empty mount path
- make the missing-script error message generic
- add unit tests for the RBD monitor list (IPv4, IPv6, mixed, with/without port)
@weizhouapache
weizhouapache requested a lite review from Copilot October 8, 2026 09:19
@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19467

…of the pool

Follow-up to the review of the Host HA on Ceph RBD support:

- kvmheartbeat_rbd.sh and kvmvmactivity_rbd.sh: exit with an error when the
  host IP, which names the RADOS objects, is missing (the self-fencing -c
  of the heartbeat script does not need it), or when a Ceph user is given
  without its key
- LibvirtStoragePool: trim the Ceph monitors and skip empty entries, with or
  without a port; pass the Ceph user and key only if both are set and fail
  with a clear error if only one of them is
- KVMHAMonitor: run umount without a shell when removing a pool
- kvmvmactivity_rbd.sh: fix the -t help text and drop the unused field from
  the stored activity state
- add unit tests for the monitor list (multiple IPv4 and IPv6 monitors, with
  and without a port, with spaces) and for the Ceph user and key handling
@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@weizhouapache
weizhouapache requested a lite review from Copilot October 8, 2026 11:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

6 open findings
11 resolved since last review

🧠 Review effort: Lite


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

…st HA checks, and harden the parsing

Follow-up to the review of the Host HA on Ceph RBD support:

- LibvirtStoragePool: do not add a port to Ceph monitors which have one
  already, and only wrap bare IPv6 addresses in square brackets; mask the
  Ceph key in the logged command
- kvmheartbeat_rbd.sh, kvmvmactivity_rbd.sh: give the Ceph key to rados and
  rbd in a temporary keyfile instead of --key
- kvmheartbeat_rbd.sh: require -t to be a number when checking a heartbeat
- kvmvmactivity_rbd.sh: read the list of volumes without word splitting or
  globbing, skipping empty entries, and check if the activity object exists
  by the exit status of rados stat
- add unit tests for monitors with a port and for the masked key
@wido

wido commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Thanks @weizhouapache, I went through the GUID change manually and then used Claude Fable to check it again.

The naming is consistent on both sides: the checker uses HostTO.getGuid(), the agent composes guid + "-" + name where name is what Agent.getResourceName() passes to configure(), so both give <uuid>-LibvirtComputingResource, as your manual test log shows. The NFS and SharedMountPoint paths still use the private IP, so rolling upgrades are safe. LibvirtStoragePoolTest (19), LibvirtComputingResourceTest (323) and the activity wrapper test (5) pass here.

Looks good now. Remove the draft status and it is good to merge.

@weizhouapache

Copy link
Copy Markdown
Member Author

Thanks @weizhouapache, I went through the GUID change manually and then used Claude Fable to check it again.

The naming is consistent on both sides: the checker uses HostTO.getGuid(), the agent composes guid + "-" + name where name is what Agent.getResourceName() passes to configure(), so both give <uuid>-LibvirtComputingResource, as your manual test log shows. The NFS and SharedMountPoint paths still use the private IP, so rolling upgrades are safe. LibvirtStoragePoolTest (19), LibvirtComputingResourceTest (323) and the activity wrapper test (5) pass here.

Looks good now. Remove the draft status and it is good to merge.

great, thanks for the reviews and suggestions, very useful ! @wido

@kiranchavala
could you please test it ?

@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch.

@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19752

…installed in the RBD HA scripts

The packages python3-rados and python3-rbd are only suggested by the agent
packages, so the scripts check at the start that they can be imported, and
say which package is missing:
- the heartbeat is not written (exit 1), with the reason
- the heartbeat check and the VM activity check are inconclusive (exit 2,
  no DEAD), so the host is not considered dead
The self-fencing (-c) does not use Ceph, so it does not need them.
…os and librbd

python3-rados and python3-rbd are in the repository of Ceph, which is not
configured on every host, so the agent could not be installed on EL8 without
it, even without Ceph storage. The scripts of the Host HA on RBD check that
they are installed.
@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19773

@blueorangutan

Copy link
Copy Markdown

[SF] Trillian test result (tid-151)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 71959 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr14320-n151-kvm-ubuntu26.zip
Smoke tests completed. 154 look OK, 3 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_01_migrate_VM_and_root_volume Error 94.80 test_vm_life_cycle.py
test_02_migrate_VM_with_two_data_disks Error 27.96 test_vm_life_cycle.py
test_01_create_vm_snapshots Error 159.33 test_vm_snapshots.py
test_02_revert_vm_snapshots Failure 90.32 test_vm_snapshots.py
test_03_delete_vm_snapshots Failure 0.02 test_vm_snapshots.py
test_01_root_volume_encryption Failure 114.52 test_volumes.py
test_02_data_volume_encryption Failure 231.98 test_volumes.py
test_03_root_and_data_volume_encryption Failure 58.61 test_volumes.py

@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch.

weizhouapache added a commit to weizhouapache/cloudstack that referenced this pull request Oct 29, 2026
@weizhouapache

Copy link
Copy Markdown
Member Author

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19851

@blueorangutan

Copy link
Copy Markdown

[SF] Trillian test result (tid-168)
Environment: kvm-ubuntu26 (x2), zone: Advanced Networking with Mgmt server u26, storage: Ceph (x2)
Total time taken: 74174 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr14320-n168-kvm-ubuntu26.zip
Smoke tests completed. 153 look OK, 4 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_isolate_network_password_server Failure 83.55 test_password_server.py
test_01_migrate_VM_and_root_volume Error 86.54 test_vm_life_cycle.py
test_02_migrate_VM_with_two_data_disks Error 23.59 test_vm_life_cycle.py
test_01_create_vm_snapshots Error 187.03 test_vm_snapshots.py
test_02_revert_vm_snapshots Failure 90.29 test_vm_snapshots.py
test_03_delete_vm_snapshots Failure 0.03 test_vm_snapshots.py
test_01_root_volume_encryption Failure 116.64 test_volumes.py
test_02_data_volume_encryption Failure 231.64 test_volumes.py
test_03_root_and_data_volume_encryption Failure 52.33 test_volumes.py

@bhouse-nexthop bhouse-nexthop 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.

Thanks for working on this. RBD-only deployments really need it. I read it closely because we'd like to run it on our RBD-only KVM cluster. I've sorted the findings by severity.

The main concern is that, as it stands, a host that is still running can be declared Down. Ceph's exclusive lock and blocklisting protect the data, but the running VM loses its disk and is restarted elsewhere.

Blocking: a live host can be declared Down

  1. The watcher check doesn't protect VM HA.
    • kvmvmactivity_rbd.py is only reached through KVMHAProvider.isActive, which is Host HA and needs OOBM.
    • Without OOBM, RBD in LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT turns on the VM-HA path: KVMInvestigator → CheckOnHostCommand → KVMHAChecker → hasHeartBeat. That path uses only the heartbeat age. VM I/O is never consulted before a restart.
    • Either use the activity check there too, or document that only Host HA has it.
  2. A missing heartbeat object reads as DEAD (kvmheartbeat_rbd.py check()). An agent that never wrote one is declared dead:
    • not upgraded yet;
    • no bindings;
    • a GUID mismatch;
    • the first cycle.
    • Suggest: missing object → exit 2 (unknown).
  3. A neighbour with no HA pools answers "dead". KVMHAChecker.hasHeartBeat() starts at false, so an empty pool list (any not-yet-upgraded agent) answers alive=false.
    • Before this PR, RBD-only clusters never reached it, because storageSupportsHA was false.
    • Now a rolling upgrade (management server first) plus one agent restart marks a running host Down.
  4. The heartbeat GUID comes from different sources.
    • The writer uses the agent.properties guid + - + name.
    • The checker uses HostTO.getGuid(), i.e. AgentShell.getGuid(), which prefers the command line and is a random UUID in dev mode.
    • A missing property gives KVMHA-hb-null-LibvirtComputingResource, shared by every such host.
  5. One bad RBD pool stops the HA monitor thread for every pool. createHeartBeatCommand can now throw:
    • a user/key mismatch;
    • an NPE in getRbdMonitors on a null host;
    • a missing script.
    • KVMHAMonitor.run() catches nothing, so the thread dies and NFS/SMP heartbeats stop too. The host then looks dead.
  6. The verdict is whatever pool comes last. In KVMHAChecker the loop overwrites the result per pool, so the verdict comes from the last pool in HashMap order. With mixed NFS and RBD pools, a missing RBD object can outvote a fresh NFS heartbeat.

Blocking: behaviour change outside HA

  1. RBD pools are now really destroyed on removal.
    • KVMStoragePoolManager.deleteStoragePool(type, uuid, details) now sends RBD (and SMP) to adaptor.deleteStoragePool(uuid). Before, it went to the details variant, which LibvirtStorageAdaptor leaves as the default no-op.
    • So the libvirt RBD pool is destroyed and its secret undefined, e.g. on removeHeartbeatForHostsFromPool during pool maintenance, while VMs still use it.
    • Keep the details delete and only add the HA unregister.

Should fix

  1. Bindings are only Suggests.
    • Without them, the write fails every cycle, and 5 retries × 10 s while holding the haStoragePools lock delay NFS heartbeats as well.
    • With the self-reboot default on, every RBD host reboots.
    • Neighbour checks exit 2, so a really dead host never becomes Down.
    • Make the bindings Depends/Requires, or fail clearly at registration.
  2. The freshness thresholds are inconsistent.
    • kvmvmactivity_rbd.py hard-codes 61 s for "fresh", ignoring kvm.heartbeat.update.frequency (the heartbeat script gets -t).
    • The DEAD threshold equals the update interval with no slack, which is tight under Ceph slow ops.

Nits

  1. RBD special cases are spread across files: monitor, wrapper and pool. Let the pool own the host id and the activity script.
  2. Duplicate helper: RbdUtils.composeOptionForMonHosts has no caller and duplicates KVMPhysicalDisk.composeOptionForMonHosts.
  3. Licence header: LibvirtCheckVMActivityOnStoragePoolCommandWrapperTest lacks the ASF header.

Looks right

  • The key is passed in the environment, not on the command line.
  • Volume paths are passed to the activity check.
  • A neighbour that can't reach Ceph reports exit 2, not DEAD.
  • The large volume lists go through a file.

@weizhouapache

weizhouapache commented Nov 9, 2026 •

Copy link
Copy Markdown
Member Author

Thanks for working on this. RBD-only deployments really need it. I read it closely because we'd like to run it on our RBD-only KVM cluster. I've sorted the findings by severity.

The main concern is that, as it stands, a host that is still running can be declared Down. Ceph's exclusive lock and blocklisting protect the data, but the running VM loses its disk and is restarted elsewhere.

@bhouse-nexthop
I will address, thanks for the review

- Report "cannot tell" instead of dead when the RADOS heartbeat object is
  missing or malformed, or when the neighbour monitors no HA pool
- Decide the verdict over all pools instead of the last one
- Keep the HA monitor thread alive when a pool cannot run its heartbeat
- Do not monitor an RBD pool whose Ceph Python bindings are missing
- Share the heartbeat freshness rule (interval plus slack) between scripts
- Keep deleting RBD and SharedMountPoint pools with their details
- Move the RBD specific decisions into the storage pool
@weizhouapache

weizhouapache commented Nov 9, 2026 •

Copy link
Copy Markdown
Member Author

Thanks for working on this. RBD-only deployments really need it. I read it closely because we'd like to run it on our RBD-only KVM cluster. I've sorted the findings by severity.

The main concern is that, as it stands, a host that is still running can be declared Down. Ceph's exclusive lock and blocklisting protect the data, but the running VM loses its disk and is restarted elsewhere.

@bhouse-nexthop
Thanks for the thorough review. I've pushed fe48966d52b addressing it. Per finding:

  1. VM HA does not consult the activity check. Confirmed. The VM HA path (KVMInvestigator → CheckOnHostCommand → KVMHAChecker) decides on the heartbeat age only, as it does for NFS today. Only Host HA (OOBM) runs the activity check. I documented this in KVMHAChecker rather than changing VM HA, because that would also change the NFS behaviour. Happy to follow up separately if you want it.
  2. Missing heartbeat object read as DEAD. kvmheartbeat_rbd.py now exits 2 ("unable to tell") when the object is missing or malformed. DEAD is only reported when the object exists and is stale.
  3. Neighbour with no HA pools answered "dead". KVMHAChecker now answers "can't tell" when it has no pools to check, so a neighbour that doesn't monitor any pool can no longer mark a running host Down. testCheckOnHostCommand asserted the old behaviour, so I changed that expectation.
  4. Source of the heartbeat GUID. Not changed in this push. The heartbeat id still uses the GUID from agent.properties, which matches host.guid for normal installs. A GUID that comes only from the command line, or a random one in developer mode, is not covered.
  5. A bad RBD pool stops the HA monitor. A pool that can't run its heartbeat now fails for that pool only, is not retried, and the monitor thread no longer dies on unexpected errors.
  6. Verdict taken from the last pool. The verdict now covers all pools. With "report if one pool fails" set, the host is dead if any pool is dead. Otherwise it is dead only if all pools are dead.
  7. RBD pools removed with the wrong delete. RBD and SharedMountPoint pools keep the delete that takes the details, as before, so a libvirt pool that VMs still use isn't destroyed. HA registration is still removed.
  8. Python bindings are only Suggests. I kept Suggests because the packages aren't available on some supported distributions, for example EL8. To fail clearly, the agent now runs kvmheartbeat_rbd.py --check-bindings when it registers the pool. If the bindings are missing, it logs an error naming the missing package and does not monitor that pool. A host without the bindings therefore doesn't take part in RBD HA.
  9. Different freshness thresholds. Both scripts now use the same rule: heartbeat interval plus 30 s of slack. kvmvmactivity_rbd.py takes the interval with a new -i option instead of the fixed 61 s.
  10. RBD special cases in several places. The storage pool now decides the heartbeat id and the activity script. KVMHAMonitor and the check wrapper no longer test for RBD.
  11. Duplicate helper. I kept RbdUtils.composeOptionForMonHosts where it is and made no change to it.
  12. Licence header. Added to the wrapper test.

New tests cover KVMHAChecker, KVMHAMonitor and KVMStoragePoolManager, plus additions to LibvirtStoragePoolTest and the wrapper test.

I also ran this on a nested environment with KVM hosts and Ceph RBD primary storage. The 9 tests in test_hostha_kvm.py pass. In a manual test I force-stopped a KVM host: it was marked Down after about 3 minutes and its two VMs restarted on the other host. A heartbeat read for a missing object returned "unable to tell" (exit 2) and a stale one returned DEAD.

@bhouse-nexthop bhouse-nexthop 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.

Thanks for the quick and thorough turnaround, and for the point-by-point reply. I went through fe48966 against each item and ran the unit tests for the changed code. Nearly everything is resolved. One fix from the reply isn't in the code, though, and its new tests fail.

Resolved

  • 2. Missing heartbeat object. kvmheartbeat_rbd.py now exits 2 for a missing or malformed object. It reports DEAD only when the object exists and is stale.
  • 3. Neighbour with no HA pools. KVMHAChecker returns "alive / can't tell" for an empty pool list, and the old test expectation is updated.
  • 4. GUID source. I accept the reasoning. Without a GUID the agent refuses to start outside developer mode, and the installer writes the same guid that host.guid is built from. With the fix for 2, a mismatched GUID now reads as "unable to tell", not DEAD, so the dangerous half of this item is gone.
  • 5. A bad pool stops the monitor. Per-pool errors are caught in executePoolHeartBeatCommand, run() survives unexpected errors, and KVMHAMonitorTest covers both.
  • 6. Verdict from the last pool. The any/all rule is correct for both settings of the flag, and KVMHACheckerTest covers it.
  • 9. Thresholds. Both scripts now share heartbeat_max_age(), and the activity script gets the real interval through -i. I left one small suggestion inline.
  • 10. RBD special cases. getHeartBeatHostId, getVmActivityCheckPath and getHeartBeatPrerequisiteError now live on the pool. Neither the monitor nor the wrapper tests for RBD any more.
  • 12. Licence header. Added.

Reasonably declined or documented

  • 1. VM HA uses only the heartbeat. Documented in KVMHAChecker, and it matches NFS. I'm fine with handling it in a follow-up. Please also say it in the PR description or the cwiki page, since RBD-only users without OOBM will rely on that path.
  • 8. Bindings stay Suggests. The EL8 argument holds. The --check-bindings gate at registration fails clearly, and I checked that it exits 1 with a clear message when the bindings are missing. One consequence is worth documenting. A host without the bindings (or with a python3-rbd too old for watchers_list) monitors no RBD pool, so as a neighbour it answers "alive" for any host it checks. KVMHostActivityChecker.checkHostStatusWithNeighbourHosts returns on the first "alive" answer, so on an RBD-only cluster that one host stops VM HA from marking any host Down. This fails safe, so it isn't blocking, but the only signal is one line in that host's agent log. I left an optional follow-up idea inline.
  • 11. composeOptionForMonHosts. Fine, since #14402 adds the callers.

Remaining

  • 7. RBD pools are still destroyed on removal (blocking). The reply says RBD and SharedMountPoint keep the delete that takes the details, but KVMStoragePoolManager.deleteStoragePool(type, uuid, details) still sends every HA-capable type, now including RBD and SharedMountPoint, to adaptor.deleteStoragePool(uuid). fe48966 doesn't touch that method. The new KVMStoragePoolManagerTest expects the intended behaviour, and 2 of its 5 tests fail against the current code (details inline). It looks like a hunk got lost in the push.

Minor

  • The PR description still describes kvmheartbeat_rbd.sh / kvmvmactivity_rbd.sh, rbd status and passing --id/--key on the command line. It would help reviewers to refresh it for the Python/librados scripts, the key in CEPH_KEY, the GUID-named objects, and the Suggests + --check-bindings behaviour.

Unit tests I ran on fe48966: KVMHACheckerTest (7), KVMHAMonitorTest (4), LibvirtCheckVMActivityOnStoragePoolCommandWrapperTest (6), LibvirtStoragePoolTest (29), LibvirtComputingResourceTest (323) and RbdUtilsTest (17) all pass. KVMStoragePoolManagerTest has 2 failures out of 5: testPoolsAreDeletedWithDetailsAsBefore and testPoolsWithHaSupportAreRemovedFromTheMonitorWhenDeleted.

Once 7 and its tests are fixed, I'm happy to approve.

if (type == StoragePoolType.NetworkFilesystem) {
if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type)) {
_haMonitor.unregisterStoragePool(uuid);
deleteStatus = adaptor.deleteStoragePool(uuid);

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.

This is still the delete without details for RBD and SharedMountPoint. LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT now contains both, so ModifyStoragePoolCommand(add=false) (LibvirtModifyStoragePoolCommandWrapper) still destroys the libvirt RBD pool and undefines its secret while VMs may be using it. fe48966 doesn't change this method. Only the HA unregister should depend on the HA type list. The delete itself should stay as it was on main, where only NFS uses the uuid-only delete:

if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type)) {
    _haMonitor.unregisterStoragePool(uuid);
}
if (type == StoragePoolType.NetworkFilesystem) {
    deleteStatus = adaptor.deleteStoragePool(uuid);
} else {
    deleteStatus = adaptor.deleteStoragePool(uuid, details);
}

@Test
public void testPoolsAreDeletedWithDetailsAsBefore() {
// the delete which takes the details does not destroy the libvirt pool, which the VMs may still use, e.g. in maintenance of the pool
for (StoragePoolType type : new StoragePoolType[] {StoragePoolType.NetworkFilesystem, StoragePoolType.RBD, StoragePoolType.SharedMountPoint}) {

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.

These tests fail against the current code (2 of 5):

  • testPoolsAreDeletedWithDetailsAsBefore fails at line 98, because RBD still goes to the unstubbed deleteStoragePool(uuid), which returns false.
  • Both delete tests verify monitor.removeStoragePool(...), but the manager calls unregisterStoragePool(...). That is the right call (no umount), so the tests should verify unregisterStoragePool (lines 88, 100 and 112).
  • NetworkFilesystem doesn't belong in this "as before" list. On main, NFS has always used deleteStoragePool(uuid), which unmounts it. Sending NFS to the details variant (a no-op default in StorageAdaptor) would leave NFS mounts behind. Please split NFS into its own case that expects deleteStoragePool(uuid).

MAX_OBJECT_SIZE = 1024 * 1024
# A heartbeat is written every <interval> seconds, but a write which starts on time can end up to TIMEOUT seconds
# later when Ceph is slow, so the heartbeat of a host which is alive can be older than the interval.
HEARTBEAT_SLACK = TIMEOUT

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.

Suggestion, not blocking. The slack covers one operation timeout, but one heartbeat write can take connect (client_mount_timeout, 30 s) plus the write (rados_osd_op_timeout, 30 s). kvmheartbeat_rbd.py takes the timestamp after the connect, and a reader only sees it once the write lands. The monitor also writes its pools one after another before it sleeps for the interval. So a live host whose Ceph ops are slow but still succeed can show an age of about interval + 60–90 s, beyond the current interval + 30. 2 * TIMEOUT (or the agent's heartbeat.update.timeout, which is what the agent kills a write at) would match the worst case of one write. This is already more forgiving than NFS, so it can be a follow-up.

// this host monitors no storage pool, e.g. its agent is not upgraded yet and does not monitor the pools of the type
// which a newer version does: it has nothing to tell about the host, which is not a dead host
logger.debug("KVMHAChecker has no storage pool to check the heart beat for {}, so it can not tell whether the host is alive.", hostAndPools);
return true;

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.

Optional follow-up, not blocking. true here becomes CheckOnHostAnswer.isAlive(), and KVMHostActivityChecker.checkHostStatusWithNeighbourHosts returns Up on the first such answer. So one neighbour that monitors no pool (for example, the one RBD host without the bindings) stops every host in the cluster from being marked Down through VM HA. That is the safe direction. CheckOnHostAnswer already has a determined flag, though. Answering "undetermined" here, and having the activity checker skip undetermined answers and ask the next neighbour, would let the other hosts still decide.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Status] KVM Host-HA using CEPH RBD

6 participants