provider_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package provider
  2. import (
  3. "testing"
  4. "github.com/opencost/opencost/core/pkg/clustercache"
  5. coreenv "github.com/opencost/opencost/core/pkg/env"
  6. "github.com/opencost/opencost/core/pkg/storage"
  7. "github.com/opencost/opencost/pkg/config"
  8. v1 "k8s.io/api/core/v1"
  9. "k8s.io/apimachinery/pkg/api/resource"
  10. )
  11. func TestParseLocalDiskID(t *testing.T) {
  12. tests := map[string]struct {
  13. input string
  14. want string
  15. }{
  16. "empty string": {
  17. input: "",
  18. want: "",
  19. },
  20. "generic string": {
  21. input: "test",
  22. want: "test",
  23. },
  24. "AWS node provider id": {
  25. input: "aws:///us-east-2a/i-0fea4fd46592d050b",
  26. want: "i-0fea4fd46592d050b",
  27. },
  28. "GCP node provider id": {
  29. input: "gce://guestbook-11111/us-central1-a/gke-niko-n1-standard-2-wlkla-8d48e58a-hfy7",
  30. want: "gke-niko-n1-standard-2-wlkla-8d48e58a-hfy7",
  31. },
  32. "Azure vmss provider id": {
  33. input: "azure:///subscriptions/ae337b64-e7ba-3387-b043-187289efe4e3/resourceGroups/mc_test_eastus2/providers/Microsoft.Compute/virtualMachineScaleSets/aks-userpool-12345678-vmss/virtualMachines/11",
  34. want: "azure:///subscriptions/ae337b64-e7ba-3387-b043-187289efe4e3/resourcegroups/mc_test_eastus2/providers/microsoft.compute/disks/aks-userpool-12345678-vmss00000b_osdisk",
  35. },
  36. "Azure vm provider id": {
  37. input: "azure:///subscriptions/ae337b64-e7ba-3387-b043-187289efe4e3/resourceGroups/mc_test_eastus2/providers/Microsoft.Compute/virtualMachines/master-0",
  38. want: "azure:///subscriptions/ae337b64-e7ba-3387-b043-187289efe4e3/resourcegroups/mc_test_eastus2/providers/microsoft.compute/disks/master-0_osdisk",
  39. },
  40. }
  41. for name, tt := range tests {
  42. t.Run(name, func(t *testing.T) {
  43. if got := ParseLocalDiskID(tt.input); got != tt.want {
  44. t.Errorf("ParseLocalDiskID() = %v, want %v", got, tt.want)
  45. }
  46. })
  47. }
  48. }
  49. func TestProviderConfigUpdateFromMapPreservesHourlyPrices(t *testing.T) {
  50. confMan := config.NewConfigFileManager(storage.NewMemoryStorage())
  51. providerConfig := NewProviderConfig(confMan, "default.json")
  52. updated, err := providerConfig.UpdateFromMap(map[string]string{
  53. "CPU": "0.031611",
  54. "spotCPU": "0.006655",
  55. "RAM": "0.004237",
  56. "spotRAM": "0.000892",
  57. "GPU": "0.95",
  58. "spotGPU": "0.308",
  59. "storage": "0.00005479452",
  60. })
  61. if err != nil {
  62. t.Fatalf("UpdateFromMap returned error: %v", err)
  63. }
  64. if updated.CPU != "0.031611" {
  65. t.Errorf("CPU = %q, want hourly value %q", updated.CPU, "0.031611")
  66. }
  67. if updated.SpotCPU != "0.006655" {
  68. t.Errorf("SpotCPU = %q, want hourly value %q", updated.SpotCPU, "0.006655")
  69. }
  70. if updated.RAM != "0.004237" {
  71. t.Errorf("RAM = %q, want hourly value %q", updated.RAM, "0.004237")
  72. }
  73. if updated.SpotRAM != "0.000892" {
  74. t.Errorf("SpotRAM = %q, want hourly value %q", updated.SpotRAM, "0.000892")
  75. }
  76. if updated.GPU != "0.95" {
  77. t.Errorf("GPU = %q, want hourly value %q", updated.GPU, "0.95")
  78. }
  79. if updated.SpotGPU != "0.308" {
  80. t.Errorf("SpotGPU = %q, want hourly value %q", updated.SpotGPU, "0.308")
  81. }
  82. if updated.Storage != "0.00005479452" {
  83. t.Errorf("Storage = %q, want hourly value %q", updated.Storage, "0.00005479452")
  84. }
  85. }
  86. func TestCustomProviderGetKeyDetectsGPUCapacity(t *testing.T) {
  87. cases := []struct {
  88. name string
  89. provider *CustomProvider
  90. labels map[string]string
  91. capacity v1.ResourceList
  92. wantGPUType string
  93. wantGPUCount int
  94. }{
  95. {
  96. name: "nvidia GPU capacity",
  97. capacity: v1.ResourceList{
  98. "nvidia.com/gpu": resource.MustParse("2"),
  99. },
  100. wantGPUType: "nvidia.com/gpu",
  101. wantGPUCount: 2,
  102. },
  103. {
  104. name: "virtual GPU capacity",
  105. capacity: v1.ResourceList{
  106. "k8s.amazonaws.com/vgpu": resource.MustParse("3"),
  107. },
  108. wantGPUType: "k8s.amazonaws.com/vgpu",
  109. wantGPUCount: 3,
  110. },
  111. {
  112. name: "configured GPU label takes precedence over capacity type",
  113. provider: &CustomProvider{
  114. GPULabel: "gpu.example/type",
  115. },
  116. labels: map[string]string{
  117. "gpu.example/type": "a100",
  118. },
  119. capacity: v1.ResourceList{
  120. "nvidia.com/gpu": resource.MustParse("4"),
  121. },
  122. wantGPUType: "a100",
  123. wantGPUCount: 4,
  124. },
  125. {
  126. name: "no GPU capacity",
  127. capacity: v1.ResourceList{},
  128. wantGPUType: "",
  129. wantGPUCount: 0,
  130. },
  131. }
  132. for _, tt := range cases {
  133. t.Run(tt.name, func(t *testing.T) {
  134. customProvider := tt.provider
  135. if customProvider == nil {
  136. customProvider = &CustomProvider{}
  137. }
  138. labels := tt.labels
  139. if labels == nil {
  140. labels = map[string]string{}
  141. }
  142. key := customProvider.GetKey(labels, &clustercache.Node{
  143. Labels: labels,
  144. Status: v1.NodeStatus{
  145. Capacity: tt.capacity,
  146. },
  147. })
  148. if got := key.GPUType(); got != tt.wantGPUType {
  149. t.Errorf("GPUType() = %q, want %q", got, tt.wantGPUType)
  150. }
  151. if got := key.GPUCount(); got != tt.wantGPUCount {
  152. t.Errorf("GPUCount() = %d, want %d", got, tt.wantGPUCount)
  153. }
  154. })
  155. }
  156. }
  157. func TestCustomProviderNodePricingUsesDetectedGPUCount(t *testing.T) {
  158. customProvider := &CustomProvider{
  159. Pricing: map[string]*NodePrice{
  160. "default": {
  161. CPU: "0.031611",
  162. RAM: "0.004237",
  163. },
  164. "default,gpu": {
  165. CPU: "0.031611",
  166. RAM: "0.004237",
  167. GPU: "0.95",
  168. },
  169. },
  170. }
  171. key := customProvider.GetKey(map[string]string{}, &clustercache.Node{
  172. Status: v1.NodeStatus{
  173. Capacity: v1.ResourceList{
  174. "nvidia.com/gpu": resource.MustParse("2"),
  175. },
  176. },
  177. })
  178. node, _, err := customProvider.NodePricing(key)
  179. if err != nil {
  180. t.Fatalf("NodePricing returned error: %v", err)
  181. }
  182. if node.VCPUCost != "0.031611" {
  183. t.Errorf("VCPUCost = %q, want %q", node.VCPUCost, "0.031611")
  184. }
  185. if node.RAMCost != "0.004237" {
  186. t.Errorf("RAMCost = %q, want %q", node.RAMCost, "0.004237")
  187. }
  188. if node.GPUCost != "0.95" {
  189. t.Errorf("GPUCost = %q, want %q", node.GPUCost, "0.95")
  190. }
  191. if node.GPU != "2" {
  192. t.Errorf("GPU = %q, want %q", node.GPU, "2")
  193. }
  194. }
  195. func TestCustomProviderClusterInfoUsesStaticDefaultName(t *testing.T) {
  196. t.Setenv(coreenv.ClusterIDEnvVar, "")
  197. customProvider := newTestCustomProvider(t, nil)
  198. info, err := customProvider.ClusterInfo()
  199. if err != nil {
  200. t.Fatalf("ClusterInfo returned error: %v", err)
  201. }
  202. if info["name"] != "Custom Cluster" {
  203. t.Errorf("name = %q, want %q", info["name"], "Custom Cluster")
  204. }
  205. if info["id"] != "default-cluster" {
  206. t.Errorf("id = %q, want %q", info["id"], "default-cluster")
  207. }
  208. }
  209. func TestCustomProviderLoadBalancerPricingEmptyConfig(t *testing.T) {
  210. customProvider := newTestCustomProvider(t, nil)
  211. lb, err := customProvider.LoadBalancerPricing()
  212. if err != nil {
  213. t.Fatalf("LoadBalancerPricing returned error: %v", err)
  214. }
  215. if lb.Cost != 0 {
  216. t.Errorf("Cost = %f, want 0", lb.Cost)
  217. }
  218. }
  219. func TestCustomProviderLoadBalancerPricingUsesDefaultLBPriceFallback(t *testing.T) {
  220. customProvider := newTestCustomProvider(t, map[string]string{
  221. "defaultLBPrice": "0.025",
  222. })
  223. lb, err := customProvider.LoadBalancerPricing()
  224. if err != nil {
  225. t.Fatalf("LoadBalancerPricing returned error: %v", err)
  226. }
  227. if lb.Cost != 0.025 {
  228. t.Errorf("Cost = %f, want 0.025", lb.Cost)
  229. }
  230. }
  231. func TestCustomProviderLoadBalancerPricingUsesForwardingRulePrice(t *testing.T) {
  232. customProvider := newTestCustomProvider(t, map[string]string{
  233. "firstFiveForwardingRulesCost": "0.02",
  234. "defaultLBPrice": "0.025",
  235. })
  236. lb, err := customProvider.LoadBalancerPricing()
  237. if err != nil {
  238. t.Fatalf("LoadBalancerPricing returned error: %v", err)
  239. }
  240. if lb.Cost != 0.02 {
  241. t.Errorf("Cost = %f, want 0.02", lb.Cost)
  242. }
  243. }
  244. func TestCustomProviderLoadBalancerPricingInvalidValue(t *testing.T) {
  245. customProvider := newTestCustomProvider(t, map[string]string{
  246. "firstFiveForwardingRulesCost": "not-a-price",
  247. })
  248. _, err := customProvider.LoadBalancerPricing()
  249. if err == nil {
  250. t.Fatal("LoadBalancerPricing returned nil error, want invalid pricing error")
  251. }
  252. }
  253. func newTestCustomProvider(t *testing.T, pricing map[string]string) *CustomProvider {
  254. t.Helper()
  255. confMan := config.NewConfigFileManager(storage.NewMemoryStorage())
  256. providerConfig := NewProviderConfig(confMan, "default.json")
  257. if pricing != nil {
  258. if _, err := providerConfig.UpdateFromMap(pricing); err != nil {
  259. t.Fatalf("UpdateFromMap returned error: %v", err)
  260. }
  261. }
  262. return &CustomProvider{
  263. Config: providerConfig,
  264. }
  265. }