Skip to content

zedkube: reconcile the longhorn disk reservation instead of latching it - #6241

Merged
eriknordmark merged 1 commit into
lf-edge:masterfrom
andrewd-zededa:eve-k-tb-rsvd-space-drift
Aug 4, 2026
Merged

zedkube: reconcile the longhorn disk reservation instead of latching it#6241
eriknordmark merged 1 commit into
lf-edge:masterfrom
andrewd-zededa:eve-k-tb-rsvd-space-drift

Conversation

@andrewd-zededa

@andrewd-zededa andrewd-zededa commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

storage.longhorn.disk.reserved.gigabytes is applied once and then never looked at again: a single in-memory flag gates the apply, and the only things that clear it are a change to the config value or a zedbox restart. Longhorn deletes and recreates its node object whenever the node leaves and rejoins the cluster, and the recreated object comes back carrying Longhorn's own default reservation of 30% of the disk. A latched apply never notices, so the EVE value silently stops being enforced. On a cluster whose disks are already near the over-provisioning budget that difference is what stops any new replica from being scheduled, and the only recovery is a manual kubectl patch.

Drop the flag and reconcile on every kubeCfgTimer tick. Steady state is one Get per interval and an Update only when a disk differs, so drift is corrected on the next tick wherever it comes from. The bool returned by SetLonghornNodeDiskReserved changes meaning from "stop retrying" to "an Update was issued", which gives zedkube something worth logging: a Notice naming the node each time it repairs the reservation. Because the reconcile now runs forever with no success latch, a persistent failure would log identically every minute, so errors are throttled to one Error on first sight and one every thirty minutes while unchanged.

Second, stop inferring tie-breaker nodes from the Longhorn Schedulable condition. That condition is not a property of the node's role: Longhorn drives it off the Kubernetes cordon, so it reads False for any cordon, including the boot-time cordon every node passes through before nodeOnBootHealthStatusWatcher uncordons it and the cordon applied during every drain. Returning "applied" for that state means an ordinary storage node that happens to be cordoned when the reconcile runs is treated as a tie-breaker and skipped. Test the controller's designation instead: EdgeNodeClusterConfig already carries TieBreakerNodeID from the EVE API, and zedkube already holds both that config and the local node UUID. The new IsTieBreakerNode fails closed, so a cluster with no designated tie-breaker treats no node as one.

PR dependencies

None

How to test and validate this PR

  • deploy one or more HV=k nodes
  • set the 'storage.longhorn.disk.reserved.gigabytes' config property to a requested value
  • 'eve enter kube' and run this while true; do kubectl -n longhorn-system get nodes.longhorn.io <nodename> -o jsonpath='{.spec.disks.*.storageReserved}{"\n"}'; date; sleep 10; done to wait for the value to set
  • in another session 'eve enter kube' and override the value to a different entry to introduce drift:
N=<nodename>; kubectl -n longhorn-system patch nodes.longhorn.io $N --type=merge -p "$(kubectl -n longhorn-system get nodes.longhorn.io $N -o json | jq -c '{spec:{disks:(.spec.disks|map_values({storageReserved:7777777777}))}}')"
node.longhorn.io/<nodename> patched
  • confirm in first terminal session that the drift value was accepted and then shortly afterwards that zedkube reset the value to the config property value.

Changelog notes

Detect and fix config drift of longhorn node storage reserved

PR Backports

  • 17.0-stable: To be backported.
  • 16.0-stable: No, as the feature is not available there.
  • 14.5-stable: No, as the feature is not available there.
  • 13.4-stable: No, as the feature is not available there.

Checklist

  • I've provided a proper description
  • I've added the proper documentation
  • I've tested my PR on amd64 device
  • I've tested my PR on arm64 device
  • I've written the test verification instructions
  • I've set the proper labels to this PR

And the last but not least:

  • I've checked the boxes above, or I've provided a good reason why I didn't
    check them.

Please, check the boxes above after submitting the PR in interactive mode.

@andrewd-zededa andrewd-zededa added the stable Should be backported to stable release(s) label Jul 28, 2026
@andrewd-zededa andrewd-zededa added the next-17.0.x-rc PR must be present in the next 17.0.x-lts release label Jul 28, 2026
@andrewd-zededa
andrewd-zededa marked this pull request as ready for review July 28, 2026 18:54
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 29.62963% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 23.30%. Comparing base (5d69266) to head (6fc3d43).
⚠️ Report is 31 commits behind head on master.

Files with missing lines Patch % Lines
pkg/pillar/cmd/zedkube/zedkube.go 0.00% 19 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6241      +/-   ##
==========================================
+ Coverage   22.93%   23.30%   +0.36%     
==========================================
  Files         510      520      +10     
  Lines       93473    95205    +1732     
==========================================
+ Hits        21440    22184     +744     
- Misses      70292    71095     +803     
- Partials     1741     1926     +185     

☔ 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.

@zedi-pramodh

Copy link
Copy Markdown

