provider.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. package oracle
  2. import (
  3. "fmt"
  4. "io"
  5. "strconv"
  6. "sync"
  7. "time"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. "github.com/opencost/opencost/core/pkg/opencost"
  10. "github.com/opencost/opencost/core/pkg/util"
  11. "github.com/opencost/opencost/core/pkg/util/json"
  12. "github.com/opencost/opencost/pkg/cloud/models"
  13. "github.com/opencost/opencost/pkg/cloud/utils"
  14. "github.com/opencost/opencost/pkg/clustercache"
  15. "github.com/opencost/opencost/pkg/env"
  16. )
  17. const nodePoolIdAnnotation = "oci.oraclecloud.com/node-pool-id"
  18. const virtualPoolIdAnnotation = "oci.oraclecloud.com/virtual-node-pool-id"
  19. const virtualNodeLabel = "node-role.kubernetes.io/virtual-node"
  20. const managementPlatformOKE = "oke"
  21. const currencyCodeUSD = "USD"
  22. type Oracle struct {
  23. Config models.ProviderConfig
  24. Clientset clustercache.ClusterCache
  25. ClusterRegion string
  26. ClusterAccountID string
  27. DownloadPricingDataLock sync.RWMutex
  28. OSEnvLock sync.Mutex
  29. RateCardStore *RateCardStore
  30. ServiceAccountChecks *models.ServiceAccountChecks
  31. DefaultPricing DefaultPricing
  32. }
  33. func (o *Oracle) ClusterInfo() (map[string]string, error) {
  34. c, err := o.GetConfig()
  35. if err != nil {
  36. return nil, err
  37. }
  38. m := make(map[string]string)
  39. m["name"] = "Oracle Cluster #1"
  40. if clusterName := o.getClusterName(c); clusterName != "" {
  41. m["name"] = clusterName
  42. }
  43. m["provider"] = opencost.OracleProvider
  44. m["account"] = o.ClusterAccountID
  45. m["region"] = o.ClusterRegion
  46. m["remoteReadEnabled"] = strconv.FormatBool(env.IsRemoteEnabled())
  47. m["id"] = env.GetClusterID()
  48. return m, nil
  49. }
  50. func (o *Oracle) NodePricing(key models.Key) (*models.Node, models.PricingMetadata, error) {
  51. if err := o.ensurePricingData(); err != nil {
  52. return nil, models.PricingMetadata{}, err
  53. }
  54. o.DownloadPricingDataLock.RLock()
  55. defer o.DownloadPricingDataLock.RUnlock()
  56. return o.RateCardStore.ForKey(key, o.DefaultPricing)
  57. }
  58. func (o *Oracle) PVPricing(pvk models.PVKey) (*models.PV, error) {
  59. if err := o.ensurePricingData(); err != nil {
  60. return nil, err
  61. }
  62. o.DownloadPricingDataLock.RLock()
  63. defer o.DownloadPricingDataLock.RUnlock()
  64. return o.RateCardStore.ForPVK(pvk, o.DefaultPricing)
  65. }
  66. func (o *Oracle) NetworkPricing() (*models.Network, error) {
  67. if err := o.ensurePricingData(); err != nil {
  68. return nil, err
  69. }
  70. o.DownloadPricingDataLock.RLock()
  71. defer o.DownloadPricingDataLock.RUnlock()
  72. return o.RateCardStore.ForEgressRegion(o.ClusterRegion, o.DefaultPricing)
  73. }
  74. func (o *Oracle) LoadBalancerPricing() (*models.LoadBalancer, error) {
  75. if err := o.ensurePricingData(); err != nil {
  76. return nil, err
  77. }
  78. o.DownloadPricingDataLock.RLock()
  79. defer o.DownloadPricingDataLock.RUnlock()
  80. return o.RateCardStore.ForLB(o.DefaultPricing)
  81. }
  82. func (o *Oracle) AllNodePricing() (interface{}, error) {
  83. if err := o.ensurePricingData(); err != nil {
  84. return nil, err
  85. }
  86. o.DownloadPricingDataLock.RLock()
  87. defer o.DownloadPricingDataLock.RUnlock()
  88. return o.RateCardStore.Store(), nil
  89. }
  90. // DownloadPricingData refreshes the RateCardStore pricing data.
  91. func (o *Oracle) DownloadPricingData() error {
  92. o.DownloadPricingDataLock.Lock()
  93. defer o.DownloadPricingDataLock.Unlock()
  94. cfg, err := o.GetConfig()
  95. if err != nil {
  96. return err
  97. }
  98. if o.RateCardStore == nil {
  99. url := env.GetOCIPricingURL()
  100. o.RateCardStore = NewRateCardStore(url, cfg.CurrencyCode)
  101. }
  102. if _, err := o.RateCardStore.Refresh(); err != nil {
  103. return err
  104. }
  105. o.DefaultPricing = DefaultPricing{
  106. OCPU: cfg.CPU,
  107. Memory: cfg.RAM,
  108. GPU: cfg.GPU,
  109. Storage: cfg.Storage,
  110. Egress: cfg.InternetNetworkEgress,
  111. LB: cfg.DefaultLBPrice,
  112. }
  113. return nil
  114. }
  115. func (o *Oracle) GetKey(labels map[string]string, n *clustercache.Node) models.Key {
  116. var gpuCount int
  117. var gpuType string
  118. if gpuc, ok := n.Status.Capacity["nvidia.com/gpu"]; ok {
  119. gpuCount = int(gpuc.Value())
  120. gpuType = "nvidia.com/gpu"
  121. }
  122. instanceType, _ := util.GetInstanceType(labels)
  123. return &oracleKey{
  124. providerID: n.SpecProviderID,
  125. instanceType: instanceType,
  126. labels: labels,
  127. gpuCount: gpuCount,
  128. gpuType: gpuType,
  129. }
  130. }
  131. func (o *Oracle) GetPVKey(pv *clustercache.PersistentVolume, parameters map[string]string, _ string) models.PVKey {
  132. var providerID string
  133. var driver string
  134. if pv.Spec.CSI != nil {
  135. providerID = pv.Spec.CSI.VolumeHandle
  136. driver = pv.Spec.CSI.Driver
  137. }
  138. return &oraclePVKey{
  139. storageClass: pv.Spec.StorageClassName,
  140. providerID: providerID,
  141. driver: driver,
  142. parameters: parameters,
  143. }
  144. }
  145. func (o *Oracle) UpdateConfig(r io.Reader, _ string) (*models.CustomPricing, error) {
  146. return o.Config.Update(func(pricing *models.CustomPricing) error {
  147. a := make(map[string]interface{})
  148. err := json.NewDecoder(r).Decode(&a)
  149. if err != nil {
  150. return err
  151. }
  152. for k, v := range a {
  153. kUpper := utils.ToTitle.String(k)
  154. vstr, ok := v.(string)
  155. if ok {
  156. err := models.SetCustomPricingField(pricing, kUpper, vstr)
  157. if err != nil {
  158. return fmt.Errorf("error setting custom pricing field: %w", err)
  159. }
  160. } else {
  161. return fmt.Errorf("type error while updating config for %s", kUpper)
  162. }
  163. }
  164. if env.IsRemoteEnabled() {
  165. err := utils.UpdateClusterMeta(env.GetClusterID(), o.getClusterName(pricing))
  166. if err != nil {
  167. return err
  168. }
  169. }
  170. return nil
  171. })
  172. }
  173. func (o *Oracle) UpdateConfigFromConfigMap(m map[string]string) (*models.CustomPricing, error) {
  174. return o.Config.UpdateFromMap(m)
  175. }
  176. func (o *Oracle) GetConfig() (*models.CustomPricing, error) {
  177. c, err := o.Config.GetCustomPricingData()
  178. if err != nil {
  179. return nil, err
  180. }
  181. if c.Discount == "" {
  182. c.Discount = "0%"
  183. }
  184. if c.NegotiatedDiscount == "" {
  185. c.NegotiatedDiscount = "0%"
  186. }
  187. if c.CurrencyCode == "" {
  188. c.CurrencyCode = currencyCodeUSD
  189. }
  190. return c, nil
  191. }
  192. func (o *Oracle) GetManagementPlatform() (string, error) {
  193. nodes := o.Clientset.GetAllNodes()
  194. for _, node := range nodes {
  195. if _, ok := node.Annotations[nodePoolIdAnnotation]; ok {
  196. return managementPlatformOKE, nil
  197. }
  198. if _, ok := node.Annotations[virtualPoolIdAnnotation]; ok {
  199. return managementPlatformOKE, nil
  200. }
  201. }
  202. return "", nil
  203. }
  204. func (o *Oracle) PricingSourceStatus() map[string]*models.PricingSource {
  205. listPricing := "List Pricing"
  206. return map[string]*models.PricingSource{
  207. listPricing: {
  208. Name: listPricing,
  209. Enabled: true,
  210. Available: true,
  211. },
  212. }
  213. }
  214. func (o *Oracle) ClusterManagementPricing() (string, float64, error) {
  215. if err := o.ensurePricingData(); err != nil {
  216. return "", 0.0, err
  217. }
  218. managementPlatform, _ := o.GetManagementPlatform()
  219. if managementPlatform != managementPlatformOKE {
  220. return "", 0.0, nil // Self-managed cluster.
  221. }
  222. o.DownloadPricingDataLock.Lock()
  223. defer o.DownloadPricingDataLock.Unlock()
  224. // TODO: Support lookup of cluster type, as BASIC_CLUSTER types are free.
  225. return managementPlatformOKE, o.RateCardStore.ForManagedCluster("ENHANCED_CLUSTER"), nil
  226. }
  227. func (o *Oracle) CombinedDiscountForNode(instanceType string, isPreemptible bool, defaultDiscount, negotiatedDiscount float64) float64 {
  228. return 1.0 - ((1.0 - defaultDiscount) * (1.0 - negotiatedDiscount))
  229. }
  230. func (o *Oracle) Regions() []string {
  231. regionOverrides := env.GetRegionOverrideList()
  232. if len(regionOverrides) > 0 {
  233. log.Debugf("Overriding Oracle regions with configured region list: %+v", regionOverrides)
  234. return regionOverrides
  235. }
  236. return oracleRegions()
  237. }
  238. func (o *Oracle) PricingSourceSummary() interface{} {
  239. if err := o.ensurePricingData(); err != nil {
  240. return err
  241. }
  242. o.DownloadPricingDataLock.Lock()
  243. defer o.DownloadPricingDataLock.Unlock()
  244. return o.RateCardStore.Store()
  245. }
  246. func (o *Oracle) getClusterName(cfg *models.CustomPricing) string {
  247. if cfg.ClusterName != "" {
  248. return cfg.ClusterName
  249. }
  250. for _, node := range o.Clientset.GetAllNodes() {
  251. if clusterName, ok := node.Labels["name"]; ok {
  252. return clusterName
  253. }
  254. }
  255. return ""
  256. }
  257. func (o *Oracle) ensurePricingData() error {
  258. if o.RateCardStore == nil {
  259. return o.DownloadPricingData()
  260. }
  261. return nil
  262. }
  263. func (o *Oracle) GetAddresses() ([]byte, error) {
  264. return nil, nil
  265. }
  266. func (o *Oracle) GetDisks() ([]byte, error) {
  267. return nil, nil
  268. }
  269. func (o *Oracle) GetOrphanedResources() ([]models.OrphanedResource, error) {
  270. return nil, nil
  271. }
  272. func (o *Oracle) GetLocalStorageQuery(duration time.Duration, duration2 time.Duration, b bool, b2 bool) string {
  273. return ""
  274. }
  275. func (o *Oracle) ApplyReservedInstancePricing(m map[string]*models.Node) {}
  276. func (o *Oracle) ServiceAccountStatus() *models.ServiceAccountStatus {
  277. return o.ServiceAccountChecks.GetStatus()
  278. }