product.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package oracle
  2. import (
  3. "embed"
  4. "encoding/json"
  5. "log"
  6. "strconv"
  7. "strings"
  8. )
  9. const blockVolumePartNumber = "B91961"
  10. const loadBalancerPartNumber = "B93030"
  11. // egressT1PartNumber for egress to NA, EU, and UK
  12. const egress1PartNumber = "B88327"
  13. // egressT2PartNumber for egress to APAC, and SA
  14. const egress2PartNumber = "B93455"
  15. // egressT3PartNumber for egress to ME, and AF
  16. const egress3PartNumber = "B93456"
  17. const enhancedClusterPartNumber = "B96545"
  18. const virualNodePartNumber = "B96109"
  19. // Compiled from cost estimator data at https://www.oracle.com/cloud/costestimator.html
  20. // The shapes.json endpoint can be queried for a snapshot of this data.
  21. // Note that this endpoint is subject to change and should not be queried directly.
  22. //
  23. //go:embed partnumbers
  24. var partNumbers embed.FS
  25. func init() {
  26. shapeInfo, err := partNumbers.ReadFile("partnumbers/shape_part_numbers.json")
  27. if err != nil {
  28. log.Fatalln("unable to read OCI Shape part numbers", err)
  29. }
  30. instanceProducts = map[string]Product{}
  31. if err := json.Unmarshal(shapeInfo, &instanceProducts); err != nil {
  32. log.Fatalln("unable to unmarshal OCI Shape part numbers", err)
  33. }
  34. }
  35. type Product struct {
  36. // OCPU product name
  37. OCPU string
  38. // Memory product name
  39. Memory string
  40. // GPU product name
  41. GPU string
  42. Disk string
  43. }
  44. type DefaultPricing struct {
  45. OCPU string
  46. Memory string
  47. GPU string
  48. Storage string
  49. LB string
  50. Egress string
  51. }
  52. type instanceProduct map[string]Product
  53. // instanceProducts maps instance types to associated part numbers.
  54. var instanceProducts instanceProduct
  55. // normalizeOCIInstanceShape parses synthetic flex shape labels in the form
  56. // <base-shape>.<ocpus>o.<memory>g.<baseline>b. Supported baselines are 1_1,
  57. // 1_2, and 1_8, which map to the burstable CPU price multipliers below.
  58. func normalizeOCIInstanceShape(shape string) (string, float64, bool) {
  59. const defaultCPUPriceMultiplier = 1.0
  60. parts := strings.Split(shape, ".")
  61. if len(parts) < 4 {
  62. return "", defaultCPUPriceMultiplier, false
  63. }
  64. ocpuPart := parts[len(parts)-3]
  65. memoryPart := parts[len(parts)-2]
  66. baselinePart := parts[len(parts)-1]
  67. if !strings.HasSuffix(ocpuPart, "o") || !strings.HasSuffix(memoryPart, "g") || !strings.HasSuffix(baselinePart, "b") {
  68. return "", defaultCPUPriceMultiplier, false
  69. }
  70. if _, err := strconv.ParseFloat(strings.TrimSuffix(ocpuPart, "o"), 64); err != nil {
  71. return "", defaultCPUPriceMultiplier, false
  72. }
  73. if _, err := strconv.ParseFloat(strings.TrimSuffix(memoryPart, "g"), 64); err != nil {
  74. return "", defaultCPUPriceMultiplier, false
  75. }
  76. var cpuPriceMultiplier float64
  77. switch strings.TrimSuffix(baselinePart, "b") {
  78. case "1_1":
  79. cpuPriceMultiplier = 1.0
  80. case "1_2":
  81. cpuPriceMultiplier = 0.5
  82. case "1_8":
  83. cpuPriceMultiplier = 0.125
  84. default:
  85. return "", defaultCPUPriceMultiplier, false
  86. }
  87. baseShape := strings.Join(parts[:len(parts)-3], ".")
  88. return baseShape, cpuPriceMultiplier, true
  89. }
  90. func (i instanceProduct) get(shape string) Product {
  91. if product, ok := i[shape]; ok {
  92. return product
  93. }
  94. // If no product mapping exists, provide a default product
  95. return Product{}
  96. }
  97. func (d DefaultPricing) TotalInstanceCost() (float64, error) {
  98. var totalCost float64
  99. addValue := func(val string) error {
  100. if val != "" {
  101. f, err := strconv.ParseFloat(val, 64)
  102. if err != nil {
  103. return err
  104. }
  105. totalCost += f
  106. }
  107. return nil
  108. }
  109. if err := addValue(d.OCPU); err != nil {
  110. return totalCost, err
  111. }
  112. if err := addValue(d.Memory); err != nil {
  113. return totalCost, err
  114. }
  115. if err := addValue(d.GPU); err != nil {
  116. return totalCost, err
  117. }
  118. return totalCost, nil
  119. }
  120. func egressRegionPartNumber(region string) string {
  121. split := strings.Split(region, "-")
  122. switch split[0] {
  123. case "us", "ca", "eu", "uk", "mx":
  124. return egress1PartNumber
  125. case "ap", "sa":
  126. return egress2PartNumber
  127. case "me", "af", "il":
  128. return egress3PartNumber
  129. default:
  130. return ""
  131. }
  132. }