Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/savings_plan_purchase.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import (
// regions and are not supported for Savings Plan purchases.
const spAPIRegion = "us-east-1"

// spCommitmentFormat is how the library formats HourlyCommitment for the
// CreateSavingsPlan call (savingsplans/client.go). A positive value that
// formats as "0.00" would be sent as a zero commitment, so preconditions
// reject it.
const spCommitmentFormat = "%.2f"

// savingsPlanRowKey identifies one Savings Plan purchase. Two rows with the
// same key buy the same plan twice.
func savingsPlanRowKey(rec common.Recommendation) string {
Expand Down Expand Up @@ -142,6 +148,9 @@ func validateSavingsPlanPreconditions(rec common.Recommendation) error {
if d.HourlyCommitment <= 0 {
return fmt.Errorf("%s recommendation has no positive hourly commitment (got %v)", rec.Service, d.HourlyCommitment)
}
if fmt.Sprintf(spCommitmentFormat, d.HourlyCommitment) == "0.00" {
return fmt.Errorf("%s recommendation's hourly commitment %v would be sent as 0.00; it must be at least 0.005", rec.Service, d.HourlyCommitment)
}
if rec.Service == common.ServiceSavingsPlansEC2Instance && d.Region == "" {
return fmt.Errorf("EC2 Instance Savings Plan needs a region to pick the offering")
}
Expand Down
19 changes: 19 additions & 0 deletions cmd/savings_plan_purchase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,25 @@ func TestValidatePurchasePreconditions(t *testing.T) {
}
}

// TestValidatePurchasePreconditions_RejectsCommitmentThatRoundsToZero covers
// issue #2163: the library sends the hourly commitment as "%.2f", so a positive
// value below 0.005 would reach CreateSavingsPlan as "0.00".
func TestValidatePurchasePreconditions_RejectsCommitmentThatRoundsToZero(t *testing.T) {
withCommitment := func(c float64) common.Recommendation {
rec := parserShapedSP()
rec.Details = &common.SavingsPlanDetails{PlanType: "Compute", HourlyCommitment: c}
return rec
}
for _, c := range []float64{0.001, 0.004, 0.00499} {
err := validatePurchasePreconditions(withCommitment(c), "", defaultTestAccount)
require.Error(t, err, "commitment %v rounds to 0.00 and must be rejected", c)
assert.Contains(t, err.Error(), "0.00", "commitment %v", c)
}
for _, c := range []float64{0.005, 0.01, 1.5} {
assert.NoError(t, validatePurchasePreconditions(withCommitment(c), "", defaultTestAccount), "commitment %v", c)
}
}

func TestPurchaseSingleRec_DryRunReportsPreconditionFailure(t *testing.T) {
cfg := Config{CallerAccountID: defaultTestAccount, AuditLog: filepath.Join(t.TempDir(), "audit.jsonl")}
broken := parserShapedSP()
Expand Down
Loading