Skip to content

fix(aws): payment-option/engine/term correctness gaps in AWS RI service clients - #1075

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/aws-provider-correctness
Jun 7, 2026
Merged

fix(aws): payment-option/engine/term correctness gaps in AWS RI service clients#1075
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/aws-provider-correctness

Conversation

@cristim

@cristim cristim commented Jun 7, 2026

Copy link
Copy Markdown
Member

What

Fixes two untracked HIGH-severity correctness gaps on the AWS RI purchase /
money-moving path, from the adversarial review in
docs/code-review/08-provider-aws.md. These were not captured by the existing
tracking issues (#1012-#1035 filed only 08-C1 and 08-H1).

08-H2 — Redshift offering selection ignored the requested payment option

providers/aws/services/redshift/client.go

scanRedshiftOfferingPage matched only node type + duration and accepted any
Regular/Upgradable offering, discarding the requested payment option. A
no-upfront recommendation could be filled with an all-upfront offering (or
vice versa), committing the buyer to terms they never approved and breaking the
OnDemand/savings math in the UI.

Redshift encodes the payment option through the offering's FixedPrice (upfront)
and recurring charge, not the Regular/Upgradable enum. The scan now matches
the offering's price shape against the requested option:

  • all-upfront: upfront > 0, recurring == 0
  • no-upfront: upfront == 0, recurring > 0
  • partial-upfront: upfront > 0, recurring > 0

An empty/unknown option matches nothing, so a malformed recommendation errors
with "no offerings found" rather than buying on an arbitrary option.
GetOfferingDetails now reports the derived payment option so callers can
reconcile the bought terms.

08-H4 — Per-service throttle could swallow a fully-throttled service as "no recs"

providers/aws/recommendations/client.go

GetAllRecommendations tolerated per-service failures and merged survivors, but
when EVERY service errored (a sustained Cost Explorer throttle exhausting each
service's per-combo retries) mergeServiceResults logged each failure at WARN
and returned an empty slice with a nil error. A throttled run was then
indistinguishable from "no savings available".

mergeServiceResults now returns ([]Recommendation, error) and yields an
error only when all services failed, still tolerating partial failure. The
exported GetAllRecommendations signature is unchanged (no pkg/provider
interface or scheduler churn); the scheduler already propagates a non-nil error,
so a totally-failed run aborts loudly instead of recording empty-as-success.

Scope note

  • 08-H3 (ElastiCache unknown-payment-option default) is already fixed on the
    base branch
    (convertPaymentOption returns (string, error)), so it is not
    re-implemented here.
  • The Medium/Low/Nit AWS findings (08-M2..M7, 08-L*, 08-N*) and 08-C1 (shared
    RateLimiter race) are deferred to keep this PR on the HIGH money-path fixes and
    under the ~400-line review budget, per 16-remaining-findings-plan.md.

Tests

Regression tests that fail pre-fix:

  • TestFindOfferingID_PaymentOptionMustMatch (08-H2): asserts the offering
    matching the requested payment option is selected and that a mismatch errors;
    verified to FAIL against the pre-fix scan (returned the wrong-payment offering).
  • TestMatchesPaymentOption, TestDerivePaymentOption (08-H2).
  • TestMergeServiceResults_AllFailIsError (08-H4): all-fail -> error,
    partial-fail -> survivors + nil error, empty-success -> nil error.

Verification

  • go build ./... (aws module + repo root): success.
  • go vet ./services/redshift/... ./recommendations/...: no issues.
  • go test ./services/redshift/... ./recommendations/...: 368 pass.
  • New tests are race-clean under go test -race. The package's pre-existing
    -race finding is 08-C1 (shared RateLimiter in the concurrent
    GetAllRecommendations path), out of scope here.
  • One pre-existing, unrelated compile break in
    providers/aws/services/savingsplans/client_test.go:991 (a 2-arg
    findOfferingID call against a now-3-arg signature) exists on the base branch;
    that file is the FOLD-1039 bucket, untouched here.

Closes #1052

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved error handling and reporting when AWS service queries fail, ensuring failures are no longer silently ignored
    • Enhanced accuracy of reserved node offering selection by validating pricing structure alignment with requested payment options
  • Tests

    • Added comprehensive test coverage for service failure scenarios and payment option matching validation

cristim added 2 commits June 7, 2026 04:22
…hape (08-H2)

Redshift offering selection matched only node type and duration and accepted
any Regular/Upgradable offering, discarding the requested payment option. A
no-upfront recommendation could therefore be filled with an all-upfront
offering (or vice versa), buying on payment terms the operator never approved
and breaking the OnDemand/savings math shown in the UI.

Redshift does not encode the payment option in the Regular/Upgradable
ReservedNodeOfferingType enum; it is expressed through the offering's FixedPrice
(upfront) and recurring charge. scanRedshiftOfferingPage now matches the
offering's price shape against the requested option (all-upfront = upfront only,
no-upfront = recurring only, partial-upfront = both) and skips offerings that do
not match. An empty/unknown requested option matches nothing, so a malformed
recommendation errors with "no offerings found" instead of buying arbitrarily.

GetOfferingDetails now reports the derived payment option (the price shape)
instead of the Regular/Upgradable enum, so callers can reconcile the bought
terms. matchesOfferingType drops its ignored payment-option parameter (the
enum-validity check is now orthogonal to payment matching).

Regression tests: TestFindOfferingID_PaymentOptionMustMatch (picks the offering
matching the requested option, errors when none matches; fails pre-fix by
returning the wrong-payment offering), TestMatchesPaymentOption,
TestDerivePaymentOption. Existing fixtures updated to carry the price shape
matching their declared payment option.
…(08-H4)

GetAllRecommendations tolerated per-service failures and merged the survivors,
but when EVERY service errored (e.g. a sustained Cost Explorer throttle that
exhausts each service's per-combo retries) mergeServiceResults logged each
failure at WARN and returned an empty slice with a nil error. A fully-throttled
run was then indistinguishable from "no savings available", which an operator
can misread as "nothing to buy".

mergeServiceResults now returns ([]Recommendation, error) and yields a wrapped
error only when all services failed, while still tolerating partial failure
(nil error plus the surviving services' recs). The signature of the exported
GetAllRecommendations is unchanged, so the pkg/provider interface and the
scheduler callers are unaffected; the scheduler already propagates a non-nil
error, so a totally-failed run now aborts loudly instead of recording an empty
result as success.

Regression test TestMergeServiceResults_AllFailIsError pins the new contract
(all-fail -> error, partial-fail -> survivors with nil error, no-fail empty ->
nil error). Tested at the merge function directly rather than through
GetAllRecommendations because the six concurrent goroutines share one
*RateLimiter (the out-of-scope 08-C1 race).
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/m Days type/bug Defect labels Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54cc79e3-7b00-4a09-a7b8-241dc0d239d1

📥 Commits

Reviewing files that changed from the base of the PR and between cbdc4be and 16b9d74.

📒 Files selected for processing (4)
  • providers/aws/recommendations/client.go
  • providers/aws/recommendations/client_test.go
  • providers/aws/services/redshift/client.go
  • providers/aws/services/redshift/client_test.go

📝 Walkthrough

Walkthrough

This PR fixes two HIGH-severity money-path correctness gaps in AWS RI clients. The recommendations client now distinguishes "fully failed services" from "no recommendations available" instead of silently dropping all results when throttled. Redshift offering selection validates payment options by matching offering price shapes (FixedPrice and RecurringCharges) instead of discarding the payment-option parameter, and surfaces the matched payment option in GetOfferingDetails.

Changes

Recommendations client: error propagation for fully-failed services

Layer / File(s) Summary
mergeServiceResults error detection and propagation
providers/aws/recommendations/client.go
mergeServiceResults signature changes to return ([]Recommendation, error), adds failure counting, and implements final decision: returns (nil, wrapped error) when all services fail, otherwise returns accumulated recommendations with nil error. GetAllRecommendations now propagates this result directly instead of unconditionally returning (recs, nil).
Test coverage for failure detection
providers/aws/recommendations/client_test.go
TestMergeServiceResults_AllFailIsError validates that mergeServiceResults surfaces an error when every service result fails, while preserving prior behavior where partial failures still return recommendations and fully successful inputs return empty recommendations with nil error.

Redshift: payment option matching by price shape instead of offering type

Layer / File(s) Summary
Payment option matching helpers and scanRedshiftOfferingPage integration
providers/aws/services/redshift/client.go
Introduces offeringRecurringRate (extracts hourly recurring amount) and matchesPaymentOption (classifies offering as all-upfront/no-upfront/partial-upfront by price shape). Updates scanRedshiftOfferingPage to validate payment option via matchesPaymentOption in addition to offering type, rejecting offerings whose price shape does not match the requested payment option.
Payment option validation and derivation
providers/aws/services/redshift/client.go
Updates matchesOfferingType to validate only ReservedNodeOfferingType identifiers (Regular/Upgradable) independently of payment option. Adds derivePaymentOption to infer canonical CUDly payment option from offering's upfront and recurring components, returning "unknown" for unclassifiable shapes.
GetOfferingDetails uses price-shape-derived payment option
providers/aws/services/redshift/client.go
GetOfferingDetails now derives details.PaymentOption via derivePaymentOption(offering) instead of using the ReservedNodeOfferingType enum, ensuring the reported payment option matches the offering's actual price shape.
Test fixtures and coverage for payment option validation
providers/aws/services/redshift/client_test.go
Updates offering fixtures across existing tests to populate FixedPrice and RecurringCharges matching the target payment option shapes. Refactors TestClient_MatchesOfferingType to test offering-type validation independently. Adds payment-option test helpers (rsPaymentRec, rsOffering) and three new test functions validating offering selection by payment option, price-shape matching, and payment option inference.

Possibly related PRs

  • LeanerCloud/CUDly#690: Modifies AWS Redshift reserved-offering discovery/matching logic and tightens payment-option validation with test updates to that selection logic.
  • LeanerCloud/CUDly#269: Modifies providers/aws/recommendations/client.go around GetAllRecommendations and mergeServiceResults to change how per-service failures are handled and merged.
  • LeanerCloud/CUDly#195: Adjusts GetAllRecommendations to handle context cancellation after GetRecommendationsForService.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit bounds through AWS clouds ☁️
Matching prices, no longer shrouded
When throttles strike, errors ring clear
And payment shapes finally cohere! 🐰💰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the primary change: fixing payment-option correctness gaps in AWS RI service clients (Redshift and Recommendations).
Linked Issues check ✅ Passed PR implements the two priority coding fixes from #1052: 08-H2 (Redshift payment-option matching via price shape) and 08-H4 (Recommendations error handling for total failures).
Out of Scope Changes check ✅ Passed All changes are scoped to the two documented HIGH-severity fixes; 08-H3 ElastiCache was intentionally deferred as already fixed on base branch.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/aws-provider-correctness

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim

cristim commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
cristim merged commit 0ffce46 into feat/multicloud-web-frontend Jun 7, 2026
4 checks passed
cristim added a commit that referenced this pull request Jun 7, 2026
…ilent defaults (#1085)

* fix(aws/savingsplans): error on unknown payment-option/term, no silent default

convertPaymentOption and convertTermToSeconds previously logged a warning
and silently substituted All Upfront (largest cash outlay) or 1 year when
given an unrecognized or empty value, risking a mis-purchase on any typo or
new/renamed AWS enum value.

Both functions now return an explicit error on unrecognized input so the
purchase and offering-lookup paths fail loud instead of buying the wrong
product. Mirrors the pattern already used by the RDS convertPaymentOption.

Regression tests for H1 (unknown/empty payment option) and H2 (unknown/empty
term) are added to TestClient_FindOfferingID_AllPaymentOptions and
TestClient_FindOfferingID_TermVariations; the previously-passing "default term"
case is updated to assert the error path.

Fixes H1 and H2 from docs/code-review/19-hardcoded-fallbacks-aws.md (PR #1039).

* fix(aws): fail loud on unknown payment-option/term/engine

Fixes H3, H4, M1-M6, L1, L2 from the 19-hardcoded-fallbacks-aws.md
audit plus H1/H2 from the now-merged #1039 branch (savingsplans).

H3 (converters.go): add convertPaymentOptionE/convertTermInYearsE/
convertLookbackPeriodE that error on unrecognized input; wire into the
SP recommendation path (parser_sp.go). The legacy non-erroring wrappers
are retained for the RI path in client.go (owned by #865/#1075) with a
deprecation comment and a follow-up reference.

H4 (elasticache/client.go): convertPaymentOption now returns (string, error)
mirroring the RDS sibling that already errors correctly.

H1/H2 (savingsplans/client.go): convertPaymentOption and
convertTermToSeconds now return (type, error); cherry-picked from the
merged #1039 branch where the fix was staged.

M4 (parser_services.go, rds/client.go): parseRDSDetails no longer
silently defaults AZConfig to "single-az" when CE omits DeploymentOption;
findOfferingID rejects empty AZConfig with an explicit error.

M5 (parser_services.go): resolveEC2Tenancy now maps only the known CE
values ("shared"/nil -> default, "dedicated" -> dedicated); any other
value (e.g. "host" for Dedicated Hosts) returns an error rather than
silently collapsing to default tenancy.

M6 (rds/client.go): normalizeEngineName now returns (string, error) and
errors for ambiguous Oracle ("oracle*"), SQL Server ("sqlserver*"/"sql-server*"),
and bare Aurora inputs; unambiguous engines (mysql, postgresql, mariadb,
aurora-mysql, aurora-postgresql) pass through unchanged.

M1/M2/M3 (ec2/client.go): replace raw "convertible" literals with
types.OfferingClassTypeConvertible; buildEC2OfferingQuery errors on empty
Platform (purchase path); centralize "Linux/UNIX" literal into
defaultEC2Platform constant (exchange-path helpers retain their safe
fallback; the purchase path does not).

Regression tests added for H3, H1/H2, H4, M2, M4, M5, M6, L1, L2; each
fails pre-fix and passes post-fix.

Closes #1084

* test(aws/rds): clarify ambiguous-engine regression test

Simplify TestNormalizeEngineName_AmbiguousErrors: remove the confusing
oracle-se2 case (strings.Contains("oracle-se2","oracle") is true, so it
errors like all other oracle inputs -- no special case needed) and remove
the misleading inline comment. All remaining cases assert that any Oracle,
SQL Server, or bare Aurora input errors, which is the invariant under test.

* refactor(aws): extract helpers to fix gocyclo violations in rds and savingsplans

gocyclo -over 10 fails on two functions added by the fail-loud refactor:
- savingsplans.(*Client).findOfferingID (complexity 12): extract
  resolveSPPlanType and buildSPOfferingsInput helpers
- rds.(*Client).paginateRDSOfferings (complexity 11): extract
  fetchRDSOfferingPage to isolate the per-page API call and scan

Also apply gofmt to rds/client.go (trailing whitespace / brace style).
Behaviour and error paths are unchanged; all rds and savingsplans
tests still pass.

* fix(aws/rds): tighten AZ-config and engine-name validation (CR #1085)

Three related fail-loud fixes in the AWS RDS purchase path:

1. parseRDSDetails: unknown DeploymentOption now errors instead of
   silently folding into "single-az". Only "Multi-AZ" and "Single-AZ"
   are accepted; any other non-nil value (e.g. "Multi-AZ-Readable-Standbys")
   returns an explicit error so the bad token surfaces rather than
   driving findOfferingID to the wrong RI class.

2. findOfferingID: AZConfig validation now uses a switch with an
   explicit default error case rather than only checking for empty string.
   A non-empty but invalid value (typo, future AWS enum addition) would
   previously fall through to multiAZ==false in paginateRDSOfferings,
   silently treating the bad input as single-AZ.

3. normalizeEngineName: Oracle and SQL Server checks changed from
   strings.Contains to == so that edition-qualified tokens (oracle-se2,
   oracle-ee, sqlserver-se, sqlserver-web, etc.) pass through as valid
   RDS ProductDescription values. The previous Contains check contradicted
   the error message itself, which told callers to supply those exact
   edition tokens.

Regression tests added for all three: each new test was verified to
fail against the pre-fix code and pass after.
@cristim
cristim deleted the fix/aws-provider-correctness branch July 27, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant