| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- package basic
- import (
- "context"
- "errors"
- "fmt"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/pricing"
- "github.com/opencost/opencost/core/pkg/reader"
- "github.com/opencost/opencost/core/pkg/unit"
- )
- type PricingModule struct {
- currency unit.Currency
- store pricing.PricingStore
- }
- func NewBasicPricingModule(store pricing.PricingStore) (*PricingModule, error) {
- pricingSet, err := store.GetPricingSet(context.Background())
- if err != nil {
- return nil, fmt.Errorf("checking pricing store: %w", err)
- }
- if pricingSet.IsEmpty() {
- // Populate store with a default pricing set.
- err := store.SetPricingSet(context.Background(), GetDefaultPricingSet())
- if err != nil {
- return nil, fmt.Errorf("setting default pricing: %w", err)
- }
- pricingSet, err = store.GetPricingSet(context.Background())
- if err != nil {
- return nil, fmt.Errorf("checking default pricing: %w", err)
- }
- if pricingSet.IsEmpty() {
- return nil, errors.New("unable to initialize store")
- }
- }
- currencies := pricingSet.Currencies()
- if len(currencies) > 0 {
- log.Warnf("detected multiple currencies in basic pricing module (%v): defaulting to %s", currencies, currencies[0])
- }
- pm := &PricingModule{
- currency: currencies[0],
- store: store,
- }
- return pm, nil
- }
- func (pm *PricingModule) GetCurrency() unit.Currency {
- return pm.currency
- }
- func (pm *PricingModule) SetCurrency(ctx context.Context, currency unit.Currency) error {
- prevCurrency := pm.currency
- if currency == prevCurrency {
- return nil
- }
- // 1. Convert existing node pricing to new currency
- np, err := pm.getNodePricing(ctx)
- if err != nil {
- return fmt.Errorf("getting node pricing: %w", err)
- }
- // Set up new Prices for the new currency
- newPrices := []pricing.Price{}
- // Convert all existing prices to the new currency
- oldPrices, ok := np.Prices[prevCurrency]
- if !ok {
- log.Warnf("setting currency to '%s': no node prices found for existing currency '%s'", currency, pm.currency)
- // There are no prices for the current currency.
- // Set default prices using the new currency.
- newPrices = GetDefaultNodePricing().Prices[unit.USD]
- }
- for _, price := range oldPrices {
- newPrices = append(newPrices, pricing.Price{
- Currency: currency,
- Unit: price.Unit,
- Price: price.Price,
- })
- }
- // Set new prices under new currency
- np.Prices = make(pricing.Prices, 1)
- np.Prices[currency] = newPrices
- // Set node pricing on the module
- err = pm.setNodePricing(ctx, np)
- if err != nil {
- return fmt.Errorf("setting node pricing: %w", err)
- }
- // 2. Convert existing volume pricing to new currency
- vp, err := pm.getVolumePricing(ctx)
- if err != nil {
- return fmt.Errorf("getting node pricing: %w", err)
- }
- // Set up new Prices for the new currency
- newPrices = []pricing.Price{}
- // Convert all existing prices to the new currency
- oldPrices, ok = vp.Prices[prevCurrency]
- if !ok {
- log.Warnf("setting currency to '%s': no node prices found for existing currency '%s'", currency, pm.currency)
- // There are no prices for the current currency.
- // Set default prices using the new currency.
- newPrices = GetDefaultVolumePricing().Prices[unit.USD]
- }
- for _, price := range oldPrices {
- newPrices = append(newPrices, pricing.Price{
- Currency: currency,
- Unit: price.Unit,
- Price: price.Price,
- })
- }
- // Set new prices under new currency
- vp.Prices = make(pricing.Prices, 1)
- vp.Prices[currency] = newPrices
- // Set node pricing on the module
- err = pm.setVolumePricing(ctx, vp)
- if err != nil {
- return fmt.Errorf("setting node pricing: %w", err)
- }
- return nil
- }
- func (pm *PricingModule) SetNodePricePerCPUCoreHour(ctx context.Context, price float64) error {
- return pm.setNodePrice(ctx, unit.VCPUHour, price)
- }
- func (pm *PricingModule) SetNodePricePerRAMGiBHour(ctx context.Context, price float64) error {
- return pm.setNodePrice(ctx, unit.RAMGiBHour, price)
- }
- func (pm *PricingModule) SetNodePricePerGPUHour(ctx context.Context, price float64) error {
- return pm.setNodePrice(ctx, unit.GPUHour, price)
- }
- func (pm *PricingModule) SetNodePricePerLocalDiskGiBHour(ctx context.Context, price float64) error {
- return pm.setNodePrice(ctx, unit.StorageGiBHour, price)
- }
- func (pm *PricingModule) SetVolumePricePerStorageGiBHour(ctx context.Context, price float64) error {
- return pm.setVolumePrice(ctx, unit.StorageGiBHour, price)
- }
- func (pm *PricingModule) NewNodePricingReader(ctx context.Context) (reader.Reader[*pricing.NodePricing], error) {
- np, err := pm.getNodePricing(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting node pricing: %w", err)
- }
- return reader.NewSliceReader([]*pricing.NodePricing{np}), nil
- }
- func (pm *PricingModule) NewVolumePricingReader(ctx context.Context) (reader.Reader[*pricing.VolumePricing], error) {
- vp, err := pm.getVolumePricing(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting volume pricing: %w", err)
- }
- return reader.NewSliceReader([]*pricing.VolumePricing{vp}), nil
- }
- func (pm *PricingModule) setNodePrice(ctx context.Context, unit unit.Unit, price float64) error {
- np, err := pm.getNodePricing(ctx)
- if err != nil {
- return fmt.Errorf("getting node pricing: %w", err)
- }
- prices, ok := np.Prices[pm.currency]
- if !ok {
- log.Warnf("setting price per %s to '%f': no node prices found for existing currency '%s'", unit, price, pm.currency)
- // There are no prices for the current currency.
- // Set default prices using the new currency.
- np = GetDefaultNodePricing()
- }
- // Set the price with unit GiBHour to the given price
- for i, p := range prices {
- if p.Unit == unit {
- prices[i] = pricing.Price{
- Currency: p.Currency,
- Unit: p.Unit,
- Price: price,
- }
- }
- }
- // Set the new node pricing
- err = pm.setNodePricing(ctx, np)
- if err != nil {
- return fmt.Errorf("setting node pricing: %w", err)
- }
- return nil
- }
- func (pm *PricingModule) setVolumePrice(ctx context.Context, unit unit.Unit, price float64) error {
- vp, err := pm.getVolumePricing(ctx)
- if err != nil {
- return fmt.Errorf("getting volume pricing: %w", err)
- }
- prices, ok := vp.Prices[pm.currency]
- if !ok {
- log.Warnf("setting price per %s to '%f': no volume prices found for existing currency '%s'", unit, price, pm.currency)
- // There are no prices for the current currency.
- // Set default prices using the new currency.
- vp = GetDefaultVolumePricing()
- }
- // Set the price with unit GiBHour to the given price
- for i, p := range prices {
- if p.Unit == unit {
- prices[i] = pricing.Price{
- Currency: p.Currency,
- Unit: p.Unit,
- Price: price,
- }
- }
- }
- // Set the new volume pricing
- err = pm.setVolumePricing(ctx, vp)
- if err != nil {
- return fmt.Errorf("setting node pricing: %w", err)
- }
- return nil
- }
- func (pm *PricingModule) getNodePricing(ctx context.Context) (*pricing.NodePricing, error) {
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting pricing: %w", err)
- }
- if len(ps.Nodes) == 0 {
- return nil, errors.New("not found")
- }
- // Only one default NodePricing is allowed in basic pricing.
- // If multiple exist, return only the first one.
- return ps.Nodes[0], nil
- }
- func (pm *PricingModule) setNodePricing(ctx context.Context, np *pricing.NodePricing) error {
- if np == nil {
- return errors.New("nil node pricing")
- }
- // Make sure precisely one currency is set
- currs := np.GetCurrencies()
- if len(currs) == 0 {
- return errors.New("pricing is empty")
- }
- if len(currs) > 1 {
- return fmt.Errorf("setting multiple currencies: %v", currs)
- }
- // Update PricingModule to use given currency
- pm.currency = currs[0]
- // Get the pricing set
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return fmt.Errorf("getting pricing: %w", err)
- }
- // Only one default NodePricing is allowed in basic pricing.
- ps.Nodes = []*pricing.NodePricing{np}
- // Set the new pricing set
- err = pm.store.SetPricingSet(ctx, ps)
- if err != nil {
- return fmt.Errorf("setting pricing: %w", err)
- }
- return nil
- }
- func (pm *PricingModule) getVolumePricing(ctx context.Context) (*pricing.VolumePricing, error) {
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting pricing: %w", err)
- }
- if len(ps.Volumes) == 0 {
- return nil, errors.New("not found")
- }
- // Only one default VolumePricing is allowed in basic pricing.
- // If multiple exist, return only the first one.
- return ps.Volumes[0], nil
- }
- func (pm *PricingModule) setVolumePricing(ctx context.Context, vp *pricing.VolumePricing) error {
- if vp == nil {
- return errors.New("nil volume pricing")
- }
- // Make sure precisely one currency is set
- currs := vp.GetCurrencies()
- if len(currs) == 0 {
- return errors.New("pricing is empty")
- }
- if len(currs) > 1 {
- return fmt.Errorf("setting multiple currencies: %v", currs)
- }
- // Update PricingModule to use given currency
- pm.currency = currs[0]
- // Get the pricing set
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return fmt.Errorf("getting pricing: %w", err)
- }
- // Only one default VolumePricing is allowed in basic pricing.
- ps.Volumes = []*pricing.VolumePricing{vp}
- // Set the new pricing set
- err = pm.store.SetPricingSet(ctx, ps)
- if err != nil {
- return fmt.Errorf("setting pricing: %w", err)
- }
- return nil
- }
|