Repository navigation
fix(azure): attribute shared-scope vm reservations to their billing subscription - #423
Conversation
…ubscription
CUDly purchases VM reservations with appliedScopeType=Shared and
billingScopeId=/subscriptions/{sub}, but the strict reservation inventory
rejected every non-Single reservation, so GetExistingCommitments failed for
the whole subscription after any CUDly purchase and the duplicate-purchase
guard stopped working.
A Shared reservation is now attributed to the subscription its
billingScopeId names: the purchasing subscription counts it, any other
subscription skips it with a logged warning (so one Shared quantity is never
credited twice). A Shared reservation without a /subscriptions/{sub} billing
scope, and any unknown scope type, still fails closed.
Closes LeanerCloud#293
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review verdict: CLEAN at
Still needed before merge: CI has not run because the fork workflows are awaiting maintainer approval. The PR is a draft, labels need to be mirrored from #293, and CodeRabbit has not reviewed it yet (draft). |
|
@IAmJSD The independent review is CLEAN at |
|
Done: I merged 🤖 Generated with Claude Code |
|
@IAmJSD Thanks for the update. CI is green, the diff is identical to the reviewed one, and the local checks pass. While CI was running, |
|
@IAmJSD Still fully gated here: CLEAN verdict, CI green, diff identical to the reviewed one. The only blocker is that main has moved since your last update, so the PR shows BEHIND. Could you merge |
|
Merged |
|
@IAmJSD Under the strict up-to-date ruleset only one PR can land per update, and main moves several times an hour, so your branches keep going BEHIND. Would you be OK with the maintainers using GitHub's Update branch button (a merge of |
|
@cristim Yes, that's fine. Maintainers can use Update branch (a merge of |
|
Merge gate record at
Squash-merging with |
Summary
CUDly buys Azure VM reservations with
appliedScopeType: "Shared"andbillingScopeId: "/subscriptions/{sub}"(providers/azure/services/compute/client.go). The strict reservation inventory (validateReservationScopeinproviders/azure/services/internal/reservations/strict.go) returned an error for every reservation whose scope was notSingle. As a result, after any CUDly VM RI purchase,GetExistingCommitmentsfailed for the whole subscription and the duplicate-purchase guard stopped working.Fix (attribution policy)
A Shared reservation from the applied-reservation list now belongs to the subscription named in its
billingScopeId, which is the subscription that bought it. This matches howproviders/azure/services/savingsplansattributes Shared savings plans (planOwnedBy)./subscriptions/{other}: skipped with alog.Printfwarning instead of failing the listing. This also prevents one Shared quantity from being credited to every subscription in the billing scope, which was the reason the earlier fail-closed rule was added (see 7998b2d).billingScopeIdmissing, null, blank, or not exactly/subscriptions/{sub}: still fails closed. The purchaser is unknown, and the reservation could be one bought an hour ago.SharedandSingle; I checkedconstants.goin the module cache. The SDK documentsProperties.BillingScopeIDas "Subscription that will be charged for purchasing Reservation".Scope: the change is limited to the inventory attribution in
strict.goplus tests. It does not touch the purchase or dedupe code (purchase.go/dedupe.go) that #290 covers.Closes #293
Verification (unit/fixture level)
All tests are hermetic. The SDK-level tests use the real armreservations clients with a stub HTTP transport and JSON fixtures, run through the public
GetExistingCommitmentsand the realrecfilter.DuplicateChecker. No live Azure tenant was used. Not verified: that real Azure responses for CUDly-bought Shared reservations populatebillingScopeIdas/subscriptions/{sub}(this follows the SDK docs and our own purchase body), and how EA/MCA billing-account-scoped Shared reservations look in practice. Those still fail closed.New or updated tests:
TestGetExistingCommitmentsSDKSharedVMAttribution(compute) replacesTestGetExistingCommitmentsSDKSharedVMFailsClosed. It covers purchaser counts (including case-insensitive match), other subscription skips, and fail-closed for missing, null, non-subscription and resource-group billing scopes.TestStrictCommitment_SharedScopeAttributedToBillingSubscription(reservations).TestStrictCommitment_ErrorTextUsesLabelswas updated for the new Shared error text, plus an unknown-scope-type case.Fail before the fix (new tests applied to the old
strict.go):Pass after the fix:
Other commands run (go1.26.9, darwin/arm64):
make build: passed for all five modules.make vet: passed.(cd <module> && go test -race -short ./...)for all five modules: pkg, azure, gcp and ci_cd_sanity_tests passed.providers/awshas 5 failingTestAWSProvider_*tests ("AWS is not configured"). They fail the same way on unmodified main on this machine, so the cause is the local environment, not this change.golangci-lint run --timeout=5m ./...inproviders/azurewith the pinned v2.10.1: 0 issues.Labels
Issue #293 carries
triaged,urgency/this-sprint,priority/p1,severity/high,impact/few,effort/mandtype/bug. A maintainer should mirror them onto this PR because the author lacks label permissions.🤖 Generated with Claude Code
Summary by CodeRabbit