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
34 changes: 34 additions & 0 deletions api/v1alpha1/servicepricing_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// rather than parsing ServiceConfiguration.
//
// +kubebuilder:validation:XValidation:rule="self.chargeType != 'Usage' || (has(self.metric) && has(self.pricingUnit) && has(self.rates))",message="Usage ServicePricing requires metric, pricingUnit, and rates"
// +kubebuilder:validation:XValidation:rule="self.chargeType == 'Usage' || !has(self.unitConversion)",message="unitConversion is only allowed for Usage ServicePricing"
// +kubebuilder:validation:XValidation:rule="self.chargeType != 'OneTime' || (has(self.amount) && has(self.trigger))",message="OneTime ServicePricing requires amount and trigger"
// +kubebuilder:validation:XValidation:rule="self.chargeType != 'Recurring' || (has(self.amount) && has(self.interval))",message="Recurring ServicePricing requires amount and interval"
type ServicePricingSpec struct {
Expand Down Expand Up @@ -59,6 +60,14 @@ type ServicePricingSpec struct {
// +kubebuilder:validation:MaxLength=64
PricingUnit string `json:"pricingUnit,omitempty"`

// UnitConversion converts the meter's usage into pricingUnit before the
// rates apply, for a metric measured in a different unit than it is
// priced in (e.g. bytes priced per GiB). Omitted when the metric is
// already measured in pricingUnit. Usage charges only.
//
// +kubebuilder:validation:Optional
UnitConversion *UnitConversion `json:"unitConversion,omitempty"`

// Rates is the ordered list of rate entries for Usage charges.
// Required when chargeType is Usage.
//
Expand All @@ -85,6 +94,31 @@ type ServicePricingSpec struct {
Interval ChargeInterval `json:"interval,omitempty"`
}

// UnitConversion converts usage measured in the meter's unit into the
// ServicePricing's pricingUnit: usage in pricingUnit = metered usage /
// meterUnitsPerPricingUnit. Rates and tier bounds stay in pricingUnit, and
// invoices show the converted quantity.
type UnitConversion struct {
// MeterUnitsPerPricingUnit is how many of the meter's units make one
// pricingUnit, as a positive decimal string (e.g. "1073741824" for a
// meter in bytes priced per GiB, "1000" for millicore-seconds priced
// per vCPU-second).
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=64
// +kubebuilder:validation:Pattern=`^([1-9]\d*(\.\d+)?|0\.\d*[1-9]\d*)$`
MeterUnitsPerPricingUnit string `json:"meterUnitsPerPricingUnit"`

// Description explains the conversion to the people who maintain the
// pricing (e.g. "Memory is metered in byte-seconds and priced per
// GiB-second; 1 GiB = 2^30 bytes").
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=512
Description string `json:"description"`
}

// ServicePricingStatus defines the observed state of a ServicePricing.
type ServicePricingStatus struct {
// Conditions represent the latest available observations of the
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions applyconfiguration/api/v1alpha1/servicepricingspec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions applyconfiguration/api/v1alpha1/unitconversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions applyconfiguration/utils.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions config/base/crd/bases/billing.miloapis.com_offers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,34 @@ spec:
enum:
- BillingAccountActivation
type: string
unitConversion:
description: |-
UnitConversion converts the meter's usage into pricingUnit before the
rates apply, for a metric measured in a different unit than it is
priced in (e.g. bytes priced per GiB). Omitted when the metric is
already measured in pricingUnit. Usage charges only.
properties:
description:
description: |-
Description explains the conversion to the people who maintain the
pricing (e.g. "Memory is metered in byte-seconds and priced per
GiB-second; 1 GiB = 2^30 bytes").
maxLength: 512
minLength: 1
type: string
meterUnitsPerPricingUnit:
description: |-
MeterUnitsPerPricingUnit is how many of the meter's units make one
pricingUnit, as a positive decimal string (e.g. "1073741824" for a
meter in bytes priced per GiB, "1000" for millicore-seconds priced
per vCPU-second).
maxLength: 64
pattern: ^([1-9]\d*(\.\d+)?|0\.\d*[1-9]\d*)$
type: string
required:
- description
- meterUnitsPerPricingUnit
type: object
required:
- chargeType
- currency
Expand All @@ -272,6 +300,8 @@ spec:
and rates
rule: self.chargeType != 'Usage' || (has(self.metric) && has(self.pricingUnit)
&& has(self.rates))
- message: unitConversion is only allowed for Usage ServicePricing
rule: self.chargeType == 'Usage' || !has(self.unitConversion)
- message: OneTime ServicePricing requires amount and trigger
rule: self.chargeType != 'OneTime' || (has(self.amount) &&
has(self.trigger))
Expand Down
30 changes: 30 additions & 0 deletions config/base/crd/bases/billing.miloapis.com_servicepricings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,34 @@ spec:
enum:
- BillingAccountActivation
type: string
unitConversion:
description: |-
UnitConversion converts the meter's usage into pricingUnit before the
rates apply, for a metric measured in a different unit than it is
priced in (e.g. bytes priced per GiB). Omitted when the metric is
already measured in pricingUnit. Usage charges only.
properties:
description:
description: |-
Description explains the conversion to the people who maintain the
pricing (e.g. "Memory is metered in byte-seconds and priced per
GiB-second; 1 GiB = 2^30 bytes").
maxLength: 512
minLength: 1
type: string
meterUnitsPerPricingUnit:
description: |-
MeterUnitsPerPricingUnit is how many of the meter's units make one
pricingUnit, as a positive decimal string (e.g. "1073741824" for a
meter in bytes priced per GiB, "1000" for millicore-seconds priced
per vCPU-second).
maxLength: 64
pattern: ^([1-9]\d*(\.\d+)?|0\.\d*[1-9]\d*)$
type: string
required:
- description
- meterUnitsPerPricingUnit
type: object
required:
- chargeType
- currency
Expand All @@ -196,6 +224,8 @@ spec:
- message: Usage ServicePricing requires metric, pricingUnit, and rates
rule: self.chargeType != 'Usage' || (has(self.metric) && has(self.pricingUnit)
&& has(self.rates))
- message: unitConversion is only allowed for Usage ServicePricing
rule: self.chargeType == 'Usage' || !has(self.unitConversion)
- message: OneTime ServicePricing requires amount and trigger
rule: self.chargeType != 'OneTime' || (has(self.amount) && has(self.trigger))
- message: Recurring ServicePricing requires amount and interval
Expand Down
4 changes: 2 additions & 2 deletions docs/emitting-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ if err != nil {
billing account. The billing controllers attribute project → account
using the `BillingAccountBinding` graph at processing time.
- **Do not use floating point.** `Quantity` is `int64`. Measure
cpu-*seconds*, not cpu-hours; bytes, not gibibytes. The meter's
`consumedUnit`/`pricingUnit` handle scale.
cpu-*seconds*, not cpu-hours; bytes, not gibibytes. A ServicePricing
priced in a larger unit converts with `unitConversion`.

## Emission contract

Expand Down
Loading