allocation_test.go 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450
  1. package kubecost
  2. import (
  3. "fmt"
  4. "math"
  5. "testing"
  6. "time"
  7. util "github.com/kubecost/cost-model/pkg/util"
  8. )
  9. const day = 24 * time.Hour
  10. func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *Properties) *Allocation {
  11. if name == "" {
  12. name = "cluster1/namespace1/pod1/container1"
  13. }
  14. properties := &Properties{}
  15. if props == nil {
  16. properties.SetCluster("cluster1")
  17. properties.SetNode("node1")
  18. properties.SetNamespace("namespace1")
  19. properties.SetControllerKind("deployment")
  20. properties.SetController("deployment1")
  21. properties.SetPod("pod1")
  22. properties.SetContainer("container1")
  23. } else {
  24. properties = props
  25. }
  26. end := start.Add(resolution)
  27. alloc := &Allocation{
  28. Name: name,
  29. Properties: *properties,
  30. Start: start,
  31. End: end,
  32. Minutes: 1440,
  33. CPUCoreHours: 1,
  34. CPUCost: 1,
  35. CPUEfficiency: 1,
  36. GPUHours: 1,
  37. GPUCost: 1,
  38. NetworkCost: 1,
  39. PVByteHours: 1,
  40. PVCost: 1,
  41. RAMByteHours: 1,
  42. RAMCost: 1,
  43. RAMEfficiency: 1,
  44. TotalCost: 5,
  45. TotalEfficiency: 1,
  46. }
  47. // If idle allocation, remove non-idle costs, but maintain total cost
  48. if alloc.IsIdle() {
  49. alloc.PVByteHours = 0.0
  50. alloc.PVCost = 0.0
  51. alloc.NetworkCost = 0.0
  52. alloc.CPUCoreHours += 1.0
  53. alloc.CPUCost += 1.0
  54. alloc.RAMByteHours += 1.0
  55. alloc.RAMCost += 1.0
  56. }
  57. return alloc
  58. }
  59. func TestAllocation_Add(t *testing.T) {
  60. var nilAlloc *Allocation
  61. zeroAlloc := &Allocation{}
  62. // nil + nil == nil
  63. nilNilSum, err := nilAlloc.Add(nilAlloc)
  64. if err != nil {
  65. t.Fatalf("Allocation.Add unexpected error: %s", err)
  66. }
  67. if nilNilSum != nil {
  68. t.Fatalf("Allocation.Add failed; exp: nil; act: %s", nilNilSum)
  69. }
  70. // nil + zero == zero
  71. nilZeroSum, err := nilAlloc.Add(zeroAlloc)
  72. if err != nil {
  73. t.Fatalf("Allocation.Add unexpected error: %s", err)
  74. }
  75. if nilZeroSum == nil || nilZeroSum.TotalCost != 0.0 {
  76. t.Fatalf("Allocation.Add failed; exp: 0.0; act: %s", nilZeroSum)
  77. }
  78. // TODO niko/etl more
  79. }
  80. // TODO niko/etl
  81. // func TestAllocation_Clone(t *testing.T) {}
  82. // TODO niko/etl
  83. // func TestAllocation_IsIdle(t *testing.T) {}
  84. func TestAllocation_String(t *testing.T) {
  85. // TODO niko/etl
  86. }
  87. func TestNewAllocationSet(t *testing.T) {
  88. // TODO niko/etl
  89. }
  90. func generateAllocationSet(start time.Time) *AllocationSet {
  91. // Idle allocations
  92. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
  93. ClusterProp: "cluster1",
  94. NodeProp: "node1",
  95. })
  96. a1i.CPUCost = 5.0
  97. a1i.RAMCost = 15.0
  98. a1i.GPUCost = 0.0
  99. a1i.TotalCost = 20.0
  100. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
  101. ClusterProp: "cluster2",
  102. })
  103. a2i.CPUCost = 5.0
  104. a2i.RAMCost = 5.0
  105. a2i.GPUCost = 0.0
  106. a2i.TotalCost = 10.0
  107. // Active allocations
  108. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
  109. ClusterProp: "cluster1",
  110. NamespaceProp: "namespace1",
  111. PodProp: "pod1",
  112. ContainerProp: "container1",
  113. })
  114. a1111.RAMCost = 11.00
  115. a1111.TotalCost = 15.00
  116. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
  117. ClusterProp: "cluster1",
  118. NamespaceProp: "namespace1",
  119. PodProp: "pod-abc",
  120. ContainerProp: "container2",
  121. })
  122. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
  123. ClusterProp: "cluster1",
  124. NamespaceProp: "namespace1",
  125. PodProp: "pod-def",
  126. ContainerProp: "container3",
  127. })
  128. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
  129. ClusterProp: "cluster1",
  130. NamespaceProp: "namespace2",
  131. PodProp: "pod-ghi",
  132. ContainerProp: "container4",
  133. })
  134. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
  135. ClusterProp: "cluster1",
  136. NamespaceProp: "namespace2",
  137. PodProp: "pod-ghi",
  138. ContainerProp: "container5",
  139. })
  140. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
  141. ClusterProp: "cluster1",
  142. NamespaceProp: "namespace2",
  143. PodProp: "pod-jkl",
  144. ContainerProp: "container6",
  145. })
  146. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
  147. ClusterProp: "cluster2",
  148. NamespaceProp: "namespace2",
  149. PodProp: "pod-mno",
  150. ContainerProp: "container4",
  151. })
  152. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
  153. ClusterProp: "cluster2",
  154. NamespaceProp: "namespace2",
  155. PodProp: "pod-mno",
  156. ContainerProp: "container5",
  157. })
  158. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
  159. ClusterProp: "cluster2",
  160. NamespaceProp: "namespace2",
  161. PodProp: "pod-pqr",
  162. ContainerProp: "container6",
  163. })
  164. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
  165. ClusterProp: "cluster2",
  166. NamespaceProp: "namespace3",
  167. PodProp: "pod-stu",
  168. ContainerProp: "container7",
  169. })
  170. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
  171. ClusterProp: "cluster2",
  172. NamespaceProp: "namespace3",
  173. PodProp: "pod-vwx",
  174. ContainerProp: "container8",
  175. })
  176. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
  177. ClusterProp: "cluster2",
  178. NamespaceProp: "namespace3",
  179. PodProp: "pod-vwx",
  180. ContainerProp: "container9",
  181. })
  182. // Controllers
  183. a11abc2.Properties.SetControllerKind("deployment")
  184. a11abc2.Properties.SetController("deployment1")
  185. a11def3.Properties.SetControllerKind("deployment")
  186. a11def3.Properties.SetController("deployment1")
  187. a12ghi4.Properties.SetControllerKind("deployment")
  188. a12ghi4.Properties.SetController("deployment2")
  189. a12ghi5.Properties.SetControllerKind("deployment")
  190. a12ghi5.Properties.SetController("deployment2")
  191. a22mno4.Properties.SetControllerKind("deployment")
  192. a22mno4.Properties.SetController("deployment2")
  193. a22mno5.Properties.SetControllerKind("deployment")
  194. a22mno5.Properties.SetController("deployment2")
  195. a23stu7.Properties.SetControllerKind("deployment")
  196. a23stu7.Properties.SetController("deployment3")
  197. a12jkl6.Properties.SetControllerKind("daemonset")
  198. a12jkl6.Properties.SetController("daemonset1")
  199. a22pqr6.Properties.SetControllerKind("daemonset")
  200. a22pqr6.Properties.SetController("daemonset1")
  201. a23vwx8.Properties.SetControllerKind("statefulset")
  202. a23vwx8.Properties.SetController("statefulset1")
  203. a23vwx9.Properties.SetControllerKind("statefulset")
  204. a23vwx9.Properties.SetController("statefulset1")
  205. // Labels
  206. a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
  207. a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  208. a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  209. a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
  210. a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
  211. //Annotations
  212. a23stu7.Properties.SetAnnotations(map[string]string{"team": "team1"})
  213. a23vwx8.Properties.SetAnnotations(map[string]string{"team": "team2"})
  214. a23vwx9.Properties.SetAnnotations(map[string]string{"team": "team1"})
  215. // Services
  216. a12jkl6.Properties.SetServices([]string{"service1"})
  217. a22pqr6.Properties.SetServices([]string{"service1"})
  218. return NewAllocationSet(start, start.Add(day),
  219. // idle
  220. a1i, a2i,
  221. // cluster 1, namespace1
  222. a1111, a11abc2, a11def3,
  223. // cluster 1, namespace 2
  224. a12ghi4, a12ghi5, a12jkl6,
  225. // cluster 2, namespace 2
  226. a22mno4, a22mno5, a22pqr6,
  227. // cluster 2, namespace 3
  228. a23stu7, a23vwx8, a23vwx9,
  229. )
  230. }
  231. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  232. if err != nil {
  233. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  234. }
  235. if as.Length() != length {
  236. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  237. }
  238. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  239. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  240. }
  241. }
  242. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  243. as.Each(func(k string, a *Allocation) {
  244. if exp, ok := exps[a.Name]; ok {
  245. if math.Round(a.TotalCost*100) != math.Round(exp*100) {
  246. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost)
  247. }
  248. } else {
  249. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  250. }
  251. })
  252. }
  253. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  254. as.Each(func(k string, a *Allocation) {
  255. if !a.Start.Equal(expStart) {
  256. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  257. }
  258. if !a.End.Equal(expEnd) {
  259. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  260. }
  261. if a.Minutes != expMinutes {
  262. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes)
  263. }
  264. })
  265. }
  266. func printAllocationSet(msg string, as *AllocationSet) {
  267. fmt.Printf("--- %s ---\n", msg)
  268. as.Each(func(k string, a *Allocation) {
  269. fmt.Printf(" > %s\n", a)
  270. })
  271. }
  272. func TestAllocationSet_AggregateBy(t *testing.T) {
  273. // Test AggregateBy against the following workload topology, which is
  274. // generated by generateAllocationSet:
  275. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
  276. // +----------------------------------------+------+------+------+------+------+------+
  277. // cluster1:
  278. // idle: 20.00 5.00 15.00 0.00 0.00 0.00
  279. // namespace1:
  280. // pod1:
  281. // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
  282. // pod-abc: (deployment1)
  283. // container2: 5.00 1.00 1.00 1.00 1.00 1.00
  284. // pod-def: (deployment1)
  285. // container3: 5.00 1.00 1.00 1.00 1.00 1.00
  286. // namespace2:
  287. // pod-ghi: (deployment2)
  288. // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  289. // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  290. // pod-jkl: (daemonset1)
  291. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  292. // +-----------------------------------------+------+------+------+------+------+------+
  293. // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
  294. // +-----------------------------------------+------+------+------+------+------+------+
  295. // cluster2:
  296. // idle: 10.00 5.00 5.00 0.00 0.00 0.00
  297. // namespace2:
  298. // pod-mno: (deployment2)
  299. // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  300. // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  301. // pod-pqr: (daemonset1)
  302. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  303. // namespace3:
  304. // pod-stu: (deployment3)
  305. // container7: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  306. // pod-vwx: (statefulset1)
  307. // container8: an[team=team2] 5.00 1.00 1.00 1.00 1.00 1.00
  308. // container9: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  309. // +----------------------------------------+------+------+------+------+------+------+
  310. // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
  311. // +----------------------------------------+------+------+------+------+------+------+
  312. // total 100.00 22.00 42.00 12.00 12.00 12.00
  313. // +----------------------------------------+------+------+------+------+------+------+
  314. // Scenarios to test:
  315. // 1 Single-aggregation
  316. // 1a AggregationProperties=(Cluster)
  317. // 1b AggregationProperties=(Namespace)
  318. // 1c AggregationProperties=(Pod)
  319. // 1d AggregationProperties=(Container)
  320. // 1e AggregationProperties=(ControllerKind)
  321. // 1f AggregationProperties=(Controller)
  322. // 1g AggregationProperties=(Service)
  323. // 1h AggregationProperties=(Label:app)
  324. // 2 Multi-aggregation
  325. // 2a AggregationProperties=(Cluster, Namespace)
  326. // 2b AggregationProperties=(Namespace, Label:app)
  327. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  328. // 2d AggregationProperties=(Label:app, Label:environment)
  329. // 3 Share idle
  330. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  331. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  332. // 4 Share resources
  333. // 4a Share namespace ShareEven
  334. // 4b Share cluster ShareWeighted
  335. // 4c Share label ShareEven
  336. // 4d Share overhead ShareWeighted
  337. // 5 Filters
  338. // 5a Filter by cluster with separate idle
  339. // 5b Filter by cluster with shared idle
  340. // TODO niko/idle more filter tests
  341. // 6 Combinations and options
  342. // 6a SplitIdle
  343. // 6b Share idle with filters
  344. // 6c Share resources with filters
  345. // 6d Share idle and share resources
  346. // 7 Edge cases and errors
  347. // 7a Empty AggregationProperties
  348. // 7b Filter all
  349. // 7c Share all
  350. // 7d Share and filter the same allocations
  351. // Definitions and set-up:
  352. var as *AllocationSet
  353. var err error
  354. endYesterday := time.Now().UTC().Truncate(day)
  355. startYesterday := endYesterday.Add(-day)
  356. numClusters := 2
  357. numNamespaces := 3
  358. numPods := 9
  359. numContainers := 9
  360. numControllerKinds := 3
  361. numControllers := 5
  362. numServices := 1
  363. numLabelApps := 2
  364. // By default, idle is reported as a single, merged allocation
  365. numIdle := 1
  366. // There will only ever be one __unallocated__
  367. numUnallocated := 1
  368. // There are two clusters, so each gets an idle entry when they are split
  369. numSplitIdle := 2
  370. activeTotalCost := 70.0
  371. idleTotalCost := 30.0
  372. sharedOverheadHourlyCost := 7.0
  373. isNamespace3 := func(a *Allocation) bool {
  374. ns, err := a.Properties.GetNamespace()
  375. return err == nil && ns == "namespace3"
  376. }
  377. isApp1 := func(a *Allocation) bool {
  378. ls, _ := a.Properties.GetLabels()
  379. if app, ok := ls["app"]; ok && app == "app1" {
  380. return true
  381. }
  382. return false
  383. }
  384. end := time.Now().UTC().Truncate(day)
  385. start := end.Add(-day)
  386. // Tests:
  387. // 1 Single-aggregation
  388. // 1a AggregationProperties=(Cluster)
  389. as = generateAllocationSet(start)
  390. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  391. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  392. assertAllocationTotals(t, as, "1a", map[string]float64{
  393. "cluster1": 40.00,
  394. "cluster2": 30.00,
  395. IdleSuffix: 30.00,
  396. })
  397. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  398. // 1b AggregationProperties=(Namespace)
  399. as = generateAllocationSet(start)
  400. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  401. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  402. assertAllocationTotals(t, as, "1b", map[string]float64{
  403. "namespace1": 25.00,
  404. "namespace2": 30.00,
  405. "namespace3": 15.00,
  406. IdleSuffix: 30.00,
  407. })
  408. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  409. // 1c AggregationProperties=(Pod)
  410. as = generateAllocationSet(start)
  411. err = as.AggregateBy(Properties{PodProp: true}, nil)
  412. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  413. assertAllocationTotals(t, as, "1c", map[string]float64{
  414. "pod-jkl": 5.00,
  415. "pod-stu": 5.00,
  416. "pod-abc": 5.00,
  417. "pod-pqr": 5.00,
  418. "pod-def": 5.00,
  419. "pod-vwx": 10.00,
  420. "pod1": 15.00,
  421. "pod-mno": 10.00,
  422. "pod-ghi": 10.00,
  423. IdleSuffix: 30.00,
  424. })
  425. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  426. // 1d AggregationProperties=(Container)
  427. as = generateAllocationSet(start)
  428. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  429. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  430. assertAllocationTotals(t, as, "1d", map[string]float64{
  431. "container2": 5.00,
  432. "container9": 5.00,
  433. "container6": 10.00,
  434. "container3": 5.00,
  435. "container4": 10.00,
  436. "container7": 5.00,
  437. "container8": 5.00,
  438. "container5": 10.00,
  439. "container1": 15.00,
  440. IdleSuffix: 30.00,
  441. })
  442. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  443. // 1e AggregationProperties=(ControllerKind)
  444. as = generateAllocationSet(start)
  445. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  446. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  447. assertAllocationTotals(t, as, "1e", map[string]float64{
  448. "daemonset": 10.00,
  449. "deployment": 35.00,
  450. "statefulset": 10.00,
  451. IdleSuffix: 30.00,
  452. UnallocatedSuffix: 15.00,
  453. })
  454. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  455. // 1f AggregationProperties=(Controller)
  456. as = generateAllocationSet(start)
  457. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  458. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  459. assertAllocationTotals(t, as, "1f", map[string]float64{
  460. "deployment/deployment2": 20.00,
  461. "daemonset/daemonset1": 10.00,
  462. "deployment/deployment3": 5.00,
  463. "statefulset/statefulset1": 10.00,
  464. "deployment/deployment1": 10.00,
  465. IdleSuffix: 30.00,
  466. UnallocatedSuffix: 15.00,
  467. })
  468. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  469. // 1g AggregationProperties=(Service)
  470. as = generateAllocationSet(start)
  471. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  472. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  473. assertAllocationTotals(t, as, "1g", map[string]float64{
  474. "service1": 10.00,
  475. IdleSuffix: 30.00,
  476. UnallocatedSuffix: 60.00,
  477. })
  478. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  479. // 1h AggregationProperties=(Label:app)
  480. as = generateAllocationSet(start)
  481. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  482. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  483. assertAllocationTotals(t, as, "1h", map[string]float64{
  484. "app=app1": 15.00,
  485. "app=app2": 20.00,
  486. IdleSuffix: 30.00,
  487. UnallocatedSuffix: 35.00,
  488. })
  489. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  490. // 1i AggregationProperties=(ControllerKind:deployment)
  491. as = generateAllocationSet(start)
  492. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  493. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  494. assertAllocationTotals(t, as, "1i", map[string]float64{
  495. "deployment": 35.00,
  496. IdleSuffix: 30.00,
  497. UnallocatedSuffix: 35.00,
  498. })
  499. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  500. // 1j AggregationProperties=(Annotation:team)
  501. as = generateAllocationSet(start)
  502. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
  503. assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  504. assertAllocationTotals(t, as, "1j", map[string]float64{
  505. "team=team1": 10.00,
  506. "team=team2": 5.00,
  507. IdleSuffix: 30.00,
  508. UnallocatedSuffix: 55.00,
  509. })
  510. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  511. // 2 Multi-aggregation
  512. // 2a AggregationProperties=(Cluster, Namespace)
  513. // 2b AggregationProperties=(Namespace, Label:app)
  514. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  515. // 2d AggregationProperties=(Label:app, Label:environment)
  516. as = generateAllocationSet(start)
  517. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  518. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  519. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  520. assertAllocationTotals(t, as, "2d", map[string]float64{
  521. "app=app1/env=env1": 15.00,
  522. "app=app2/env=env2": 10.00,
  523. "app=app2/" + UnallocatedSuffix: 10.00,
  524. IdleSuffix: 30.00,
  525. UnallocatedSuffix: 35.00,
  526. })
  527. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  528. as = generateAllocationSet(start)
  529. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  530. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  531. assertAllocationTotals(t, as, "2e", map[string]float64{
  532. "cluster1/app=app2/env=env2": 10.00,
  533. "__idle__": 30.00,
  534. "cluster1/app=app1/env=env1": 15.00,
  535. "cluster1/" + UnallocatedSuffix: 15.00,
  536. "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
  537. "cluster2/" + UnallocatedSuffix: 20.00,
  538. })
  539. // 2f AggregationProperties=(annotation:team, pod)
  540. as = generateAllocationSet(start)
  541. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
  542. assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
  543. assertAllocationTotals(t, as, "2e", map[string]float64{
  544. "pod-jkl/" + UnallocatedSuffix: 5.00,
  545. "pod-stu/team=team1": 5.00,
  546. "pod-abc/" + UnallocatedSuffix: 5.00,
  547. "pod-pqr/" + UnallocatedSuffix: 5.00,
  548. "pod-def/" + UnallocatedSuffix: 5.00,
  549. "pod-vwx/team=team1": 5.00,
  550. "pod-vwx/team=team2": 5.00,
  551. "pod1/" + UnallocatedSuffix: 15.00,
  552. "pod-mno/" + UnallocatedSuffix: 10.00,
  553. "pod-ghi/" + UnallocatedSuffix: 10.00,
  554. IdleSuffix: 30.00,
  555. })
  556. // // TODO niko/etl
  557. // // 3 Share idle
  558. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  559. // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  560. // namespace2: 40.3125 = 30.00 + 5.0*(3.0/6.0) + 15.0*(3.0/16.0) + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  561. // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  562. as = generateAllocationSet(start)
  563. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  564. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  565. assertAllocationTotals(t, as, "3a", map[string]float64{
  566. "namespace1": 39.69,
  567. "namespace2": 40.31,
  568. "namespace3": 20.00,
  569. })
  570. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  571. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  572. // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  573. // namespace2: 45.0000 = 30.00 + 5.0*(1.0/2.0) + 15.0*(1.0/2.0) + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  574. // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  575. as = generateAllocationSet(start)
  576. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  577. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  578. assertAllocationTotals(t, as, "3a", map[string]float64{
  579. "namespace1": 35.00,
  580. "namespace2": 45.00,
  581. "namespace3": 20.00,
  582. })
  583. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  584. // 4 Share resources
  585. // 4a Share namespace ShareEven
  586. // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
  587. // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
  588. // idle: 30.0000
  589. as = generateAllocationSet(start)
  590. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  591. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  592. ShareSplit: ShareEven,
  593. })
  594. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  595. assertAllocationTotals(t, as, "4a", map[string]float64{
  596. "namespace1": 32.50,
  597. "namespace2": 37.50,
  598. IdleSuffix: 30.00,
  599. })
  600. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  601. // 4b Share namespace ShareWeighted
  602. // namespace1: 32.5000 =
  603. // namespace2: 37.5000 =
  604. // idle: 30.0000
  605. as = generateAllocationSet(start)
  606. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  607. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  608. ShareSplit: ShareWeighted,
  609. })
  610. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  611. assertAllocationTotals(t, as, "4b", map[string]float64{
  612. "namespace1": 31.82,
  613. "namespace2": 38.18,
  614. IdleSuffix: 30.00,
  615. })
  616. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  617. // 4c Share label ShareEven
  618. // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
  619. // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
  620. // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
  621. // idle: 30.0000
  622. as = generateAllocationSet(start)
  623. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  624. ShareFuncs: []AllocationMatchFunc{isApp1},
  625. ShareSplit: ShareEven,
  626. })
  627. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  628. assertAllocationTotals(t, as, "4c", map[string]float64{
  629. "namespace1": 15.00,
  630. "namespace2": 35.00,
  631. "namespace3": 20.00,
  632. IdleSuffix: 30.00,
  633. })
  634. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  635. // 4d Share overhead ShareWeighted
  636. // namespace1: 37.5000 = 25.00 + (7.0*24.0)*(25.00/70.00)
  637. // namespace2: 45.0000 = 30.00 + (7.0*24.0)*(30.00/70.00)
  638. // namespace3: 22.5000 = 15.00 + (7.0*24.0)*(15.00/70.00)
  639. // idle: 30.0000
  640. as = generateAllocationSet(start)
  641. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  642. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  643. ShareSplit: ShareWeighted,
  644. })
  645. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  646. assertAllocationTotals(t, as, "4d", map[string]float64{
  647. "namespace1": 85.00,
  648. "namespace2": 102.00,
  649. "namespace3": 51.00,
  650. IdleSuffix: 30.00,
  651. })
  652. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  653. // 5 Filters
  654. isCluster := func(matchCluster string) func(*Allocation) bool {
  655. return func(a *Allocation) bool {
  656. cluster, err := a.Properties.GetCluster()
  657. return err == nil && cluster == matchCluster
  658. }
  659. }
  660. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  661. return func(a *Allocation) bool {
  662. namespace, err := a.Properties.GetNamespace()
  663. return err == nil && namespace == matchNamespace
  664. }
  665. }
  666. // 5a Filter by cluster with separate idle
  667. as = generateAllocationSet(start)
  668. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  669. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  670. ShareIdle: ShareNone,
  671. })
  672. assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
  673. assertAllocationTotals(t, as, "5a", map[string]float64{
  674. "cluster1": 40.00,
  675. IdleSuffix: 20.00,
  676. })
  677. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  678. // 5b Filter by cluster with shared idle
  679. as = generateAllocationSet(start)
  680. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  681. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  682. ShareIdle: ShareWeighted,
  683. })
  684. assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
  685. assertAllocationTotals(t, as, "5b", map[string]float64{
  686. "cluster1": 60.00,
  687. })
  688. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  689. // 5c Filter by cluster, agg by namespace, with separate idle
  690. as = generateAllocationSet(start)
  691. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  692. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  693. ShareIdle: ShareNone,
  694. })
  695. assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
  696. assertAllocationTotals(t, as, "5c", map[string]float64{
  697. "namespace1": 25.00,
  698. "namespace2": 15.00,
  699. IdleSuffix: 20.00,
  700. })
  701. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  702. // 5d Filter by namespace, agg by cluster, with separate idle
  703. as = generateAllocationSet(start)
  704. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  705. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  706. ShareIdle: ShareNone,
  707. })
  708. assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
  709. assertAllocationTotals(t, as, "5d", map[string]float64{
  710. "cluster1": 15.00,
  711. "cluster2": 15.00,
  712. IdleSuffix: 10.31,
  713. })
  714. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  715. // 6 Combinations and options
  716. // 6a SplitIdle
  717. as = generateAllocationSet(start)
  718. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  719. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  720. assertAllocationTotals(t, as, "6a", map[string]float64{
  721. "namespace1": 25.00,
  722. "namespace2": 30.00,
  723. "namespace3": 15.00,
  724. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  725. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  726. })
  727. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  728. // 6b Share idle weighted with filters
  729. // Should match values from unfiltered aggregation
  730. // as = generateAllocationSet(start)
  731. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  732. // printAllocationSet("6b unfiltered", as)
  733. as = generateAllocationSet(start)
  734. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  735. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  736. ShareIdle: ShareWeighted,
  737. })
  738. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  739. assertAllocationTotals(t, as, "6b", map[string]float64{
  740. "namespace2": 40.31,
  741. })
  742. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  743. // 6c Share idle even with filters
  744. // Should match values from unfiltered aggregation
  745. // as = generateAllocationSet(start)
  746. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  747. // printAllocationSet("6c unfiltered", as)
  748. as = generateAllocationSet(start)
  749. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  750. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  751. ShareIdle: ShareEven,
  752. })
  753. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  754. assertAllocationTotals(t, as, "6b", map[string]float64{
  755. "namespace2": 45.00,
  756. })
  757. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  758. // 6d Share resources with filters
  759. // 6e Share idle and share resources
  760. // 7 Edge cases and errors
  761. // 7a Empty AggregationProperties
  762. // 7b Filter all
  763. // 7c Share all
  764. // 7d Share and filter the same allocations
  765. }
  766. // TODO niko/etl
  767. //func TestAllocationSet_Clone(t *testing.T) {}
  768. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  769. var as *AllocationSet
  770. var err error
  771. var idles map[string]*Allocation
  772. end := time.Now().UTC().Truncate(day)
  773. start := end.Add(-day)
  774. // Generate AllocationSet and strip out any existing idle allocations
  775. as = generateAllocationSet(start)
  776. for key := range as.idleKeys {
  777. as.Delete(key)
  778. }
  779. // Create an AssetSet representing cluster costs for two clusters (cluster1
  780. // and cluster2). Include Nodes and Disks for both, even though only
  781. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  782. // by type, here we will provide multiple Nodes for one of the clusters to
  783. // make sure the function still holds.
  784. // NOTE: we're re-using generateAllocationSet so this has to line up with
  785. // the allocated node costs from that function. See table above.
  786. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  787. // +-----------------------------------------+------+------+------+------+------------+
  788. // cluster1:
  789. // nodes 100.00 55.00 44.00 11.00 -10.00
  790. // +-----------------------------------------+------+------+------+------+------------+
  791. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  792. // +-----------------------------------------+------+------+------+------+------------+
  793. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  794. // +-----------------------------------------+------+------+------+------+------------+
  795. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  796. // +-----------------------------------------+------+------+------+------+------------+
  797. // cluster2:
  798. // node1 35.00 20.00 15.00 0.00 0.00
  799. // node2 35.00 20.00 15.00 0.00 0.00
  800. // node3 30.00 10.00 10.00 10.00 0.00
  801. // (disks should not matter for idle)
  802. // +-----------------------------------------+------+------+------+------+------------+
  803. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  804. // +-----------------------------------------+------+------+------+------+------------+
  805. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  806. // +-----------------------------------------+------+------+------+------+------------+
  807. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  808. // +-----------------------------------------+------+------+------+------+------------+
  809. cluster1Nodes := NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  810. cluster1Nodes.CPUCost = 55.0
  811. cluster1Nodes.RAMCost = 44.0
  812. cluster1Nodes.GPUCost = 11.0
  813. cluster1Nodes.adjustment = -10.00
  814. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  815. cluster2Node1.CPUCost = 20.0
  816. cluster2Node1.RAMCost = 15.0
  817. cluster2Node1.GPUCost = 0.0
  818. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  819. cluster2Node2.CPUCost = 20.0
  820. cluster2Node2.RAMCost = 15.0
  821. cluster2Node2.GPUCost = 0.0
  822. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  823. cluster2Node3.CPUCost = 10.0
  824. cluster2Node3.RAMCost = 10.0
  825. cluster2Node3.GPUCost = 10.0
  826. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  827. cluster2Disk1.Cost = 5.0
  828. assetSet := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  829. idles, err = as.ComputeIdleAllocations(assetSet)
  830. if err != nil {
  831. t.Fatalf("unexpected error: %s", err)
  832. }
  833. if len(idles) != 2 {
  834. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  835. }
  836. if idle, ok := idles["cluster1"]; !ok {
  837. t.Fatalf("expected idle cost for %s", "cluster1")
  838. } else {
  839. if !util.IsApproximately(idle.TotalCost, 72.0) {
  840. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost)
  841. }
  842. }
  843. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  844. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  845. }
  846. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  847. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  848. }
  849. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  850. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  851. }
  852. if idle, ok := idles["cluster2"]; !ok {
  853. t.Fatalf("expected idle cost for %s", "cluster2")
  854. } else {
  855. if !util.IsApproximately(idle.TotalCost, 82.0) {
  856. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost)
  857. }
  858. }
  859. // NOTE: we're re-using generateAllocationSet so this has to line up with
  860. // the allocated node costs from that function. See table above.
  861. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  862. // +-----------------------------------------+------+------+------+------+------------+
  863. // cluster1:
  864. // nodes 100.00 5.00 4.00 1.00 90.00
  865. // +-----------------------------------------+------+------+------+------+------------+
  866. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  867. // +-----------------------------------------+------+------+------+------+------------+
  868. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  869. // +-----------------------------------------+------+------+------+------+------------+
  870. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  871. // +-----------------------------------------+------+------+------+------+------------+
  872. // cluster2:
  873. // node1 35.00 20.00 15.00 0.00 0.00
  874. // node2 35.00 20.00 15.00 0.00 0.00
  875. // node3 30.00 10.00 10.00 10.00 0.00
  876. // (disks should not matter for idle)
  877. // +-----------------------------------------+------+------+------+------+------------+
  878. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  879. // +-----------------------------------------+------+------+------+------+------------+
  880. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  881. // +-----------------------------------------+------+------+------+------+------------+
  882. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  883. // +-----------------------------------------+------+------+------+------+------------+
  884. cluster1Nodes = NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  885. cluster1Nodes.CPUCost = 5.0
  886. cluster1Nodes.RAMCost = 4.0
  887. cluster1Nodes.GPUCost = 1.0
  888. cluster1Nodes.adjustment = 90.00
  889. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  890. cluster2Node1.CPUCost = 20.0
  891. cluster2Node1.RAMCost = 15.0
  892. cluster2Node1.GPUCost = 0.0
  893. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  894. cluster2Node2.CPUCost = 20.0
  895. cluster2Node2.RAMCost = 15.0
  896. cluster2Node2.GPUCost = 0.0
  897. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  898. cluster2Node3.CPUCost = 10.0
  899. cluster2Node3.RAMCost = 10.0
  900. cluster2Node3.GPUCost = 10.0
  901. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  902. cluster2Disk1.Cost = 5.0
  903. assetSet = NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  904. idles, err = as.ComputeIdleAllocations(assetSet)
  905. if err != nil {
  906. t.Fatalf("unexpected error: %s", err)
  907. }
  908. if len(idles) != 2 {
  909. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  910. }
  911. if idle, ok := idles["cluster1"]; !ok {
  912. t.Fatalf("expected idle cost for %s", "cluster1")
  913. } else {
  914. if !util.IsApproximately(idle.TotalCost, 72.0) {
  915. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost)
  916. }
  917. }
  918. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  919. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  920. }
  921. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  922. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  923. }
  924. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  925. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  926. }
  927. if idle, ok := idles["cluster2"]; !ok {
  928. t.Fatalf("expected idle cost for %s", "cluster2")
  929. } else {
  930. if !util.IsApproximately(idle.TotalCost, 82.0) {
  931. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost)
  932. }
  933. }
  934. // TODO assert value of each resource cost precisely
  935. }
  936. // TODO niko/etl
  937. //func TestAllocationSet_Delete(t *testing.T) {}
  938. // TODO niko/etl
  939. //func TestAllocationSet_End(t *testing.T) {}
  940. // TODO niko/etl
  941. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  942. // TODO niko/etl
  943. //func TestAllocationSet_Insert(t *testing.T) {}
  944. // TODO niko/etl
  945. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  946. // TODO niko/etl
  947. //func TestAllocationSet_Length(t *testing.T) {}
  948. // TODO niko/etl
  949. //func TestAllocationSet_Map(t *testing.T) {}
  950. // TODO niko/etl
  951. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  952. // TODO niko/etl
  953. //func TestAllocationSet_Resolution(t *testing.T) {}
  954. // TODO niko/etl
  955. //func TestAllocationSet_Seconds(t *testing.T) {}
  956. // TODO niko/etl
  957. //func TestAllocationSet_Set(t *testing.T) {}
  958. // TODO niko/etl
  959. //func TestAllocationSet_Start(t *testing.T) {}
  960. // TODO niko/etl
  961. //func TestAllocationSet_TotalCost(t *testing.T) {}
  962. // TODO niko/etl
  963. //func TestNewAllocationSetRange(t *testing.T) {}
  964. func TestAllocationSetRange_Accumulate(t *testing.T) {
  965. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  966. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  967. today := time.Now().UTC().Truncate(day)
  968. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  969. // Accumulating any combination of nil and/or empty set should result in empty set
  970. result, err := NewAllocationSetRange(nil).Accumulate()
  971. if err != nil {
  972. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  973. }
  974. if !result.IsEmpty() {
  975. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  976. }
  977. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  978. if err != nil {
  979. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  980. }
  981. if !result.IsEmpty() {
  982. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  983. }
  984. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  985. if err != nil {
  986. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  987. }
  988. if !result.IsEmpty() {
  989. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  990. }
  991. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  992. if err != nil {
  993. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  994. }
  995. if !result.IsEmpty() {
  996. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  997. }
  998. todayAS := NewAllocationSet(today, tomorrow)
  999. todayAS.Set(NewUnitAllocation("", today, day, nil))
  1000. yesterdayAS := NewAllocationSet(yesterday, today)
  1001. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  1002. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  1003. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  1004. if err != nil {
  1005. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1006. }
  1007. if result == nil {
  1008. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1009. }
  1010. if result.TotalCost() != 5.0 {
  1011. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1012. }
  1013. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  1014. if err != nil {
  1015. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1016. }
  1017. if result == nil {
  1018. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1019. }
  1020. if result.TotalCost() != 5.0 {
  1021. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1022. }
  1023. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  1024. if err != nil {
  1025. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1026. }
  1027. if result == nil {
  1028. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1029. }
  1030. if result.TotalCost() != 5.0 {
  1031. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1032. }
  1033. // Accumulate two non-nil should result in sum of both with appropriate start, end
  1034. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  1035. if err != nil {
  1036. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1037. }
  1038. if result == nil {
  1039. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1040. }
  1041. if result.TotalCost() != 10.0 {
  1042. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  1043. }
  1044. allocMap := result.Map()
  1045. if len(allocMap) != 1 {
  1046. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  1047. }
  1048. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  1049. if alloc == nil {
  1050. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  1051. }
  1052. if alloc.CPUCoreHours != 2.0 {
  1053. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  1054. }
  1055. if alloc.CPUCost != 2.0 {
  1056. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  1057. }
  1058. if alloc.CPUEfficiency != 1.0 {
  1059. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency)
  1060. }
  1061. if alloc.GPUHours != 2.0 {
  1062. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  1063. }
  1064. if alloc.GPUCost != 2.0 {
  1065. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  1066. }
  1067. if alloc.NetworkCost != 2.0 {
  1068. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  1069. }
  1070. if alloc.PVByteHours != 2.0 {
  1071. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  1072. }
  1073. if alloc.PVCost != 2.0 {
  1074. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  1075. }
  1076. if alloc.RAMByteHours != 2.0 {
  1077. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  1078. }
  1079. if alloc.RAMCost != 2.0 {
  1080. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  1081. }
  1082. if alloc.RAMEfficiency != 1.0 {
  1083. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency)
  1084. }
  1085. if alloc.TotalCost != 10.0 {
  1086. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost)
  1087. }
  1088. if alloc.TotalEfficiency != 1.0 {
  1089. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency)
  1090. }
  1091. if !alloc.Start.Equal(yesterday) {
  1092. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1093. }
  1094. if !alloc.End.Equal(tomorrow) {
  1095. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1096. }
  1097. if alloc.Minutes != 2880.0 {
  1098. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes)
  1099. }
  1100. }
  1101. // TODO niko/etl
  1102. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1103. // TODO niko/etl
  1104. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1105. // TODO niko/etl
  1106. // func TestAllocationSetRange_Append(t *testing.T) {}
  1107. // TODO niko/etl
  1108. // func TestAllocationSetRange_Each(t *testing.T) {}
  1109. // TODO niko/etl
  1110. // func TestAllocationSetRange_Get(t *testing.T) {}
  1111. func TestAllocationSetRange_InsertRange(t *testing.T) {
  1112. // Set up
  1113. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1114. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1115. today := time.Now().UTC().Truncate(day)
  1116. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1117. unit := NewUnitAllocation("", today, day, nil)
  1118. ago2dAS := NewAllocationSet(ago2d, yesterday)
  1119. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  1120. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  1121. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  1122. yesterdayAS := NewAllocationSet(yesterday, today)
  1123. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  1124. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  1125. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  1126. todayAS := NewAllocationSet(today, tomorrow)
  1127. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  1128. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  1129. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  1130. var nilASR *AllocationSetRange
  1131. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  1132. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  1133. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  1134. var err error
  1135. // Expect an error calling InsertRange on nil
  1136. err = nilASR.InsertRange(thatASR)
  1137. if err == nil {
  1138. t.Fatalf("expected error, got nil")
  1139. }
  1140. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  1141. err = thisASR.InsertRange(nil)
  1142. if err != nil {
  1143. t.Fatalf("unexpected error: %s", err)
  1144. }
  1145. thisASR.Each(func(i int, as *AllocationSet) {
  1146. as.Each(func(k string, a *Allocation) {
  1147. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1148. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1149. }
  1150. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1151. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1152. }
  1153. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1154. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1155. }
  1156. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1157. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1158. }
  1159. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1160. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1161. }
  1162. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1163. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1164. }
  1165. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1166. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1167. }
  1168. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1169. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1170. }
  1171. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1172. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1173. }
  1174. if !util.IsApproximately(a.TotalCost, unit.TotalCost) {
  1175. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost, a.TotalCost)
  1176. }
  1177. })
  1178. })
  1179. // Expect an error calling InsertRange with a range exceeding the receiver
  1180. err = thisASR.InsertRange(longASR)
  1181. if err == nil {
  1182. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  1183. }
  1184. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  1185. // precisely double when inserting a range that only has a duplicate of
  1186. // "yesterday", but no entry for "today"
  1187. err = thisASR.InsertRange(thatASR)
  1188. if err != nil {
  1189. t.Fatalf("unexpected error: %s", err)
  1190. }
  1191. yAS, err := thisASR.Get(0)
  1192. yAS.Each(func(k string, a *Allocation) {
  1193. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  1194. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1195. }
  1196. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  1197. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1198. }
  1199. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  1200. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1201. }
  1202. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  1203. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1204. }
  1205. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  1206. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1207. }
  1208. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  1209. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1210. }
  1211. if !util.IsApproximately(a.PVByteHours, 2*unit.PVByteHours) {
  1212. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1213. }
  1214. if !util.IsApproximately(a.PVCost, 2*unit.PVCost) {
  1215. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1216. }
  1217. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  1218. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1219. }
  1220. if !util.IsApproximately(a.TotalCost, 2*unit.TotalCost) {
  1221. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost, a.TotalCost)
  1222. }
  1223. })
  1224. tAS, err := thisASR.Get(1)
  1225. tAS.Each(func(k string, a *Allocation) {
  1226. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1227. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1228. }
  1229. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1230. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1231. }
  1232. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1233. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1234. }
  1235. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1236. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1237. }
  1238. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1239. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1240. }
  1241. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1242. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1243. }
  1244. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1245. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1246. }
  1247. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1248. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1249. }
  1250. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1251. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1252. }
  1253. if !util.IsApproximately(a.TotalCost, unit.TotalCost) {
  1254. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost, a.TotalCost)
  1255. }
  1256. })
  1257. }
  1258. // TODO niko/etl
  1259. // func TestAllocationSetRange_Length(t *testing.T) {}
  1260. // TODO niko/etl
  1261. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1262. // TODO niko/etl
  1263. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1264. // TODO niko/etl
  1265. // func TestAllocationSetRange_Window(t *testing.T) {}