costmodel_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package costmodel
  2. import (
  3. "testing"
  4. "github.com/opencost/opencost/core/pkg/util"
  5. "github.com/opencost/opencost/pkg/clustercache"
  6. "github.com/stretchr/testify/assert"
  7. v1 "k8s.io/api/core/v1"
  8. "k8s.io/apimachinery/pkg/api/resource"
  9. )
  10. func TestGetGPUCount(t *testing.T) {
  11. tests := []struct {
  12. name string
  13. node *clustercache.Node
  14. expectedGPU float64
  15. expectedVGPU float64
  16. expectedError bool
  17. }{
  18. {
  19. name: "Standard NVIDIA GPU",
  20. node: &clustercache.Node{
  21. Status: v1.NodeStatus{
  22. Capacity: v1.ResourceList{
  23. "nvidia.com/gpu": resource.MustParse("2"),
  24. },
  25. },
  26. },
  27. expectedGPU: 2.0,
  28. expectedVGPU: 2.0,
  29. },
  30. {
  31. name: "NVIDIA GPU with GFD - renameByDefault=true",
  32. node: &clustercache.Node{
  33. Labels: map[string]string{
  34. "nvidia.com/gpu.replicas": "4",
  35. "nvidia.com/gpu.count": "1",
  36. },
  37. Status: v1.NodeStatus{
  38. Capacity: v1.ResourceList{
  39. "nvidia.com/gpu.shared": resource.MustParse("4"),
  40. },
  41. },
  42. },
  43. expectedGPU: 1.0,
  44. expectedVGPU: 4.0,
  45. },
  46. {
  47. name: "NVIDIA GPU with GFD - renameByDefault=false",
  48. node: &clustercache.Node{
  49. Labels: map[string]string{
  50. "nvidia.com/gpu.replicas": "4",
  51. "nvidia.com/gpu.count": "1",
  52. },
  53. Status: v1.NodeStatus{
  54. Capacity: v1.ResourceList{
  55. "nvidia.com/gpu": resource.MustParse("4"),
  56. },
  57. },
  58. },
  59. expectedGPU: 1.0,
  60. expectedVGPU: 4.0,
  61. },
  62. {
  63. name: "No GPU",
  64. node: &clustercache.Node{
  65. Status: v1.NodeStatus{
  66. Capacity: v1.ResourceList{},
  67. },
  68. },
  69. expectedGPU: -1.0,
  70. expectedVGPU: -1.0,
  71. },
  72. }
  73. for _, tt := range tests {
  74. t.Run(tt.name, func(t *testing.T) {
  75. gpu, vgpu, err := getGPUCount(nil, tt.node)
  76. if tt.expectedError {
  77. assert.Error(t, err)
  78. } else {
  79. assert.NoError(t, err)
  80. assert.Equal(t, tt.expectedGPU, gpu)
  81. assert.Equal(t, tt.expectedVGPU, vgpu)
  82. }
  83. })
  84. }
  85. }
  86. func Test_CostData_GetController_CronJob(t *testing.T) {
  87. cases := []struct {
  88. name string
  89. cd CostData
  90. expectedName string
  91. expectedKind string
  92. expectedHasController bool
  93. }{
  94. {
  95. name: "batch/v1beta1 CronJob Job name",
  96. cd: CostData{
  97. // batch/v1beta1 CronJobs create Jobs with a 10 character
  98. // timestamp appended to the end of the name.
  99. //
  100. // It looks like this:
  101. // CronJob: cronjob-1
  102. // Job: cronjob-1-1651057200
  103. // Pod: cronjob-1-1651057200-mf5c9
  104. Jobs: []string{"cronjob-1-1651057200"},
  105. },
  106. expectedName: "cronjob-1",
  107. expectedKind: "job",
  108. expectedHasController: true,
  109. },
  110. {
  111. name: "batch/v1 CronJob Job name",
  112. cd: CostData{
  113. // batch/v1CronJobs create Jobs with an 8 character timestamp
  114. // appended to the end of the name.
  115. //
  116. // It looks like this:
  117. // CronJob: cj-v1
  118. // Job: cj-v1-27517770
  119. // Pod: cj-v1-27517770-xkrgn
  120. Jobs: []string{"cj-v1-27517770"},
  121. },
  122. expectedName: "cj-v1",
  123. expectedKind: "job",
  124. expectedHasController: true,
  125. },
  126. }
  127. for _, c := range cases {
  128. t.Run(c.name, func(t *testing.T) {
  129. name, kind, hasController := c.cd.GetController()
  130. if name != c.expectedName {
  131. t.Errorf("Name mismatch. Expected: %s. Got: %s", c.expectedName, name)
  132. }
  133. if kind != c.expectedKind {
  134. t.Errorf("Kind mismatch. Expected: %s. Got: %s", c.expectedKind, kind)
  135. }
  136. if hasController != c.expectedHasController {
  137. t.Errorf("HasController mismatch. Expected: %t. Got: %t", c.expectedHasController, hasController)
  138. }
  139. })
  140. }
  141. }
  142. func Test_getContainerAllocation(t *testing.T) {
  143. cases := []struct {
  144. name string
  145. cd CostData
  146. expectedCPUAllocation []*util.Vector
  147. expectedRAMAllocation []*util.Vector
  148. }{
  149. {
  150. name: "Requests greater than usage",
  151. cd: CostData{
  152. CPUReq: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  153. CPUUsed: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  154. RAMReq: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  155. RAMUsed: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  156. },
  157. expectedCPUAllocation: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  158. expectedRAMAllocation: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  159. },
  160. {
  161. name: "Requests less than usage",
  162. cd: CostData{
  163. CPUReq: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  164. CPUUsed: []*util.Vector{{Value: 2.2, Timestamp: 1686929350}},
  165. RAMReq: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  166. RAMUsed: []*util.Vector{{Value: 75000000, Timestamp: 1686929350}},
  167. },
  168. expectedCPUAllocation: []*util.Vector{{Value: 2.2, Timestamp: 1686929350}},
  169. expectedRAMAllocation: []*util.Vector{{Value: 75000000, Timestamp: 1686929350}},
  170. },
  171. {
  172. // Expected behavior for getContainerAllocation is to always use the
  173. // highest Timestamp value. The significance of 10 seconds comes
  174. // from the current default in ApplyVectorOp() in
  175. // pkg/util/vector.go.
  176. name: "Mismatched timestamps less than 10 seconds apart",
  177. cd: CostData{
  178. CPUReq: []*util.Vector{{Value: 1.0, Timestamp: 1686929354}},
  179. CPUUsed: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  180. RAMReq: []*util.Vector{{Value: 10000000, Timestamp: 1686929354}},
  181. RAMUsed: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  182. },
  183. expectedCPUAllocation: []*util.Vector{{Value: 1.0, Timestamp: 1686929354}},
  184. expectedRAMAllocation: []*util.Vector{{Value: 10000000, Timestamp: 1686929354}},
  185. },
  186. {
  187. // Expected behavior for getContainerAllocation is to always use the
  188. // hightest Timestamp value. The significance of 10 seconds comes
  189. // from the current default in ApplyVectorOp() in
  190. // pkg/util/vector.go.
  191. name: "Mismatched timestamps greater than 10 seconds apart",
  192. cd: CostData{
  193. CPUReq: []*util.Vector{{Value: 1.0, Timestamp: 1686929399}},
  194. CPUUsed: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  195. RAMReq: []*util.Vector{{Value: 10000000, Timestamp: 1686929399}},
  196. RAMUsed: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  197. },
  198. expectedCPUAllocation: []*util.Vector{{Value: 1.0, Timestamp: 1686929399}},
  199. expectedRAMAllocation: []*util.Vector{{Value: 10000000, Timestamp: 1686929399}},
  200. },
  201. {
  202. name: "Requests has no values",
  203. cd: CostData{
  204. CPUReq: []*util.Vector{{Value: 0, Timestamp: 0}},
  205. CPUUsed: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  206. RAMReq: []*util.Vector{{Value: 0, Timestamp: 0}},
  207. RAMUsed: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  208. },
  209. expectedCPUAllocation: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  210. expectedRAMAllocation: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  211. },
  212. {
  213. name: "Usage has no values",
  214. cd: CostData{
  215. CPUReq: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  216. CPUUsed: []*util.Vector{{Value: 0, Timestamp: 0}},
  217. RAMReq: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  218. RAMUsed: []*util.Vector{{Value: 0, Timestamp: 0}},
  219. },
  220. expectedCPUAllocation: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  221. expectedRAMAllocation: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  222. },
  223. {
  224. // WRN Log should be thrown
  225. name: "Both have no values",
  226. cd: CostData{
  227. CPUReq: []*util.Vector{{Value: 0, Timestamp: 0}},
  228. CPUUsed: []*util.Vector{{Value: 0, Timestamp: 0}},
  229. RAMReq: []*util.Vector{{Value: 0, Timestamp: 0}},
  230. RAMUsed: []*util.Vector{{Value: 0, Timestamp: 0}},
  231. },
  232. expectedCPUAllocation: []*util.Vector{{Value: 0, Timestamp: 0}},
  233. expectedRAMAllocation: []*util.Vector{{Value: 0, Timestamp: 0}},
  234. },
  235. {
  236. name: "Requests is Nil",
  237. cd: CostData{
  238. CPUReq: []*util.Vector{nil},
  239. CPUUsed: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  240. RAMReq: []*util.Vector{nil},
  241. RAMUsed: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  242. },
  243. expectedCPUAllocation: []*util.Vector{{Value: .01, Timestamp: 1686929350}},
  244. expectedRAMAllocation: []*util.Vector{{Value: 5500000, Timestamp: 1686929350}},
  245. },
  246. {
  247. name: "Usage is nil",
  248. cd: CostData{
  249. CPUReq: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  250. CPUUsed: []*util.Vector{nil},
  251. RAMReq: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  252. RAMUsed: []*util.Vector{nil},
  253. },
  254. expectedCPUAllocation: []*util.Vector{{Value: 1.0, Timestamp: 1686929350}},
  255. expectedRAMAllocation: []*util.Vector{{Value: 10000000, Timestamp: 1686929350}},
  256. },
  257. }
  258. for _, c := range cases {
  259. t.Run(c.name, func(t *testing.T) {
  260. cpuAllocation := getContainerAllocation(c.cd.CPUReq[0], c.cd.CPUUsed[0], "CPU")
  261. ramAllocation := getContainerAllocation(c.cd.RAMReq[0], c.cd.RAMUsed[0], "RAM")
  262. if cpuAllocation[0].Value != c.expectedCPUAllocation[0].Value {
  263. t.Errorf("CPU Allocation mismatch. Expected Value: %f. Got: %f", cpuAllocation[0].Value, c.expectedCPUAllocation[0].Value)
  264. }
  265. if cpuAllocation[0].Timestamp != c.expectedCPUAllocation[0].Timestamp {
  266. t.Errorf("CPU Allocation mismatch. Expected Timestamp: %f. Got: %f", cpuAllocation[0].Timestamp, c.expectedCPUAllocation[0].Timestamp)
  267. }
  268. if ramAllocation[0].Value != c.expectedRAMAllocation[0].Value {
  269. t.Errorf("RAM Allocation mismatch. Expected Value: %f. Got: %f", ramAllocation[0].Value, c.expectedRAMAllocation[0].Value)
  270. }
  271. if ramAllocation[0].Timestamp != c.expectedRAMAllocation[0].Timestamp {
  272. t.Errorf("RAM Allocation mismatch. Expected Timestamp: %f. Got: %f", ramAllocation[0].Timestamp, c.expectedRAMAllocation[0].Timestamp)
  273. }
  274. })
  275. }
  276. }