| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- package basic
- import (
- "context"
- "errors"
- "fmt"
- "slices"
- "sync"
- "github.com/opencost/opencost/core/pkg/pricing"
- "github.com/opencost/opencost/core/pkg/reader"
- "github.com/opencost/opencost/core/pkg/unit"
- )
- // PricingModule must satisfy the pricing.PricingModule interface
- var _ pricing.PricingModule = (*PricingModule)(nil)
- type PricingModule struct {
- mu sync.RWMutex
- 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")
- }
- }
- pm := &PricingModule{
- store: store,
- }
- return pm, nil
- }
- func (pm *PricingModule) GetClusterPricing(ctx context.Context, props pricing.ClusterPricingProperties) (*pricing.ClusterPricing, error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- cp, err := pm.getClusterPricing(ctx)
- if err != nil {
- return nil, err
- }
- if cp != nil {
- return cp, nil
- }
- return nil, errors.New("no cluster pricing")
- }
- func (pm *PricingModule) NewClusterPricingReader(ctx context.Context) (reader.Reader[*pricing.ClusterPricing], error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- cp, err := pm.getClusterPricing(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting node pricing: %w", err)
- }
- return reader.NewSliceReader([]*pricing.ClusterPricing{cp}), nil
- }
- func (pm *PricingModule) GetNetworkPricing(ctx context.Context, props pricing.NetworkPricingProperties) (*pricing.NetworkPricing, error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- nps, err := pm.getNetworkPricing(ctx)
- if err != nil {
- return nil, err
- }
- // Search through the mock data for a matching network pricing entry
- for _, np := range nps {
- if np.Properties.Provider == props.Provider &&
- np.Properties.TrafficDirection == props.TrafficDirection &&
- np.Properties.TrafficType == props.TrafficType &&
- np.Properties.IsNatGateway == props.IsNatGateway {
- return np, nil
- }
- }
- return nil, fmt.Errorf("network pricing not found for provider=%s, trafficDirection=%s, trafficType=%s, isNatGateway=%t",
- props.Provider, props.TrafficDirection, props.TrafficType, props.IsNatGateway)
- }
- func (pm *PricingModule) NewNetworkPricingReader(ctx context.Context) (reader.Reader[*pricing.NetworkPricing], error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- np, err := pm.getNetworkPricing(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting node pricing: %w", err)
- }
- return reader.NewSliceReader(slices.Clone(np)), nil
- }
- func (pm *PricingModule) GetNodePricing(ctx context.Context, props pricing.NodePricingProperties) (*pricing.NodePricing, error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- np, err := pm.getNodePricing(ctx)
- if err != nil {
- return nil, err
- }
- if np != nil {
- return np, nil
- }
- return nil, errors.New("no node pricing")
- }
- func (pm *PricingModule) NewNodePricingReader(ctx context.Context) (reader.Reader[*pricing.NodePricing], error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- 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) GetPersistentVolumePricing(ctx context.Context, props pricing.PersistentVolumePricingProperties) (*pricing.PersistentVolumePricing, error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- pvp, err := pm.getPersistentVolumePricing(ctx)
- if err != nil {
- return nil, err
- }
- if pvp != nil {
- return pvp, nil
- }
- return nil, errors.New("no persistent volume pricing")
- }
- func (pm *PricingModule) NewPersistentVolumePricingReader(ctx context.Context) (reader.Reader[*pricing.PersistentVolumePricing], error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- pvp, err := pm.getPersistentVolumePricing(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting volume pricing: %w", err)
- }
- return reader.NewSliceReader([]*pricing.PersistentVolumePricing{pvp}), nil
- }
- func (pm *PricingModule) GetServicePricing(ctx context.Context, props pricing.ServicePricingProperties) (*pricing.ServicePricing, error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- sp, err := pm.getServicePricing(ctx)
- if err != nil {
- return nil, err
- }
- if sp != nil {
- return sp, nil
- }
- return nil, errors.New("no service pricing")
- }
- func (pm *PricingModule) NewServicePricingReader(ctx context.Context) (reader.Reader[*pricing.ServicePricing], error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- sp, err := pm.getServicePricing(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting service pricing: %w", err)
- }
- return reader.NewSliceReader([]*pricing.ServicePricing{sp}), nil
- }
- func (pm *PricingModule) GetPricingSet(ctx context.Context) (*pricing.PricingSet, error) {
- pm.mu.RLock()
- defer pm.mu.RUnlock()
- return pm.store.GetPricingSet(ctx)
- }
- func (pm *PricingModule) SourceKind() string {
- return "basic"
- }
- func (pm *PricingModule) SourceName() string {
- return "basic"
- }
- func (pm *PricingModule) Checksum(ctx context.Context) (string, error) {
- pricingSet, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return "", fmt.Errorf("basic pricing module: error getting pricing set: %w", err)
- }
- checksum, err := pricingSet.Checksum()
- if err != nil {
- return "", fmt.Errorf("basic pricing module: error computing checksum: %s", err)
- }
- return checksum, nil
- }
- // Public CRUD functions
- func (pm *PricingModule) SetNodePricePerCPUCoreHour(ctx context.Context, price float64) error {
- pm.mu.Lock()
- defer pm.mu.Unlock()
- return pm.setNodePrice(ctx, pricing.ResourceCPU, unit.VCPUHour, price)
- }
- func (pm *PricingModule) SetNodePricePerRAMGiBHour(ctx context.Context, price float64) error {
- pm.mu.Lock()
- defer pm.mu.Unlock()
- return pm.setNodePrice(ctx, pricing.ResourceRAM, unit.GiBHour, price)
- }
- func (pm *PricingModule) SetNodePricePerGPUHour(ctx context.Context, price float64) error {
- pm.mu.Lock()
- defer pm.mu.Unlock()
- return pm.setNodePrice(ctx, pricing.ResourceGPU, unit.GPUHour, price)
- }
- func (pm *PricingModule) SetNodePricePerLocalDiskGiBHour(ctx context.Context, price float64) error {
- pm.mu.Lock()
- defer pm.mu.Unlock()
- return pm.setNodePrice(ctx, pricing.ResourceStorage, unit.GiBHour, price)
- }
- func (pm *PricingModule) SetVolumePricePerStorageGiBHour(ctx context.Context, price float64) error {
- pm.mu.Lock()
- defer pm.mu.Unlock()
- return pm.setVolumePrice(ctx, pricing.ResourceStorage, unit.GiBHour, price)
- }
- // Private functions to set a price by resource and unit
- func (pm *PricingModule) setNodePrice(ctx context.Context, resource pricing.Resource, unit unit.Unit, price float64) error {
- np, err := pm.getNodePricing(ctx)
- if err != nil {
- return fmt.Errorf("getting node pricing: %w", err)
- }
- if np.Prices == nil {
- np.Prices = pricing.Prices{}
- }
- np.Prices[resource] = pricing.Price{Unit: unit, Price: price}
- 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, resource pricing.Resource, unit unit.Unit, price float64) error {
- vp, err := pm.getPersistentVolumePricing(ctx)
- if err != nil {
- return fmt.Errorf("getting volume pricing: %w", err)
- }
- if vp.Prices == nil {
- vp.Prices = pricing.Prices{}
- }
- vp.Prices[resource] = pricing.Price{Unit: unit, Price: price}
- err = pm.setPersistentVolumePricing(ctx, vp)
- if err != nil {
- return fmt.Errorf("setting volume pricing: %w", err)
- }
- return nil
- }
- // Private functions to get and set pricing
- func (pm *PricingModule) getClusterPricing(ctx context.Context) (*pricing.ClusterPricing, error) {
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting pricing: %w", err)
- }
- if len(ps.ClusterPricing) == 0 {
- return nil, errors.New("not found")
- }
- // Only one default ClusterPricing is allowed in basic pricing.
- // If multiple exist, return only the first one.
- return ps.ClusterPricing[0], nil
- }
- func (pm *PricingModule) setClusterPricing(ctx context.Context, cp *pricing.ClusterPricing) error {
- // TODO
- return errors.New("not implemented")
- }
- func (pm *PricingModule) getNetworkPricing(ctx context.Context) ([]*pricing.NetworkPricing, error) {
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting pricing: %w", err)
- }
- if len(ps.NetworkPricing) == 0 {
- return nil, errors.New("not found")
- }
- return ps.NetworkPricing, nil
- }
- func (pm *PricingModule) setNetworkPricing(ctx context.Context, np *pricing.NetworkPricing) error {
- // TODO
- return errors.New("not implemented")
- }
- 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.NodePricing) == 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.NodePricing[0], nil
- }
- func (pm *PricingModule) setNodePricing(ctx context.Context, np *pricing.NodePricing) error {
- if np == nil {
- return errors.New("nil node pricing")
- }
- // 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.NodePricing = []*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) getPersistentVolumePricing(ctx context.Context) (*pricing.PersistentVolumePricing, error) {
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting pricing: %w", err)
- }
- if len(ps.PersistentVolumePricing) == 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.PersistentVolumePricing[0], nil
- }
- func (pm *PricingModule) setPersistentVolumePricing(ctx context.Context, vp *pricing.PersistentVolumePricing) error {
- if vp == nil {
- return errors.New("nil volume pricing")
- }
- // 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.PersistentVolumePricing = []*pricing.PersistentVolumePricing{vp}
- // 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) getServicePricing(ctx context.Context) (*pricing.ServicePricing, error) {
- ps, err := pm.store.GetPricingSet(ctx)
- if err != nil {
- return nil, fmt.Errorf("getting pricing: %w", err)
- }
- if len(ps.ServicePricing) == 0 {
- return nil, errors.New("not found")
- }
- // Only one default ServicePricing is allowed in basic pricing.
- // If multiple exist, return only the first one.
- return ps.ServicePricing[0], nil
- }
- func (pm *PricingModule) setServicePricing(ctx context.Context, sp *pricing.ServicePricing) error {
- // TODO
- return errors.New("not implemented")
- }
|