cluster_helpers_test.go 27 KB

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