cluster_helpers_test.go 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. package costmodel
  2. import (
  3. "reflect"
  4. "testing"
  5. "time"
  6. "github.com/kubecost/cost-model/pkg/cloud"
  7. "github.com/kubecost/cost-model/pkg/prom"
  8. "github.com/kubecost/cost-model/pkg/util"
  9. "github.com/davecgh/go-spew/spew"
  10. )
  11. func TestMergeTypeMaps(t *testing.T) {
  12. cases := []struct {
  13. name string
  14. map1 map[nodeIdentifierNoProviderID]string
  15. map2 map[nodeIdentifierNoProviderID]string
  16. expected map[nodeIdentifierNoProviderID]string
  17. }{
  18. {
  19. name: "both empty",
  20. map1: map[nodeIdentifierNoProviderID]string{},
  21. map2: map[nodeIdentifierNoProviderID]string{},
  22. expected: map[nodeIdentifierNoProviderID]string{},
  23. },
  24. {
  25. name: "map2 empty",
  26. map1: map[nodeIdentifierNoProviderID]string{
  27. nodeIdentifierNoProviderID{
  28. Cluster: "cluster1",
  29. Name: "node1",
  30. }: "type1",
  31. },
  32. map2: map[nodeIdentifierNoProviderID]string{},
  33. expected: map[nodeIdentifierNoProviderID]string{
  34. nodeIdentifierNoProviderID{
  35. Cluster: "cluster1",
  36. Name: "node1",
  37. }: "type1",
  38. },
  39. },
  40. {
  41. name: "map1 empty",
  42. map1: map[nodeIdentifierNoProviderID]string{},
  43. map2: map[nodeIdentifierNoProviderID]string{
  44. nodeIdentifierNoProviderID{
  45. Cluster: "cluster1",
  46. Name: "node1",
  47. }: "type1",
  48. },
  49. expected: map[nodeIdentifierNoProviderID]string{
  50. nodeIdentifierNoProviderID{
  51. Cluster: "cluster1",
  52. Name: "node1",
  53. }: "type1",
  54. },
  55. },
  56. {
  57. name: "no overlap",
  58. map1: map[nodeIdentifierNoProviderID]string{
  59. nodeIdentifierNoProviderID{
  60. Cluster: "cluster1",
  61. Name: "node1",
  62. }: "type1",
  63. },
  64. map2: map[nodeIdentifierNoProviderID]string{
  65. nodeIdentifierNoProviderID{
  66. Cluster: "cluster1",
  67. Name: "node2",
  68. }: "type2",
  69. nodeIdentifierNoProviderID{
  70. Cluster: "cluster1",
  71. Name: "node4",
  72. }: "type4",
  73. },
  74. expected: map[nodeIdentifierNoProviderID]string{
  75. nodeIdentifierNoProviderID{
  76. Cluster: "cluster1",
  77. Name: "node1",
  78. }: "type1",
  79. nodeIdentifierNoProviderID{
  80. Cluster: "cluster1",
  81. Name: "node2",
  82. }: "type2",
  83. nodeIdentifierNoProviderID{
  84. Cluster: "cluster1",
  85. Name: "node4",
  86. }: "type4",
  87. },
  88. },
  89. {
  90. name: "with overlap",
  91. map1: map[nodeIdentifierNoProviderID]string{
  92. nodeIdentifierNoProviderID{
  93. Cluster: "cluster1",
  94. Name: "node1",
  95. }: "type1",
  96. },
  97. map2: map[nodeIdentifierNoProviderID]string{
  98. nodeIdentifierNoProviderID{
  99. Cluster: "cluster1",
  100. Name: "node2",
  101. }: "type2",
  102. nodeIdentifierNoProviderID{
  103. Cluster: "cluster1",
  104. Name: "node1",
  105. }: "type4",
  106. },
  107. expected: map[nodeIdentifierNoProviderID]string{
  108. nodeIdentifierNoProviderID{
  109. Cluster: "cluster1",
  110. Name: "node1",
  111. }: "type1",
  112. nodeIdentifierNoProviderID{
  113. Cluster: "cluster1",
  114. Name: "node2",
  115. }: "type2",
  116. },
  117. },
  118. }
  119. for _, testCase := range cases {
  120. t.Run(testCase.name, func(t *testing.T) {
  121. result := mergeTypeMaps(testCase.map1, testCase.map2)
  122. if !reflect.DeepEqual(result, testCase.expected) {
  123. t.Errorf("mergeTypeMaps case %s failed. Got %+v but expected %+v", testCase.name, result, testCase.expected)
  124. }
  125. })
  126. }
  127. }
  128. func TestBuildNodeMap(t *testing.T) {
  129. cases := []struct {
  130. name string
  131. cpuCostMap map[NodeIdentifier]float64
  132. ramCostMap map[NodeIdentifier]float64
  133. gpuCostMap map[NodeIdentifier]float64
  134. gpuCountMap map[NodeIdentifier]float64
  135. cpuCoresMap map[nodeIdentifierNoProviderID]float64
  136. ramBytesMap map[nodeIdentifierNoProviderID]float64
  137. ramUserPctMap map[nodeIdentifierNoProviderID]float64
  138. ramSystemPctMap map[nodeIdentifierNoProviderID]float64
  139. cpuBreakdownMap map[nodeIdentifierNoProviderID]*ClusterCostsBreakdown
  140. activeDataMap map[NodeIdentifier]activeData
  141. preemptibleMap map[NodeIdentifier]bool
  142. labelsMap map[nodeIdentifierNoProviderID]map[string]string
  143. clusterAndNameToType map[nodeIdentifierNoProviderID]string
  144. expected map[NodeIdentifier]*Node
  145. }{
  146. {
  147. name: "empty",
  148. expected: map[NodeIdentifier]*Node{},
  149. },
  150. {
  151. name: "just cpu cost",
  152. cpuCostMap: map[NodeIdentifier]float64{
  153. NodeIdentifier{
  154. Cluster: "cluster1",
  155. Name: "node1",
  156. ProviderID: "prov_node1",
  157. }: 0.048,
  158. },
  159. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  160. nodeIdentifierNoProviderID{
  161. Cluster: "cluster1",
  162. Name: "node1",
  163. }: "type1",
  164. },
  165. expected: map[NodeIdentifier]*Node{
  166. NodeIdentifier{
  167. Cluster: "cluster1",
  168. Name: "node1",
  169. ProviderID: "prov_node1",
  170. }: &Node{
  171. Cluster: "cluster1",
  172. Name: "node1",
  173. ProviderID: "prov_node1",
  174. NodeType: "type1",
  175. CPUCost: 0.048,
  176. CPUBreakdown: &ClusterCostsBreakdown{},
  177. RAMBreakdown: &ClusterCostsBreakdown{},
  178. },
  179. },
  180. },
  181. {
  182. name: "just cpu cost with empty provider ID",
  183. cpuCostMap: map[NodeIdentifier]float64{
  184. NodeIdentifier{
  185. Cluster: "cluster1",
  186. Name: "node1",
  187. }: 0.048,
  188. },
  189. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  190. nodeIdentifierNoProviderID{
  191. Cluster: "cluster1",
  192. Name: "node1",
  193. }: "type1",
  194. },
  195. expected: map[NodeIdentifier]*Node{
  196. NodeIdentifier{
  197. Cluster: "cluster1",
  198. Name: "node1",
  199. }: &Node{
  200. Cluster: "cluster1",
  201. Name: "node1",
  202. NodeType: "type1",
  203. CPUCost: 0.048,
  204. CPUBreakdown: &ClusterCostsBreakdown{},
  205. RAMBreakdown: &ClusterCostsBreakdown{},
  206. },
  207. },
  208. },
  209. {
  210. name: "cpu cost with overlapping node names",
  211. cpuCostMap: map[NodeIdentifier]float64{
  212. NodeIdentifier{
  213. Cluster: "cluster1",
  214. Name: "node1",
  215. ProviderID: "prov_node1_A",
  216. }: 0.048,
  217. NodeIdentifier{
  218. Cluster: "cluster1",
  219. Name: "node1",
  220. ProviderID: "prov_node1_B",
  221. }: 0.087,
  222. },
  223. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  224. nodeIdentifierNoProviderID{
  225. Cluster: "cluster1",
  226. Name: "node1",
  227. }: "type1",
  228. },
  229. expected: map[NodeIdentifier]*Node{
  230. NodeIdentifier{
  231. Cluster: "cluster1",
  232. Name: "node1",
  233. ProviderID: "prov_node1_A",
  234. }: &Node{
  235. Cluster: "cluster1",
  236. Name: "node1",
  237. ProviderID: "prov_node1_A",
  238. NodeType: "type1",
  239. CPUCost: 0.048,
  240. CPUBreakdown: &ClusterCostsBreakdown{},
  241. RAMBreakdown: &ClusterCostsBreakdown{},
  242. },
  243. NodeIdentifier{
  244. Cluster: "cluster1",
  245. Name: "node1",
  246. ProviderID: "prov_node1_B",
  247. }: &Node{
  248. Cluster: "cluster1",
  249. Name: "node1",
  250. ProviderID: "prov_node1_B",
  251. NodeType: "type1",
  252. CPUCost: 0.087,
  253. CPUBreakdown: &ClusterCostsBreakdown{},
  254. RAMBreakdown: &ClusterCostsBreakdown{},
  255. },
  256. },
  257. },
  258. {
  259. name: "all fields + overlapping node names",
  260. cpuCostMap: map[NodeIdentifier]float64{
  261. NodeIdentifier{
  262. Cluster: "cluster1",
  263. Name: "node1",
  264. ProviderID: "prov_node1_A",
  265. }: 0.048,
  266. NodeIdentifier{
  267. Cluster: "cluster1",
  268. Name: "node1",
  269. ProviderID: "prov_node1_B",
  270. }: 0.087,
  271. NodeIdentifier{
  272. Cluster: "cluster1",
  273. Name: "node2",
  274. ProviderID: "prov_node2_A",
  275. }: 0.033,
  276. },
  277. ramCostMap: map[NodeIdentifier]float64{
  278. NodeIdentifier{
  279. Cluster: "cluster1",
  280. Name: "node1",
  281. ProviderID: "prov_node1_A",
  282. }: 0.09,
  283. NodeIdentifier{
  284. Cluster: "cluster1",
  285. Name: "node1",
  286. ProviderID: "prov_node1_B",
  287. }: 0.3,
  288. NodeIdentifier{
  289. Cluster: "cluster1",
  290. Name: "node2",
  291. ProviderID: "prov_node2_A",
  292. }: 0.024,
  293. },
  294. gpuCostMap: map[NodeIdentifier]float64{
  295. NodeIdentifier{
  296. Cluster: "cluster1",
  297. Name: "node1",
  298. ProviderID: "prov_node1_A",
  299. }: 0.8,
  300. NodeIdentifier{
  301. Cluster: "cluster1",
  302. Name: "node1",
  303. ProviderID: "prov_node1_B",
  304. }: 1.4,
  305. NodeIdentifier{
  306. Cluster: "cluster1",
  307. Name: "node2",
  308. ProviderID: "prov_node2_A",
  309. }: 3.1,
  310. },
  311. gpuCountMap: map[NodeIdentifier]float64{
  312. NodeIdentifier{
  313. Cluster: "cluster1",
  314. Name: "node1",
  315. ProviderID: "prov_node1_A",
  316. }: 1.0,
  317. NodeIdentifier{
  318. Cluster: "cluster1",
  319. Name: "node1",
  320. ProviderID: "prov_node1_B",
  321. }: 1.0,
  322. NodeIdentifier{
  323. Cluster: "cluster1",
  324. Name: "node2",
  325. ProviderID: "prov_node2_A",
  326. }: 2.0,
  327. },
  328. cpuCoresMap: map[nodeIdentifierNoProviderID]float64{
  329. nodeIdentifierNoProviderID{
  330. Cluster: "cluster1",
  331. Name: "node1",
  332. }: 2.0,
  333. nodeIdentifierNoProviderID{
  334. Cluster: "cluster1",
  335. Name: "node2",
  336. }: 5.0,
  337. },
  338. ramBytesMap: map[nodeIdentifierNoProviderID]float64{
  339. nodeIdentifierNoProviderID{
  340. Cluster: "cluster1",
  341. Name: "node1",
  342. }: 2048.0,
  343. nodeIdentifierNoProviderID{
  344. Cluster: "cluster1",
  345. Name: "node2",
  346. }: 6303.0,
  347. },
  348. ramUserPctMap: map[nodeIdentifierNoProviderID]float64{
  349. nodeIdentifierNoProviderID{
  350. Cluster: "cluster1",
  351. Name: "node1",
  352. }: 30.0,
  353. nodeIdentifierNoProviderID{
  354. Cluster: "cluster1",
  355. Name: "node2",
  356. }: 42.6,
  357. },
  358. ramSystemPctMap: map[nodeIdentifierNoProviderID]float64{
  359. nodeIdentifierNoProviderID{
  360. Cluster: "cluster1",
  361. Name: "node1",
  362. }: 15.0,
  363. nodeIdentifierNoProviderID{
  364. Cluster: "cluster1",
  365. Name: "node2",
  366. }: 20.1,
  367. },
  368. cpuBreakdownMap: map[nodeIdentifierNoProviderID]*ClusterCostsBreakdown{
  369. nodeIdentifierNoProviderID{
  370. Cluster: "cluster1",
  371. Name: "node1",
  372. }: &ClusterCostsBreakdown{
  373. System: 20.2,
  374. User: 68.0,
  375. },
  376. nodeIdentifierNoProviderID{
  377. Cluster: "cluster1",
  378. Name: "node2",
  379. }: &ClusterCostsBreakdown{
  380. System: 28.9,
  381. User: 34.0,
  382. },
  383. },
  384. activeDataMap: map[NodeIdentifier]activeData{
  385. NodeIdentifier{
  386. Cluster: "cluster1",
  387. Name: "node1",
  388. ProviderID: "prov_node1_A",
  389. }: activeData{
  390. start: time.Date(2020, 6, 16, 3, 45, 28, 0, time.UTC),
  391. end: time.Date(2020, 6, 16, 9, 20, 39, 0, time.UTC),
  392. minutes: 5*60 + 35 + (11.0 / 60.0),
  393. },
  394. NodeIdentifier{
  395. Cluster: "cluster1",
  396. Name: "node1",
  397. ProviderID: "prov_node1_B",
  398. }: activeData{
  399. start: time.Date(2020, 6, 16, 3, 45, 28, 0, time.UTC),
  400. end: time.Date(2020, 6, 16, 9, 21, 39, 0, time.UTC),
  401. minutes: 5*60 + 36 + (11.0 / 60.0),
  402. },
  403. NodeIdentifier{
  404. Cluster: "cluster1",
  405. Name: "node2",
  406. ProviderID: "prov_node2_A",
  407. }: activeData{
  408. start: time.Date(2020, 6, 16, 3, 45, 28, 0, time.UTC),
  409. end: time.Date(2020, 6, 16, 9, 10, 39, 0, time.UTC),
  410. minutes: 5*60 + 25 + (11.0 / 60.0),
  411. },
  412. },
  413. preemptibleMap: map[NodeIdentifier]bool{
  414. NodeIdentifier{
  415. Cluster: "cluster1",
  416. Name: "node1",
  417. ProviderID: "prov_node1_A",
  418. }: true,
  419. NodeIdentifier{
  420. Cluster: "cluster1",
  421. Name: "node1",
  422. ProviderID: "prov_node1_B",
  423. }: false,
  424. NodeIdentifier{
  425. Cluster: "cluster1",
  426. Name: "node2",
  427. ProviderID: "prov_node2_A",
  428. }: false,
  429. },
  430. labelsMap: map[nodeIdentifierNoProviderID]map[string]string{
  431. nodeIdentifierNoProviderID{
  432. Cluster: "cluster1",
  433. Name: "node1",
  434. }: map[string]string{
  435. "labelname1_A": "labelvalue1_A",
  436. "labelname1_B": "labelvalue1_B",
  437. },
  438. nodeIdentifierNoProviderID{
  439. Cluster: "cluster1",
  440. Name: "node2",
  441. }: map[string]string{
  442. "labelname2_A": "labelvalue2_A",
  443. "labelname2_B": "labelvalue2_B",
  444. },
  445. },
  446. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  447. nodeIdentifierNoProviderID{
  448. Cluster: "cluster1",
  449. Name: "node1",
  450. }: "type1",
  451. nodeIdentifierNoProviderID{
  452. Cluster: "cluster1",
  453. Name: "node2",
  454. }: "type2",
  455. },
  456. expected: map[NodeIdentifier]*Node{
  457. NodeIdentifier{
  458. Cluster: "cluster1",
  459. Name: "node1",
  460. ProviderID: "prov_node1_A",
  461. }: &Node{
  462. Cluster: "cluster1",
  463. Name: "node1",
  464. ProviderID: "prov_node1_A",
  465. NodeType: "type1",
  466. CPUCost: 0.048,
  467. RAMCost: 0.09,
  468. GPUCost: 0.8,
  469. CPUCores: 2.0,
  470. GPUCount: 1.0,
  471. RAMBytes: 2048.0,
  472. RAMBreakdown: &ClusterCostsBreakdown{
  473. User: 30.0,
  474. System: 15.0,
  475. },
  476. CPUBreakdown: &ClusterCostsBreakdown{
  477. System: 20.2,
  478. User: 68.0,
  479. },
  480. Start: time.Date(2020, 6, 16, 3, 45, 28, 0, time.UTC),
  481. End: time.Date(2020, 6, 16, 9, 20, 39, 0, time.UTC),
  482. Minutes: 5*60 + 35 + (11.0 / 60.0),
  483. Preemptible: true,
  484. Labels: map[string]string{
  485. "labelname1_A": "labelvalue1_A",
  486. "labelname1_B": "labelvalue1_B",
  487. },
  488. },
  489. NodeIdentifier{
  490. Cluster: "cluster1",
  491. Name: "node1",
  492. ProviderID: "prov_node1_B",
  493. }: &Node{
  494. Cluster: "cluster1",
  495. Name: "node1",
  496. ProviderID: "prov_node1_B",
  497. NodeType: "type1",
  498. CPUCost: 0.087,
  499. RAMCost: 0.3,
  500. GPUCost: 1.4,
  501. CPUCores: 2.0,
  502. GPUCount: 1.0,
  503. RAMBytes: 2048.0,
  504. RAMBreakdown: &ClusterCostsBreakdown{
  505. User: 30.0,
  506. System: 15.0,
  507. },
  508. CPUBreakdown: &ClusterCostsBreakdown{
  509. System: 20.2,
  510. User: 68.0,
  511. },
  512. Start: time.Date(2020, 6, 16, 3, 45, 28, 0, time.UTC),
  513. End: time.Date(2020, 6, 16, 9, 21, 39, 0, time.UTC),
  514. Minutes: 5*60 + 36 + (11.0 / 60.0),
  515. Preemptible: false,
  516. Labels: map[string]string{
  517. "labelname1_A": "labelvalue1_A",
  518. "labelname1_B": "labelvalue1_B",
  519. },
  520. },
  521. NodeIdentifier{
  522. Cluster: "cluster1",
  523. Name: "node2",
  524. ProviderID: "prov_node2_A",
  525. }: &Node{
  526. Cluster: "cluster1",
  527. Name: "node2",
  528. ProviderID: "prov_node2_A",
  529. NodeType: "type2",
  530. CPUCost: 0.033,
  531. RAMCost: 0.024,
  532. GPUCost: 3.1,
  533. CPUCores: 5.0,
  534. GPUCount: 2.0,
  535. RAMBytes: 6303.0,
  536. RAMBreakdown: &ClusterCostsBreakdown{
  537. User: 42.6,
  538. System: 20.1,
  539. },
  540. CPUBreakdown: &ClusterCostsBreakdown{
  541. System: 28.9,
  542. User: 34.0,
  543. },
  544. Start: time.Date(2020, 6, 16, 3, 45, 28, 0, time.UTC),
  545. End: time.Date(2020, 6, 16, 9, 10, 39, 0, time.UTC),
  546. Minutes: 5*60 + 25 + (11.0 / 60.0),
  547. Preemptible: false,
  548. Labels: map[string]string{
  549. "labelname2_A": "labelvalue2_A",
  550. "labelname2_B": "labelvalue2_B",
  551. },
  552. },
  553. },
  554. },
  555. {
  556. name: "e2-micro cpu cost adjustment",
  557. cpuCostMap: map[NodeIdentifier]float64{
  558. NodeIdentifier{
  559. Cluster: "cluster1",
  560. Name: "node1",
  561. ProviderID: "prov_node1",
  562. }: 0.048,
  563. },
  564. cpuCoresMap: map[nodeIdentifierNoProviderID]float64{
  565. nodeIdentifierNoProviderID{
  566. Cluster: "cluster1",
  567. Name: "node1",
  568. }: 6.0, // GKE lies about number of cores
  569. },
  570. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  571. nodeIdentifierNoProviderID{
  572. Cluster: "cluster1",
  573. Name: "node1",
  574. }: "e2-micro", // for this node type
  575. },
  576. expected: map[NodeIdentifier]*Node{
  577. NodeIdentifier{
  578. Cluster: "cluster1",
  579. Name: "node1",
  580. ProviderID: "prov_node1",
  581. }: &Node{
  582. Cluster: "cluster1",
  583. Name: "node1",
  584. ProviderID: "prov_node1",
  585. NodeType: "e2-micro",
  586. CPUCost: 0.048 * (partialCPUMap["e2-micro"] / 6.0), // adjustmentFactor is (v / GKE cores)
  587. CPUCores: partialCPUMap["e2-micro"],
  588. CPUBreakdown: &ClusterCostsBreakdown{},
  589. RAMBreakdown: &ClusterCostsBreakdown{},
  590. },
  591. },
  592. },
  593. {
  594. name: "e2-small cpu cost adjustment",
  595. cpuCostMap: map[NodeIdentifier]float64{
  596. NodeIdentifier{
  597. Cluster: "cluster1",
  598. Name: "node1",
  599. ProviderID: "prov_node1",
  600. }: 0.048,
  601. },
  602. cpuCoresMap: map[nodeIdentifierNoProviderID]float64{
  603. nodeIdentifierNoProviderID{
  604. Cluster: "cluster1",
  605. Name: "node1",
  606. }: 6.0, // GKE lies about number of cores
  607. },
  608. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  609. nodeIdentifierNoProviderID{
  610. Cluster: "cluster1",
  611. Name: "node1",
  612. }: "e2-small", // for this node type
  613. },
  614. expected: map[NodeIdentifier]*Node{
  615. NodeIdentifier{
  616. Cluster: "cluster1",
  617. Name: "node1",
  618. ProviderID: "prov_node1",
  619. }: &Node{
  620. Cluster: "cluster1",
  621. Name: "node1",
  622. ProviderID: "prov_node1",
  623. NodeType: "e2-small",
  624. CPUCost: 0.048 * (partialCPUMap["e2-small"] / 6.0), // adjustmentFactor is (v / GKE cores)
  625. CPUCores: partialCPUMap["e2-small"],
  626. CPUBreakdown: &ClusterCostsBreakdown{},
  627. RAMBreakdown: &ClusterCostsBreakdown{},
  628. },
  629. },
  630. },
  631. {
  632. name: "e2-medium cpu cost adjustment",
  633. cpuCostMap: map[NodeIdentifier]float64{
  634. NodeIdentifier{
  635. Cluster: "cluster1",
  636. Name: "node1",
  637. ProviderID: "prov_node1",
  638. }: 0.048,
  639. },
  640. cpuCoresMap: map[nodeIdentifierNoProviderID]float64{
  641. nodeIdentifierNoProviderID{
  642. Cluster: "cluster1",
  643. Name: "node1",
  644. }: 6.0, // GKE lies about number of cores
  645. },
  646. clusterAndNameToType: map[nodeIdentifierNoProviderID]string{
  647. nodeIdentifierNoProviderID{
  648. Cluster: "cluster1",
  649. Name: "node1",
  650. }: "e2-medium", // for this node type
  651. },
  652. expected: map[NodeIdentifier]*Node{
  653. NodeIdentifier{
  654. Cluster: "cluster1",
  655. Name: "node1",
  656. ProviderID: "prov_node1",
  657. }: &Node{
  658. Cluster: "cluster1",
  659. Name: "node1",
  660. ProviderID: "prov_node1",
  661. NodeType: "e2-medium",
  662. CPUCost: 0.048 * (partialCPUMap["e2-medium"] / 6.0), // adjustmentFactor is (v / GKE cores)
  663. CPUCores: partialCPUMap["e2-medium"],
  664. CPUBreakdown: &ClusterCostsBreakdown{},
  665. RAMBreakdown: &ClusterCostsBreakdown{},
  666. },
  667. },
  668. },
  669. }
  670. for _, testCase := range cases {
  671. t.Run(testCase.name, func(t *testing.T) {
  672. result := buildNodeMap(
  673. testCase.cpuCostMap, testCase.ramCostMap, testCase.gpuCostMap, testCase.gpuCountMap,
  674. testCase.cpuCoresMap, testCase.ramBytesMap, testCase.ramUserPctMap,
  675. testCase.ramSystemPctMap,
  676. testCase.cpuBreakdownMap,
  677. testCase.activeDataMap,
  678. testCase.preemptibleMap,
  679. testCase.labelsMap,
  680. testCase.clusterAndNameToType,
  681. )
  682. if !reflect.DeepEqual(result, testCase.expected) {
  683. t.Errorf("buildNodeMap case %s failed. Got %+v but expected %+v", testCase.name, result, testCase.expected)
  684. // Use spew because we have to follow pointers to figure out
  685. // what isn't matching up
  686. t.Logf("Got: %s", spew.Sdump(result))
  687. t.Logf("Expected: %s", spew.Sdump(testCase.expected))
  688. }
  689. })
  690. }
  691. }
  692. func TestBuildGPUCostMap(t *testing.T) {
  693. cases := []struct {
  694. name string
  695. promResult []*prom.QueryResult
  696. countMap map[NodeIdentifier]float64
  697. expected map[NodeIdentifier]float64
  698. }{
  699. {
  700. name: "All Zeros",
  701. promResult: []*prom.QueryResult{
  702. {
  703. Metric: map[string]interface{}{
  704. "cluster_id": "cluster1",
  705. "node": "node1",
  706. "instance_type": "type1",
  707. "provider_id": "provider1",
  708. },
  709. Values: []*util.Vector{
  710. &util.Vector{
  711. Timestamp: 0,
  712. Value: 0,
  713. },
  714. },
  715. },
  716. },
  717. countMap: map[NodeIdentifier]float64{
  718. NodeIdentifier{
  719. Cluster: "cluster1",
  720. Name: "node1",
  721. ProviderID: "provider1",
  722. }: 0,
  723. },
  724. expected: map[NodeIdentifier]float64{
  725. NodeIdentifier{
  726. Cluster: "cluster1",
  727. Name: "node1",
  728. ProviderID: "provider1",
  729. }: 0,
  730. },
  731. },
  732. {
  733. name: "Zero Node Count",
  734. promResult: []*prom.QueryResult{
  735. {
  736. Metric: map[string]interface{}{
  737. "cluster_id": "cluster1",
  738. "node": "node1",
  739. "instance_type": "type1",
  740. "provider_id": "provider1",
  741. },
  742. Values: []*util.Vector{
  743. &util.Vector{
  744. Timestamp: 0,
  745. Value: 2,
  746. },
  747. },
  748. },
  749. },
  750. countMap: map[NodeIdentifier]float64{
  751. NodeIdentifier{
  752. Cluster: "cluster1",
  753. Name: "node1",
  754. ProviderID: "provider1",
  755. }: 0,
  756. },
  757. expected: map[NodeIdentifier]float64{
  758. NodeIdentifier{
  759. Cluster: "cluster1",
  760. Name: "node1",
  761. ProviderID: "provider1",
  762. }: 2,
  763. },
  764. },
  765. {
  766. name: "Missing Node Count",
  767. promResult: []*prom.QueryResult{
  768. {
  769. Metric: map[string]interface{}{
  770. "cluster_id": "cluster1",
  771. "node": "node1",
  772. "instance_type": "type1",
  773. "provider_id": "provider1",
  774. },
  775. Values: []*util.Vector{
  776. &util.Vector{
  777. Timestamp: 0,
  778. Value: 2,
  779. },
  780. },
  781. },
  782. },
  783. countMap: map[NodeIdentifier]float64{},
  784. expected: map[NodeIdentifier]float64{
  785. NodeIdentifier{
  786. Cluster: "cluster1",
  787. Name: "node1",
  788. ProviderID: "provider1",
  789. }: 2,
  790. },
  791. },
  792. {
  793. name: "missing cost data",
  794. promResult: []*prom.QueryResult{
  795. {},
  796. },
  797. countMap: map[NodeIdentifier]float64{
  798. NodeIdentifier{
  799. Cluster: "cluster1",
  800. Name: "node1",
  801. ProviderID: "provider1",
  802. }: 0,
  803. },
  804. expected: map[NodeIdentifier]float64{},
  805. },
  806. {
  807. name: "All values present",
  808. promResult: []*prom.QueryResult{
  809. {
  810. Metric: map[string]interface{}{
  811. "cluster_id": "cluster1",
  812. "node": "node1",
  813. "instance_type": "type1",
  814. "provider_id": "provider1",
  815. },
  816. Values: []*util.Vector{
  817. &util.Vector{
  818. Timestamp: 0,
  819. Value: 2,
  820. },
  821. },
  822. },
  823. },
  824. countMap: map[NodeIdentifier]float64{
  825. NodeIdentifier{
  826. Cluster: "cluster1",
  827. Name: "node1",
  828. ProviderID: "provider1",
  829. }: 2,
  830. },
  831. expected: map[NodeIdentifier]float64{
  832. NodeIdentifier{
  833. Cluster: "cluster1",
  834. Name: "node1",
  835. ProviderID: "provider1",
  836. }: 4,
  837. },
  838. },
  839. }
  840. for _, testCase := range cases {
  841. t.Run(testCase.name, func(t *testing.T) {
  842. testProvider := &cloud.CustomProvider{
  843. Config: cloud.NewProviderConfig("fakeFile"),
  844. }
  845. testPreemptible := make(map[NodeIdentifier]bool)
  846. result, _ := buildGPUCostMap(testCase.promResult, testCase.countMap, testProvider, testPreemptible)
  847. if !reflect.DeepEqual(result, testCase.expected) {
  848. t.Errorf("buildGPUCostMap case %s failed. Got %+v but expected %+v", testCase.name, result, testCase.expected)
  849. }
  850. })
  851. }
  852. }
  853. func TestAssetCustompricing(t *testing.T) {
  854. nodePromResult := []*prom.QueryResult{
  855. {
  856. Metric: map[string]interface{}{
  857. "cluster_id": "cluster1",
  858. "node": "node1",
  859. "instance_type": "type1",
  860. "provider_id": "provider1",
  861. },
  862. Values: []*util.Vector{
  863. &util.Vector{
  864. Timestamp: 0,
  865. Value: 0.5,
  866. },
  867. },
  868. },
  869. }
  870. pvCostPromResult := []*prom.QueryResult{
  871. {
  872. Metric: map[string]interface{}{
  873. "cluster_id": "cluster1",
  874. "persistentvolume": "pvc1",
  875. "provider_id": "provider1",
  876. },
  877. Values: []*util.Vector{
  878. &util.Vector{
  879. Timestamp: 0,
  880. Value: 1.0,
  881. },
  882. },
  883. },
  884. }
  885. pvSizePromResult := []*prom.QueryResult{
  886. {
  887. Metric: map[string]interface{}{
  888. "cluster_id": "cluster1",
  889. "persistentvolume": "pvc1",
  890. "provider_id": "provider1",
  891. },
  892. Values: []*util.Vector{
  893. &util.Vector{
  894. Timestamp: 0,
  895. Value: 1073741824.0,
  896. },
  897. },
  898. },
  899. }
  900. pvMinsPromResult := []*prom.QueryResult{
  901. {
  902. Metric: map[string]interface{}{
  903. "cluster_id": "cluster1",
  904. "persistentvolume": "pvc1",
  905. "provider_id": "provider1",
  906. },
  907. Values: []*util.Vector{
  908. &util.Vector{
  909. Timestamp: 0,
  910. Value: 60.0,
  911. },
  912. },
  913. },
  914. }
  915. gpuCountMap := map[NodeIdentifier]float64{
  916. NodeIdentifier{
  917. Cluster: "cluster1",
  918. Name: "node1",
  919. ProviderID: "provider1",
  920. }: 2,
  921. }
  922. nodeKey := NodeIdentifier{
  923. Cluster: "cluster1",
  924. Name: "node1",
  925. ProviderID: "provider1",
  926. }
  927. cases := []struct {
  928. name string
  929. customPricingMap map[string]string
  930. expectedPricing map[string]float64
  931. }{
  932. {
  933. name: "No custom pricing",
  934. customPricingMap: map[string]string{},
  935. expectedPricing: map[string]float64{
  936. "CPU": 0.5,
  937. "RAM": 0.5,
  938. "GPU": 1.0,
  939. "Storage": 1.0,
  940. },
  941. },
  942. {
  943. name: "Custom pricing enabled",
  944. customPricingMap: map[string]string{
  945. "CPU": "20.0",
  946. "RAM": "4.0",
  947. "GPU": "500.0",
  948. "Storage": "0.1",
  949. "customPricesEnabled": "true",
  950. },
  951. expectedPricing: map[string]float64{
  952. "CPU": 0.027397, // 20.0 / 730
  953. "RAM": 5.102716386318207e-12, // 4.0 / 730 / 1024^3
  954. "GPU": 1.369864, // 500.0 / 730 * 2
  955. "Storage": 0.000137, // 0.1 / 730 * (1073741824.0 / 1024 / 1024 / 1024) * (60 / 60) => 0.1 / 730 * 1 * 1
  956. },
  957. },
  958. }
  959. for _, testCase := range cases {
  960. t.Run(testCase.name, func(t *testing.T) {
  961. testProvider := &cloud.CustomProvider{
  962. Config: cloud.NewProviderConfig(""),
  963. }
  964. testProvider.UpdateConfigFromConfigMap(testCase.customPricingMap)
  965. testPreemptible := make(map[NodeIdentifier]bool)
  966. cpuMap, _ := buildCPUCostMap(nodePromResult, testProvider, testPreemptible)
  967. ramMap, _ := buildRAMCostMap(nodePromResult, testProvider, testPreemptible)
  968. gpuMap, _ := buildGPUCostMap(nodePromResult, gpuCountMap, testProvider, testPreemptible)
  969. cpuResult := cpuMap[nodeKey]
  970. ramResult := ramMap[nodeKey]
  971. gpuResult := gpuMap[nodeKey]
  972. diskMap := map[string]*Disk{}
  973. pvCosts(diskMap, time.Hour, pvMinsPromResult, pvSizePromResult, pvCostPromResult, testProvider)
  974. diskResult := diskMap["cluster1/pvc1"].Cost
  975. if !util.IsApproximately(cpuResult, testCase.expectedPricing["CPU"]) {
  976. t.Errorf("CPU custom pricing error in %s. Got %v but expected %v", testCase.name, cpuResult, testCase.expectedPricing["CPU"])
  977. }
  978. if !util.IsApproximately(ramResult, testCase.expectedPricing["RAM"]) {
  979. t.Errorf("RAM custom pricing error in %s. Got %v but expected %v", testCase.name, ramResult, testCase.expectedPricing["RAM"])
  980. }
  981. if !util.IsApproximately(gpuResult, testCase.expectedPricing["GPU"]) {
  982. t.Errorf("GPU custom pricing error in %s. Got %v but expected %v", testCase.name, gpuResult, testCase.expectedPricing["GPU"])
  983. }
  984. if !util.IsApproximately(diskResult, testCase.expectedPricing["Storage"]) {
  985. t.Errorf("Disk custom pricing error in %s. Got %v but expected %v", testCase.name, diskResult, testCase.expectedPricing["Storage"])
  986. }
  987. })
  988. }
  989. }