ratecard_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package oracle
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "os"
  6. "strconv"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestRCSForKey(t *testing.T) {
  12. rcs, server := testSetupRateCardStore(t)
  13. defer server.Close()
  14. testCases := map[string]struct {
  15. cost string
  16. arm bool
  17. }{
  18. "VM.DenseIO.E4.Flex": {
  19. "0.014061",
  20. false,
  21. },
  22. "BM.GPU3.8": {
  23. "3.01455",
  24. false,
  25. },
  26. "Pod.Standard.A1.Flex": {
  27. "0.0115",
  28. true,
  29. },
  30. "VM.Standard.E4.Flex": {
  31. "0.014000",
  32. false,
  33. },
  34. "VM.Standard.E3.Flex": {
  35. "0.014000",
  36. false,
  37. },
  38. "unknown-shape.2o.32g.1_2b": {
  39. "0.600000",
  40. false,
  41. },
  42. "unknown-shape": {
  43. "0.600000",
  44. false,
  45. },
  46. }
  47. for instanceType, testCase := range testCases {
  48. t.Run(instanceType, func(t *testing.T) {
  49. key := &oracleKey{
  50. instanceType: instanceType,
  51. labels: make(map[string]string),
  52. }
  53. if testCase.arm {
  54. key.labels["kubernetes.io/arch"] = "arm64"
  55. }
  56. node, _, err := rcs.ForKey(key, DefaultPricing{
  57. OCPU: "0.2",
  58. Memory: "0.1",
  59. GPU: "0.3",
  60. })
  61. require.NoError(t, err)
  62. assertFloatStrings(t, testCase.cost, node.Cost, 0.001)
  63. })
  64. }
  65. }
  66. func TestRCSForKey_KarpenterFlexShape(t *testing.T) {
  67. rcs, server := testSetupRateCardStore(t)
  68. defer server.Close()
  69. defaultPricing := DefaultPricing{
  70. OCPU: "0.2",
  71. Memory: "0.1",
  72. GPU: "0.3",
  73. }
  74. testCases := map[string]struct {
  75. baseShape string
  76. flexShape string
  77. cpuMultiplier float64
  78. assertCost bool
  79. }{
  80. "baseline-1_1": {
  81. baseShape: "VM.Standard.E3.Flex",
  82. flexShape: "VM.Standard.E3.Flex.2o.32g.1_1b",
  83. cpuMultiplier: 1,
  84. assertCost: true,
  85. },
  86. "baseline-1_2": {
  87. baseShape: "VM.Standard.E4.Flex",
  88. flexShape: "VM.Standard.E4.Flex.8o.32g.1_2b",
  89. cpuMultiplier: 0.5,
  90. },
  91. "baseline-1_8": {
  92. baseShape: "VM.Standard.E4.Flex",
  93. flexShape: "VM.Standard.E4.Flex.8o.32g.1_8b",
  94. cpuMultiplier: 0.125,
  95. },
  96. }
  97. for name, testCase := range testCases {
  98. t.Run(name, func(t *testing.T) {
  99. baseNode, _, err := rcs.ForKey(&oracleKey{instanceType: testCase.baseShape, labels: make(map[string]string)}, defaultPricing)
  100. require.NoError(t, err)
  101. flexNode, _, err := rcs.ForKey(&oracleKey{instanceType: testCase.flexShape, labels: make(map[string]string)}, defaultPricing)
  102. require.NoError(t, err)
  103. assertFloatStrings(t, baseNode.RAMCost, flexNode.RAMCost, 0.000001)
  104. assert.InDelta(t, mustParseFloat(t, baseNode.VCPUCost)*testCase.cpuMultiplier, mustParseFloat(t, flexNode.VCPUCost), 0.000001)
  105. if testCase.assertCost {
  106. assertFloatStrings(t, baseNode.Cost, flexNode.Cost, 0.000001)
  107. }
  108. })
  109. }
  110. }
  111. func TestRCSForPVK(t *testing.T) {
  112. rcs, server := testSetupRateCardStore(t)
  113. defer server.Close()
  114. var testCases = map[string]struct {
  115. cost string
  116. }{
  117. driverOCIBV: {
  118. "0.000034",
  119. },
  120. driverOCI: {
  121. "0.000034",
  122. },
  123. "unknown": {
  124. "0.25",
  125. },
  126. }
  127. for driver, testCase := range testCases {
  128. t.Run(driver, func(t *testing.T) {
  129. pvk := &oraclePVKey{
  130. driver: driver,
  131. }
  132. pv, err := rcs.ForPVK(pvk, DefaultPricing{
  133. Storage: "0.25",
  134. })
  135. require.NoError(t, err)
  136. assertFloatStrings(t, testCase.cost, pv.Cost, 0.00001)
  137. })
  138. }
  139. }
  140. func TestRCSEgressForRegion(t *testing.T) {
  141. rcs, server := testSetupRateCardStore(t)
  142. defer server.Close()
  143. var testCases = map[string]struct {
  144. cost float64
  145. }{
  146. "ap-mumbai-1": {
  147. 0.025,
  148. },
  149. "sa-saopaulo-1": {
  150. 0.025,
  151. },
  152. "me-dubai-1": {
  153. 0.05,
  154. },
  155. "af-johannesburg-1": {
  156. 0.05,
  157. },
  158. "il-jerusalem-1": {
  159. 0.05,
  160. },
  161. "eu-madrid-1": {
  162. 0.0085,
  163. },
  164. "uk-cardiff-1": {
  165. 0.0085,
  166. },
  167. "mx-monterrey-1": {
  168. 0.0085,
  169. },
  170. "us-chicago-1": {
  171. 0.0085,
  172. },
  173. "ca-montreal-1": {
  174. 0.0085,
  175. },
  176. "unknown": {
  177. 0.000123,
  178. },
  179. }
  180. for region, testCase := range testCases {
  181. net, err := rcs.ForEgressRegion(region, DefaultPricing{
  182. Egress: "0.000123",
  183. })
  184. assert.NoError(t, err)
  185. assert.InDelta(t, float64(0), net.ZoneNetworkEgressCost, 0.1)
  186. assert.InDelta(t, testCase.cost, net.RegionNetworkEgressCost, 0.001)
  187. assert.InDelta(t, testCase.cost, net.InternetNetworkEgressCost, 0.001)
  188. }
  189. }
  190. func testSetupRateCardStore(t *testing.T) (*RateCardStore, *httptest.Server) {
  191. pricesUSDBytes, err := os.ReadFile("test/prices_usd.json")
  192. require.NoError(t, err)
  193. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  194. w.WriteHeader(http.StatusOK)
  195. w.Write(pricesUSDBytes)
  196. }))
  197. rcs := NewRateCardStore(server.URL, currencyCodeUSD)
  198. store, err := rcs.Refresh()
  199. require.NoError(t, err)
  200. require.NotEmpty(t, store)
  201. return rcs, server
  202. }
  203. func assertFloatStrings(t *testing.T, s1, s2 string, delta float64) {
  204. t.Helper()
  205. f1, err := strconv.ParseFloat(s1, 64)
  206. require.NoError(t, err)
  207. f2, err := strconv.ParseFloat(s2, 64)
  208. require.NoError(t, err)
  209. assert.InDelta(t, f1, f2, delta)
  210. }
  211. func mustParseFloat(t *testing.T, s string) float64 {
  212. t.Helper()
  213. f, err := strconv.ParseFloat(s, 64)
  214. require.NoError(t, err)
  215. return f
  216. }