allocation_test.go 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577
  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: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
  637. // namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
  638. // namespace3: 51 = 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 (3a)
  730. // 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)
  731. as = generateAllocationSet(start)
  732. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  733. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  734. ShareIdle: ShareWeighted,
  735. })
  736. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  737. assertAllocationTotals(t, as, "6b", map[string]float64{
  738. "namespace2": 40.31,
  739. })
  740. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  741. // 6c Share idle even with filters
  742. // Should match values from unfiltered aggregation (3b)
  743. // 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)
  744. as = generateAllocationSet(start)
  745. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  746. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  747. ShareIdle: ShareEven,
  748. })
  749. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  750. assertAllocationTotals(t, as, "6b", map[string]float64{
  751. "namespace2": 45.00,
  752. })
  753. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  754. // 6d Share overhead with filters
  755. // namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
  756. // namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
  757. // namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
  758. // idle: 30.0000
  759. // Then namespace 2 is filtered.
  760. as = generateAllocationSet(start)
  761. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  762. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  763. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  764. ShareSplit: ShareWeighted,
  765. })
  766. assertAllocationSetTotals(t, as, "6d", err, 2, 132.00)
  767. assertAllocationTotals(t, as, "6d", map[string]float64{
  768. "namespace2": 102.00,
  769. IdleSuffix: 30.00,
  770. })
  771. assertAllocationWindow(t, as, "6d", startYesterday, endYesterday, 1440.0)
  772. // 6e Share resources with filters
  773. // --- Shared ---
  774. // namespace1: 25.00 (gets shared among namespace2 and namespace3)
  775. // --- Filtered ---
  776. // namespace3: 23.33 = 15.00 + (25.00)*(15.00/45.00) (filtered out)
  777. // --- Results ---
  778. // namespace2: 46.67 = 30.00 + (25.00)*(15.00/45.00)
  779. // idle: 30.0000
  780. as = generateAllocationSet(start)
  781. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  782. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  783. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  784. ShareSplit: ShareWeighted,
  785. })
  786. assertAllocationSetTotals(t, as, "6e", err, 2, 76.67)
  787. assertAllocationTotals(t, as, "6e", map[string]float64{
  788. "namespace2": 46.67,
  789. IdleSuffix: 30.00,
  790. })
  791. assertAllocationWindow(t, as, "6e", startYesterday, endYesterday, 1440.0)
  792. // 6f Share idle weighted and share resources weighted
  793. //
  794. // First, share idle weighted produces:
  795. //
  796. // namespace1: 39.6875
  797. // initial cost 25.0000
  798. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  799. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  800. //
  801. // namespace2: 40.3125
  802. // initial cost 30.0000
  803. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  804. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  805. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  806. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  807. //
  808. // namespace3: 20.0000
  809. // initial cost 15.0000
  810. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  811. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  812. //
  813. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  814. // computed before allocating idle (so that weighting idle differently
  815. // doesn't adversely affect the sharing mechanism):
  816. //
  817. // namespace2: 66.7708
  818. // initial cost 30.0000
  819. // idle cost 10.3125
  820. // shared cost 26.4583 = (39.6875)*(30.0/45.0)
  821. //
  822. // namespace3: 33.2292
  823. // initial cost 15.0000
  824. // idle cost 5.0000
  825. // shared cost 13.2292 = (39.6875)*(15.0/45.0)
  826. //
  827. as = generateAllocationSet(start)
  828. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  829. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  830. ShareSplit: ShareWeighted,
  831. ShareIdle: ShareWeighted,
  832. })
  833. assertAllocationSetTotals(t, as, "6f", err, 2, activeTotalCost+idleTotalCost)
  834. assertAllocationTotals(t, as, "6f", map[string]float64{
  835. "namespace2": 66.77,
  836. "namespace3": 33.23,
  837. })
  838. assertAllocationWindow(t, as, "6f", startYesterday, endYesterday, 1440.0)
  839. // 6g Share idle, share resources, and filter
  840. //
  841. // First, share idle weighted produces:
  842. //
  843. // namespace1: 39.6875
  844. // initial cost 25.0000
  845. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  846. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  847. //
  848. // namespace2: 40.3125
  849. // initial cost 30.0000
  850. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  851. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  852. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  853. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  854. //
  855. // namespace3: 20.0000
  856. // initial cost 15.0000
  857. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  858. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  859. //
  860. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  861. // computed before allocating idle (so that weighting idle differently
  862. // doesn't adversely affect the sharing mechanism):
  863. //
  864. // namespace2: 66.7708
  865. // initial cost 30.0000
  866. // idle cost 10.3125
  867. // shared cost 26.4583 = (39.6875)*(30.0/45.0)
  868. //
  869. // namespace3: 33.2292
  870. // initial cost 15.0000
  871. // idle cost 5.0000
  872. // shared cost 13.2292 = (39.6875)*(15.0/45.0)
  873. //
  874. // Then, filter for namespace2: 66.7708
  875. //
  876. as = generateAllocationSet(start)
  877. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  878. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  879. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  880. ShareSplit: ShareWeighted,
  881. ShareIdle: ShareWeighted,
  882. })
  883. assertAllocationSetTotals(t, as, "6g", err, 1, 66.77)
  884. assertAllocationTotals(t, as, "6g", map[string]float64{
  885. "namespace2": 66.77,
  886. })
  887. assertAllocationWindow(t, as, "6g", startYesterday, endYesterday, 1440.0)
  888. // 7 Edge cases and errors
  889. // 7a Empty AggregationProperties
  890. // 7b Filter all
  891. // 7c Share all
  892. // 7d Share and filter the same allocations
  893. }
  894. // TODO niko/etl
  895. //func TestAllocationSet_Clone(t *testing.T) {}
  896. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  897. var as *AllocationSet
  898. var err error
  899. var idles map[string]*Allocation
  900. end := time.Now().UTC().Truncate(day)
  901. start := end.Add(-day)
  902. // Generate AllocationSet and strip out any existing idle allocations
  903. as = generateAllocationSet(start)
  904. for key := range as.idleKeys {
  905. as.Delete(key)
  906. }
  907. // Create an AssetSet representing cluster costs for two clusters (cluster1
  908. // and cluster2). Include Nodes and Disks for both, even though only
  909. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  910. // by type, here we will provide multiple Nodes for one of the clusters to
  911. // make sure the function still holds.
  912. // NOTE: we're re-using generateAllocationSet so this has to line up with
  913. // the allocated node costs from that function. See table above.
  914. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  915. // +-----------------------------------------+------+------+------+------+------------+
  916. // cluster1:
  917. // nodes 100.00 55.00 44.00 11.00 -10.00
  918. // +-----------------------------------------+------+------+------+------+------------+
  919. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  920. // +-----------------------------------------+------+------+------+------+------------+
  921. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  922. // +-----------------------------------------+------+------+------+------+------------+
  923. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  924. // +-----------------------------------------+------+------+------+------+------------+
  925. // cluster2:
  926. // node1 35.00 20.00 15.00 0.00 0.00
  927. // node2 35.00 20.00 15.00 0.00 0.00
  928. // node3 30.00 10.00 10.00 10.00 0.00
  929. // (disks should not matter for idle)
  930. // +-----------------------------------------+------+------+------+------+------------+
  931. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  932. // +-----------------------------------------+------+------+------+------+------------+
  933. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  934. // +-----------------------------------------+------+------+------+------+------------+
  935. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  936. // +-----------------------------------------+------+------+------+------+------------+
  937. cluster1Nodes := NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  938. cluster1Nodes.CPUCost = 55.0
  939. cluster1Nodes.RAMCost = 44.0
  940. cluster1Nodes.GPUCost = 11.0
  941. cluster1Nodes.adjustment = -10.00
  942. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  943. cluster2Node1.CPUCost = 20.0
  944. cluster2Node1.RAMCost = 15.0
  945. cluster2Node1.GPUCost = 0.0
  946. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  947. cluster2Node2.CPUCost = 20.0
  948. cluster2Node2.RAMCost = 15.0
  949. cluster2Node2.GPUCost = 0.0
  950. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  951. cluster2Node3.CPUCost = 10.0
  952. cluster2Node3.RAMCost = 10.0
  953. cluster2Node3.GPUCost = 10.0
  954. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  955. cluster2Disk1.Cost = 5.0
  956. assetSet := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  957. idles, err = as.ComputeIdleAllocations(assetSet)
  958. if err != nil {
  959. t.Fatalf("unexpected error: %s", err)
  960. }
  961. if len(idles) != 2 {
  962. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  963. }
  964. if idle, ok := idles["cluster1"]; !ok {
  965. t.Fatalf("expected idle cost for %s", "cluster1")
  966. } else {
  967. if !util.IsApproximately(idle.TotalCost, 72.0) {
  968. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost)
  969. }
  970. }
  971. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  972. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  973. }
  974. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  975. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  976. }
  977. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  978. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  979. }
  980. if idle, ok := idles["cluster2"]; !ok {
  981. t.Fatalf("expected idle cost for %s", "cluster2")
  982. } else {
  983. if !util.IsApproximately(idle.TotalCost, 82.0) {
  984. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost)
  985. }
  986. }
  987. // NOTE: we're re-using generateAllocationSet so this has to line up with
  988. // the allocated node costs from that function. See table above.
  989. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  990. // +-----------------------------------------+------+------+------+------+------------+
  991. // cluster1:
  992. // nodes 100.00 5.00 4.00 1.00 90.00
  993. // +-----------------------------------------+------+------+------+------+------------+
  994. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  995. // +-----------------------------------------+------+------+------+------+------------+
  996. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  997. // +-----------------------------------------+------+------+------+------+------------+
  998. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  999. // +-----------------------------------------+------+------+------+------+------------+
  1000. // cluster2:
  1001. // node1 35.00 20.00 15.00 0.00 0.00
  1002. // node2 35.00 20.00 15.00 0.00 0.00
  1003. // node3 30.00 10.00 10.00 10.00 0.00
  1004. // (disks should not matter for idle)
  1005. // +-----------------------------------------+------+------+------+------+------------+
  1006. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1007. // +-----------------------------------------+------+------+------+------+------------+
  1008. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1009. // +-----------------------------------------+------+------+------+------+------------+
  1010. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1011. // +-----------------------------------------+------+------+------+------+------------+
  1012. cluster1Nodes = NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1013. cluster1Nodes.CPUCost = 5.0
  1014. cluster1Nodes.RAMCost = 4.0
  1015. cluster1Nodes.GPUCost = 1.0
  1016. cluster1Nodes.adjustment = 90.00
  1017. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1018. cluster2Node1.CPUCost = 20.0
  1019. cluster2Node1.RAMCost = 15.0
  1020. cluster2Node1.GPUCost = 0.0
  1021. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1022. cluster2Node2.CPUCost = 20.0
  1023. cluster2Node2.RAMCost = 15.0
  1024. cluster2Node2.GPUCost = 0.0
  1025. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1026. cluster2Node3.CPUCost = 10.0
  1027. cluster2Node3.RAMCost = 10.0
  1028. cluster2Node3.GPUCost = 10.0
  1029. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1030. cluster2Disk1.Cost = 5.0
  1031. assetSet = NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1032. idles, err = as.ComputeIdleAllocations(assetSet)
  1033. if err != nil {
  1034. t.Fatalf("unexpected error: %s", err)
  1035. }
  1036. if len(idles) != 2 {
  1037. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1038. }
  1039. if idle, ok := idles["cluster1"]; !ok {
  1040. t.Fatalf("expected idle cost for %s", "cluster1")
  1041. } else {
  1042. if !util.IsApproximately(idle.TotalCost, 72.0) {
  1043. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost)
  1044. }
  1045. }
  1046. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1047. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1048. }
  1049. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1050. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1051. }
  1052. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1053. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1054. }
  1055. if idle, ok := idles["cluster2"]; !ok {
  1056. t.Fatalf("expected idle cost for %s", "cluster2")
  1057. } else {
  1058. if !util.IsApproximately(idle.TotalCost, 82.0) {
  1059. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost)
  1060. }
  1061. }
  1062. // TODO assert value of each resource cost precisely
  1063. }
  1064. // TODO niko/etl
  1065. //func TestAllocationSet_Delete(t *testing.T) {}
  1066. // TODO niko/etl
  1067. //func TestAllocationSet_End(t *testing.T) {}
  1068. // TODO niko/etl
  1069. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  1070. // TODO niko/etl
  1071. //func TestAllocationSet_Insert(t *testing.T) {}
  1072. // TODO niko/etl
  1073. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  1074. // TODO niko/etl
  1075. //func TestAllocationSet_Length(t *testing.T) {}
  1076. // TODO niko/etl
  1077. //func TestAllocationSet_Map(t *testing.T) {}
  1078. // TODO niko/etl
  1079. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  1080. // TODO niko/etl
  1081. //func TestAllocationSet_Resolution(t *testing.T) {}
  1082. // TODO niko/etl
  1083. //func TestAllocationSet_Seconds(t *testing.T) {}
  1084. // TODO niko/etl
  1085. //func TestAllocationSet_Set(t *testing.T) {}
  1086. // TODO niko/etl
  1087. //func TestAllocationSet_Start(t *testing.T) {}
  1088. // TODO niko/etl
  1089. //func TestAllocationSet_TotalCost(t *testing.T) {}
  1090. // TODO niko/etl
  1091. //func TestNewAllocationSetRange(t *testing.T) {}
  1092. func TestAllocationSetRange_Accumulate(t *testing.T) {
  1093. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1094. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1095. today := time.Now().UTC().Truncate(day)
  1096. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1097. // Accumulating any combination of nil and/or empty set should result in empty set
  1098. result, err := NewAllocationSetRange(nil).Accumulate()
  1099. if err != nil {
  1100. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1101. }
  1102. if !result.IsEmpty() {
  1103. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1104. }
  1105. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  1106. if err != nil {
  1107. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1108. }
  1109. if !result.IsEmpty() {
  1110. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1111. }
  1112. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  1113. if err != nil {
  1114. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1115. }
  1116. if !result.IsEmpty() {
  1117. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1118. }
  1119. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  1120. if err != nil {
  1121. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1122. }
  1123. if !result.IsEmpty() {
  1124. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1125. }
  1126. todayAS := NewAllocationSet(today, tomorrow)
  1127. todayAS.Set(NewUnitAllocation("", today, day, nil))
  1128. yesterdayAS := NewAllocationSet(yesterday, today)
  1129. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  1130. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  1131. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  1132. if err != nil {
  1133. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1134. }
  1135. if result == nil {
  1136. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1137. }
  1138. if result.TotalCost() != 5.0 {
  1139. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1140. }
  1141. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  1142. if err != nil {
  1143. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1144. }
  1145. if result == nil {
  1146. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1147. }
  1148. if result.TotalCost() != 5.0 {
  1149. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1150. }
  1151. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  1152. if err != nil {
  1153. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1154. }
  1155. if result == nil {
  1156. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1157. }
  1158. if result.TotalCost() != 5.0 {
  1159. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1160. }
  1161. // Accumulate two non-nil should result in sum of both with appropriate start, end
  1162. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  1163. if err != nil {
  1164. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1165. }
  1166. if result == nil {
  1167. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1168. }
  1169. if result.TotalCost() != 10.0 {
  1170. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  1171. }
  1172. allocMap := result.Map()
  1173. if len(allocMap) != 1 {
  1174. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  1175. }
  1176. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  1177. if alloc == nil {
  1178. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  1179. }
  1180. if alloc.CPUCoreHours != 2.0 {
  1181. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  1182. }
  1183. if alloc.CPUCost != 2.0 {
  1184. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  1185. }
  1186. if alloc.CPUEfficiency != 1.0 {
  1187. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency)
  1188. }
  1189. if alloc.GPUHours != 2.0 {
  1190. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  1191. }
  1192. if alloc.GPUCost != 2.0 {
  1193. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  1194. }
  1195. if alloc.NetworkCost != 2.0 {
  1196. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  1197. }
  1198. if alloc.PVByteHours != 2.0 {
  1199. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  1200. }
  1201. if alloc.PVCost != 2.0 {
  1202. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  1203. }
  1204. if alloc.RAMByteHours != 2.0 {
  1205. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  1206. }
  1207. if alloc.RAMCost != 2.0 {
  1208. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  1209. }
  1210. if alloc.RAMEfficiency != 1.0 {
  1211. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency)
  1212. }
  1213. if alloc.TotalCost != 10.0 {
  1214. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost)
  1215. }
  1216. if alloc.TotalEfficiency != 1.0 {
  1217. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency)
  1218. }
  1219. if !alloc.Start.Equal(yesterday) {
  1220. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1221. }
  1222. if !alloc.End.Equal(tomorrow) {
  1223. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1224. }
  1225. if alloc.Minutes != 2880.0 {
  1226. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes)
  1227. }
  1228. }
  1229. // TODO niko/etl
  1230. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1231. // TODO niko/etl
  1232. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1233. // TODO niko/etl
  1234. // func TestAllocationSetRange_Append(t *testing.T) {}
  1235. // TODO niko/etl
  1236. // func TestAllocationSetRange_Each(t *testing.T) {}
  1237. // TODO niko/etl
  1238. // func TestAllocationSetRange_Get(t *testing.T) {}
  1239. func TestAllocationSetRange_InsertRange(t *testing.T) {
  1240. // Set up
  1241. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1242. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1243. today := time.Now().UTC().Truncate(day)
  1244. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1245. unit := NewUnitAllocation("", today, day, nil)
  1246. ago2dAS := NewAllocationSet(ago2d, yesterday)
  1247. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  1248. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  1249. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  1250. yesterdayAS := NewAllocationSet(yesterday, today)
  1251. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  1252. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  1253. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  1254. todayAS := NewAllocationSet(today, tomorrow)
  1255. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  1256. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  1257. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  1258. var nilASR *AllocationSetRange
  1259. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  1260. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  1261. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  1262. var err error
  1263. // Expect an error calling InsertRange on nil
  1264. err = nilASR.InsertRange(thatASR)
  1265. if err == nil {
  1266. t.Fatalf("expected error, got nil")
  1267. }
  1268. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  1269. err = thisASR.InsertRange(nil)
  1270. if err != nil {
  1271. t.Fatalf("unexpected error: %s", err)
  1272. }
  1273. thisASR.Each(func(i int, as *AllocationSet) {
  1274. as.Each(func(k string, a *Allocation) {
  1275. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1276. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1277. }
  1278. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1279. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1280. }
  1281. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1282. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1283. }
  1284. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1285. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1286. }
  1287. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1288. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1289. }
  1290. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1291. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1292. }
  1293. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1294. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1295. }
  1296. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1297. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1298. }
  1299. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1300. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1301. }
  1302. if !util.IsApproximately(a.TotalCost, unit.TotalCost) {
  1303. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost, a.TotalCost)
  1304. }
  1305. })
  1306. })
  1307. // Expect an error calling InsertRange with a range exceeding the receiver
  1308. err = thisASR.InsertRange(longASR)
  1309. if err == nil {
  1310. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  1311. }
  1312. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  1313. // precisely double when inserting a range that only has a duplicate of
  1314. // "yesterday", but no entry for "today"
  1315. err = thisASR.InsertRange(thatASR)
  1316. if err != nil {
  1317. t.Fatalf("unexpected error: %s", err)
  1318. }
  1319. yAS, err := thisASR.Get(0)
  1320. yAS.Each(func(k string, a *Allocation) {
  1321. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  1322. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1323. }
  1324. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  1325. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1326. }
  1327. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  1328. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1329. }
  1330. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  1331. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1332. }
  1333. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  1334. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1335. }
  1336. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  1337. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1338. }
  1339. if !util.IsApproximately(a.PVByteHours, 2*unit.PVByteHours) {
  1340. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1341. }
  1342. if !util.IsApproximately(a.PVCost, 2*unit.PVCost) {
  1343. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1344. }
  1345. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  1346. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1347. }
  1348. if !util.IsApproximately(a.TotalCost, 2*unit.TotalCost) {
  1349. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost, a.TotalCost)
  1350. }
  1351. })
  1352. tAS, err := thisASR.Get(1)
  1353. tAS.Each(func(k string, a *Allocation) {
  1354. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1355. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1356. }
  1357. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1358. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1359. }
  1360. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1361. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1362. }
  1363. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1364. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1365. }
  1366. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1367. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1368. }
  1369. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1370. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1371. }
  1372. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1373. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1374. }
  1375. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1376. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1377. }
  1378. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1379. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1380. }
  1381. if !util.IsApproximately(a.TotalCost, unit.TotalCost) {
  1382. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost, a.TotalCost)
  1383. }
  1384. })
  1385. }
  1386. // TODO niko/etl
  1387. // func TestAllocationSetRange_Length(t *testing.T) {}
  1388. // TODO niko/etl
  1389. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1390. // TODO niko/etl
  1391. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1392. // TODO niko/etl
  1393. // func TestAllocationSetRange_Window(t *testing.T) {}