2
0

module.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. package basic
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "sync"
  7. "github.com/opencost/opencost/core/pkg/pricing"
  8. "github.com/opencost/opencost/core/pkg/reader"
  9. "github.com/opencost/opencost/core/pkg/unit"
  10. )
  11. // PricingModule must satisfy the pricing.PricingModule interface
  12. var _ pricing.PricingModule = (*PricingModule)(nil)
  13. const SourceKind = "basic"
  14. const SourceName = "basic"
  15. type PricingModule struct {
  16. mu sync.RWMutex
  17. store pricing.PricingStore
  18. }
  19. func NewBasicPricingModule(store pricing.PricingStore) (*PricingModule, error) {
  20. pricingSet, err := store.GetPricingSet(context.Background())
  21. if err != nil {
  22. return nil, fmt.Errorf("checking pricing store: %w", err)
  23. }
  24. if pricingSet.IsEmpty() {
  25. // Populate store with a default pricing set.
  26. err := store.SetPricingSet(context.Background(), GetDefaultPricingSet())
  27. if err != nil {
  28. return nil, fmt.Errorf("setting default pricing: %w", err)
  29. }
  30. pricingSet, err = store.GetPricingSet(context.Background())
  31. if err != nil {
  32. return nil, fmt.Errorf("checking default pricing: %w", err)
  33. }
  34. if pricingSet.IsEmpty() {
  35. return nil, errors.New("unable to initialize store")
  36. }
  37. }
  38. pm := &PricingModule{
  39. store: store,
  40. }
  41. return pm, nil
  42. }
  43. func (pm *PricingModule) GetClusterPricing(ctx context.Context, props pricing.ClusterPricingProperties) (*pricing.ClusterPricing, error) {
  44. pm.mu.RLock()
  45. defer pm.mu.RUnlock()
  46. cp, err := pm.getClusterPricing(ctx)
  47. if err != nil {
  48. return nil, err
  49. }
  50. if cp != nil {
  51. return cp, nil
  52. }
  53. return nil, errors.New("no cluster pricing")
  54. }
  55. func (pm *PricingModule) NewClusterPricingReader(ctx context.Context) (reader.Reader[*pricing.ClusterPricing], error) {
  56. pm.mu.RLock()
  57. defer pm.mu.RUnlock()
  58. cp, err := pm.getClusterPricing(ctx)
  59. if err != nil {
  60. return nil, fmt.Errorf("getting node pricing: %w", err)
  61. }
  62. return reader.NewSliceReader([]*pricing.ClusterPricing{cp}), nil
  63. }
  64. func (pm *PricingModule) GetNetworkPricing(ctx context.Context, props pricing.NetworkPricingProperties) (*pricing.NetworkPricing, error) {
  65. pm.mu.RLock()
  66. defer pm.mu.RUnlock()
  67. np, err := pm.getNetworkPricing(ctx)
  68. if err != nil {
  69. return nil, err
  70. }
  71. if np != nil {
  72. return np, nil
  73. }
  74. return nil, errors.New("no network pricing")
  75. }
  76. func (pm *PricingModule) NewNetworkPricingReader(ctx context.Context) (reader.Reader[*pricing.NetworkPricing], error) {
  77. pm.mu.RLock()
  78. defer pm.mu.RUnlock()
  79. np, err := pm.getNetworkPricing(ctx)
  80. if err != nil {
  81. return nil, fmt.Errorf("getting node pricing: %w", err)
  82. }
  83. return reader.NewSliceReader([]*pricing.NetworkPricing{np}), nil
  84. }
  85. func (pm *PricingModule) GetNodePricing(ctx context.Context, props pricing.NodePricingProperties) (*pricing.NodePricing, error) {
  86. pm.mu.RLock()
  87. defer pm.mu.RUnlock()
  88. np, err := pm.getNodePricing(ctx)
  89. if err != nil {
  90. return nil, err
  91. }
  92. if np != nil {
  93. return np, nil
  94. }
  95. return nil, errors.New("no node pricing")
  96. }
  97. func (pm *PricingModule) NewNodePricingReader(ctx context.Context) (reader.Reader[*pricing.NodePricing], error) {
  98. pm.mu.RLock()
  99. defer pm.mu.RUnlock()
  100. np, err := pm.getNodePricing(ctx)
  101. if err != nil {
  102. return nil, fmt.Errorf("getting node pricing: %w", err)
  103. }
  104. return reader.NewSliceReader([]*pricing.NodePricing{np}), nil
  105. }
  106. func (pm *PricingModule) GetPersistentVolumePricing(ctx context.Context, props pricing.PersistentVolumePricingProperties) (*pricing.PersistentVolumePricing, error) {
  107. pm.mu.RLock()
  108. defer pm.mu.RUnlock()
  109. pvp, err := pm.getPersistentVolumePricing(ctx)
  110. if err != nil {
  111. return nil, err
  112. }
  113. if pvp != nil {
  114. return pvp, nil
  115. }
  116. return nil, errors.New("no persistent volume pricing")
  117. }
  118. func (pm *PricingModule) NewPersistentVolumePricingReader(ctx context.Context) (reader.Reader[*pricing.PersistentVolumePricing], error) {
  119. pm.mu.RLock()
  120. defer pm.mu.RUnlock()
  121. pvp, err := pm.getPersistentVolumePricing(ctx)
  122. if err != nil {
  123. return nil, fmt.Errorf("getting volume pricing: %w", err)
  124. }
  125. return reader.NewSliceReader([]*pricing.PersistentVolumePricing{pvp}), nil
  126. }
  127. func (pm *PricingModule) GetServicePricing(ctx context.Context, props pricing.ServicePricingProperties) (*pricing.ServicePricing, error) {
  128. pm.mu.RLock()
  129. defer pm.mu.RUnlock()
  130. sp, err := pm.getServicePricing(ctx)
  131. if err != nil {
  132. return nil, err
  133. }
  134. if sp != nil {
  135. return sp, nil
  136. }
  137. return nil, errors.New("no service pricing")
  138. }
  139. func (pm *PricingModule) NewServicePricingReader(ctx context.Context) (reader.Reader[*pricing.ServicePricing], error) {
  140. pm.mu.RLock()
  141. defer pm.mu.RUnlock()
  142. sp, err := pm.getServicePricing(ctx)
  143. if err != nil {
  144. return nil, fmt.Errorf("getting service pricing: %w", err)
  145. }
  146. return reader.NewSliceReader([]*pricing.ServicePricing{sp}), nil
  147. }
  148. func (pm *PricingModule) GetPricingSet(ctx context.Context) (*pricing.PricingSet, error) {
  149. pm.mu.RLock()
  150. defer pm.mu.RUnlock()
  151. return pm.store.GetPricingSet(ctx)
  152. }
  153. func (pm *PricingModule) SourceKind() string {
  154. return SourceKind
  155. }
  156. func (pm *PricingModule) SourceName() string {
  157. return SourceName
  158. }
  159. func (pm *PricingModule) Checksum(ctx context.Context) (string, error) {
  160. pricingSet, err := pm.store.GetPricingSet(ctx)
  161. if err != nil {
  162. return "", fmt.Errorf("basic pricing module: error getting pricing set: %w", err)
  163. }
  164. checksum, err := pricingSet.Checksum()
  165. if err != nil {
  166. return "", fmt.Errorf("basic pricing module: error computing checksum: %s", err)
  167. }
  168. return checksum, nil
  169. }
  170. // Public CRUD functions
  171. func (pm *PricingModule) SetClusterPricePerHour(ctx context.Context, price float64) error {
  172. pm.mu.Lock()
  173. defer pm.mu.Unlock()
  174. return pm.setClusterPrice(ctx, pricing.ResourceCluster, unit.Hour, price)
  175. }
  176. func (pm *PricingModule) SetNetworkLocalEgressPricePerGiB(ctx context.Context, price float64) error {
  177. pm.mu.Lock()
  178. defer pm.mu.Unlock()
  179. return pm.setNetworkPrice(ctx, pricing.ResourceLocalEgress, unit.GiB, price)
  180. }
  181. func (pm *PricingModule) SetNetworkCrossZoneEgressPricePerGiB(ctx context.Context, price float64) error {
  182. pm.mu.Lock()
  183. defer pm.mu.Unlock()
  184. return pm.setNetworkPrice(ctx, pricing.ResourceCrossZoneEgress, unit.GiB, price)
  185. }
  186. func (pm *PricingModule) SetNetworkCrossRegionEgressPricePerGiB(ctx context.Context, price float64) error {
  187. pm.mu.Lock()
  188. defer pm.mu.Unlock()
  189. return pm.setNetworkPrice(ctx, pricing.ResourceCrossRegionEgress, unit.GiB, price)
  190. }
  191. func (pm *PricingModule) SetNetworkInternetEgressPricePerGiB(ctx context.Context, price float64) error {
  192. pm.mu.Lock()
  193. defer pm.mu.Unlock()
  194. return pm.setNetworkPrice(ctx, pricing.ResourceInternetEgress, unit.GiB, price)
  195. }
  196. func (pm *PricingModule) SetNetworkNATGatewayEgressPricePerGiB(ctx context.Context, price float64) error {
  197. pm.mu.Lock()
  198. defer pm.mu.Unlock()
  199. return pm.setNetworkPrice(ctx, pricing.ResourceNATGatewayEgress, unit.GiB, price)
  200. }
  201. func (pm *PricingModule) SetNetworkNATGatewayIngressPricePerGiB(ctx context.Context, price float64) error {
  202. pm.mu.Lock()
  203. defer pm.mu.Unlock()
  204. return pm.setNetworkPrice(ctx, pricing.ResourceNATGatewayIngress, unit.GiB, price)
  205. }
  206. func (pm *PricingModule) SetNodePricePerCPUCoreHour(ctx context.Context, price float64) error {
  207. pm.mu.Lock()
  208. defer pm.mu.Unlock()
  209. return pm.setNodePrice(ctx, pricing.ResourceCPU, unit.VCPUHour, price)
  210. }
  211. func (pm *PricingModule) SetNodePricePerRAMGiBHour(ctx context.Context, price float64) error {
  212. pm.mu.Lock()
  213. defer pm.mu.Unlock()
  214. return pm.setNodePrice(ctx, pricing.ResourceRAM, unit.GiBHour, price)
  215. }
  216. func (pm *PricingModule) SetNodePricePerGPUHour(ctx context.Context, price float64) error {
  217. pm.mu.Lock()
  218. defer pm.mu.Unlock()
  219. return pm.setNodePrice(ctx, pricing.ResourceGPU, unit.GPUHour, price)
  220. }
  221. func (pm *PricingModule) SetNodePricePerLocalDiskGiBHour(ctx context.Context, price float64) error {
  222. pm.mu.Lock()
  223. defer pm.mu.Unlock()
  224. return pm.setNodePrice(ctx, pricing.ResourceStorage, unit.GiBHour, price)
  225. }
  226. func (pm *PricingModule) SetPersistentVolumePricePerStorageGiBHour(ctx context.Context, price float64) error {
  227. pm.mu.Lock()
  228. defer pm.mu.Unlock()
  229. return pm.setPersistentVolumePrice(ctx, pricing.ResourceStorage, unit.GiBHour, price)
  230. }
  231. func (pm *PricingModule) SetServicePricePerHour(ctx context.Context, price float64) error {
  232. pm.mu.Lock()
  233. defer pm.mu.Unlock()
  234. return pm.setServicePrice(ctx, pricing.ResourceService, unit.Hour, price)
  235. }
  236. // Private functions to set a price by resource and unit
  237. func (pm *PricingModule) setClusterPrice(ctx context.Context, resource pricing.Resource, unit unit.Unit, price float64) error {
  238. cp, err := pm.getClusterPricing(ctx)
  239. if err != nil {
  240. return fmt.Errorf("getting cluster pricing: %w", err)
  241. }
  242. if cp.Prices == nil {
  243. cp.Prices = pricing.Prices{}
  244. }
  245. cp.Prices[resource] = pricing.Price{Unit: unit, Price: price}
  246. err = pm.setClusterPricing(ctx, cp)
  247. if err != nil {
  248. return fmt.Errorf("setting cluster pricing: %w", err)
  249. }
  250. return nil
  251. }
  252. func (pm *PricingModule) setNetworkPrice(ctx context.Context, resource pricing.Resource, unit unit.Unit, price float64) error {
  253. np, err := pm.getNetworkPricing(ctx)
  254. if err != nil {
  255. return fmt.Errorf("getting network pricing: %w", err)
  256. }
  257. if np.Prices == nil {
  258. np.Prices = pricing.Prices{}
  259. }
  260. np.Prices[resource] = pricing.Price{Unit: unit, Price: price}
  261. err = pm.setNetworkPricing(ctx, np)
  262. if err != nil {
  263. return fmt.Errorf("setting network pricing: %w", err)
  264. }
  265. return nil
  266. }
  267. func (pm *PricingModule) setNodePrice(ctx context.Context, resource pricing.Resource, unit unit.Unit, price float64) error {
  268. np, err := pm.getNodePricing(ctx)
  269. if err != nil {
  270. return fmt.Errorf("getting node pricing: %w", err)
  271. }
  272. if np.Prices == nil {
  273. np.Prices = pricing.Prices{}
  274. }
  275. np.Prices[resource] = pricing.Price{Unit: unit, Price: price}
  276. err = pm.setNodePricing(ctx, np)
  277. if err != nil {
  278. return fmt.Errorf("setting node pricing: %w", err)
  279. }
  280. return nil
  281. }
  282. func (pm *PricingModule) setPersistentVolumePrice(ctx context.Context, resource pricing.Resource, unit unit.Unit, price float64) error {
  283. vp, err := pm.getPersistentVolumePricing(ctx)
  284. if err != nil {
  285. return fmt.Errorf("getting volume pricing: %w", err)
  286. }
  287. if vp.Prices == nil {
  288. vp.Prices = pricing.Prices{}
  289. }
  290. vp.Prices[resource] = pricing.Price{Unit: unit, Price: price}
  291. err = pm.setPersistentVolumePricing(ctx, vp)
  292. if err != nil {
  293. return fmt.Errorf("setting volume pricing: %w", err)
  294. }
  295. return nil
  296. }
  297. func (pm *PricingModule) setServicePrice(ctx context.Context, resource pricing.Resource, unit unit.Unit, price float64) error {
  298. sp, err := pm.getServicePricing(ctx)
  299. if err != nil {
  300. return fmt.Errorf("getting service pricing: %w", err)
  301. }
  302. if sp.Prices == nil {
  303. sp.Prices = pricing.Prices{}
  304. }
  305. sp.Prices[resource] = pricing.Price{Unit: unit, Price: price}
  306. err = pm.setServicePricing(ctx, sp)
  307. if err != nil {
  308. return fmt.Errorf("setting service pricing: %w", err)
  309. }
  310. return nil
  311. }
  312. // Private functions to get and set pricing
  313. func (pm *PricingModule) getClusterPricing(ctx context.Context) (*pricing.ClusterPricing, error) {
  314. ps, err := pm.store.GetPricingSet(ctx)
  315. if err != nil {
  316. return nil, fmt.Errorf("getting pricing: %w", err)
  317. }
  318. if len(ps.ClusterPricing) == 0 {
  319. return nil, errors.New("not found")
  320. }
  321. // Only one default ClusterPricing is allowed in basic pricing.
  322. // If multiple exist, return only the first one.
  323. return ps.ClusterPricing[0], nil
  324. }
  325. func (pm *PricingModule) setClusterPricing(ctx context.Context, cp *pricing.ClusterPricing) error {
  326. if cp == nil {
  327. return errors.New("nil cluster pricing")
  328. }
  329. // Get the pricing set
  330. ps, err := pm.store.GetPricingSet(ctx)
  331. if err != nil {
  332. return fmt.Errorf("getting pricing: %w", err)
  333. }
  334. // Only one default ClusterPricing is allowed in basic pricing.
  335. ps.ClusterPricing = []*pricing.ClusterPricing{cp}
  336. // Set the new pricing set
  337. err = pm.store.SetPricingSet(ctx, ps)
  338. if err != nil {
  339. return fmt.Errorf("setting pricing: %w", err)
  340. }
  341. return nil
  342. }
  343. func (pm *PricingModule) getNetworkPricing(ctx context.Context) (*pricing.NetworkPricing, error) {
  344. ps, err := pm.store.GetPricingSet(ctx)
  345. if err != nil {
  346. return nil, fmt.Errorf("getting pricing: %w", err)
  347. }
  348. if len(ps.NetworkPricing) == 0 {
  349. return nil, errors.New("not found")
  350. }
  351. return ps.NetworkPricing[0], nil
  352. }
  353. func (pm *PricingModule) setNetworkPricing(ctx context.Context, np *pricing.NetworkPricing) error {
  354. if np == nil {
  355. return errors.New("nil network pricing")
  356. }
  357. // Get the pricing set
  358. ps, err := pm.store.GetPricingSet(ctx)
  359. if err != nil {
  360. return fmt.Errorf("getting pricing: %w", err)
  361. }
  362. // Only one default NetworkPricing is allowed in basic pricing.
  363. ps.NetworkPricing = []*pricing.NetworkPricing{np}
  364. // Set the new pricing set
  365. err = pm.store.SetPricingSet(ctx, ps)
  366. if err != nil {
  367. return fmt.Errorf("setting pricing: %w", err)
  368. }
  369. return nil
  370. }
  371. func (pm *PricingModule) getNodePricing(ctx context.Context) (*pricing.NodePricing, error) {
  372. ps, err := pm.store.GetPricingSet(ctx)
  373. if err != nil {
  374. return nil, fmt.Errorf("getting pricing: %w", err)
  375. }
  376. if len(ps.NodePricing) == 0 {
  377. return nil, errors.New("not found")
  378. }
  379. // Only one default NodePricing is allowed in basic pricing.
  380. // If multiple exist, return only the first one.
  381. return ps.NodePricing[0], nil
  382. }
  383. func (pm *PricingModule) setNodePricing(ctx context.Context, np *pricing.NodePricing) error {
  384. if np == nil {
  385. return errors.New("nil node pricing")
  386. }
  387. // Get the pricing set
  388. ps, err := pm.store.GetPricingSet(ctx)
  389. if err != nil {
  390. return fmt.Errorf("getting pricing: %w", err)
  391. }
  392. // Only one default NodePricing is allowed in basic pricing.
  393. ps.NodePricing = []*pricing.NodePricing{np}
  394. // Set the new pricing set
  395. err = pm.store.SetPricingSet(ctx, ps)
  396. if err != nil {
  397. return fmt.Errorf("setting pricing: %w", err)
  398. }
  399. return nil
  400. }
  401. func (pm *PricingModule) getPersistentVolumePricing(ctx context.Context) (*pricing.PersistentVolumePricing, error) {
  402. ps, err := pm.store.GetPricingSet(ctx)
  403. if err != nil {
  404. return nil, fmt.Errorf("getting pricing: %w", err)
  405. }
  406. if len(ps.PersistentVolumePricing) == 0 {
  407. return nil, errors.New("not found")
  408. }
  409. // Only one default VolumePricing is allowed in basic pricing.
  410. // If multiple exist, return only the first one.
  411. return ps.PersistentVolumePricing[0], nil
  412. }
  413. func (pm *PricingModule) setPersistentVolumePricing(ctx context.Context, vp *pricing.PersistentVolumePricing) error {
  414. if vp == nil {
  415. return errors.New("nil volume pricing")
  416. }
  417. // Get the pricing set
  418. ps, err := pm.store.GetPricingSet(ctx)
  419. if err != nil {
  420. return fmt.Errorf("getting pricing: %w", err)
  421. }
  422. // Only one default VolumePricing is allowed in basic pricing.
  423. ps.PersistentVolumePricing = []*pricing.PersistentVolumePricing{vp}
  424. // Set the new pricing set
  425. err = pm.store.SetPricingSet(ctx, ps)
  426. if err != nil {
  427. return fmt.Errorf("setting pricing: %w", err)
  428. }
  429. return nil
  430. }
  431. func (pm *PricingModule) getServicePricing(ctx context.Context) (*pricing.ServicePricing, error) {
  432. ps, err := pm.store.GetPricingSet(ctx)
  433. if err != nil {
  434. return nil, fmt.Errorf("getting pricing: %w", err)
  435. }
  436. if len(ps.ServicePricing) == 0 {
  437. return nil, errors.New("not found")
  438. }
  439. // Only one default ServicePricing is allowed in basic pricing.
  440. // If multiple exist, return only the first one.
  441. return ps.ServicePricing[0], nil
  442. }
  443. func (pm *PricingModule) setServicePricing(ctx context.Context, sp *pricing.ServicePricing) error {
  444. if sp == nil {
  445. return errors.New("nil service pricing")
  446. }
  447. // Get the pricing set
  448. ps, err := pm.store.GetPricingSet(ctx)
  449. if err != nil {
  450. return fmt.Errorf("getting pricing: %w", err)
  451. }
  452. // Only one default ServicePricing is allowed in basic pricing.
  453. ps.ServicePricing = []*pricing.ServicePricing{sp}
  454. // Set the new pricing set
  455. err = pm.store.SetPricingSet(ctx, ps)
  456. if err != nil {
  457. return fmt.Errorf("setting pricing: %w", err)
  458. }
  459. return nil
  460. }