cluster_helpers_test.go 28 KB

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