Hey @andrewd-zededa hope all is well :) Its not clear to me why we even need to set storage.longhorn.disk.reserved.gigabytes in first place ?

Comment thread pkg/pillar/types/clustertypes_test.go Outdated

@eriknordmark eriknordmark 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.

Run tests

@rene

rene commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@claude

@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Claude finished @rene's task in 2m 21s —— View job


Code review

  • Read PR diff and surrounding code
  • Analyze reconcile logic, error throttling, tie-breaker check
  • Post inline review comments

I reviewed the reconcile rewrite, the error-throttling helper, and the new IsTieBreakerNode gate. The change is sound: clusterConfig is populated before the first applyLonghornDiskReserved call (line 575 vs 592) and is kept fresh via the ENCC handler (reset to zero-value on delete → IsTieBreakerNode fails closed), the removed longhornDiskReservedSet flag has no remaining references, and SetLonghornNodeDiskReserved's new (false, nil) semantics (in-sync / not-installed / not-found) are consistent across the wrapper and the tested inner function.

Two minor points, posted inline:

  1. clustertypes_test.go:99 — a code suggestion for the codespell issue eriknordmark flagged (unparseableunparsable).
  2. zedkube.go:1030-1033 — the failure path is throttled but the success Notice is not; a persistent external re-write of the reservation would re-log every tick. Silent in the normal (drift-free) case, so this is a conscious-tradeoff nit rather than a bug.

No correctness, security, or performance blockers found. Steady-state cost is one Get per interval as intended.
· branch eve-k-tb-rsvd-space-drift

Comment thread pkg/pillar/types/clustertypes_test.go Outdated
Comment on lines +1030 to +1033
if updated {
log.Noticef("applyLonghornDiskReserved: set reserved space to %d GB on node %s",
reservedGB, z.nodeName)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The failure path is now carefully throttled (one Error, then one every 30 min), but the success Notice is not. In steady state this is silent (an Update only fires on drift), so it's fine for the normal case. However, if something outside EVE persistently re-writes the reservation back — Longhorn or another controller fighting this reconcile — every kubeCfgTimer tick would issue an Update and emit an identical Notice forever, the same spam scenario the error path was hardened against. Worth either throttling this the same way or consciously accepting that a repeated repair is a signal worth logging each time.

storage.longhorn.disk.reserved.gigabytes is applied once and then never
looked at again: a single in-memory flag gates the apply, and the only
things that clear it are a change to the config value or a zedbox
restart. Longhorn deletes and recreates its node object whenever the node
leaves and rejoins the cluster, and the recreated object comes back
carrying Longhorn's own default reservation of 30% of the disk. A latched
apply never notices, so the EVE value silently stops being enforced. On a
cluster whose disks are already near the over-provisioning budget that
difference is what stops any new replica from being scheduled, and the
only recovery is a manual kubectl patch.

Drop the flag and reconcile on every kubeCfgTimer tick. Steady state is
one Get per interval and an Update only when a disk differs, so drift is
corrected on the next tick wherever it comes from. The bool returned by
SetLonghornNodeDiskReserved changes meaning from "stop retrying" to "an
Update was issued", which gives zedkube something worth logging: a Notice
naming the node each time it repairs the reservation. Because the
reconcile now runs forever with no success latch, a persistent failure
would log identically every minute, so errors are throttled to one Error
on first sight and one every thirty minutes while unchanged.

Second, stop inferring tie-breaker nodes from the Longhorn Schedulable
condition. That condition is not a property of the node's role: Longhorn
drives it off the Kubernetes cordon, so it reads False for any cordon,
including the boot-time cordon every node passes through before
nodeOnBootHealthStatusWatcher uncordons it and the cordon applied during
every drain. Returning "applied" for that state means an ordinary storage
node that happens to be cordoned when the reconcile runs is treated as a
tie-breaker and skipped. Test the controller's designation instead:
EdgeNodeClusterConfig already carries TieBreakerNodeID from the EVE API,
and zedkube already holds both that config and the local node UUID. The
new IsTieBreakerNode fails closed, so a cluster with no designated
tie-breaker treats no node as one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrew Durbin <andrewd@zededa.com>
@andrewd-zededa
andrewd-zededa force-pushed the eve-k-tb-rsvd-space-drift branch from b5e1ba3 to 6fc3d43 Compare August 3, 2026 21:08
@github-actions
github-actions Bot requested a review from eriknordmark August 3, 2026 21:08
@andrewd-zededa

Copy link
Copy Markdown
Contributor Author

Hey @andrewd-zededa hope all is well :) Its not clear to me why we even need to set storage.longhorn.disk.reserved.gigabytes in first place ?

Hi @zedi-pramodh, the default reserved space configured by longhorn leads to 30% of the persist vault disk unusable for scheduling replicas. This setting allows for per cluster configuring, allowing for multiple storage users of vault.

@eriknordmark
eriknordmark merged commit 992c28d into lf-edge:master Aug 4, 2026
50 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next-17.0.x-rc PR must be present in the next 17.0.x-lts release stable Should be backported to stable release(s)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants