cluster_helpers_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. package costmodel
  2. import (
  3. "github.com/kubecost/cost-model/pkg/config"
  4. "reflect"
  5. "testing"
  6. "time"
  7. "github.com/kubecost/cost-model/pkg/cloud"
  8. "github.com/kubecost/cost-model/pkg/prom"
  9. "github.com/kubecost/cost-model/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. )
  683. if !reflect.DeepEqual(result, testCase.expected) {
  684. t.Errorf("buildNodeMap case %s failed. Got %+v but expected %+v", testCase.name, result, testCase.expected)
  685. // Use spew because we have to follow pointers to figure out
  686. // what isn't matching up
  687. t.Logf("Got: %s", spew.Sdump(result))
  688. t.Logf("Expected: %s", spew.Sdump(testCase.expected))
  689. }
  690. })
  691. }
  692. }
  693. func TestBuildGPUCostMap(t *testing.T) {
  694. cases := []struct {
  695. name string
  696. promResult []*prom.QueryResult
  697. countMap map[NodeIdentifier]float64
  698. expected map[NodeIdentifier]float64
  699. }{
  700. {
  701. name: "All Zeros",
  702. promResult: []*prom.QueryResult{
  703. {
  704. Metric: map[string]interface{}{
  705. "cluster_id": "cluster1",
  706. "node": "node1",
  707. "instance_type": "type1",
  708. "provider_id": "provider1",
  709. },
  710. Values: []*util.Vector{
  711. &util.Vector{
  712. Timestamp: 0,
  713. Value: 0,
  714. },
  715. },
  716. },
  717. },
  718. countMap: map[NodeIdentifier]float64{
  719. NodeIdentifier{
  720. Cluster: "cluster1",
  721. Name: "node1",
  722. ProviderID: "provider1",
  723. }: 0,
  724. },
  725. expected: map[NodeIdentifier]float64{
  726. NodeIdentifier{
  727. Cluster: "cluster1",
  728. Name: "node1",
  729. ProviderID: "provider1",
  730. }: 0,
  731. },
  732. },
  733. {
  734. name: "Zero Node Count",
  735. promResult: []*prom.QueryResult{
  736. {
  737. Metric: map[string]interface{}{
  738. "cluster_id": "cluster1",
  739. "node": "node1",
  740. "instance_type": "type1",
  741. "provider_id": "provider1",
  742. },
  743. Values: []*util.Vector{
  744. &util.Vector{
  745. Timestamp: 0,
  746. Value: 2,
  747. },
  748. },
  749. },
  750. },
  751. countMap: map[NodeIdentifier]float64{
  752. NodeIdentifier{
  753. Cluster: "cluster1",
  754. Name: "node1",
  755. ProviderID: "provider1",
  756. }: 0,
  757. },
  758. expected: map[NodeIdentifier]float64{
  759. NodeIdentifier{
  760. Cluster: "cluster1",
  761. Name: "node1",
  762. ProviderID: "provider1",
  763. }: 2,
  764. },
  765. },
  766. {
  767. name: "Missing Node Count",
  768. promResult: []*prom.QueryResult{
  769. {
  770. Metric: map[string]interface{}{
  771. "cluster_id": "cluster1",
  772. "node": "node1",
  773. "instance_type": "type1",
  774. "provider_id": "provider1",
  775. },
  776. Values: []*util.Vector{
  777. &util.Vector{
  778. Timestamp: 0,
  779. Value: 2,
  780. },
  781. },
  782. },
  783. },
  784. countMap: map[NodeIdentifier]float64{},
  785. expected: map[NodeIdentifier]float64{
  786. NodeIdentifier{
  787. Cluster: "cluster1",
  788. Name: "node1",
  789. ProviderID: "provider1",
  790. }: 2,
  791. },
  792. },
  793. {
  794. name: "missing cost data",
  795. promResult: []*prom.QueryResult{
  796. {},
  797. },
  798. countMap: map[NodeIdentifier]float64{
  799. NodeIdentifier{
  800. Cluster: "cluster1",
  801. Name: "node1",
  802. ProviderID: "provider1",
  803. }: 0,
  804. },
  805. expected: map[NodeIdentifier]float64{},
  806. },
  807. {
  808. name: "All values present",
  809. promResult: []*prom.QueryResult{
  810. {
  811. Metric: map[string]interface{}{
  812. "cluster_id": "cluster1",
  813. "node": "node1",
  814. "instance_type": "type1",
  815. "provider_id": "provider1",
  816. },
  817. Values: []*util.Vector{
  818. &util.Vector{
  819. Timestamp: 0,
  820. Value: 2,
  821. },
  822. },
  823. },
  824. },
  825. countMap: map[NodeIdentifier]float64{
  826. NodeIdentifier{
  827. Cluster: "cluster1",
  828. Name: "node1",
  829. ProviderID: "provider1",
  830. }: 2,
  831. },
  832. expected: map[NodeIdentifier]float64{
  833. NodeIdentifier{
  834. Cluster: "cluster1",
  835. Name: "node1",
  836. ProviderID: "provider1",
  837. }: 4,
  838. },
  839. },
  840. }
  841. for _, testCase := range cases {
  842. t.Run(testCase.name, func(t *testing.T) {
  843. testProvider := &cloud.CustomProvider{
  844. Config: cloud.NewProviderConfig(config.NewConfigFileManager(nil), "fakeFile"),
  845. }
  846. testPreemptible := make(map[NodeIdentifier]bool)
  847. result, _ := buildGPUCostMap(testCase.promResult, testCase.countMap, testProvider, testPreemptible)
  848. if !reflect.DeepEqual(result, testCase.expected) {
  849. t.Errorf("buildGPUCostMap case %s failed. Got %+v but expected %+v", testCase.name, result, testCase.expected)
  850. }
  851. })
  852. }
  853. }
  854. func TestAssetCustompricing(t *testing.T) {
  855. nodePromResult := []*prom.QueryResult{
  856. {
  857. Metric: map[string]interface{}{
  858. "cluster_id": "cluster1",
  859. "node": "node1",
  860. "instance_type": "type1",
  861. "provider_id": "provider1",
  862. },
  863. Values: []*util.Vector{
  864. &util.Vector{
  865. Timestamp: 0,
  866. Value: 0.5,
  867. },
  868. },
  869. },
  870. }
  871. pvCostPromResult := []*prom.QueryResult{
  872. {
  873. Metric: map[string]interface{}{
  874. "cluster_id": "cluster1",
  875. "persistentvolume": "pvc1",
  876. "provider_id": "provider1",
  877. },
  878. Values: []*util.Vector{
  879. &util.Vector{
  880. Timestamp: 0,
  881. Value: 1.0,
  882. },
  883. },
  884. },
  885. }
  886. pvSizePromResult := []*prom.QueryResult{
  887. {
  888. Metric: map[string]interface{}{
  889. "cluster_id": "cluster1",
  890. "persistentvolume": "pvc1",
  891. "provider_id": "provider1",
  892. },
  893. Values: []*util.Vector{
  894. &util.Vector{
  895. Timestamp: 0,
  896. Value: 1073741824.0,
  897. },
  898. },
  899. },
  900. }
  901. pvMinsPromResult := []*prom.QueryResult{
  902. {
  903. Metric: map[string]interface{}{
  904. "cluster_id": "cluster1",
  905. "persistentvolume": "pvc1",
  906. "provider_id": "provider1",
  907. },
  908. Values: []*util.Vector{
  909. &util.Vector{
  910. Timestamp: 0,
  911. Value: 60.0,
  912. },
  913. },
  914. },
  915. }
  916. gpuCountMap := map[NodeIdentifier]float64{
  917. NodeIdentifier{
  918. Cluster: "cluster1",
  919. Name: "node1",
  920. ProviderID: "provider1",
  921. }: 2,
  922. }
  923. nodeKey := NodeIdentifier{
  924. Cluster: "cluster1",
  925. Name: "node1",
  926. ProviderID: "provider1",
  927. }
  928. cases := []struct {
  929. name string
  930. customPricingMap map[string]string
  931. expectedPricing map[string]float64
  932. }{
  933. {
  934. name: "No custom pricing",
  935. customPricingMap: map[string]string{},
  936. expectedPricing: map[string]float64{
  937. "CPU": 0.5,
  938. "RAM": 0.5,
  939. "GPU": 1.0,
  940. "Storage": 1.0,
  941. },
  942. },
  943. {
  944. name: "Custom pricing enabled",
  945. customPricingMap: map[string]string{
  946. "CPU": "20.0",
  947. "RAM": "4.0",
  948. "GPU": "500.0",
  949. "Storage": "0.1",
  950. "customPricesEnabled": "true",
  951. },
  952. expectedPricing: map[string]float64{
  953. "CPU": 0.027397, // 20.0 / 730
  954. "RAM": 5.102716386318207e-12, // 4.0 / 730 / 1024^3
  955. "GPU": 1.369864, // 500.0 / 730 * 2
  956. "Storage": 0.000137, // 0.1 / 730 * (1073741824.0 / 1024 / 1024 / 1024) * (60 / 60) => 0.1 / 730 * 1 * 1
  957. },
  958. },
  959. }
  960. for _, testCase := range cases {
  961. t.Run(testCase.name, func(t *testing.T) {
  962. testProvider := &cloud.CustomProvider{
  963. Config: cloud.NewProviderConfig(config.NewConfigFileManager(nil), ""),
  964. }
  965. testProvider.UpdateConfigFromConfigMap(testCase.customPricingMap)
  966. testPreemptible := make(map[NodeIdentifier]bool)
  967. cpuMap, _ := buildCPUCostMap(nodePromResult, testProvider, testPreemptible)
  968. ramMap, _ := buildRAMCostMap(nodePromResult, testProvider, testPreemptible)
  969. gpuMap, _ := buildGPUCostMap(nodePromResult, gpuCountMap, testProvider, testPreemptible)
  970. cpuResult := cpuMap[nodeKey]
  971. ramResult := ramMap[nodeKey]
  972. gpuResult := gpuMap[nodeKey]
  973. diskMap := map[string]*Disk{}
  974. pvCosts(diskMap, time.Hour, pvMinsPromResult, pvSizePromResult, pvCostPromResult, testProvider)
  975. diskResult := diskMap["cluster1/pvc1"].Cost
  976. if !util.IsApproximately(cpuResult, testCase.expectedPricing["CPU"]) {
  977. t.Errorf("CPU custom pricing error in %s. Got %v but expected %v", testCase.name, cpuResult, testCase.expectedPricing["CPU"])
  978. }
  979. if !util.IsApproximately(ramResult, testCase.expectedPricing["RAM"]) {
  980. t.Errorf("RAM custom pricing error in %s. Got %v but expected %v", testCase.name, ramResult, testCase.expectedPricing["RAM"])
  981. }
  982. if !util.IsApproximately(gpuResult, testCase.expectedPricing["GPU"]) {
  983. t.Errorf("GPU custom pricing error in %s. Got %v but expected %v", testCase.name, gpuResult, testCase.expectedPricing["GPU"])
  984. }
  985. if !util.IsApproximately(diskResult, testCase.expectedPricing["Storage"]) {
  986. t.Errorf("Disk custom pricing error in %s. Got %v but expected %v", testCase.name, diskResult, testCase.expectedPricing["Storage"])
  987. }
  988. })
  989. }
  990. }