2 Commits (006ce3568b57733b347bf3c4e3e45e15830273ae)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
5b63fdb86c |
Deliver the storage a customer actually buys
A booked storage pack was a row in `subscription_addons` and nothing else: priced, frozen, charged every month, and delivered nowhere. Nextcloud's quota came from `instances.quota_gb`, which is the package alone; the disk was sized for the package alone; and on the one occasion a disk did grow — a plan upgrade — the guest never saw it, because nothing stretched the partition or the filesystem over the new space. One authority for the allowance. StorageAllowance adds the package and the booked packs, and everything that needs the figure asks it: the quota step, the downgrade check, the portal, the console, the repair command. It is DERIVED rather than stored — a column would have to be rewritten by booking, cancelling, granting, a grant expiring and a plan change, and the day one of those paths forgot it would be silently wrong in whichever direction costs somebody money. `quota_applied_gb` keeps its own meaning: what the guest was last actually told, which is how a machine with the allowance enforced is told from one where the figure has only ever been a row in our database. Buying it delivers it. BookAddon asks ApplyStorageAllowance for a run on booking AND on cancellation; the new `storage` pipeline grows the virtual disk to the allowance plus the package's own overhead (read off the package — 20/40/50/100 GB on the four catalogue packages — never a ratio invented here), then GrowGuestFilesystem makes the guest see it, then the quota is applied. No cold boot is involved: the data disk is scsi0 and Proxmox's resize on a running guest is a qemu block_resize, so the capacity reaches the guest while it runs. The new step rescans, growparts and grows the filesystem with the tool its type actually needs — ext2/3/4, xfs, btrfs — and fails loudly on one it does not know rather than handing it to resize2fs and hoping. Idempotent end to end: NOCHANGE from growpart is not a failure, and every tool here exits 0 when there is nothing left to grow. The same step now runs in the plan-change pipeline, where an upgrade used to stop one step short of the customer. A downgrade blocked by data gets a way out. The block stays — it is correct — but the check now measures against the target package PLUS the packs the customer already owns, and reports the numbers behind the refusal: what is stored, what would be allowed, how much has to go, and how many packs would cover it instead. The portal offers both routes: book exactly those packs (confirmed in a modal, R23), or delete data and have the fill level measured on demand rather than waiting for the nightly sampler. That reading is taken by DiskUsageProbe, which CollectInstanceTraffic now uses too, so there is one notion of "how full is it" and not two. A plan change keeps booked packs. They were paid for separately and have nothing to do with which package the customer is on; the new package's disk and quota are sized with them included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
34874adec3 |
Apply a bought plan change instead of only pricing it
tests / pest (push) Failing after 8m6s
Details
tests / assets (push) Successful in 21s
Details
tests / release (push) Has been skipped
Details
PlanChange could say what a move would cost and whether it was allowed, and that was all it could do. Billing::purchase() wrote an upgrade order and nothing ever consumed it: same snapshot, same machine, same quota. A customer could pay for a bigger package and receive nothing. ApplyPlanChange is now the single place a change lands — it moves the contract onto the target's current version, writes one register row, settles the custom domain, and starts a run that resizes the machine. Applying the same order twice is a no-op, enforced by a unique event key rather than by a check two callers could both pass. Two things a plan change must not do, and now does not. A disk is never shrunk — Proxmox cannot, so the QUOTA shrinks instead, which is what was sold anyway. And a live machine is never rebooted as a side effect: cores and RAM are written, and where they need a restart the instance says so where an operator and the customer can both see it. The storage allowance also joins the build pipeline. ApplyStorageQuota was written for plan changes and ran only there, so a brand-new customer still got no quota at all — quota_gb reached the instance row and stopped, and every package delivered the whole disk. The test guarding that pipeline only counted its steps, which is how a list missing the one step that makes a package's storage real stayed green for its whole life. It now names the step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |