Skip to content
Open
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
35 changes: 35 additions & 0 deletions providers/gcp/internal/skumatch/paid_rate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package skumatch

import (
"testing"

"github.com/stretchr/testify/assert"
"google.golang.org/api/cloudbilling/v1"
)

func tier(units, nanos int64) *cloudbilling.TierRate {
return &cloudbilling.TierRate{UnitPrice: &cloudbilling.Money{CurrencyCode: "USD", Units: units, Nanos: nanos}}
}

func TestPaidRate(t *testing.T) {
free, paid, whole := tier(0, 0), tier(0, 50000000), tier(2, 0)
tests := []struct {
name string
expr *cloudbilling.PricingExpression
want *cloudbilling.TierRate
}{
{"nil expression", nil, nil},
{"no tiers", &cloudbilling.PricingExpression{}, nil},
{"single paid tier", &cloudbilling.PricingExpression{TieredRates: []*cloudbilling.TierRate{paid}}, paid},
{"free first tier then paid", &cloudbilling.PricingExpression{TieredRates: []*cloudbilling.TierRate{free, paid}}, paid},
{"first paid tier wins over later ones", &cloudbilling.PricingExpression{TieredRates: []*cloudbilling.TierRate{free, paid, whole}}, paid},
{"whole-unit price counts as paid", &cloudbilling.PricingExpression{TieredRates: []*cloudbilling.TierRate{free, whole}}, whole},
{"nil tier and nil price are skipped", &cloudbilling.PricingExpression{TieredRates: []*cloudbilling.TierRate{nil, {}, paid}}, paid},
{"all tiers free", &cloudbilling.PricingExpression{TieredRates: []*cloudbilling.TierRate{free, tier(0, 0)}}, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Same(t, tt.want, PaidRate(tt.expr))
})
}
}
18 changes: 18 additions & 0 deletions providers/gcp/internal/skumatch/skumatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,21 @@ func Slot(sku *cloudbilling.Sku) (PriceSlot, error) {
}
return 0, fmt.Errorf("sku %s has unrecognized usage type %q", sku.SkuId, sku.Category.UsageType)
}

// PaidRate returns the first tier whose unit price is non-zero, or nil when
// every tier is free or has no price. The catalog lists tiers by ascending
// StartUsageAmount, and a free allowance shows up as a leading zero-price
// tier; the first non-zero tier is the marginal rate a workload sized for a
// commitment pays, so it is the one to price from. Reading only the first
// tier would treat a [free, paid] SKU as free and drop it.
func PaidRate(expr *cloudbilling.PricingExpression) *cloudbilling.TierRate {
if expr == nil {
return nil
}
for _, rate := range expr.TieredRates {
if rate != nil && rate.UnitPrice != nil && (rate.UnitPrice.Units != 0 || rate.UnitPrice.Nanos != 0) {
return rate
}
}
return nil
}
8 changes: 2 additions & 6 deletions providers/gcp/services/cloudsql/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,8 @@ func extractSQLPriceFromSKU(sku *cloudbilling.Sku) (price float64, currency stri
}

pricingInfo := sku.PricingInfo[0]
if pricingInfo.PricingExpression == nil || len(pricingInfo.PricingExpression.TieredRates) == 0 {
return 0, ""
}

rate := pricingInfo.PricingExpression.TieredRates[0]
if rate.UnitPrice == nil {
rate := skumatch.PaidRate(pricingInfo.PricingExpression)
if rate == nil {
return 0, ""
}

Expand Down
42 changes: 42 additions & 0 deletions providers/gcp/services/cloudsql/tiered_rates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cloudsql

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/cloudbilling/v1"
)

// freeFirstTierSKU returns a SKU whose first tier is a free allowance and whose
// second tier carries the paid rate (issue #242).
func freeFirstTierSKU(id, description, usageType, region string, paidNanos int64) *cloudbilling.Sku {
sku := currencySKU(id, description, usageType, region, "USD", 0)
sku.PricingInfo[0].PricingExpression.TieredRates = append(sku.PricingInfo[0].PricingExpression.TieredRates,
&cloudbilling.TierRate{StartUsageAmount: 100, UnitPrice: &cloudbilling.Money{CurrencyCode: "USD", Nanos: paidNanos}})
return sku
}

func TestExtractSQLPricing_FreeFirstTierUsesPaidTier(t *testing.T) {
const tier, region = "db-n1-standard-1", "us-central1"
od := freeFirstTierSKU("od", tier+" Cloud SQL", "OnDemand", region, 50000000)

price, currency := extractSQLPriceFromSKU(od)
assert.InDelta(t, 0.05, price, 1e-12)
assert.Equal(t, "USD", currency)

onDemand, commitment, currency, err := extractSQLPricingFromSKUs([]*cloudbilling.Sku{
od,
freeFirstTierSKU("cud", tier+" Cloud SQL commitment 1yr", "Commit1Yr", region, 42000000),
}, tier, region)
require.NoError(t, err)
assert.InDelta(t, 0.05, onDemand, 1e-12)
assert.InDelta(t, 0.042, commitment, 1e-12)
assert.Equal(t, "USD", currency)
}

