allocation_test.go 54 KB

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