pim_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. package stackit
  2. import (
  3. "testing"
  4. )
  5. func TestGpuCountFromFlavor(t *testing.T) {
  6. tests := []struct {
  7. flavor string
  8. want int
  9. }{
  10. {"n1.14d.g1", 1},
  11. {"n1.28d.g2", 2},
  12. {"n3.104d.g8", 8},
  13. {"c1i.2", 0},
  14. {"g2i.1", 0},
  15. {"n1.14d", 0},
  16. }
  17. for _, tt := range tests {
  18. got := gpuCountFromFlavor(tt.flavor)
  19. if got != tt.want {
  20. t.Errorf("gpuCountFromFlavor(%q) = %d, want %d", tt.flavor, got, tt.want)
  21. }
  22. }
  23. }
  24. func TestGpuTypeFromFlavor(t *testing.T) {
  25. tests := []struct {
  26. flavor string
  27. want string
  28. }{
  29. {"n1.14d.g1", "NVIDIA A100"},
  30. {"n2.28d.g2", "NVIDIA L40S"},
  31. {"n3.104d.g8", "NVIDIA H100 HGX"},
  32. {"c1i.2", ""},
  33. }
  34. for _, tt := range tests {
  35. got := gpuTypeFromFlavor(tt.flavor)
  36. if got != tt.want {
  37. t.Errorf("gpuTypeFromFlavor(%q) = %q, want %q", tt.flavor, got, tt.want)
  38. }
  39. }
  40. }
  41. func TestParsePIMVMFlavors(t *testing.T) {
  42. metro := true
  43. nonMetro := false
  44. vcpu2 := 2
  45. ram4 := 4.0
  46. skus := []pimSKU{
  47. {
  48. Name: "g2i.1",
  49. ProductSpecificAttributes: pimProductSpecificAttrs{
  50. Flavor: "g2i.1",
  51. VCPU: &vcpu2,
  52. RAM: &ram4,
  53. Metro: &nonMetro,
  54. },
  55. Prices: []pimPrice{{Value: "0.05045"}},
  56. },
  57. {
  58. // Metro variant should be skipped
  59. Name: "g2i.1-metro",
  60. ProductSpecificAttributes: pimProductSpecificAttrs{
  61. Flavor: "g2i.1",
  62. VCPU: &vcpu2,
  63. RAM: &ram4,
  64. Metro: &metro,
  65. },
  66. Prices: []pimPrice{{Value: "0.10"}},
  67. },
  68. {
  69. // No flavor -> skipped
  70. Name: "no-flavor",
  71. ProductSpecificAttributes: pimProductSpecificAttrs{},
  72. Prices: []pimPrice{{Value: "0.01"}},
  73. },
  74. {
  75. // No price -> skipped
  76. Name: "no-price",
  77. ProductSpecificAttributes: pimProductSpecificAttrs{
  78. Flavor: "c1i.2",
  79. VCPU: &vcpu2,
  80. RAM: &ram4,
  81. },
  82. Prices: []pimPrice{},
  83. },
  84. }
  85. flavors := parsePIMVMFlavors(skus)
  86. if len(flavors) != 1 {
  87. t.Fatalf("expected 1 flavor, got %d", len(flavors))
  88. }
  89. f, ok := flavors["g2i.1"]
  90. if !ok {
  91. t.Fatal("expected flavor g2i.1")
  92. }
  93. if f.HourlyCost != "0.05045" {
  94. t.Errorf("expected hourly cost 0.05045, got %s", f.HourlyCost)
  95. }
  96. if f.VCPU != 2 {
  97. t.Errorf("expected 2 vCPU, got %d", f.VCPU)
  98. }
  99. if f.RAMGB != 4.0 {
  100. t.Errorf("expected 4.0 RAM GB, got %f", f.RAMGB)
  101. }
  102. }
  103. func TestParsePIMVMFlavorsGPU(t *testing.T) {
  104. nonMetro := false
  105. vcpu14 := 14
  106. ram56 := 56.0
  107. skus := []pimSKU{
  108. {
  109. Name: "n1.14d.g1",
  110. ProductSpecificAttributes: pimProductSpecificAttrs{
  111. Flavor: "n1.14d.g1",
  112. VCPU: &vcpu14,
  113. RAM: &ram56,
  114. Metro: &nonMetro,
  115. },
  116. Prices: []pimPrice{{Value: "3.50"}},
  117. },
  118. }
  119. flavors := parsePIMVMFlavors(skus)
  120. f, ok := flavors["n1.14d.g1"]
  121. if !ok {
  122. t.Fatal("expected flavor n1.14d.g1")
  123. }
  124. if f.GPUCount != 1 {
  125. t.Errorf("expected GPU count 1, got %d", f.GPUCount)
  126. }
  127. if f.GPUType != "NVIDIA A100" {
  128. t.Errorf("expected GPU type NVIDIA A100, got %s", f.GPUType)
  129. }
  130. }
  131. func TestParsePIMVMFlavorsNonGPUNPrefix(t *testing.T) {
  132. nonMetro := false
  133. vcpu14 := 14
  134. ram56 := 56.0
  135. skus := []pimSKU{
  136. {
  137. Name: "n1.14d",
  138. ProductSpecificAttributes: pimProductSpecificAttrs{
  139. Flavor: "n1.14d",
  140. VCPU: &vcpu14,
  141. RAM: &ram56,
  142. Metro: &nonMetro,
  143. },
  144. Prices: []pimPrice{{Value: "1.50"}},
  145. },
  146. }
  147. flavors := parsePIMVMFlavors(skus)
  148. f, ok := flavors["n1.14d"]
  149. if !ok {
  150. t.Fatal("expected flavor n1.14d")
  151. }
  152. if f.GPUCount != 0 {
  153. t.Errorf("expected GPU count 0, got %d", f.GPUCount)
  154. }
  155. if f.GPUType != "" {
  156. t.Errorf("expected empty GPU type for non-GPU n1 flavor, got %q", f.GPUType)
  157. }
  158. }
  159. func TestParsePIMStoragePricing(t *testing.T) {
  160. nonMetro := false
  161. skus := []pimSKU{
  162. {
  163. Name: "premium-perf0",
  164. UnitBilling: "per GB/hour",
  165. ProductSpecificAttributes: pimProductSpecificAttrs{
  166. Class: "storage_premium_perf0",
  167. Metro: &nonMetro,
  168. },
  169. Prices: []pimPrice{{Value: "0.0001"}},
  170. },
  171. {
  172. Name: "premium-perf2",
  173. UnitBilling: "per GB/hour",
  174. ProductSpecificAttributes: pimProductSpecificAttrs{
  175. Class: "storage_premium_perf2",
  176. Metro: &nonMetro,
  177. },
  178. Prices: []pimPrice{{Value: "0.0005"}},
  179. },
  180. {
  181. // Non-GB/hour billing -> skipped
  182. Name: "iops-based",
  183. UnitBilling: "per IOPS/hour",
  184. ProductSpecificAttributes: pimProductSpecificAttrs{
  185. Class: "storage_iops",
  186. Metro: &nonMetro,
  187. },
  188. Prices: []pimPrice{{Value: "0.01"}},
  189. },
  190. }
  191. pricing := parsePIMStoragePricing(skus)
  192. if _, ok := pricing["storage_premium_perf0"]; !ok {
  193. t.Error("expected storage_premium_perf0")
  194. }
  195. if _, ok := pricing["storage_premium_perf2"]; !ok {
  196. t.Error("expected storage_premium_perf2")
  197. }
  198. if _, ok := pricing["storage_iops"]; ok {
  199. t.Error("storage_iops should have been skipped (non GB/hour billing)")
  200. }
  201. // Default should be the cheapest
  202. def, ok := pricing["default"]
  203. if !ok {
  204. t.Fatal("expected default storage entry")
  205. }
  206. if def.CostPerGBHr != "0.0001" {
  207. t.Errorf("expected default cost 0.0001, got %s", def.CostPerGBHr)
  208. }
  209. }
  210. func TestPaginationTermination(t *testing.T) {
  211. // Verify that empty data or empty cursor terminates pagination.
  212. // This is a logic check - fetchAllPIMSKUs breaks on:
  213. // searchResp.Meta.NextCursor == "" || len(searchResp.Data) == 0
  214. // We can't call the real API, but we verify the parsing logic
  215. // handles the termination conditions in parsePIMVMFlavors.
  216. // Empty input should produce empty output
  217. flavors := parsePIMVMFlavors(nil)
  218. if len(flavors) != 0 {
  219. t.Errorf("expected 0 flavors from nil input, got %d", len(flavors))
  220. }
  221. flavors = parsePIMVMFlavors([]pimSKU{})
  222. if len(flavors) != 0 {
  223. t.Errorf("expected 0 flavors from empty input, got %d", len(flavors))
  224. }
  225. }