Documentation
¶
Overview ¶
Package pricing reads a pricing model: the versioned document that says what a unit of every priced metric costs, per platform and resource type. It takes the YAML file an operator writes, checks it against the JSON Schema embedded here, and returns the typed model the rating pass multiplies usage by.
A file becomes a canonical JSON document on the way in, and that document is what a version is stored from. Parse hands it back beside the model and ParseDocument reads it again, so a model that came from a file and one that came back from the database go through one code path: a stored version is held to the schema its file was held to. Every scalar keeps the literal text the file spells it with and every mapping key is written in sorted order, so the same file yields the same bytes however often it is imported. What the database holds is JSONB, which Postgres parses on the way in and writes back with its own key order and number formatting, so a stored version is never the bytes Parse returned: a re-import is compared as a parsed model rather than byte for byte.
Prices never touch float64. A scalar reaches decimal.NewFromString as text, whether the file spells a price as a number or as a string, because a price that went through a float would carry its rounding error into every amount rated from it. The forbidigo rules in .golangci.yml keep it that way.
The normative specification is roadmap/03-phase-3-metering-rating.md, WP 3.5.
Index ¶
Constants ¶
const ( // TypeTimeGauge is billed over the time a quantity was held, so the // dimension carries a price per unit and hour. TypeTimeGauge = "time_gauge" // TypeCounter is billed over a quantity the counters pass measured, so the // dimension carries a price per unit. TypeCounter = "counter" )
Variables ¶
var ErrNoModel = errors.New("no pricing model is valid for this period")
ErrNoModel is what ForPeriod returns for a period that begins before every stored version. Nothing prices it, and rating it against no model would bill every metered resource at zero rather than say what is missing.
var ErrVersionConflict = errors.New(
"this version is already imported and prices something else, and a corrected price belongs in a new version")
ErrVersionConflict is what Import returns for a version the database already holds under other prices. An invoice names the version it was rated from and has to stay reproducible from it, so a stored version keeps pricing what it priced when the invoice was written.
var ErrVersionNotFound = errors.New("no pricing model is stored under this version")
ErrVersionNotFound is what ByVersion returns for a version no stored model carries. A correction rates with the version its finalized run recorded, and a version the database does not hold prices nothing, so the correction is refused before it opens a run rather than rated against no model.
Functions ¶
func Import ¶
Import stores one version of the pricing model and reports whether the database already held it. It only ever inserts: the document a version was rated from is the document that stays stored under it, and a price that changes is imported under a new version instead of over an old one.
A version the database does not hold is written and Import returns false. A version it holds is read back and compared against m: equal models make the import a replay, which returns true and leaves the row alone, and unequal ones an error wrapping ErrVersionConflict. A second version claiming the valid_from of a stored one is refused too, because one instant is priced by one version. That collision is its own error rather than a version conflict: the version is new, it is the instant it starts at that is taken.
Types ¶
type Dimension ¶
type Dimension struct {
Metric string
Type string
PricePerUnitHour decimal.Decimal
PricePerUnit decimal.Decimal
}
Dimension is one priced metric of a resource type. Type decides which of the two prices carries the value: PricePerUnitHour for TypeTimeGauge, PricePerUnit for TypeCounter. The other one is zero.
type Model ¶
type Model struct {
Version string
ValidFrom time.Time
Currency string
Pricing map[string]map[string]ResourcePricing
}
Model is one version of the pricing model. Pricing is keyed by platform and then by resource type; a resource type the map does not hold is not priced.
func ByVersion ¶
ByVersion returns the stored model of one version. It is what a correction rates with: a correction rates a finalized month with the version the finalized run recorded, whatever was imported since, so it corrects the usage of that month and leaves its prices where they were (D6).
A version no stored model carries yields an error wrapping ErrVersionNotFound.
func ForPeriod ¶
ForPeriod returns the version that prices the period beginning at periodFrom: the newest one whose valid_from is at or before that instant. A version that becomes valid later prices later periods only, so importing the prices of April does not reprice March.
A period that begins before every stored version yields an error wrapping ErrNoModel.
func Parse ¶
Parse reads a pricing model file. It returns the typed model and the canonical JSON document the model was built from, which is the form a version is stored in.
The document is canonical in that the same file always yields the same bytes: mapping keys come out sorted, and every scalar keeps the text the file spells it with. That is what lets a re-import be compared against the stored version instead of being decided by the order a map was walked in.
func ParseDocument ¶
ParseDocument reads a canonical JSON document into a Model. It is the path a document read back from the database takes, and the path Parse takes once it has turned a file into one.
The document is checked against the embedded schema first, so what the model is built from is a document the schema accepts. Two rules the schema cannot state are checked afterwards: valid_from has to be an RFC 3339 timestamp, and no resource type may price one metric twice.
func (Model) Equal ¶
Equal reports whether both models price the same thing. Decimals are compared by value, so a price the file respells as 0.50 rather than "0.5" leaves the model equal, which is what a re-import of an unchanged version has to be. The dimensions of a resource type are compared in order: reordering them reorders the rated records they produce, so it is a different model.
type ResourcePricing ¶
type ResourcePricing struct {
Dimensions []Dimension
StateModifiers map[string]decimal.Decimal
TypeModifiers map[string]decimal.Decimal
}
ResourcePricing is what one resource type of one platform costs. Dimensions are in the order the file lists them, which is the order the rated records of a resource are written in. The modifier maps are empty where the file sets none, and a state or type the map does not hold is billed unmodified.