allocation_test.go 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948
  1. package kubecost
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "math"
  6. "testing"
  7. "time"
  8. "github.com/kubecost/cost-model/pkg/util"
  9. )
  10. const day = 24 * time.Hour
  11. func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *Properties) *Allocation {
  12. if name == "" {
  13. name = "cluster1/namespace1/pod1/container1"
  14. }
  15. properties := &Properties{}
  16. if props == nil {
  17. properties.SetCluster("cluster1")
  18. properties.SetNode("node1")
  19. properties.SetNamespace("namespace1")
  20. properties.SetControllerKind("deployment")
  21. properties.SetController("deployment1")
  22. properties.SetPod("pod1")
  23. properties.SetContainer("container1")
  24. } else {
  25. properties = props
  26. }
  27. end := start.Add(resolution)
  28. alloc := &Allocation{
  29. Name: name,
  30. Properties: *properties,
  31. Window: NewWindow(&start, &end).Clone(),
  32. Start: start,
  33. End: end,
  34. CPUCoreHours: 1,
  35. CPUCost: 1,
  36. CPUCoreRequestAverage: 1,
  37. CPUCoreUsageAverage: 1,
  38. GPUHours: 1,
  39. GPUCost: 1,
  40. NetworkCost: 1,
  41. PVByteHours: 1,
  42. PVCost: 1,
  43. RAMByteHours: 1,
  44. RAMCost: 1,
  45. RAMBytesRequestAverage: 1,
  46. RAMBytesUsageAverage: 1,
  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. cpuPrice := 0.02
  80. gpuPrice := 2.00
  81. ramPrice := 0.01
  82. pvPrice := 0.00005
  83. gib := 1024.0 * 1024.0 * 1024.0
  84. s1 := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  85. e1 := time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)
  86. hrs1 := e1.Sub(s1).Hours()
  87. a1 := &Allocation{
  88. Start: s1,
  89. End: e1,
  90. CPUCoreHours: 2.0 * hrs1,
  91. CPUCoreRequestAverage: 2.0,
  92. CPUCoreUsageAverage: 1.0,
  93. CPUCost: 2.0 * hrs1 * cpuPrice,
  94. GPUHours: 1.0 * hrs1,
  95. GPUCost: 1.0 * hrs1 * gpuPrice,
  96. PVByteHours: 100.0 * gib * hrs1,
  97. PVCost: 100.0 * hrs1 * pvPrice,
  98. RAMByteHours: 8.0 * gib * hrs1,
  99. RAMBytesRequestAverage: 8.0 * gib,
  100. RAMBytesUsageAverage: 4.0 * gib,
  101. RAMCost: 8.0 * hrs1 * ramPrice,
  102. SharedCost: 2.00,
  103. ExternalCost: 1.00,
  104. }
  105. a1b := a1.Clone()
  106. s2 := time.Date(2021, time.January, 1, 6, 0, 0, 0, time.UTC)
  107. e2 := time.Date(2021, time.January, 1, 24, 0, 0, 0, time.UTC)
  108. hrs2 := e1.Sub(s1).Hours()
  109. a2 := &Allocation{
  110. Start: s2,
  111. End: e2,
  112. CPUCoreHours: 1.0 * hrs2,
  113. CPUCoreRequestAverage: 1.0,
  114. CPUCoreUsageAverage: 1.0,
  115. CPUCost: 1.0 * hrs2 * cpuPrice,
  116. GPUHours: 0.0,
  117. GPUCost: 0.0,
  118. PVByteHours: 0,
  119. PVCost: 0,
  120. RAMByteHours: 8.0 * gib * hrs2,
  121. RAMBytesRequestAverage: 0.0,
  122. RAMBytesUsageAverage: 8.0 * gib,
  123. RAMCost: 8.0 * hrs2 * ramPrice,
  124. NetworkCost: 0.01,
  125. SharedCost: 0.00,
  126. ExternalCost: 1.00,
  127. }
  128. a2b := a2.Clone()
  129. act, err := a1.Add(a2)
  130. if err != nil {
  131. t.Fatalf("Allocation.Add: unexpected error: %s", err)
  132. }
  133. // Neither Allocation should be mutated
  134. if !a1.Equal(a1b) {
  135. t.Fatalf("Allocation.Add: a1 illegally mutated")
  136. }
  137. if !a2.Equal(a2b) {
  138. t.Fatalf("Allocation.Add: a1 illegally mutated")
  139. }
  140. // Costs should be cumulative
  141. if !util.IsApproximately(a1.TotalCost()+a2.TotalCost(), act.TotalCost()) {
  142. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.TotalCost()+a2.TotalCost(), act.TotalCost())
  143. }
  144. if !util.IsApproximately(a1.CPUCost+a2.CPUCost, act.CPUCost) {
  145. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCost+a2.CPUCost, act.CPUCost)
  146. }
  147. if !util.IsApproximately(a1.GPUCost+a2.GPUCost, act.GPUCost) {
  148. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCost+a2.GPUCost, act.GPUCost)
  149. }
  150. if !util.IsApproximately(a1.RAMCost+a2.RAMCost, act.RAMCost) {
  151. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCost+a2.RAMCost, act.RAMCost)
  152. }
  153. if !util.IsApproximately(a1.PVCost+a2.PVCost, act.PVCost) {
  154. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVCost+a2.PVCost, act.PVCost)
  155. }
  156. if !util.IsApproximately(a1.NetworkCost+a2.NetworkCost, act.NetworkCost) {
  157. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.NetworkCost+a2.NetworkCost, act.NetworkCost)
  158. }
  159. if !util.IsApproximately(a1.SharedCost+a2.SharedCost, act.SharedCost) {
  160. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.SharedCost+a2.SharedCost, act.SharedCost)
  161. }
  162. if !util.IsApproximately(a1.ExternalCost+a2.ExternalCost, act.ExternalCost) {
  163. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.ExternalCost+a2.ExternalCost, act.ExternalCost)
  164. }
  165. // ResourceHours should be cumulative
  166. if !util.IsApproximately(a1.CPUCoreHours+a2.CPUCoreHours, act.CPUCoreHours) {
  167. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCoreHours+a2.CPUCoreHours, act.CPUCoreHours)
  168. }
  169. if !util.IsApproximately(a1.RAMByteHours+a2.RAMByteHours, act.RAMByteHours) {
  170. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMByteHours+a2.RAMByteHours, act.RAMByteHours)
  171. }
  172. if !util.IsApproximately(a1.PVByteHours+a2.PVByteHours, act.PVByteHours) {
  173. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVByteHours+a2.PVByteHours, act.PVByteHours)
  174. }
  175. // Minutes should be the duration between min(starts) and max(ends)
  176. if !act.Start.Equal(a1.Start) || !act.End.Equal(a2.End) {
  177. t.Fatalf("Allocation.Add: expected %s; actual %s", NewWindow(&a1.Start, &a2.End), NewWindow(&act.Start, &act.End))
  178. }
  179. if act.Minutes() != 1440.0 {
  180. t.Fatalf("Allocation.Add: expected %f; actual %f", 1440.0, act.Minutes())
  181. }
  182. // Requests and Usage should be averaged correctly
  183. // CPU requests = (2.0*12.0 + 1.0*18.0)/(24.0) = 1.75
  184. // CPU usage = (1.0*12.0 + 1.0*18.0)/(24.0) = 1.25
  185. // RAM requests = (8.0*12.0 + 0.0*18.0)/(24.0) = 4.00
  186. // RAM usage = (4.0*12.0 + 8.0*18.0)/(24.0) = 8.00
  187. if !util.IsApproximately(1.75, act.CPUCoreRequestAverage) {
  188. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.75, act.CPUCoreRequestAverage)
  189. }
  190. if !util.IsApproximately(1.25, act.CPUCoreUsageAverage) {
  191. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.25, act.CPUCoreUsageAverage)
  192. }
  193. if !util.IsApproximately(4.00*gib, act.RAMBytesRequestAverage) {
  194. t.Fatalf("Allocation.Add: expected %f; actual %f", 4.00*gib, act.RAMBytesRequestAverage)
  195. }
  196. if !util.IsApproximately(8.00*gib, act.RAMBytesUsageAverage) {
  197. t.Fatalf("Allocation.Add: expected %f; actual %f", 8.00*gib, act.RAMBytesUsageAverage)
  198. }
  199. // Efficiency should be computed accurately from new request/usage
  200. // CPU efficiency = 1.25/1.75 = 0.7142857
  201. // RAM efficiency = 8.00/4.00 = 2.0000000
  202. // Total efficiency = (0.7142857*0.72 + 2.0*1.92)/(2.64) = 1.6493506
  203. if !util.IsApproximately(0.7142857, act.CPUEfficiency()) {
  204. t.Fatalf("Allocation.Add: expected %f; actual %f", 0.7142857, act.CPUEfficiency())
  205. }
  206. if !util.IsApproximately(2.0000000, act.RAMEfficiency()) {
  207. t.Fatalf("Allocation.Add: expected %f; actual %f", 2.0000000, act.RAMEfficiency())
  208. }
  209. if !util.IsApproximately(1.6493506, act.TotalEfficiency()) {
  210. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.6493506, act.TotalEfficiency())
  211. }
  212. }
  213. func TestAllocation_Share(t *testing.T) {
  214. cpuPrice := 0.02
  215. gpuPrice := 2.00
  216. ramPrice := 0.01
  217. pvPrice := 0.00005
  218. gib := 1024.0 * 1024.0 * 1024.0
  219. s1 := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  220. e1 := time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)
  221. hrs1 := e1.Sub(s1).Hours()
  222. a1 := &Allocation{
  223. Start: s1,
  224. End: e1,
  225. CPUCoreHours: 2.0 * hrs1,
  226. CPUCoreRequestAverage: 2.0,
  227. CPUCoreUsageAverage: 1.0,
  228. CPUCost: 2.0 * hrs1 * cpuPrice,
  229. GPUHours: 1.0 * hrs1,
  230. GPUCost: 1.0 * hrs1 * gpuPrice,
  231. PVByteHours: 100.0 * gib * hrs1,
  232. PVCost: 100.0 * hrs1 * pvPrice,
  233. RAMByteHours: 8.0 * gib * hrs1,
  234. RAMBytesRequestAverage: 8.0 * gib,
  235. RAMBytesUsageAverage: 4.0 * gib,
  236. RAMCost: 8.0 * hrs1 * ramPrice,
  237. SharedCost: 2.00,
  238. ExternalCost: 1.00,
  239. }
  240. a1b := a1.Clone()
  241. s2 := time.Date(2021, time.January, 1, 6, 0, 0, 0, time.UTC)
  242. e2 := time.Date(2021, time.January, 1, 24, 0, 0, 0, time.UTC)
  243. hrs2 := e1.Sub(s1).Hours()
  244. a2 := &Allocation{
  245. Start: s2,
  246. End: e2,
  247. CPUCoreHours: 1.0 * hrs2,
  248. CPUCoreRequestAverage: 1.0,
  249. CPUCoreUsageAverage: 1.0,
  250. CPUCost: 1.0 * hrs2 * cpuPrice,
  251. GPUHours: 0.0,
  252. GPUCost: 0.0,
  253. PVByteHours: 0,
  254. PVCost: 0,
  255. RAMByteHours: 8.0 * gib * hrs2,
  256. RAMBytesRequestAverage: 0.0,
  257. RAMBytesUsageAverage: 8.0 * gib,
  258. RAMCost: 8.0 * hrs2 * ramPrice,
  259. NetworkCost: 0.01,
  260. SharedCost: 0.00,
  261. ExternalCost: 1.00,
  262. }
  263. a2b := a2.Clone()
  264. act, err := a1.Share(a2)
  265. if err != nil {
  266. t.Fatalf("Allocation.Share: unexpected error: %s", err)
  267. }
  268. // Neither Allocation should be mutated
  269. if !a1.Equal(a1b) {
  270. t.Fatalf("Allocation.Share: a1 illegally mutated")
  271. }
  272. if !a2.Equal(a2b) {
  273. t.Fatalf("Allocation.Share: a1 illegally mutated")
  274. }
  275. // SharedCost and TotalCost should reflect increase by a2.TotalCost
  276. if !util.IsApproximately(a1.TotalCost()+a2.TotalCost(), act.TotalCost()) {
  277. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.TotalCost()+a2.TotalCost(), act.TotalCost())
  278. }
  279. if !util.IsApproximately(a1.SharedCost+a2.TotalCost(), act.SharedCost) {
  280. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.SharedCost+a2.TotalCost(), act.SharedCost)
  281. }
  282. // Costs should match before (expect TotalCost and SharedCost)
  283. if !util.IsApproximately(a1.CPUCost, act.CPUCost) {
  284. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCost, act.CPUCost)
  285. }
  286. if !util.IsApproximately(a1.GPUCost, act.GPUCost) {
  287. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.GPUCost, act.GPUCost)
  288. }
  289. if !util.IsApproximately(a1.RAMCost, act.RAMCost) {
  290. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMCost, act.RAMCost)
  291. }
  292. if !util.IsApproximately(a1.PVCost, act.PVCost) {
  293. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.PVCost, act.PVCost)
  294. }
  295. if !util.IsApproximately(a1.NetworkCost, act.NetworkCost) {
  296. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.NetworkCost, act.NetworkCost)
  297. }
  298. if !util.IsApproximately(a1.ExternalCost, act.ExternalCost) {
  299. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.ExternalCost, act.ExternalCost)
  300. }
  301. // ResourceHours should match before
  302. if !util.IsApproximately(a1.CPUCoreHours, act.CPUCoreHours) {
  303. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreHours, act.CPUCoreHours)
  304. }
  305. if !util.IsApproximately(a1.RAMByteHours, act.RAMByteHours) {
  306. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMByteHours, act.RAMByteHours)
  307. }
  308. if !util.IsApproximately(a1.PVByteHours, act.PVByteHours) {
  309. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.PVByteHours, act.PVByteHours)
  310. }
  311. // Minutes should match before
  312. if !act.Start.Equal(a1.Start) || !act.End.Equal(a1.End) {
  313. t.Fatalf("Allocation.Share: expected %s; actual %s", NewWindow(&a1.Start, &a1.End), NewWindow(&act.Start, &act.End))
  314. }
  315. if act.Minutes() != a1.Minutes() {
  316. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.Minutes(), act.Minutes())
  317. }
  318. // Requests and Usage should match before
  319. if !util.IsApproximately(a1.CPUCoreRequestAverage, act.CPUCoreRequestAverage) {
  320. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreRequestAverage, act.CPUCoreRequestAverage)
  321. }
  322. if !util.IsApproximately(a1.CPUCoreUsageAverage, act.CPUCoreUsageAverage) {
  323. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreUsageAverage, act.CPUCoreUsageAverage)
  324. }
  325. if !util.IsApproximately(a1.RAMBytesRequestAverage, act.RAMBytesRequestAverage) {
  326. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMBytesRequestAverage, act.RAMBytesRequestAverage)
  327. }
  328. if !util.IsApproximately(a1.RAMBytesUsageAverage, act.RAMBytesUsageAverage) {
  329. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMBytesUsageAverage, act.RAMBytesUsageAverage)
  330. }
  331. // Efficiency should match before
  332. if !util.IsApproximately(a1.CPUEfficiency(), act.CPUEfficiency()) {
  333. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUEfficiency(), act.CPUEfficiency())
  334. }
  335. if !util.IsApproximately(a1.RAMEfficiency(), act.RAMEfficiency()) {
  336. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMEfficiency(), act.RAMEfficiency())
  337. }
  338. if !util.IsApproximately(a1.TotalEfficiency(), act.TotalEfficiency()) {
  339. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.TotalEfficiency(), act.TotalEfficiency())
  340. }
  341. }
  342. func TestAllocation_MarshalJSON(t *testing.T) {
  343. start := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  344. end := time.Date(2021, time.January, 2, 0, 0, 0, 0, time.UTC)
  345. hrs := 24.0
  346. gib := 1024.0 * 1024.0 * 1024.0
  347. cpuPrice := 0.02
  348. gpuPrice := 2.00
  349. ramPrice := 0.01
  350. pvPrice := 0.00005
  351. before := &Allocation{
  352. Name: "cluster1/namespace1/node1/pod1/container1",
  353. Properties: Properties{
  354. ClusterProp: "cluster1",
  355. NodeProp: "node1",
  356. NamespaceProp: "namespace1",
  357. PodProp: "pod1",
  358. ContainerProp: "container1",
  359. },
  360. Window: NewWindow(&start, &end),
  361. Start: start,
  362. End: end,
  363. CPUCoreHours: 2.0 * hrs,
  364. CPUCoreRequestAverage: 2.0,
  365. CPUCoreUsageAverage: 1.0,
  366. CPUCost: 2.0 * hrs * cpuPrice,
  367. GPUHours: 1.0 * hrs,
  368. GPUCost: 1.0 * hrs * gpuPrice,
  369. NetworkCost: 0.05,
  370. PVByteHours: 100.0 * gib * hrs,
  371. PVCost: 100.0 * hrs * pvPrice,
  372. RAMByteHours: 8.0 * gib * hrs,
  373. RAMBytesRequestAverage: 8.0 * gib,
  374. RAMBytesUsageAverage: 4.0 * gib,
  375. RAMCost: 8.0 * hrs * ramPrice,
  376. SharedCost: 2.00,
  377. ExternalCost: 1.00,
  378. }
  379. data, err := json.Marshal(before)
  380. if err != nil {
  381. t.Fatalf("Allocation.MarshalJSON: unexpected error: %s", err)
  382. }
  383. after := &Allocation{}
  384. err = json.Unmarshal(data, after)
  385. if err != nil {
  386. t.Fatalf("Allocation.UnmarshalJSON: unexpected error: %s", err)
  387. }
  388. // TODO:CLEANUP fix json marshaling of Window so that all of this works.
  389. // In the meantime, just set the Window so that we can test the rest.
  390. after.Window = before.Window.Clone()
  391. if !after.Equal(before) {
  392. t.Fatalf("Allocation.MarshalJSON: before and after are not equal")
  393. }
  394. }
  395. func TestNewAllocationSet(t *testing.T) {
  396. // TODO niko/etl
  397. }
  398. func generateAllocationSet(start time.Time) *AllocationSet {
  399. // Idle allocations
  400. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
  401. ClusterProp: "cluster1",
  402. NodeProp: "node1",
  403. })
  404. a1i.CPUCost = 5.0
  405. a1i.RAMCost = 15.0
  406. a1i.GPUCost = 0.0
  407. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
  408. ClusterProp: "cluster2",
  409. })
  410. a2i.CPUCost = 5.0
  411. a2i.RAMCost = 5.0
  412. a2i.GPUCost = 0.0
  413. // Active allocations
  414. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
  415. ClusterProp: "cluster1",
  416. NamespaceProp: "namespace1",
  417. PodProp: "pod1",
  418. ContainerProp: "container1",
  419. })
  420. a1111.RAMCost = 11.00
  421. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
  422. ClusterProp: "cluster1",
  423. NamespaceProp: "namespace1",
  424. PodProp: "pod-abc",
  425. ContainerProp: "container2",
  426. })
  427. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
  428. ClusterProp: "cluster1",
  429. NamespaceProp: "namespace1",
  430. PodProp: "pod-def",
  431. ContainerProp: "container3",
  432. })
  433. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
  434. ClusterProp: "cluster1",
  435. NamespaceProp: "namespace2",
  436. PodProp: "pod-ghi",
  437. ContainerProp: "container4",
  438. })
  439. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
  440. ClusterProp: "cluster1",
  441. NamespaceProp: "namespace2",
  442. PodProp: "pod-ghi",
  443. ContainerProp: "container5",
  444. })
  445. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
  446. ClusterProp: "cluster1",
  447. NamespaceProp: "namespace2",
  448. PodProp: "pod-jkl",
  449. ContainerProp: "container6",
  450. })
  451. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
  452. ClusterProp: "cluster2",
  453. NamespaceProp: "namespace2",
  454. PodProp: "pod-mno",
  455. ContainerProp: "container4",
  456. })
  457. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
  458. ClusterProp: "cluster2",
  459. NamespaceProp: "namespace2",
  460. PodProp: "pod-mno",
  461. ContainerProp: "container5",
  462. })
  463. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
  464. ClusterProp: "cluster2",
  465. NamespaceProp: "namespace2",
  466. PodProp: "pod-pqr",
  467. ContainerProp: "container6",
  468. })
  469. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
  470. ClusterProp: "cluster2",
  471. NamespaceProp: "namespace3",
  472. PodProp: "pod-stu",
  473. ContainerProp: "container7",
  474. })
  475. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
  476. ClusterProp: "cluster2",
  477. NamespaceProp: "namespace3",
  478. PodProp: "pod-vwx",
  479. ContainerProp: "container8",
  480. })
  481. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
  482. ClusterProp: "cluster2",
  483. NamespaceProp: "namespace3",
  484. PodProp: "pod-vwx",
  485. ContainerProp: "container9",
  486. })
  487. // Controllers
  488. a11abc2.Properties.SetControllerKind("deployment")
  489. a11abc2.Properties.SetController("deployment1")
  490. a11def3.Properties.SetControllerKind("deployment")
  491. a11def3.Properties.SetController("deployment1")
  492. a12ghi4.Properties.SetControllerKind("deployment")
  493. a12ghi4.Properties.SetController("deployment2")
  494. a12ghi5.Properties.SetControllerKind("deployment")
  495. a12ghi5.Properties.SetController("deployment2")
  496. a22mno4.Properties.SetControllerKind("deployment")
  497. a22mno4.Properties.SetController("deployment2")
  498. a22mno5.Properties.SetControllerKind("deployment")
  499. a22mno5.Properties.SetController("deployment2")
  500. a23stu7.Properties.SetControllerKind("deployment")
  501. a23stu7.Properties.SetController("deployment3")
  502. a12jkl6.Properties.SetControllerKind("daemonset")
  503. a12jkl6.Properties.SetController("daemonset1")
  504. a22pqr6.Properties.SetControllerKind("daemonset")
  505. a22pqr6.Properties.SetController("daemonset1")
  506. a23vwx8.Properties.SetControllerKind("statefulset")
  507. a23vwx8.Properties.SetController("statefulset1")
  508. a23vwx9.Properties.SetControllerKind("statefulset")
  509. a23vwx9.Properties.SetController("statefulset1")
  510. // Labels
  511. a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
  512. a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  513. a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  514. a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
  515. a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
  516. //Annotations
  517. a23stu7.Properties.SetAnnotations(map[string]string{"team": "team1"})
  518. a23vwx8.Properties.SetAnnotations(map[string]string{"team": "team2"})
  519. a23vwx9.Properties.SetAnnotations(map[string]string{"team": "team1"})
  520. // Services
  521. a12jkl6.Properties.SetServices([]string{"service1"})
  522. a22pqr6.Properties.SetServices([]string{"service1"})
  523. return NewAllocationSet(start, start.Add(day),
  524. // idle
  525. a1i, a2i,
  526. // cluster 1, namespace1
  527. a1111, a11abc2, a11def3,
  528. // cluster 1, namespace 2
  529. a12ghi4, a12ghi5, a12jkl6,
  530. // cluster 2, namespace 2
  531. a22mno4, a22mno5, a22pqr6,
  532. // cluster 2, namespace 3
  533. a23stu7, a23vwx8, a23vwx9,
  534. )
  535. }
  536. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  537. if err != nil {
  538. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  539. }
  540. if as.Length() != length {
  541. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  542. }
  543. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  544. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  545. }
  546. }
  547. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  548. as.Each(func(k string, a *Allocation) {
  549. if exp, ok := exps[a.Name]; ok {
  550. if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
  551. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost())
  552. }
  553. } else {
  554. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  555. }
  556. })
  557. }
  558. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  559. as.Each(func(k string, a *Allocation) {
  560. if !a.Start.Equal(expStart) {
  561. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  562. }
  563. if !a.End.Equal(expEnd) {
  564. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  565. }
  566. if a.Minutes() != expMinutes {
  567. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes())
  568. }
  569. })
  570. }
  571. func printAllocationSet(msg string, as *AllocationSet) {
  572. fmt.Printf("--- %s ---\n", msg)
  573. as.Each(func(k string, a *Allocation) {
  574. fmt.Printf(" > %s\n", a)
  575. })
  576. }
  577. func TestAllocationSet_AggregateBy(t *testing.T) {
  578. // Test AggregateBy against the following workload topology, which is
  579. // generated by generateAllocationSet:
  580. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
  581. // +----------------------------------------+------+------+------+------+------+------+
  582. // cluster1:
  583. // idle: 20.00 5.00 15.00 0.00 0.00 0.00
  584. // namespace1:
  585. // pod1:
  586. // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
  587. // pod-abc: (deployment1)
  588. // container2: 5.00 1.00 1.00 1.00 1.00 1.00
  589. // pod-def: (deployment1)
  590. // container3: 5.00 1.00 1.00 1.00 1.00 1.00
  591. // namespace2:
  592. // pod-ghi: (deployment2)
  593. // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  594. // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  595. // pod-jkl: (daemonset1)
  596. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  597. // +-----------------------------------------+------+------+------+------+------+------+
  598. // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
  599. // +-----------------------------------------+------+------+------+------+------+------+
  600. // cluster2:
  601. // idle: 10.00 5.00 5.00 0.00 0.00 0.00
  602. // namespace2:
  603. // pod-mno: (deployment2)
  604. // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  605. // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  606. // pod-pqr: (daemonset1)
  607. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  608. // namespace3:
  609. // pod-stu: (deployment3)
  610. // container7: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  611. // pod-vwx: (statefulset1)
  612. // container8: an[team=team2] 5.00 1.00 1.00 1.00 1.00 1.00
  613. // container9: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  614. // +----------------------------------------+------+------+------+------+------+------+
  615. // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
  616. // +----------------------------------------+------+------+------+------+------+------+
  617. // total 100.00 22.00 42.00 12.00 12.00 12.00
  618. // +----------------------------------------+------+------+------+------+------+------+
  619. // Scenarios to test:
  620. // 1 Single-aggregation
  621. // 1a AggregationProperties=(Cluster)
  622. // 1b AggregationProperties=(Namespace)
  623. // 1c AggregationProperties=(Pod)
  624. // 1d AggregationProperties=(Container)
  625. // 1e AggregationProperties=(ControllerKind)
  626. // 1f AggregationProperties=(Controller)
  627. // 1g AggregationProperties=(Service)
  628. // 1h AggregationProperties=(Label:app)
  629. // 2 Multi-aggregation
  630. // 2a AggregationProperties=(Cluster, Namespace)
  631. // 2b AggregationProperties=(Namespace, Label:app)
  632. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  633. // 2d AggregationProperties=(Label:app, Label:environment)
  634. // 3 Share idle
  635. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  636. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  637. // 4 Share resources
  638. // 4a Share namespace ShareEven
  639. // 4b Share cluster ShareWeighted
  640. // 4c Share label ShareEven
  641. // 4d Share overhead ShareWeighted
  642. // 5 Filters
  643. // 5a Filter by cluster with separate idle
  644. // 5b Filter by cluster with shared idle
  645. // TODO niko/idle more filter tests
  646. // 6 Combinations and options
  647. // 6a SplitIdle
  648. // 6b Share idle with filters
  649. // 6c Share resources with filters
  650. // 6d Share idle and share resources
  651. // 7 Edge cases and errors
  652. // 7a Empty AggregationProperties
  653. // 7b Filter all
  654. // 7c Share all
  655. // 7d Share and filter the same allocations
  656. // Definitions and set-up:
  657. var as *AllocationSet
  658. var err error
  659. endYesterday := time.Now().UTC().Truncate(day)
  660. startYesterday := endYesterday.Add(-day)
  661. numClusters := 2
  662. numNamespaces := 3
  663. numPods := 9
  664. numContainers := 9
  665. numControllerKinds := 3
  666. numControllers := 5
  667. numServices := 1
  668. numLabelApps := 2
  669. // By default, idle is reported as a single, merged allocation
  670. numIdle := 1
  671. // There will only ever be one __unallocated__
  672. numUnallocated := 1
  673. // There are two clusters, so each gets an idle entry when they are split
  674. numSplitIdle := 2
  675. activeTotalCost := 70.0
  676. idleTotalCost := 30.0
  677. sharedOverheadHourlyCost := 7.0
  678. isNamespace3 := func(a *Allocation) bool {
  679. ns, err := a.Properties.GetNamespace()
  680. return err == nil && ns == "namespace3"
  681. }
  682. isApp1 := func(a *Allocation) bool {
  683. ls, _ := a.Properties.GetLabels()
  684. if app, ok := ls["app"]; ok && app == "app1" {
  685. return true
  686. }
  687. return false
  688. }
  689. end := time.Now().UTC().Truncate(day)
  690. start := end.Add(-day)
  691. // Tests:
  692. // 1 Single-aggregation
  693. // 1a AggregationProperties=(Cluster)
  694. as = generateAllocationSet(start)
  695. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  696. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  697. assertAllocationTotals(t, as, "1a", map[string]float64{
  698. "cluster1": 40.00,
  699. "cluster2": 30.00,
  700. IdleSuffix: 30.00,
  701. })
  702. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  703. // 1b AggregationProperties=(Namespace)
  704. as = generateAllocationSet(start)
  705. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  706. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  707. assertAllocationTotals(t, as, "1b", map[string]float64{
  708. "namespace1": 25.00,
  709. "namespace2": 30.00,
  710. "namespace3": 15.00,
  711. IdleSuffix: 30.00,
  712. })
  713. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  714. // 1c AggregationProperties=(Pod)
  715. as = generateAllocationSet(start)
  716. err = as.AggregateBy(Properties{PodProp: true}, nil)
  717. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  718. assertAllocationTotals(t, as, "1c", map[string]float64{
  719. "pod-jkl": 5.00,
  720. "pod-stu": 5.00,
  721. "pod-abc": 5.00,
  722. "pod-pqr": 5.00,
  723. "pod-def": 5.00,
  724. "pod-vwx": 10.00,
  725. "pod1": 15.00,
  726. "pod-mno": 10.00,
  727. "pod-ghi": 10.00,
  728. IdleSuffix: 30.00,
  729. })
  730. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  731. // 1d AggregationProperties=(Container)
  732. as = generateAllocationSet(start)
  733. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  734. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  735. assertAllocationTotals(t, as, "1d", map[string]float64{
  736. "container2": 5.00,
  737. "container9": 5.00,
  738. "container6": 10.00,
  739. "container3": 5.00,
  740. "container4": 10.00,
  741. "container7": 5.00,
  742. "container8": 5.00,
  743. "container5": 10.00,
  744. "container1": 15.00,
  745. IdleSuffix: 30.00,
  746. })
  747. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  748. // 1e AggregationProperties=(ControllerKind)
  749. as = generateAllocationSet(start)
  750. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  751. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  752. assertAllocationTotals(t, as, "1e", map[string]float64{
  753. "daemonset": 10.00,
  754. "deployment": 35.00,
  755. "statefulset": 10.00,
  756. IdleSuffix: 30.00,
  757. UnallocatedSuffix: 15.00,
  758. })
  759. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  760. // 1f AggregationProperties=(Controller)
  761. as = generateAllocationSet(start)
  762. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  763. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  764. assertAllocationTotals(t, as, "1f", map[string]float64{
  765. "deployment/deployment2": 20.00,
  766. "daemonset/daemonset1": 10.00,
  767. "deployment/deployment3": 5.00,
  768. "statefulset/statefulset1": 10.00,
  769. "deployment/deployment1": 10.00,
  770. IdleSuffix: 30.00,
  771. UnallocatedSuffix: 15.00,
  772. })
  773. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  774. // 1g AggregationProperties=(Service)
  775. as = generateAllocationSet(start)
  776. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  777. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  778. assertAllocationTotals(t, as, "1g", map[string]float64{
  779. "service1": 10.00,
  780. IdleSuffix: 30.00,
  781. UnallocatedSuffix: 60.00,
  782. })
  783. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  784. // 1h AggregationProperties=(Label:app)
  785. as = generateAllocationSet(start)
  786. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  787. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  788. assertAllocationTotals(t, as, "1h", map[string]float64{
  789. "app=app1": 15.00,
  790. "app=app2": 20.00,
  791. IdleSuffix: 30.00,
  792. UnallocatedSuffix: 35.00,
  793. })
  794. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  795. // 1i AggregationProperties=(ControllerKind:deployment)
  796. as = generateAllocationSet(start)
  797. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  798. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  799. assertAllocationTotals(t, as, "1i", map[string]float64{
  800. "deployment": 35.00,
  801. IdleSuffix: 30.00,
  802. UnallocatedSuffix: 35.00,
  803. })
  804. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  805. // 1j AggregationProperties=(Annotation:team)
  806. as = generateAllocationSet(start)
  807. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
  808. assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  809. assertAllocationTotals(t, as, "1j", map[string]float64{
  810. "team=team1": 10.00,
  811. "team=team2": 5.00,
  812. IdleSuffix: 30.00,
  813. UnallocatedSuffix: 55.00,
  814. })
  815. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  816. // 2 Multi-aggregation
  817. // 2a AggregationProperties=(Cluster, Namespace)
  818. // 2b AggregationProperties=(Namespace, Label:app)
  819. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  820. // 2d AggregationProperties=(Label:app, Label:environment)
  821. as = generateAllocationSet(start)
  822. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  823. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  824. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  825. assertAllocationTotals(t, as, "2d", map[string]float64{
  826. "app=app1/env=env1": 15.00,
  827. "app=app2/env=env2": 10.00,
  828. "app=app2/" + UnallocatedSuffix: 10.00,
  829. IdleSuffix: 30.00,
  830. UnallocatedSuffix: 35.00,
  831. })
  832. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  833. as = generateAllocationSet(start)
  834. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  835. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  836. assertAllocationTotals(t, as, "2e", map[string]float64{
  837. "cluster1/app=app2/env=env2": 10.00,
  838. "__idle__": 30.00,
  839. "cluster1/app=app1/env=env1": 15.00,
  840. "cluster1/" + UnallocatedSuffix: 15.00,
  841. "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
  842. "cluster2/" + UnallocatedSuffix: 20.00,
  843. })
  844. // 2f AggregationProperties=(annotation:team, pod)
  845. as = generateAllocationSet(start)
  846. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
  847. assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
  848. assertAllocationTotals(t, as, "2e", map[string]float64{
  849. "pod-jkl/" + UnallocatedSuffix: 5.00,
  850. "pod-stu/team=team1": 5.00,
  851. "pod-abc/" + UnallocatedSuffix: 5.00,
  852. "pod-pqr/" + UnallocatedSuffix: 5.00,
  853. "pod-def/" + UnallocatedSuffix: 5.00,
  854. "pod-vwx/team=team1": 5.00,
  855. "pod-vwx/team=team2": 5.00,
  856. "pod1/" + UnallocatedSuffix: 15.00,
  857. "pod-mno/" + UnallocatedSuffix: 10.00,
  858. "pod-ghi/" + UnallocatedSuffix: 10.00,
  859. IdleSuffix: 30.00,
  860. })
  861. // // TODO niko/etl
  862. // // 3 Share idle
  863. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  864. // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  865. // 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)
  866. // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  867. as = generateAllocationSet(start)
  868. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  869. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  870. assertAllocationTotals(t, as, "3a", map[string]float64{
  871. "namespace1": 39.69,
  872. "namespace2": 40.31,
  873. "namespace3": 20.00,
  874. })
  875. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  876. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  877. // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  878. // 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)
  879. // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  880. as = generateAllocationSet(start)
  881. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  882. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  883. assertAllocationTotals(t, as, "3a", map[string]float64{
  884. "namespace1": 35.00,
  885. "namespace2": 45.00,
  886. "namespace3": 20.00,
  887. })
  888. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  889. // 4 Share resources
  890. // 4a Share namespace ShareEven
  891. // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
  892. // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
  893. // idle: 30.0000
  894. as = generateAllocationSet(start)
  895. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  896. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  897. ShareSplit: ShareEven,
  898. })
  899. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  900. assertAllocationTotals(t, as, "4a", map[string]float64{
  901. "namespace1": 32.50,
  902. "namespace2": 37.50,
  903. IdleSuffix: 30.00,
  904. })
  905. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  906. // 4b Share namespace ShareWeighted
  907. // namespace1: 32.5000 =
  908. // namespace2: 37.5000 =
  909. // idle: 30.0000
  910. as = generateAllocationSet(start)
  911. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  912. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  913. ShareSplit: ShareWeighted,
  914. })
  915. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  916. assertAllocationTotals(t, as, "4b", map[string]float64{
  917. "namespace1": 31.82,
  918. "namespace2": 38.18,
  919. IdleSuffix: 30.00,
  920. })
  921. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  922. // 4c Share label ShareEven
  923. // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
  924. // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
  925. // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
  926. // idle: 30.0000
  927. as = generateAllocationSet(start)
  928. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  929. ShareFuncs: []AllocationMatchFunc{isApp1},
  930. ShareSplit: ShareEven,
  931. })
  932. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  933. assertAllocationTotals(t, as, "4c", map[string]float64{
  934. "namespace1": 15.00,
  935. "namespace2": 35.00,
  936. "namespace3": 20.00,
  937. IdleSuffix: 30.00,
  938. })
  939. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  940. // 4d Share overhead ShareWeighted
  941. // namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
  942. // namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
  943. // namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
  944. // idle: 30.0000
  945. as = generateAllocationSet(start)
  946. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  947. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  948. ShareSplit: ShareWeighted,
  949. })
  950. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  951. assertAllocationTotals(t, as, "4d", map[string]float64{
  952. "namespace1": 85.00,
  953. "namespace2": 102.00,
  954. "namespace3": 51.00,
  955. IdleSuffix: 30.00,
  956. })
  957. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  958. // 5 Filters
  959. isCluster := func(matchCluster string) func(*Allocation) bool {
  960. return func(a *Allocation) bool {
  961. cluster, err := a.Properties.GetCluster()
  962. return err == nil && cluster == matchCluster
  963. }
  964. }
  965. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  966. return func(a *Allocation) bool {
  967. namespace, err := a.Properties.GetNamespace()
  968. return err == nil && namespace == matchNamespace
  969. }
  970. }
  971. // 5a Filter by cluster with separate idle
  972. as = generateAllocationSet(start)
  973. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  974. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  975. ShareIdle: ShareNone,
  976. })
  977. assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
  978. assertAllocationTotals(t, as, "5a", map[string]float64{
  979. "cluster1": 40.00,
  980. IdleSuffix: 20.00,
  981. })
  982. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  983. // 5b Filter by cluster with shared idle
  984. as = generateAllocationSet(start)
  985. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  986. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  987. ShareIdle: ShareWeighted,
  988. })
  989. assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
  990. assertAllocationTotals(t, as, "5b", map[string]float64{
  991. "cluster1": 60.00,
  992. })
  993. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  994. // 5c Filter by cluster, agg by namespace, with separate idle
  995. as = generateAllocationSet(start)
  996. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  997. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  998. ShareIdle: ShareNone,
  999. })
  1000. assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
  1001. assertAllocationTotals(t, as, "5c", map[string]float64{
  1002. "namespace1": 25.00,
  1003. "namespace2": 15.00,
  1004. IdleSuffix: 20.00,
  1005. })
  1006. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  1007. // 5d Filter by namespace, agg by cluster, with separate idle
  1008. as = generateAllocationSet(start)
  1009. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  1010. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1011. ShareIdle: ShareNone,
  1012. })
  1013. assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
  1014. assertAllocationTotals(t, as, "5d", map[string]float64{
  1015. "cluster1": 15.00,
  1016. "cluster2": 15.00,
  1017. IdleSuffix: 10.31,
  1018. })
  1019. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  1020. // 6 Combinations and options
  1021. // 6a SplitIdle
  1022. as = generateAllocationSet(start)
  1023. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  1024. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  1025. assertAllocationTotals(t, as, "6a", map[string]float64{
  1026. "namespace1": 25.00,
  1027. "namespace2": 30.00,
  1028. "namespace3": 15.00,
  1029. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  1030. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  1031. })
  1032. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  1033. // 6b Share idle weighted with filters
  1034. // Should match values from unfiltered aggregation (3a)
  1035. // 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)
  1036. as = generateAllocationSet(start)
  1037. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1038. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1039. ShareIdle: ShareWeighted,
  1040. })
  1041. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  1042. assertAllocationTotals(t, as, "6b", map[string]float64{
  1043. "namespace2": 40.31,
  1044. })
  1045. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  1046. // 6c Share idle even with filters
  1047. // Should match values from unfiltered aggregation (3b)
  1048. // 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)
  1049. as = generateAllocationSet(start)
  1050. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1051. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1052. ShareIdle: ShareEven,
  1053. })
  1054. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  1055. assertAllocationTotals(t, as, "6b", map[string]float64{
  1056. "namespace2": 45.00,
  1057. })
  1058. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  1059. // 6d Share overhead with filters
  1060. // namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
  1061. // namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
  1062. // namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
  1063. // idle: 30.0000
  1064. // Then namespace 2 is filtered.
  1065. as = generateAllocationSet(start)
  1066. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1067. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1068. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1069. ShareSplit: ShareWeighted,
  1070. })
  1071. assertAllocationSetTotals(t, as, "6d", err, 2, 132.00)
  1072. assertAllocationTotals(t, as, "6d", map[string]float64{
  1073. "namespace2": 102.00,
  1074. IdleSuffix: 30.00,
  1075. })
  1076. assertAllocationWindow(t, as, "6d", startYesterday, endYesterday, 1440.0)
  1077. // 6e Share resources with filters
  1078. // --- Shared ---
  1079. // namespace1: 25.00 (gets shared among namespace2 and namespace3)
  1080. // --- Filtered ---
  1081. // namespace3: 23.33 = 15.00 + (25.00)*(15.00/45.00) (filtered out)
  1082. // --- Results ---
  1083. // namespace2: 46.67 = 30.00 + (25.00)*(15.00/45.00)
  1084. // idle: 30.0000
  1085. as = generateAllocationSet(start)
  1086. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1087. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1088. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1089. ShareSplit: ShareWeighted,
  1090. })
  1091. assertAllocationSetTotals(t, as, "6e", err, 2, 76.67)
  1092. assertAllocationTotals(t, as, "6e", map[string]float64{
  1093. "namespace2": 46.67,
  1094. IdleSuffix: 30.00,
  1095. })
  1096. assertAllocationWindow(t, as, "6e", startYesterday, endYesterday, 1440.0)
  1097. // 6f Share idle weighted and share resources weighted
  1098. //
  1099. // First, share idle weighted produces:
  1100. //
  1101. // namespace1: 39.6875
  1102. // initial cost 25.0000
  1103. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1104. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1105. //
  1106. // namespace2: 40.3125
  1107. // initial cost 30.0000
  1108. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1109. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1110. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1111. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1112. //
  1113. // namespace3: 20.0000
  1114. // initial cost 15.0000
  1115. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1116. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1117. //
  1118. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1119. // computed before allocating idle (so that weighting idle differently
  1120. // doesn't adversely affect the sharing mechanism):
  1121. //
  1122. // namespace2: 66.7708
  1123. // initial cost 30.0000
  1124. // idle cost 10.3125
  1125. // shared cost 26.4583 = (39.6875)*(30.0/45.0)
  1126. //
  1127. // namespace3: 33.2292
  1128. // initial cost 15.0000
  1129. // idle cost 5.0000
  1130. // shared cost 13.2292 = (39.6875)*(15.0/45.0)
  1131. //
  1132. as = generateAllocationSet(start)
  1133. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1134. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1135. ShareSplit: ShareWeighted,
  1136. ShareIdle: ShareWeighted,
  1137. })
  1138. assertAllocationSetTotals(t, as, "6f", err, 2, activeTotalCost+idleTotalCost)
  1139. assertAllocationTotals(t, as, "6f", map[string]float64{
  1140. "namespace2": 66.77,
  1141. "namespace3": 33.23,
  1142. })
  1143. assertAllocationWindow(t, as, "6f", startYesterday, endYesterday, 1440.0)
  1144. // 6g Share idle, share resources, and filter
  1145. //
  1146. // First, share idle weighted produces:
  1147. //
  1148. // namespace1: 39.6875
  1149. // initial cost 25.0000
  1150. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1151. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1152. //
  1153. // namespace2: 40.3125
  1154. // initial cost 30.0000
  1155. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1156. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1157. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1158. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1159. //
  1160. // namespace3: 20.0000
  1161. // initial cost 15.0000
  1162. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1163. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1164. //
  1165. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1166. // computed before allocating idle (so that weighting idle differently
  1167. // doesn't adversely affect the sharing mechanism):
  1168. //
  1169. // namespace2: 66.7708
  1170. // initial cost 30.0000
  1171. // idle cost 10.3125
  1172. // shared cost 26.4583 = (39.6875)*(30.0/45.0)
  1173. //
  1174. // namespace3: 33.2292
  1175. // initial cost 15.0000
  1176. // idle cost 5.0000
  1177. // shared cost 13.2292 = (39.6875)*(15.0/45.0)
  1178. //
  1179. // Then, filter for namespace2: 66.7708
  1180. //
  1181. as = generateAllocationSet(start)
  1182. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1183. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1184. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1185. ShareSplit: ShareWeighted,
  1186. ShareIdle: ShareWeighted,
  1187. })
  1188. assertAllocationSetTotals(t, as, "6g", err, 1, 66.77)
  1189. assertAllocationTotals(t, as, "6g", map[string]float64{
  1190. "namespace2": 66.77,
  1191. })
  1192. assertAllocationWindow(t, as, "6g", startYesterday, endYesterday, 1440.0)
  1193. // 6h Share idle, share resources, share overhead
  1194. //
  1195. // Share idle weighted:
  1196. //
  1197. // namespace1: 39.6875
  1198. // initial cost 25.0000
  1199. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1200. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1201. //
  1202. // namespace2: 40.3125
  1203. // initial cost 30.0000
  1204. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1205. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1206. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1207. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1208. //
  1209. // namespace3: 20.0000
  1210. // initial cost 15.0000
  1211. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1212. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1213. //
  1214. // Then share overhead:
  1215. //
  1216. // namespace1: 99.6875 = 39.6875 + (7.0*24.0)*(25.00/70.00)
  1217. // namespace2: 112.3125 = 40.3125 + (7.0*24.0)*(30.00/70.00)
  1218. // namespace3: 56.0000 = 20.0000 + (7.0*24.0)*(15.00/70.00)
  1219. //
  1220. // Then namespace 2 is filtered.
  1221. as = generateAllocationSet(start)
  1222. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1223. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1224. ShareSplit: ShareWeighted,
  1225. ShareIdle: ShareWeighted,
  1226. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1227. })
  1228. assertAllocationSetTotals(t, as, "6h", err, 1, 112.31)
  1229. assertAllocationTotals(t, as, "6h", map[string]float64{
  1230. "namespace2": 112.31,
  1231. })
  1232. assertAllocationWindow(t, as, "6h", startYesterday, endYesterday, 1440.0)
  1233. // 7 Edge cases and errors
  1234. // 7a Empty AggregationProperties
  1235. // 7b Filter all
  1236. // 7c Share all
  1237. // 7d Share and filter the same allocations
  1238. }
  1239. // TODO niko/etl
  1240. //func TestAllocationSet_Clone(t *testing.T) {}
  1241. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  1242. var as *AllocationSet
  1243. var err error
  1244. var idles map[string]*Allocation
  1245. end := time.Now().UTC().Truncate(day)
  1246. start := end.Add(-day)
  1247. // Generate AllocationSet and strip out any existing idle allocations
  1248. as = generateAllocationSet(start)
  1249. for key := range as.idleKeys {
  1250. as.Delete(key)
  1251. }
  1252. // Create an AssetSet representing cluster costs for two clusters (cluster1
  1253. // and cluster2). Include Nodes and Disks for both, even though only
  1254. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  1255. // by type, here we will provide multiple Nodes for one of the clusters to
  1256. // make sure the function still holds.
  1257. // NOTE: we're re-using generateAllocationSet so this has to line up with
  1258. // the allocated node costs from that function. See table above.
  1259. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  1260. // +-----------------------------------------+------+------+------+------+------------+
  1261. // cluster1:
  1262. // nodes 100.00 55.00 44.00 11.00 -10.00
  1263. // +-----------------------------------------+------+------+------+------+------------+
  1264. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  1265. // +-----------------------------------------+------+------+------+------+------------+
  1266. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  1267. // +-----------------------------------------+------+------+------+------+------------+
  1268. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  1269. // +-----------------------------------------+------+------+------+------+------------+
  1270. // cluster2:
  1271. // node1 35.00 20.00 15.00 0.00 0.00
  1272. // node2 35.00 20.00 15.00 0.00 0.00
  1273. // node3 30.00 10.00 10.00 10.00 0.00
  1274. // (disks should not matter for idle)
  1275. // +-----------------------------------------+------+------+------+------+------------+
  1276. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1277. // +-----------------------------------------+------+------+------+------+------------+
  1278. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1279. // +-----------------------------------------+------+------+------+------+------------+
  1280. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1281. // +-----------------------------------------+------+------+------+------+------------+
  1282. cluster1Nodes := NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1283. cluster1Nodes.CPUCost = 55.0
  1284. cluster1Nodes.RAMCost = 44.0
  1285. cluster1Nodes.GPUCost = 11.0
  1286. cluster1Nodes.adjustment = -10.00
  1287. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1288. cluster2Node1.CPUCost = 20.0
  1289. cluster2Node1.RAMCost = 15.0
  1290. cluster2Node1.GPUCost = 0.0
  1291. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1292. cluster2Node2.CPUCost = 20.0
  1293. cluster2Node2.RAMCost = 15.0
  1294. cluster2Node2.GPUCost = 0.0
  1295. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1296. cluster2Node3.CPUCost = 10.0
  1297. cluster2Node3.RAMCost = 10.0
  1298. cluster2Node3.GPUCost = 10.0
  1299. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1300. cluster2Disk1.Cost = 5.0
  1301. assetSet := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1302. idles, err = as.ComputeIdleAllocations(assetSet)
  1303. if err != nil {
  1304. t.Fatalf("unexpected error: %s", err)
  1305. }
  1306. if len(idles) != 2 {
  1307. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1308. }
  1309. if idle, ok := idles["cluster1"]; !ok {
  1310. t.Fatalf("expected idle cost for %s", "cluster1")
  1311. } else {
  1312. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  1313. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  1314. }
  1315. }
  1316. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1317. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1318. }
  1319. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1320. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1321. }
  1322. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1323. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1324. }
  1325. if idle, ok := idles["cluster2"]; !ok {
  1326. t.Fatalf("expected idle cost for %s", "cluster2")
  1327. } else {
  1328. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  1329. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  1330. }
  1331. }
  1332. // NOTE: we're re-using generateAllocationSet so this has to line up with
  1333. // the allocated node costs from that function. See table above.
  1334. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  1335. // +-----------------------------------------+------+------+------+------+------------+
  1336. // cluster1:
  1337. // nodes 100.00 5.00 4.00 1.00 90.00
  1338. // +-----------------------------------------+------+------+------+------+------------+
  1339. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  1340. // +-----------------------------------------+------+------+------+------+------------+
  1341. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  1342. // +-----------------------------------------+------+------+------+------+------------+
  1343. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  1344. // +-----------------------------------------+------+------+------+------+------------+
  1345. // cluster2:
  1346. // node1 35.00 20.00 15.00 0.00 0.00
  1347. // node2 35.00 20.00 15.00 0.00 0.00
  1348. // node3 30.00 10.00 10.00 10.00 0.00
  1349. // (disks should not matter for idle)
  1350. // +-----------------------------------------+------+------+------+------+------------+
  1351. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1352. // +-----------------------------------------+------+------+------+------+------------+
  1353. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1354. // +-----------------------------------------+------+------+------+------+------------+
  1355. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1356. // +-----------------------------------------+------+------+------+------+------------+
  1357. cluster1Nodes = NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1358. cluster1Nodes.CPUCost = 5.0
  1359. cluster1Nodes.RAMCost = 4.0
  1360. cluster1Nodes.GPUCost = 1.0
  1361. cluster1Nodes.adjustment = 90.00
  1362. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1363. cluster2Node1.CPUCost = 20.0
  1364. cluster2Node1.RAMCost = 15.0
  1365. cluster2Node1.GPUCost = 0.0
  1366. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1367. cluster2Node2.CPUCost = 20.0
  1368. cluster2Node2.RAMCost = 15.0
  1369. cluster2Node2.GPUCost = 0.0
  1370. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1371. cluster2Node3.CPUCost = 10.0
  1372. cluster2Node3.RAMCost = 10.0
  1373. cluster2Node3.GPUCost = 10.0
  1374. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1375. cluster2Disk1.Cost = 5.0
  1376. assetSet = NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1377. idles, err = as.ComputeIdleAllocations(assetSet)
  1378. if err != nil {
  1379. t.Fatalf("unexpected error: %s", err)
  1380. }
  1381. if len(idles) != 2 {
  1382. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1383. }
  1384. if idle, ok := idles["cluster1"]; !ok {
  1385. t.Fatalf("expected idle cost for %s", "cluster1")
  1386. } else {
  1387. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  1388. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  1389. }
  1390. }
  1391. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1392. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1393. }
  1394. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1395. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1396. }
  1397. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1398. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1399. }
  1400. if idle, ok := idles["cluster2"]; !ok {
  1401. t.Fatalf("expected idle cost for %s", "cluster2")
  1402. } else {
  1403. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  1404. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  1405. }
  1406. }
  1407. // TODO assert value of each resource cost precisely
  1408. }
  1409. // TODO niko/etl
  1410. //func TestAllocationSet_Delete(t *testing.T) {}
  1411. // TODO niko/etl
  1412. //func TestAllocationSet_End(t *testing.T) {}
  1413. // TODO niko/etl
  1414. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  1415. // TODO niko/etl
  1416. //func TestAllocationSet_Insert(t *testing.T) {}
  1417. // TODO niko/etl
  1418. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  1419. // TODO niko/etl
  1420. //func TestAllocationSet_Length(t *testing.T) {}
  1421. // TODO niko/etl
  1422. //func TestAllocationSet_Map(t *testing.T) {}
  1423. // TODO niko/etl
  1424. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  1425. // TODO niko/etl
  1426. //func TestAllocationSet_Resolution(t *testing.T) {}
  1427. // TODO niko/etl
  1428. //func TestAllocationSet_Seconds(t *testing.T) {}
  1429. // TODO niko/etl
  1430. //func TestAllocationSet_Set(t *testing.T) {}
  1431. // TODO niko/etl
  1432. //func TestAllocationSet_Start(t *testing.T) {}
  1433. // TODO niko/etl
  1434. //func TestAllocationSet_TotalCost(t *testing.T) {}
  1435. // TODO niko/etl
  1436. //func TestNewAllocationSetRange(t *testing.T) {}
  1437. func TestAllocationSetRange_Accumulate(t *testing.T) {
  1438. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1439. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1440. today := time.Now().UTC().Truncate(day)
  1441. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1442. // Accumulating any combination of nil and/or empty set should result in empty set
  1443. result, err := NewAllocationSetRange(nil).Accumulate()
  1444. if err != nil {
  1445. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1446. }
  1447. if !result.IsEmpty() {
  1448. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1449. }
  1450. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  1451. if err != nil {
  1452. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1453. }
  1454. if !result.IsEmpty() {
  1455. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1456. }
  1457. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  1458. if err != nil {
  1459. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1460. }
  1461. if !result.IsEmpty() {
  1462. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1463. }
  1464. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  1465. if err != nil {
  1466. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1467. }
  1468. if !result.IsEmpty() {
  1469. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1470. }
  1471. todayAS := NewAllocationSet(today, tomorrow)
  1472. todayAS.Set(NewUnitAllocation("", today, day, nil))
  1473. yesterdayAS := NewAllocationSet(yesterday, today)
  1474. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  1475. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  1476. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  1477. if err != nil {
  1478. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1479. }
  1480. if result == nil {
  1481. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1482. }
  1483. if result.TotalCost() != 5.0 {
  1484. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1485. }
  1486. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  1487. if err != nil {
  1488. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1489. }
  1490. if result == nil {
  1491. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1492. }
  1493. if result.TotalCost() != 5.0 {
  1494. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1495. }
  1496. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  1497. if err != nil {
  1498. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1499. }
  1500. if result == nil {
  1501. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1502. }
  1503. if result.TotalCost() != 5.0 {
  1504. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1505. }
  1506. // Accumulate two non-nil should result in sum of both with appropriate start, end
  1507. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  1508. if err != nil {
  1509. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1510. }
  1511. if result == nil {
  1512. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1513. }
  1514. if result.TotalCost() != 10.0 {
  1515. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  1516. }
  1517. allocMap := result.Map()
  1518. if len(allocMap) != 1 {
  1519. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  1520. }
  1521. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  1522. if alloc == nil {
  1523. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  1524. }
  1525. if alloc.CPUCoreHours != 2.0 {
  1526. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  1527. }
  1528. if alloc.CPUCost != 2.0 {
  1529. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  1530. }
  1531. if alloc.CPUEfficiency() != 1.0 {
  1532. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency())
  1533. }
  1534. if alloc.GPUHours != 2.0 {
  1535. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  1536. }
  1537. if alloc.GPUCost != 2.0 {
  1538. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  1539. }
  1540. if alloc.NetworkCost != 2.0 {
  1541. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  1542. }
  1543. if alloc.PVByteHours != 2.0 {
  1544. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  1545. }
  1546. if alloc.PVCost != 2.0 {
  1547. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  1548. }
  1549. if alloc.RAMByteHours != 2.0 {
  1550. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  1551. }
  1552. if alloc.RAMCost != 2.0 {
  1553. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  1554. }
  1555. if alloc.RAMEfficiency() != 1.0 {
  1556. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
  1557. }
  1558. if alloc.TotalCost() != 10.0 {
  1559. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost())
  1560. }
  1561. if alloc.TotalEfficiency() != 1.0 {
  1562. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
  1563. }
  1564. if !alloc.Start.Equal(yesterday) {
  1565. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1566. }
  1567. if !alloc.End.Equal(tomorrow) {
  1568. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1569. }
  1570. if alloc.Minutes() != 2880.0 {
  1571. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes())
  1572. }
  1573. }
  1574. // TODO niko/etl
  1575. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1576. // TODO niko/etl
  1577. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1578. // TODO niko/etl
  1579. // func TestAllocationSetRange_Append(t *testing.T) {}
  1580. // TODO niko/etl
  1581. // func TestAllocationSetRange_Each(t *testing.T) {}
  1582. // TODO niko/etl
  1583. // func TestAllocationSetRange_Get(t *testing.T) {}
  1584. func TestAllocationSetRange_InsertRange(t *testing.T) {
  1585. // Set up
  1586. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1587. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1588. today := time.Now().UTC().Truncate(day)
  1589. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1590. unit := NewUnitAllocation("", today, day, nil)
  1591. ago2dAS := NewAllocationSet(ago2d, yesterday)
  1592. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  1593. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  1594. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  1595. yesterdayAS := NewAllocationSet(yesterday, today)
  1596. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  1597. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  1598. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  1599. todayAS := NewAllocationSet(today, tomorrow)
  1600. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  1601. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  1602. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  1603. var nilASR *AllocationSetRange
  1604. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  1605. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  1606. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  1607. var err error
  1608. // Expect an error calling InsertRange on nil
  1609. err = nilASR.InsertRange(thatASR)
  1610. if err == nil {
  1611. t.Fatalf("expected error, got nil")
  1612. }
  1613. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  1614. err = thisASR.InsertRange(nil)
  1615. if err != nil {
  1616. t.Fatalf("unexpected error: %s", err)
  1617. }
  1618. thisASR.Each(func(i int, as *AllocationSet) {
  1619. as.Each(func(k string, a *Allocation) {
  1620. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1621. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1622. }
  1623. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1624. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1625. }
  1626. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1627. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1628. }
  1629. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1630. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1631. }
  1632. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1633. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1634. }
  1635. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1636. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1637. }
  1638. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1639. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1640. }
  1641. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1642. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1643. }
  1644. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1645. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1646. }
  1647. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1648. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1649. }
  1650. })
  1651. })
  1652. // Expect an error calling InsertRange with a range exceeding the receiver
  1653. err = thisASR.InsertRange(longASR)
  1654. if err == nil {
  1655. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  1656. }
  1657. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  1658. // precisely double when inserting a range that only has a duplicate of
  1659. // "yesterday", but no entry for "today"
  1660. err = thisASR.InsertRange(thatASR)
  1661. if err != nil {
  1662. t.Fatalf("unexpected error: %s", err)
  1663. }
  1664. yAS, err := thisASR.Get(0)
  1665. yAS.Each(func(k string, a *Allocation) {
  1666. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  1667. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1668. }
  1669. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  1670. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1671. }
  1672. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  1673. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1674. }
  1675. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  1676. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1677. }
  1678. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  1679. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1680. }
  1681. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  1682. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1683. }
  1684. if !util.IsApproximately(a.PVByteHours, 2*unit.PVByteHours) {
  1685. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1686. }
  1687. if !util.IsApproximately(a.PVCost, 2*unit.PVCost) {
  1688. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1689. }
  1690. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  1691. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1692. }
  1693. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  1694. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1695. }
  1696. })
  1697. tAS, err := thisASR.Get(1)
  1698. tAS.Each(func(k string, a *Allocation) {
  1699. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1700. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1701. }
  1702. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1703. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1704. }
  1705. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1706. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1707. }
  1708. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1709. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1710. }
  1711. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1712. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1713. }
  1714. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1715. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1716. }
  1717. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1718. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1719. }
  1720. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1721. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1722. }
  1723. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1724. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1725. }
  1726. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1727. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1728. }
  1729. })
  1730. }
  1731. // TODO niko/etl
  1732. // func TestAllocationSetRange_Length(t *testing.T) {}
  1733. // TODO niko/etl
  1734. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1735. // TODO niko/etl
  1736. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1737. // TODO niko/etl
  1738. // func TestAllocationSetRange_Window(t *testing.T) {}