Repository navigation
fix(rds): deduct expiring RIs from family coverage in target-coverage sizing - #2175
Conversation
… sizing applyCoverageAndOverrides adjusted per-rec coverage for RIs expiring inside --rebuy-window-days but passed the original coverage map to the RDS family-NU pass, which dropped the family at gap <= 0. Call ApplyFamilyNUSizingRDSWithExpiry with the existing commitments and the rebuy window, and log FamilyExpiryReport.Unusable so an expiring RI that could not be deducted is never read as "no expiry". RDS counts and costs deliberately increase in the expiring-RI case. Non-RDS and EC2 paths and the nil-commitments caller are unchanged. Add an rds-expiry mode to the recorded-HTTP completeness proxy with command-level scenarios (already-at-target, below-target, large RI vs xlarge usage, multi-size with linked accounts, partial inventory, unusable RIs) and labeled controls. Closes #2132 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Independent merge-gate review (Opus 5.5,
|
| scenario | parent (cmd/multi_service_helpers.go from 1cd8507) |
this PR |
|---|---|---|
| re-run, partial inventory | 0 selected | Count 3 (db.r6g.large, upfront 300.00), log "deducting 16 normalized units" |
| re-run, full inventory (same plus the 12 other-account RIs listed) | 0 | 0 (correct) |
| replacement payment-pending under 24h, partial | 0 | family sized 4, then checkDuplicates "4 instances → 0" (safe only inside --idempotency-window) |
A daily scheduled run with --rebuy-window-days rebuys like this on each run until the coverage lookback catches up. RDS did not do this before this PR.
Suggested direction (design call for the plan owner; the fix belongs in cloud-commitments-go adjustFamilyCoverageForExpiry, followed by a pin bump): credit visible active, non-expiring RIs in the family that started recently as replacements already bought in the C - E branch. For example, max(0, max(min(C, C - E + R), min(C, A - E))), where R is the NU of those RIs. With that change the re-run case buys 0 and the existing partial-inventory case still buys 4. Add the re-run case as a regression. The EC2 per-pool path (adjustCoverageForExpiry) has the same re-run property even with full inventory. That behaviour already ships, so it should be a separate follow-up issue and not part of this PR.
Verified clean
- Call site (
multi_service_helpers.go:651-654): passescoverageMap,cfg.TargetCoverage, the realGetExistingCommitmentslist andcfg.RebuyWindowDays. The library deducts from a fresh family aggregate, so the per-rec adjusted values are not reused, andannotateFamilyProjectionoverwrites them. Multi-size and linked-account rows give 2/4/2, so expiry is not deducted twice. The legacy caller at:474passes nil commitments, which makes it identical toApplyFamilyNUSizingRDS(the go#341 golden test covers this). Non-RDS recs pass throughrestunchanged, and the EC2TestReservationExpiryCommandpasses. - Regression tests reproduced (parent helper restored in a scratch copy): 6 of 8 fail and both controls pass.
- already-at-target, large-ri-xlarge-usage, partial-inventory: "0 recommendations selected (0 via family-NU)", no CSV written.
- below-target: expected "10", actual "8".
- multi-size-linked-accounts: expected "2", actual "1".
- unusable-expiring-warned: logs do not contain "2 RDS reserved instances expiring within 30 days could not be applied to family sizing".
- All 8 pass at this head.
- Other over-purchase probes (scratch, at this head):
- Underutilized full inventory (14 large owned, 10 running, 4 expiring): 0.
- payment-pending replacement in full inventory: 0.
- Retired RI: 0.
- 100% coverage with a partial inventory: capped at 80%, buys 0.
- Expiring NU greater than covered NU: floors at 0 and buys 16 (correct).
- No purchase reachable: the proxy fails on any unexpected operation and asserts that no
PurchaseReservedDBInstancesOfferingcall was made. The runs are dry runs. Account IDs are built at runtime withstrings.Repeat; there are no 12-digit literals in the new file. - Unusable warning: the text is accurate and goes through AppLogger, which is the same path in dry and real runs. It does not appear in the CSV or in the end-of-run summary (non-blocking).
- PR body: says
Closes #2132, states the deliberate RDS count and cost increase, and keeps cli#2131 and cli#2174 out of scope. - Build, vet and lint:
go build,go vet,go test -p 2 ./...and golangci-lint all pass at this head (lint: 0 issues).git ls-files -z | xargs -0 git secrets --scanwith--register-awsis clean under BSD grep and GNU grep 3.12. - CI and labels: CI is green at this SHA (CI Success, pre-commit, unit, integration) and the PR is CLEAN. Labels match cli#2132.
Non-blocking
- The log line "deducting N normalized units" prints the expiring NU, not the amount actually deducted. With the underutilized full inventory it reads 16 when 0 was deducted. Tracked in go#342.
- Mutation run (8 mutants on changed lines): 6 killed, 2 survived.
Unusable > 1survives because the only fixture uses 2. Disabling the ExpiringNU log line also survives because that line is not asserted.
Bump cloud-commitments-go providers/aws and pkg to 218912bb (go#347) and call ApplyFamilyNUSizingRDSWithExpiryOptions with CoverageLookbackDays set to the same value given to GetRICoverageMap (coverageLookbackDays), so RIs bought inside the lagged coverage window are not bought again on a re-run. The log line now reports the expiring and credited replacement NU instead of claiming a deduction. Adds the gate F1 re-run scenario and an outside-lookback control to TestRDSFamilyExpiryCommand. Adapts the archera client to the opaque insurance.Secret key and its tests to pkg no longer echoing rejected values in decode errors (both come with the pkg bump). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Pushed f4eb6a0: merged origin/main (already up to date), bumped go pins to 218912bb, call site now uses ApplyFamilyNUSizingRDSWithExpiryOptions with the same lookback as GetRICoverageMap, added rerun-replacement-bought (fails on 9e3f9eb with count 3, passes now) and control-replacement-outside-lookback, log line reports expiring and credited NU. Archera client/tests adapted to the pkg bump. Unusable warning in the end-of-run summary left as follow-up. Ready for re-review. |
Independent merge-gate review (Opus 5.5,
|
Closes #2132
What
applyCoverageAndOverridesadjusted per-rec coverage for RIs expiring inside--rebuy-window-days, then handed the original coverage map to the RDS family-NU pass, so a family at target was dropped before the rebuy window could matter. It now callsApplyFamilyNUSizingRDSWithExpiryOptions(cloud-commitments-go#341 plus #347, pin bumped in #2173 and again here) with the existing commitments, the window, andCoverageLookbackDays= the samecoverageLookbackDays(cfg)given toGetRICoverageMap. Replacement RIs bought inside the lagged coverage window are credited, so a daily re-run does not buy them again (gate F1).FamilyExpiryReport.Unusableis logged as a warning ("N RDS reserved instances expiring within D days could not be applied to family sizing ... their expiry was NOT deducted"); the log line reports the expiring NU and the credited replacement NU (it no longer claims a net deduction; exact net is go#342).RDS purchase counts and costs deliberately increase in the expiring-RI case (the family is sized against post-expiry coverage). No purchase flag or execution path changes.
Callers:
fetchAndFilterRegionRecspasses the real inventory. The legacy test-only per-region path (multi_service_helpers.go:472) keepsnilcommitments, which makes the new call identical toApplyFamilyNUSizingRDS; documented on both functions. Non-RDS and EC2 paths are untouched. The deduction is capped at the expiring NU because the CLI inventory is single-account (#2131, out of scope here); CSV AZ (#2174) is also separate.Tests
Extends the recorded-HTTP
completenessProxyharness (newrds-expirymode, dry run, no purchase op allowed) withTestRDSFamilyExpiryCommand: already-at-target, below-target (exact count and cost), large RI vs xlarge usage, multi-size with 3 linked-account rows (exact count, no double deduction), partial inventory (deduction capped at expiring), unusable RIs warned, re-run after the replacements were bought (lagged coverage 82.5%, 4 expiring, 4 replacements bought 2 days ago, 12 other-account large not in the inventory: 0 purchased); controls: replacement outside the lookback (3), outside-window, engine isolation. The non-RDS control is the existing EC2TestReservationExpiryCommand. Fixtures are synthetic, account IDs built at runtime.Fails before the fix (same tests,
cmd/multi_service_helpers.goreverted to the parent):Local:
go test ./cmdfull pass, golangci-lint 0 issues,pre-commiton changed files andgit secrets --scanclean.Go bump in this update (218912bb, on top of #2173's 2c1e87b)
ApplyFamilyNUSizingRDSWithExpiryOptions,ReplacementNU).SavingsPlanDetails.Currencyfield in pkg and Azure Savings Plan currency requirement; the CLI Azure pin is unchanged, no CLI behavior change.insurance.SecretAPI key feat(cli): size purchases by target RI/SP utilization (--target-utilization) #338, decode errors no longer echo vendor values fix(email): unbreak factory_test.go after #333 merge glitch #337): archera client now wraps the key withinsurance.NewSecret, and the decode-path archera tests assert that the key never appears instead of the withheld marker.Follow-up (not in this PR): surface the Unusable warning in the end-of-run summary.
🤖 Generated with Claude Code
Summary by CodeRabbit
Gate addendum: full pkg range in this bump (30bf383 -> 218912b, verified with
git log)providers/aws@218912b requires pkg@3838efd, so MVS forces the whole pkg range below; it cannot be split from the RDS fix. Commits the list above did not name:
archera-comparison: anARCHERA_API_KEYcontaining a non-printable or non-ASCII byte is now rejected before any request ("archera API key contains a non-printable or non-ASCII character at byte N"; the key is not echoed). Retry-After values past the int range now clamp to the 24h cap.