func TestExtractSQLPricing_AllTiersFreeIsSkipped(t *testing.T) {
sku := freeFirstTierSKU("od", "db-n1-standard-1 Cloud SQL", "OnDemand", "us-central1", 0)
price, _ := extractSQLPriceFromSKU(sku)
assert.Zero(t, price)
}
8 changes: 2 additions & 6 deletions providers/gcp/services/cloudstorage/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,8 @@ func extractStoragePriceFromSKU(sku *cloudbilling.Sku) (price float64, currency
}

pricingInfo := sku.PricingInfo[0]
if pricingInfo.PricingExpression == nil || len(pricingInfo.PricingExpression.TieredRates) == 0 {
return 0, "", nil
}

rate := pricingInfo.PricingExpression.TieredRates[0]
if rate.UnitPrice == nil {
rate := skumatch.PaidRate(pricingInfo.PricingExpression)
if rate == nil {
return 0, "", nil
}

Expand Down
43 changes: 43 additions & 0 deletions providers/gcp/services/cloudstorage/tiered_rates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package cloudstorage

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/cloudbilling/v1"
)

// freeFirstTierStorageSKU returns a SKU whose first tier is a free allowance and
// whose second tier carries the paid rate (issue #242).
func freeFirstTierStorageSKU(description, usageType string, paidNanos int64) *cloudbilling.Sku {
sku := usageTypeSKU(description, usageType, 0)
sku.PricingInfo[0].PricingExpression.TieredRates = append(sku.PricingInfo[0].PricingExpression.TieredRates,
&cloudbilling.TierRate{StartUsageAmount: 5, UnitPrice: &cloudbilling.Money{CurrencyCode: "USD", Nanos: paidNanos}})
return sku
}

func TestExtractStoragePricing_FreeFirstTierUsesPaidTier(t *testing.T) {
od := freeFirstTierStorageSKU("STANDARD Storage in us-central1", "OnDemand", 20000000)

price, currency, err := extractStoragePriceFromSKU(od)
require.NoError(t, err)
assert.InDelta(t, 0.02/averageHoursPerMonth, price, 1e-15)
assert.Equal(t, "USD", currency)

onDemand, commitment, currency, err := extractStoragePricingFromSKUs([]*cloudbilling.Sku{
od,
freeFirstTierStorageSKU("STANDARD Storage in us-central1 Committed Use Discount", "Commit1Yr", 16000000),
}, "STANDARD", "us-central1")
require.NoError(t, err)
assert.InDelta(t, 0.02/averageHoursPerMonth, onDemand, 1e-15)
assert.InDelta(t, 0.016/averageHoursPerMonth, commitment, 1e-15)
assert.Equal(t, "USD", currency)
}

func TestExtractStoragePricing_AllTiersFreeIsSkipped(t *testing.T) {
sku := freeFirstTierStorageSKU("STANDARD Storage in us-central1", "OnDemand", 0)
price, _, err := extractStoragePriceFromSKU(sku)
require.NoError(t, err)
assert.Zero(t, price)
}
8 changes: 2 additions & 6 deletions providers/gcp/services/memorystore/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,8 @@ func extractPriceFromSKU(sku *cloudbilling.Sku) (price float64, currency string)
}

pricingInfo := sku.PricingInfo[0]
if pricingInfo.PricingExpression == nil || len(pricingInfo.PricingExpression.TieredRates) == 0 {
return 0, ""
}

rate := pricingInfo.PricingExpression.TieredRates[0]
if rate.UnitPrice == nil {
rate := skumatch.PaidRate(pricingInfo.PricingExpression)
if rate == nil {
return 0, ""
}

Expand Down
42 changes: 42 additions & 0 deletions providers/gcp/services/memorystore/tiered_rates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package memorystore

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/cloudbilling/v1"
)

// freeFirstTierSKU returns a SKU whose first tier is a free allowance and whose
// second tier carries the paid rate (issue #242).
func freeFirstTierSKU(id, description, usageType, region string, paidNanos int64) *cloudbilling.Sku {
sku := currencySKU(id, description, usageType, region, "USD", 0)
sku.PricingInfo[0].PricingExpression.TieredRates = append(sku.PricingInfo[0].PricingExpression.TieredRates,
&cloudbilling.TierRate{StartUsageAmount: 100, UnitPrice: &cloudbilling.Money{CurrencyCode: "USD", Nanos: paidNanos}})
return sku
}

func TestExtractPricing_FreeFirstTierUsesPaidTier(t *testing.T) {
const tier, region = "STANDARD_HA", "us-central1"
od := freeFirstTierSKU("od", tier+" Memorystore", "OnDemand", region, 50000000)

price, currency := extractPriceFromSKU(od)
assert.InDelta(t, 0.05, price, 1e-12)
assert.Equal(t, "USD", currency)

onDemand, commitment, currency, err := extractPricingFromSKUs([]*cloudbilling.Sku{
od,
freeFirstTierSKU("cud", tier+" Memorystore commitment 1yr", "Commit1Yr", region, 42000000),
}, tier, region)
require.NoError(t, err)
assert.InDelta(t, 0.05, onDemand, 1e-12)
assert.InDelta(t, 0.042, commitment, 1e-12)
assert.Equal(t, "USD", currency)
}

func TestExtractPricing_AllTiersFreeIsSkipped(t *testing.T) {
sku := freeFirstTierSKU("od", "STANDARD_HA Memorystore", "OnDemand", "us-central1", 0)
price, _ := extractPriceFromSKU(sku)
assert.Zero(t, price)
}
Loading