provider.go 8.2 KB

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