allocation_test.go 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996
  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 TestAllocationSet_generateKey(t *testing.T) {
  396. var alloc *Allocation
  397. var key string
  398. props := Properties{}
  399. props.SetCluster("")
  400. key = alloc.generateKey(props)
  401. if key != "" {
  402. t.Fatalf("generateKey: expected \"\"; actual \"%s\"", key)
  403. }
  404. alloc = &Allocation{}
  405. alloc.Properties = Properties{
  406. ClusterProp: "cluster1",
  407. LabelProp: map[string]string{
  408. "app": "app1",
  409. "env": "env1",
  410. },
  411. }
  412. key = alloc.generateKey(props)
  413. if key != "cluster1" {
  414. t.Fatalf("generateKey: expected \"cluster1\"; actual \"%s\"", key)
  415. }
  416. props.SetNamespace("")
  417. props.SetLabels(map[string]string{"app": ""})
  418. key = alloc.generateKey(props)
  419. if key != "cluster1//app=app1" {
  420. t.Fatalf("generateKey: expected \"cluster1//app=app1\"; actual \"%s\"", key)
  421. }
  422. alloc.Properties = Properties{
  423. ClusterProp: "cluster1",
  424. NamespaceProp: "namespace1",
  425. LabelProp: map[string]string{
  426. "app": "app1",
  427. "env": "env1",
  428. },
  429. }
  430. key = alloc.generateKey(props)
  431. if key != "cluster1/namespace1/app=app1" {
  432. t.Fatalf("generateKey: expected \"cluster1/namespace1/app=app1\"; actual \"%s\"", key)
  433. }
  434. }
  435. func TestNewAllocationSet(t *testing.T) {
  436. // TODO niko/etl
  437. }
  438. func generateAllocationSet(start time.Time) *AllocationSet {
  439. // Idle allocations
  440. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
  441. ClusterProp: "cluster1",
  442. NodeProp: "node1",
  443. })
  444. a1i.CPUCost = 5.0
  445. a1i.RAMCost = 15.0
  446. a1i.GPUCost = 0.0
  447. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
  448. ClusterProp: "cluster2",
  449. })
  450. a2i.CPUCost = 5.0
  451. a2i.RAMCost = 5.0
  452. a2i.GPUCost = 0.0
  453. // Active allocations
  454. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
  455. ClusterProp: "cluster1",
  456. NamespaceProp: "namespace1",
  457. PodProp: "pod1",
  458. ContainerProp: "container1",
  459. })
  460. a1111.RAMCost = 11.00
  461. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
  462. ClusterProp: "cluster1",
  463. NamespaceProp: "namespace1",
  464. PodProp: "pod-abc",
  465. ContainerProp: "container2",
  466. })
  467. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
  468. ClusterProp: "cluster1",
  469. NamespaceProp: "namespace1",
  470. PodProp: "pod-def",
  471. ContainerProp: "container3",
  472. })
  473. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
  474. ClusterProp: "cluster1",
  475. NamespaceProp: "namespace2",
  476. PodProp: "pod-ghi",
  477. ContainerProp: "container4",
  478. })
  479. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
  480. ClusterProp: "cluster1",
  481. NamespaceProp: "namespace2",
  482. PodProp: "pod-ghi",
  483. ContainerProp: "container5",
  484. })
  485. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
  486. ClusterProp: "cluster1",
  487. NamespaceProp: "namespace2",
  488. PodProp: "pod-jkl",
  489. ContainerProp: "container6",
  490. })
  491. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
  492. ClusterProp: "cluster2",
  493. NamespaceProp: "namespace2",
  494. PodProp: "pod-mno",
  495. ContainerProp: "container4",
  496. })
  497. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
  498. ClusterProp: "cluster2",
  499. NamespaceProp: "namespace2",
  500. PodProp: "pod-mno",
  501. ContainerProp: "container5",
  502. })
  503. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
  504. ClusterProp: "cluster2",
  505. NamespaceProp: "namespace2",
  506. PodProp: "pod-pqr",
  507. ContainerProp: "container6",
  508. })
  509. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
  510. ClusterProp: "cluster2",
  511. NamespaceProp: "namespace3",
  512. PodProp: "pod-stu",
  513. ContainerProp: "container7",
  514. })
  515. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
  516. ClusterProp: "cluster2",
  517. NamespaceProp: "namespace3",
  518. PodProp: "pod-vwx",
  519. ContainerProp: "container8",
  520. })
  521. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
  522. ClusterProp: "cluster2",
  523. NamespaceProp: "namespace3",
  524. PodProp: "pod-vwx",
  525. ContainerProp: "container9",
  526. })
  527. // Controllers
  528. a11abc2.Properties.SetControllerKind("deployment")
  529. a11abc2.Properties.SetController("deployment1")
  530. a11def3.Properties.SetControllerKind("deployment")
  531. a11def3.Properties.SetController("deployment1")
  532. a12ghi4.Properties.SetControllerKind("deployment")
  533. a12ghi4.Properties.SetController("deployment2")
  534. a12ghi5.Properties.SetControllerKind("deployment")
  535. a12ghi5.Properties.SetController("deployment2")
  536. a22mno4.Properties.SetControllerKind("deployment")
  537. a22mno4.Properties.SetController("deployment2")
  538. a22mno5.Properties.SetControllerKind("deployment")
  539. a22mno5.Properties.SetController("deployment2")
  540. a23stu7.Properties.SetControllerKind("deployment")
  541. a23stu7.Properties.SetController("deployment3")
  542. a12jkl6.Properties.SetControllerKind("daemonset")
  543. a12jkl6.Properties.SetController("daemonset1")
  544. a22pqr6.Properties.SetControllerKind("daemonset")
  545. a22pqr6.Properties.SetController("daemonset1")
  546. a23vwx8.Properties.SetControllerKind("statefulset")
  547. a23vwx8.Properties.SetController("statefulset1")
  548. a23vwx9.Properties.SetControllerKind("statefulset")
  549. a23vwx9.Properties.SetController("statefulset1")
  550. // Labels
  551. a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
  552. a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  553. a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  554. a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
  555. a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
  556. //Annotations
  557. a23stu7.Properties.SetAnnotations(map[string]string{"team": "team1"})
  558. a23vwx8.Properties.SetAnnotations(map[string]string{"team": "team2"})
  559. a23vwx9.Properties.SetAnnotations(map[string]string{"team": "team1"})
  560. // Services
  561. a12jkl6.Properties.SetServices([]string{"service1"})
  562. a22pqr6.Properties.SetServices([]string{"service1"})
  563. return NewAllocationSet(start, start.Add(day),
  564. // idle
  565. a1i, a2i,
  566. // cluster 1, namespace1
  567. a1111, a11abc2, a11def3,
  568. // cluster 1, namespace 2
  569. a12ghi4, a12ghi5, a12jkl6,
  570. // cluster 2, namespace 2
  571. a22mno4, a22mno5, a22pqr6,
  572. // cluster 2, namespace 3
  573. a23stu7, a23vwx8, a23vwx9,
  574. )
  575. }
  576. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  577. if err != nil {
  578. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  579. }
  580. if as.Length() != length {
  581. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  582. }
  583. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  584. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  585. }
  586. }
  587. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  588. as.Each(func(k string, a *Allocation) {
  589. if exp, ok := exps[a.Name]; ok {
  590. if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
  591. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost())
  592. }
  593. } else {
  594. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  595. }
  596. })
  597. }
  598. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  599. as.Each(func(k string, a *Allocation) {
  600. if !a.Start.Equal(expStart) {
  601. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  602. }
  603. if !a.End.Equal(expEnd) {
  604. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  605. }
  606. if a.Minutes() != expMinutes {
  607. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes())
  608. }
  609. })
  610. }
  611. func printAllocationSet(msg string, as *AllocationSet) {
  612. fmt.Printf("--- %s ---\n", msg)
  613. as.Each(func(k string, a *Allocation) {
  614. fmt.Printf(" > %s\n", a)
  615. })
  616. }
  617. func TestAllocationSet_AggregateBy(t *testing.T) {
  618. // Test AggregateBy against the following workload topology, which is
  619. // generated by generateAllocationSet:
  620. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
  621. // +----------------------------------------+------+------+------+------+------+------+
  622. // cluster1:
  623. // idle: 20.00 5.00 15.00 0.00 0.00 0.00
  624. // namespace1:
  625. // pod1:
  626. // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
  627. // pod-abc: (deployment1)
  628. // container2: 5.00 1.00 1.00 1.00 1.00 1.00
  629. // pod-def: (deployment1)
  630. // container3: 5.00 1.00 1.00 1.00 1.00 1.00
  631. // namespace2:
  632. // pod-ghi: (deployment2)
  633. // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  634. // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  635. // pod-jkl: (daemonset1)
  636. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  637. // +-----------------------------------------+------+------+------+------+------+------+
  638. // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
  639. // +-----------------------------------------+------+------+------+------+------+------+
  640. // cluster2:
  641. // idle: 10.00 5.00 5.00 0.00 0.00 0.00
  642. // namespace2:
  643. // pod-mno: (deployment2)
  644. // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  645. // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  646. // pod-pqr: (daemonset1)
  647. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  648. // namespace3:
  649. // pod-stu: (deployment3)
  650. // container7: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  651. // pod-vwx: (statefulset1)
  652. // container8: an[team=team2] 5.00 1.00 1.00 1.00 1.00 1.00
  653. // container9: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  654. // +----------------------------------------+------+------+------+------+------+------+
  655. // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
  656. // +----------------------------------------+------+------+------+------+------+------+
  657. // total 100.00 22.00 42.00 12.00 12.00 12.00
  658. // +----------------------------------------+------+------+------+------+------+------+
  659. // Scenarios to test:
  660. // 1 Single-aggregation
  661. // 1a AggregationProperties=(Cluster)
  662. // 1b AggregationProperties=(Namespace)
  663. // 1c AggregationProperties=(Pod)
  664. // 1d AggregationProperties=(Container)
  665. // 1e AggregationProperties=(ControllerKind)
  666. // 1f AggregationProperties=(Controller)
  667. // 1g AggregationProperties=(Service)
  668. // 1h AggregationProperties=(Label:app)
  669. // 2 Multi-aggregation
  670. // 2a AggregationProperties=(Cluster, Namespace)
  671. // 2b AggregationProperties=(Namespace, Label:app)
  672. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  673. // 2d AggregationProperties=(Label:app, Label:environment)
  674. // 3 Share idle
  675. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  676. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  677. // 4 Share resources
  678. // 4a Share namespace ShareEven
  679. // 4b Share cluster ShareWeighted
  680. // 4c Share label ShareEven
  681. // 4d Share overhead ShareWeighted
  682. // 5 Filters
  683. // 5a Filter by cluster with separate idle
  684. // 5b Filter by cluster with shared idle
  685. // TODO niko/idle more filter tests
  686. // 6 Combinations and options
  687. // 6a SplitIdle
  688. // 6b Share idle with filters
  689. // 6c Share resources with filters
  690. // 6d Share idle and share resources
  691. // 7 Edge cases and errors
  692. // 7a Empty AggregationProperties
  693. // 7b Filter all
  694. // 7c Share all
  695. // 7d Share and filter the same allocations
  696. // Definitions and set-up:
  697. var as *AllocationSet
  698. var err error
  699. endYesterday := time.Now().UTC().Truncate(day)
  700. startYesterday := endYesterday.Add(-day)
  701. numClusters := 2
  702. numNamespaces := 3
  703. numPods := 9
  704. numContainers := 9
  705. numControllerKinds := 3
  706. numControllers := 5
  707. numServices := 1
  708. numLabelApps := 2
  709. // By default, idle is reported as a single, merged allocation
  710. numIdle := 1
  711. // There will only ever be one __unallocated__
  712. numUnallocated := 1
  713. // There are two clusters, so each gets an idle entry when they are split
  714. numSplitIdle := 2
  715. activeTotalCost := 70.0
  716. idleTotalCost := 30.0
  717. sharedOverheadHourlyCost := 7.0
  718. isNamespace3 := func(a *Allocation) bool {
  719. ns, err := a.Properties.GetNamespace()
  720. return err == nil && ns == "namespace3"
  721. }
  722. isApp1 := func(a *Allocation) bool {
  723. ls, _ := a.Properties.GetLabels()
  724. if app, ok := ls["app"]; ok && app == "app1" {
  725. return true
  726. }
  727. return false
  728. }
  729. end := time.Now().UTC().Truncate(day)
  730. start := end.Add(-day)
  731. // Tests:
  732. // 1 Single-aggregation
  733. // 1a AggregationProperties=(Cluster)
  734. as = generateAllocationSet(start)
  735. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  736. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  737. assertAllocationTotals(t, as, "1a", map[string]float64{
  738. "cluster1": 40.00,
  739. "cluster2": 30.00,
  740. IdleSuffix: 30.00,
  741. })
  742. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  743. // 1b AggregationProperties=(Namespace)
  744. as = generateAllocationSet(start)
  745. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  746. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  747. assertAllocationTotals(t, as, "1b", map[string]float64{
  748. "namespace1": 25.00,
  749. "namespace2": 30.00,
  750. "namespace3": 15.00,
  751. IdleSuffix: 30.00,
  752. })
  753. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  754. // 1c AggregationProperties=(Pod)
  755. as = generateAllocationSet(start)
  756. err = as.AggregateBy(Properties{PodProp: true}, nil)
  757. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  758. assertAllocationTotals(t, as, "1c", map[string]float64{
  759. "pod-jkl": 5.00,
  760. "pod-stu": 5.00,
  761. "pod-abc": 5.00,
  762. "pod-pqr": 5.00,
  763. "pod-def": 5.00,
  764. "pod-vwx": 10.00,
  765. "pod1": 15.00,
  766. "pod-mno": 10.00,
  767. "pod-ghi": 10.00,
  768. IdleSuffix: 30.00,
  769. })
  770. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  771. // 1d AggregationProperties=(Container)
  772. as = generateAllocationSet(start)
  773. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  774. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  775. assertAllocationTotals(t, as, "1d", map[string]float64{
  776. "container2": 5.00,
  777. "container9": 5.00,
  778. "container6": 10.00,
  779. "container3": 5.00,
  780. "container4": 10.00,
  781. "container7": 5.00,
  782. "container8": 5.00,
  783. "container5": 10.00,
  784. "container1": 15.00,
  785. IdleSuffix: 30.00,
  786. })
  787. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  788. // 1e AggregationProperties=(ControllerKind)
  789. as = generateAllocationSet(start)
  790. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  791. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  792. assertAllocationTotals(t, as, "1e", map[string]float64{
  793. "daemonset": 10.00,
  794. "deployment": 35.00,
  795. "statefulset": 10.00,
  796. IdleSuffix: 30.00,
  797. UnallocatedSuffix: 15.00,
  798. })
  799. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  800. // 1f AggregationProperties=(Controller)
  801. as = generateAllocationSet(start)
  802. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  803. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  804. assertAllocationTotals(t, as, "1f", map[string]float64{
  805. "deployment/deployment2": 20.00,
  806. "daemonset/daemonset1": 10.00,
  807. "deployment/deployment3": 5.00,
  808. "statefulset/statefulset1": 10.00,
  809. "deployment/deployment1": 10.00,
  810. IdleSuffix: 30.00,
  811. UnallocatedSuffix: 15.00,
  812. })
  813. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  814. // 1g AggregationProperties=(Service)
  815. as = generateAllocationSet(start)
  816. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  817. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  818. assertAllocationTotals(t, as, "1g", map[string]float64{
  819. "service1": 10.00,
  820. IdleSuffix: 30.00,
  821. UnallocatedSuffix: 60.00,
  822. })
  823. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  824. // 1h AggregationProperties=(Label:app)
  825. as = generateAllocationSet(start)
  826. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  827. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  828. assertAllocationTotals(t, as, "1h", map[string]float64{
  829. "app=app1": 15.00,
  830. "app=app2": 20.00,
  831. IdleSuffix: 30.00,
  832. UnallocatedSuffix: 35.00,
  833. })
  834. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  835. // 1i AggregationProperties=(ControllerKind:deployment)
  836. as = generateAllocationSet(start)
  837. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  838. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  839. assertAllocationTotals(t, as, "1i", map[string]float64{
  840. "deployment": 35.00,
  841. IdleSuffix: 30.00,
  842. UnallocatedSuffix: 35.00,
  843. })
  844. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  845. // 1j AggregationProperties=(Annotation:team)
  846. as = generateAllocationSet(start)
  847. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
  848. assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  849. assertAllocationTotals(t, as, "1j", map[string]float64{
  850. "team=team1": 10.00,
  851. "team=team2": 5.00,
  852. IdleSuffix: 30.00,
  853. UnallocatedSuffix: 55.00,
  854. })
  855. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  856. // 2 Multi-aggregation
  857. // 2a AggregationProperties=(Cluster, Namespace)
  858. // 2b AggregationProperties=(Namespace, Label:app)
  859. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  860. // 2d AggregationProperties=(Label:app, Label:environment)
  861. as = generateAllocationSet(start)
  862. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  863. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  864. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  865. assertAllocationTotals(t, as, "2d", map[string]float64{
  866. "app=app1/env=env1": 15.00,
  867. "app=app2/env=env2": 10.00,
  868. "app=app2/" + UnallocatedSuffix: 10.00,
  869. IdleSuffix: 30.00,
  870. UnallocatedSuffix: 35.00,
  871. })
  872. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  873. as = generateAllocationSet(start)
  874. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  875. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  876. assertAllocationTotals(t, as, "2e", map[string]float64{
  877. "cluster1/app=app2/env=env2": 10.00,
  878. "__idle__": 30.00,
  879. "cluster1/app=app1/env=env1": 15.00,
  880. "cluster1/" + UnallocatedSuffix: 15.00,
  881. "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
  882. "cluster2/" + UnallocatedSuffix: 20.00,
  883. })
  884. // 2f AggregationProperties=(annotation:team, pod)
  885. as = generateAllocationSet(start)
  886. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
  887. assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
  888. assertAllocationTotals(t, as, "2e", map[string]float64{
  889. "pod-jkl/" + UnallocatedSuffix: 5.00,
  890. "pod-stu/team=team1": 5.00,
  891. "pod-abc/" + UnallocatedSuffix: 5.00,
  892. "pod-pqr/" + UnallocatedSuffix: 5.00,
  893. "pod-def/" + UnallocatedSuffix: 5.00,
  894. "pod-vwx/team=team1": 5.00,
  895. "pod-vwx/team=team2": 5.00,
  896. "pod1/" + UnallocatedSuffix: 15.00,
  897. "pod-mno/" + UnallocatedSuffix: 10.00,
  898. "pod-ghi/" + UnallocatedSuffix: 10.00,
  899. IdleSuffix: 30.00,
  900. })
  901. // // TODO niko/etl
  902. // // 3 Share idle
  903. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  904. // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  905. // 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)
  906. // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  907. as = generateAllocationSet(start)
  908. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  909. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  910. assertAllocationTotals(t, as, "3a", map[string]float64{
  911. "namespace1": 39.69,
  912. "namespace2": 40.31,
  913. "namespace3": 20.00,
  914. })
  915. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  916. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  917. // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  918. // 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)
  919. // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  920. as = generateAllocationSet(start)
  921. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  922. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  923. assertAllocationTotals(t, as, "3a", map[string]float64{
  924. "namespace1": 35.00,
  925. "namespace2": 45.00,
  926. "namespace3": 20.00,
  927. })
  928. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  929. // 4 Share resources
  930. // 4a Share namespace ShareEven
  931. // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
  932. // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
  933. // idle: 30.0000
  934. as = generateAllocationSet(start)
  935. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  936. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  937. ShareSplit: ShareEven,
  938. })
  939. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  940. assertAllocationTotals(t, as, "4a", map[string]float64{
  941. "namespace1": 32.50,
  942. "namespace2": 37.50,
  943. IdleSuffix: 30.00,
  944. })
  945. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  946. // 4b Share namespace ShareWeighted
  947. // namespace1: 32.5000 =
  948. // namespace2: 37.5000 =
  949. // idle: 30.0000
  950. as = generateAllocationSet(start)
  951. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  952. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  953. ShareSplit: ShareWeighted,
  954. })
  955. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  956. assertAllocationTotals(t, as, "4b", map[string]float64{
  957. "namespace1": 31.82,
  958. "namespace2": 38.18,
  959. IdleSuffix: 30.00,
  960. })
  961. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  962. // 4c Share label ShareEven
  963. // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
  964. // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
  965. // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
  966. // idle: 30.0000
  967. as = generateAllocationSet(start)
  968. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  969. ShareFuncs: []AllocationMatchFunc{isApp1},
  970. ShareSplit: ShareEven,
  971. })
  972. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  973. assertAllocationTotals(t, as, "4c", map[string]float64{
  974. "namespace1": 15.00,
  975. "namespace2": 35.00,
  976. "namespace3": 20.00,
  977. IdleSuffix: 30.00,
  978. })
  979. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  980. // 4d Share overhead ShareWeighted
  981. // namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
  982. // namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
  983. // namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
  984. // idle: 30.0000
  985. as = generateAllocationSet(start)
  986. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  987. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  988. ShareSplit: ShareWeighted,
  989. })
  990. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  991. assertAllocationTotals(t, as, "4d", map[string]float64{
  992. "namespace1": 85.00,
  993. "namespace2": 102.00,
  994. "namespace3": 51.00,
  995. IdleSuffix: 30.00,
  996. })
  997. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  998. // 5 Filters
  999. isCluster := func(matchCluster string) func(*Allocation) bool {
  1000. return func(a *Allocation) bool {
  1001. cluster, err := a.Properties.GetCluster()
  1002. return err == nil && cluster == matchCluster
  1003. }
  1004. }
  1005. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  1006. return func(a *Allocation) bool {
  1007. namespace, err := a.Properties.GetNamespace()
  1008. return err == nil && namespace == matchNamespace
  1009. }
  1010. }
  1011. // 5a Filter by cluster with separate idle
  1012. as = generateAllocationSet(start)
  1013. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  1014. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1015. ShareIdle: ShareNone,
  1016. })
  1017. assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
  1018. assertAllocationTotals(t, as, "5a", map[string]float64{
  1019. "cluster1": 40.00,
  1020. IdleSuffix: 20.00,
  1021. })
  1022. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  1023. // 5b Filter by cluster with shared idle
  1024. as = generateAllocationSet(start)
  1025. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  1026. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1027. ShareIdle: ShareWeighted,
  1028. })
  1029. assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
  1030. assertAllocationTotals(t, as, "5b", map[string]float64{
  1031. "cluster1": 60.00,
  1032. })
  1033. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  1034. // 5c Filter by cluster, agg by namespace, with separate idle
  1035. as = generateAllocationSet(start)
  1036. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1037. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1038. ShareIdle: ShareNone,
  1039. })
  1040. assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
  1041. assertAllocationTotals(t, as, "5c", map[string]float64{
  1042. "namespace1": 25.00,
  1043. "namespace2": 15.00,
  1044. IdleSuffix: 20.00,
  1045. })
  1046. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  1047. // 5d Filter by namespace, agg by cluster, with separate idle
  1048. as = generateAllocationSet(start)
  1049. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  1050. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1051. ShareIdle: ShareNone,
  1052. })
  1053. assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
  1054. assertAllocationTotals(t, as, "5d", map[string]float64{
  1055. "cluster1": 15.00,
  1056. "cluster2": 15.00,
  1057. IdleSuffix: 10.31,
  1058. })
  1059. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  1060. // 6 Combinations and options
  1061. // 6a SplitIdle
  1062. as = generateAllocationSet(start)
  1063. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  1064. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  1065. assertAllocationTotals(t, as, "6a", map[string]float64{
  1066. "namespace1": 25.00,
  1067. "namespace2": 30.00,
  1068. "namespace3": 15.00,
  1069. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  1070. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  1071. })
  1072. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  1073. // 6b Share idle weighted with filters
  1074. // Should match values from unfiltered aggregation (3a)
  1075. // 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)
  1076. as = generateAllocationSet(start)
  1077. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1078. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1079. ShareIdle: ShareWeighted,
  1080. })
  1081. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  1082. assertAllocationTotals(t, as, "6b", map[string]float64{
  1083. "namespace2": 40.31,
  1084. })
  1085. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  1086. // 6c Share idle even with filters
  1087. // Should match values from unfiltered aggregation (3b)
  1088. // 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)
  1089. as = generateAllocationSet(start)
  1090. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1091. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1092. ShareIdle: ShareEven,
  1093. })
  1094. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  1095. assertAllocationTotals(t, as, "6b", map[string]float64{
  1096. "namespace2": 45.00,
  1097. })
  1098. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  1099. // 6d Share overhead with filters
  1100. // namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
  1101. // namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
  1102. // namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
  1103. // idle: 30.0000
  1104. // Then namespace 2 is filtered.
  1105. as = generateAllocationSet(start)
  1106. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1107. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1108. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1109. ShareSplit: ShareWeighted,
  1110. })
  1111. assertAllocationSetTotals(t, as, "6d", err, 2, 132.00)
  1112. assertAllocationTotals(t, as, "6d", map[string]float64{
  1113. "namespace2": 102.00,
  1114. IdleSuffix: 30.00,
  1115. })
  1116. assertAllocationWindow(t, as, "6d", startYesterday, endYesterday, 1440.0)
  1117. // 6e Share resources with filters
  1118. // --- Shared ---
  1119. // namespace1: 25.00 (gets shared among namespace2 and namespace3)
  1120. // --- Filtered ---
  1121. // namespace3: 23.33 = 15.00 + (25.00)*(15.00/45.00) (filtered out)
  1122. // --- Results ---
  1123. // namespace2: 46.67 = 30.00 + (25.00)*(15.00/45.00)
  1124. // idle: 30.0000
  1125. as = generateAllocationSet(start)
  1126. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1127. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1128. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1129. ShareSplit: ShareWeighted,
  1130. })
  1131. assertAllocationSetTotals(t, as, "6e", err, 2, 76.67)
  1132. assertAllocationTotals(t, as, "6e", map[string]float64{
  1133. "namespace2": 46.67,
  1134. IdleSuffix: 30.00,
  1135. })
  1136. assertAllocationWindow(t, as, "6e", startYesterday, endYesterday, 1440.0)
  1137. // 6f Share idle weighted and share resources weighted
  1138. //
  1139. // First, share idle weighted produces:
  1140. //
  1141. // namespace1: 39.6875
  1142. // initial cost 25.0000
  1143. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1144. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1145. //
  1146. // namespace2: 40.3125
  1147. // initial cost 30.0000
  1148. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1149. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1150. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1151. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1152. //
  1153. // namespace3: 20.0000
  1154. // initial cost 15.0000
  1155. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1156. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1157. //
  1158. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1159. // computed before allocating idle (so that weighting idle differently
  1160. // doesn't adversely affect the sharing mechanism):
  1161. //
  1162. // namespace2: 66.7708
  1163. // initial cost 30.0000
  1164. // idle cost 10.3125
  1165. // shared cost 26.4583 = (39.6875)*(30.0/45.0)
  1166. //
  1167. // namespace3: 33.2292
  1168. // initial cost 15.0000
  1169. // idle cost 5.0000
  1170. // shared cost 13.2292 = (39.6875)*(15.0/45.0)
  1171. //
  1172. as = generateAllocationSet(start)
  1173. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1174. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1175. ShareSplit: ShareWeighted,
  1176. ShareIdle: ShareWeighted,
  1177. })
  1178. assertAllocationSetTotals(t, as, "6f", err, 2, activeTotalCost+idleTotalCost)
  1179. assertAllocationTotals(t, as, "6f", map[string]float64{
  1180. "namespace2": 66.77,
  1181. "namespace3": 33.23,
  1182. })
  1183. assertAllocationWindow(t, as, "6f", startYesterday, endYesterday, 1440.0)
  1184. // 6g Share idle, share resources, and filter
  1185. //
  1186. // First, share idle weighted produces:
  1187. //
  1188. // namespace1: 39.6875
  1189. // initial cost 25.0000
  1190. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1191. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1192. //
  1193. // namespace2: 40.3125
  1194. // initial cost 30.0000
  1195. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1196. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1197. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1198. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1199. //
  1200. // namespace3: 20.0000
  1201. // initial cost 15.0000
  1202. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1203. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1204. //
  1205. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1206. // computed before allocating idle (so that weighting idle differently
  1207. // doesn't adversely affect the sharing mechanism):
  1208. //
  1209. // namespace2: 66.7708
  1210. // initial cost 30.0000
  1211. // idle cost 10.3125
  1212. // shared cost 26.4583 = (39.6875)*(30.0/45.0)
  1213. //
  1214. // namespace3: 33.2292
  1215. // initial cost 15.0000
  1216. // idle cost 5.0000
  1217. // shared cost 13.2292 = (39.6875)*(15.0/45.0)
  1218. //
  1219. // Then, filter for namespace2: 66.7708
  1220. //
  1221. as = generateAllocationSet(start)
  1222. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1223. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1224. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1225. ShareSplit: ShareWeighted,
  1226. ShareIdle: ShareWeighted,
  1227. })
  1228. assertAllocationSetTotals(t, as, "6g", err, 1, 66.77)
  1229. assertAllocationTotals(t, as, "6g", map[string]float64{
  1230. "namespace2": 66.77,
  1231. })
  1232. assertAllocationWindow(t, as, "6g", startYesterday, endYesterday, 1440.0)
  1233. // 6h Share idle, share resources, share overhead
  1234. //
  1235. // Share idle weighted:
  1236. //
  1237. // namespace1: 39.6875
  1238. // initial cost 25.0000
  1239. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1240. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1241. //
  1242. // namespace2: 40.3125
  1243. // initial cost 30.0000
  1244. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1245. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1246. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1247. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1248. //
  1249. // namespace3: 20.0000
  1250. // initial cost 15.0000
  1251. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1252. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1253. //
  1254. // Then share overhead:
  1255. //
  1256. // namespace1: 99.6875 = 39.6875 + (7.0*24.0)*(25.00/70.00)
  1257. // namespace2: 112.3125 = 40.3125 + (7.0*24.0)*(30.00/70.00)
  1258. // namespace3: 56.0000 = 20.0000 + (7.0*24.0)*(15.00/70.00)
  1259. //
  1260. // Then namespace 2 is filtered.
  1261. as = generateAllocationSet(start)
  1262. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1263. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1264. ShareSplit: ShareWeighted,
  1265. ShareIdle: ShareWeighted,
  1266. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1267. })
  1268. assertAllocationSetTotals(t, as, "6h", err, 1, 112.31)
  1269. assertAllocationTotals(t, as, "6h", map[string]float64{
  1270. "namespace2": 112.31,
  1271. })
  1272. assertAllocationWindow(t, as, "6h", startYesterday, endYesterday, 1440.0)
  1273. // 7 Edge cases and errors
  1274. // 7a Empty AggregationProperties
  1275. // 7b Filter all
  1276. // 7c Share all
  1277. // 7d Share and filter the same allocations
  1278. }
  1279. // TODO niko/etl
  1280. //func TestAllocationSet_Clone(t *testing.T) {}
  1281. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  1282. var as *AllocationSet
  1283. var err error
  1284. var idles map[string]*Allocation
  1285. end := time.Now().UTC().Truncate(day)
  1286. start := end.Add(-day)
  1287. // Generate AllocationSet and strip out any existing idle allocations
  1288. as = generateAllocationSet(start)
  1289. for key := range as.idleKeys {
  1290. as.Delete(key)
  1291. }
  1292. // Create an AssetSet representing cluster costs for two clusters (cluster1
  1293. // and cluster2). Include Nodes and Disks for both, even though only
  1294. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  1295. // by type, here we will provide multiple Nodes for one of the clusters to
  1296. // make sure the function still holds.
  1297. // NOTE: we're re-using generateAllocationSet so this has to line up with
  1298. // the allocated node costs from that function. See table above.
  1299. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  1300. // +-----------------------------------------+------+------+------+------+------------+
  1301. // cluster1:
  1302. // nodes 100.00 55.00 44.00 11.00 -10.00
  1303. // +-----------------------------------------+------+------+------+------+------------+
  1304. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  1305. // +-----------------------------------------+------+------+------+------+------------+
  1306. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  1307. // +-----------------------------------------+------+------+------+------+------------+
  1308. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  1309. // +-----------------------------------------+------+------+------+------+------------+
  1310. // cluster2:
  1311. // node1 35.00 20.00 15.00 0.00 0.00
  1312. // node2 35.00 20.00 15.00 0.00 0.00
  1313. // node3 30.00 10.00 10.00 10.00 0.00
  1314. // (disks should not matter for idle)
  1315. // +-----------------------------------------+------+------+------+------+------------+
  1316. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1317. // +-----------------------------------------+------+------+------+------+------------+
  1318. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1319. // +-----------------------------------------+------+------+------+------+------------+
  1320. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1321. // +-----------------------------------------+------+------+------+------+------------+
  1322. cluster1Nodes := NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1323. cluster1Nodes.CPUCost = 55.0
  1324. cluster1Nodes.RAMCost = 44.0
  1325. cluster1Nodes.GPUCost = 11.0
  1326. cluster1Nodes.adjustment = -10.00
  1327. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1328. cluster2Node1.CPUCost = 20.0
  1329. cluster2Node1.RAMCost = 15.0
  1330. cluster2Node1.GPUCost = 0.0
  1331. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1332. cluster2Node2.CPUCost = 20.0
  1333. cluster2Node2.RAMCost = 15.0
  1334. cluster2Node2.GPUCost = 0.0
  1335. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1336. cluster2Node3.CPUCost = 10.0
  1337. cluster2Node3.RAMCost = 10.0
  1338. cluster2Node3.GPUCost = 10.0
  1339. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1340. cluster2Disk1.Cost = 5.0
  1341. assetSet := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1342. idles, err = as.ComputeIdleAllocations(assetSet)
  1343. if err != nil {
  1344. t.Fatalf("unexpected error: %s", err)
  1345. }
  1346. if len(idles) != 2 {
  1347. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1348. }
  1349. if idle, ok := idles["cluster1"]; !ok {
  1350. t.Fatalf("expected idle cost for %s", "cluster1")
  1351. } else {
  1352. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  1353. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  1354. }
  1355. }
  1356. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1357. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1358. }
  1359. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1360. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1361. }
  1362. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1363. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1364. }
  1365. if idle, ok := idles["cluster2"]; !ok {
  1366. t.Fatalf("expected idle cost for %s", "cluster2")
  1367. } else {
  1368. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  1369. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  1370. }
  1371. }
  1372. // NOTE: we're re-using generateAllocationSet so this has to line up with
  1373. // the allocated node costs from that function. See table above.
  1374. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  1375. // +-----------------------------------------+------+------+------+------+------------+
  1376. // cluster1:
  1377. // nodes 100.00 5.00 4.00 1.00 90.00
  1378. // +-----------------------------------------+------+------+------+------+------------+
  1379. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  1380. // +-----------------------------------------+------+------+------+------+------------+
  1381. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  1382. // +-----------------------------------------+------+------+------+------+------------+
  1383. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  1384. // +-----------------------------------------+------+------+------+------+------------+
  1385. // cluster2:
  1386. // node1 35.00 20.00 15.00 0.00 0.00
  1387. // node2 35.00 20.00 15.00 0.00 0.00
  1388. // node3 30.00 10.00 10.00 10.00 0.00
  1389. // (disks should not matter for idle)
  1390. // +-----------------------------------------+------+------+------+------+------------+
  1391. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1392. // +-----------------------------------------+------+------+------+------+------------+
  1393. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1394. // +-----------------------------------------+------+------+------+------+------------+
  1395. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1396. // +-----------------------------------------+------+------+------+------+------------+
  1397. cluster1Nodes = NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1398. cluster1Nodes.CPUCost = 5.0
  1399. cluster1Nodes.RAMCost = 4.0
  1400. cluster1Nodes.GPUCost = 1.0
  1401. cluster1Nodes.adjustment = 90.00
  1402. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1403. cluster2Node1.CPUCost = 20.0
  1404. cluster2Node1.RAMCost = 15.0
  1405. cluster2Node1.GPUCost = 0.0
  1406. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1407. cluster2Node2.CPUCost = 20.0
  1408. cluster2Node2.RAMCost = 15.0
  1409. cluster2Node2.GPUCost = 0.0
  1410. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1411. cluster2Node3.CPUCost = 10.0
  1412. cluster2Node3.RAMCost = 10.0
  1413. cluster2Node3.GPUCost = 10.0
  1414. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1415. cluster2Disk1.Cost = 5.0
  1416. assetSet = NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1417. idles, err = as.ComputeIdleAllocations(assetSet)
  1418. if err != nil {
  1419. t.Fatalf("unexpected error: %s", err)
  1420. }
  1421. if len(idles) != 2 {
  1422. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1423. }
  1424. if idle, ok := idles["cluster1"]; !ok {
  1425. t.Fatalf("expected idle cost for %s", "cluster1")
  1426. } else {
  1427. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  1428. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  1429. }
  1430. }
  1431. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1432. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1433. }
  1434. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1435. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1436. }
  1437. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1438. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1439. }
  1440. if idle, ok := idles["cluster2"]; !ok {
  1441. t.Fatalf("expected idle cost for %s", "cluster2")
  1442. } else {
  1443. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  1444. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  1445. }
  1446. }
  1447. // TODO assert value of each resource cost precisely
  1448. }
  1449. // TODO niko/etl
  1450. //func TestAllocationSet_Delete(t *testing.T) {}
  1451. // TODO niko/etl
  1452. //func TestAllocationSet_End(t *testing.T) {}
  1453. // TODO niko/etl
  1454. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  1455. // TODO niko/etl
  1456. //func TestAllocationSet_Insert(t *testing.T) {}
  1457. // TODO niko/etl
  1458. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  1459. // TODO niko/etl
  1460. //func TestAllocationSet_Length(t *testing.T) {}
  1461. // TODO niko/etl
  1462. //func TestAllocationSet_Map(t *testing.T) {}
  1463. // TODO niko/etl
  1464. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  1465. // TODO niko/etl
  1466. //func TestAllocationSet_Resolution(t *testing.T) {}
  1467. // TODO niko/etl
  1468. //func TestAllocationSet_Seconds(t *testing.T) {}
  1469. // TODO niko/etl
  1470. //func TestAllocationSet_Set(t *testing.T) {}
  1471. // TODO niko/etl
  1472. //func TestAllocationSet_Start(t *testing.T) {}
  1473. // TODO niko/etl
  1474. //func TestAllocationSet_TotalCost(t *testing.T) {}
  1475. // TODO niko/etl
  1476. //func TestNewAllocationSetRange(t *testing.T) {}
  1477. func TestAllocationSetRange_Accumulate(t *testing.T) {
  1478. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1479. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1480. today := time.Now().UTC().Truncate(day)
  1481. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1482. // Accumulating any combination of nil and/or empty set should result in empty set
  1483. result, err := NewAllocationSetRange(nil).Accumulate()
  1484. if err != nil {
  1485. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1486. }
  1487. if !result.IsEmpty() {
  1488. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1489. }
  1490. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  1491. if err != nil {
  1492. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1493. }
  1494. if !result.IsEmpty() {
  1495. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1496. }
  1497. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  1498. if err != nil {
  1499. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1500. }
  1501. if !result.IsEmpty() {
  1502. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1503. }
  1504. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  1505. if err != nil {
  1506. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1507. }
  1508. if !result.IsEmpty() {
  1509. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1510. }
  1511. todayAS := NewAllocationSet(today, tomorrow)
  1512. todayAS.Set(NewUnitAllocation("", today, day, nil))
  1513. yesterdayAS := NewAllocationSet(yesterday, today)
  1514. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  1515. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  1516. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  1517. if err != nil {
  1518. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1519. }
  1520. if result == nil {
  1521. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1522. }
  1523. if result.TotalCost() != 5.0 {
  1524. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1525. }
  1526. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  1527. if err != nil {
  1528. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1529. }
  1530. if result == nil {
  1531. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1532. }
  1533. if result.TotalCost() != 5.0 {
  1534. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1535. }
  1536. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  1537. if err != nil {
  1538. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1539. }
  1540. if result == nil {
  1541. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1542. }
  1543. if result.TotalCost() != 5.0 {
  1544. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1545. }
  1546. // Accumulate two non-nil should result in sum of both with appropriate start, end
  1547. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  1548. if err != nil {
  1549. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1550. }
  1551. if result == nil {
  1552. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1553. }
  1554. if result.TotalCost() != 10.0 {
  1555. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  1556. }
  1557. allocMap := result.Map()
  1558. if len(allocMap) != 1 {
  1559. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  1560. }
  1561. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  1562. if alloc == nil {
  1563. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  1564. }
  1565. if alloc.CPUCoreHours != 2.0 {
  1566. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  1567. }
  1568. if alloc.CPUCost != 2.0 {
  1569. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  1570. }
  1571. if alloc.CPUEfficiency() != 1.0 {
  1572. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency())
  1573. }
  1574. if alloc.GPUHours != 2.0 {
  1575. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  1576. }
  1577. if alloc.GPUCost != 2.0 {
  1578. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  1579. }
  1580. if alloc.NetworkCost != 2.0 {
  1581. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  1582. }
  1583. if alloc.PVByteHours != 2.0 {
  1584. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  1585. }
  1586. if alloc.PVCost != 2.0 {
  1587. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  1588. }
  1589. if alloc.RAMByteHours != 2.0 {
  1590. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  1591. }
  1592. if alloc.RAMCost != 2.0 {
  1593. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  1594. }
  1595. if alloc.RAMEfficiency() != 1.0 {
  1596. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
  1597. }
  1598. if alloc.TotalCost() != 10.0 {
  1599. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost())
  1600. }
  1601. if alloc.TotalEfficiency() != 1.0 {
  1602. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
  1603. }
  1604. if !alloc.Start.Equal(yesterday) {
  1605. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1606. }
  1607. if !alloc.End.Equal(tomorrow) {
  1608. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1609. }
  1610. if alloc.Minutes() != 2880.0 {
  1611. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes())
  1612. }
  1613. }
  1614. // TODO niko/etl
  1615. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1616. // TODO niko/etl
  1617. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1618. // TODO niko/etl
  1619. // func TestAllocationSetRange_Append(t *testing.T) {}
  1620. // TODO niko/etl
  1621. // func TestAllocationSetRange_Each(t *testing.T) {}
  1622. // TODO niko/etl
  1623. // func TestAllocationSetRange_Get(t *testing.T) {}
  1624. func TestAllocationSetRange_InsertRange(t *testing.T) {
  1625. // Set up
  1626. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1627. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1628. today := time.Now().UTC().Truncate(day)
  1629. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1630. unit := NewUnitAllocation("", today, day, nil)
  1631. ago2dAS := NewAllocationSet(ago2d, yesterday)
  1632. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  1633. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  1634. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  1635. yesterdayAS := NewAllocationSet(yesterday, today)
  1636. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  1637. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  1638. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  1639. todayAS := NewAllocationSet(today, tomorrow)
  1640. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  1641. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  1642. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  1643. var nilASR *AllocationSetRange
  1644. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  1645. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  1646. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  1647. var err error
  1648. // Expect an error calling InsertRange on nil
  1649. err = nilASR.InsertRange(thatASR)
  1650. if err == nil {
  1651. t.Fatalf("expected error, got nil")
  1652. }
  1653. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  1654. err = thisASR.InsertRange(nil)
  1655. if err != nil {
  1656. t.Fatalf("unexpected error: %s", err)
  1657. }
  1658. thisASR.Each(func(i int, as *AllocationSet) {
  1659. as.Each(func(k string, a *Allocation) {
  1660. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1661. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1662. }
  1663. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1664. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1665. }
  1666. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1667. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1668. }
  1669. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1670. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1671. }
  1672. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1673. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1674. }
  1675. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1676. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1677. }
  1678. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1679. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1680. }
  1681. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1682. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1683. }
  1684. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1685. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1686. }
  1687. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1688. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1689. }
  1690. })
  1691. })
  1692. // Expect an error calling InsertRange with a range exceeding the receiver
  1693. err = thisASR.InsertRange(longASR)
  1694. if err == nil {
  1695. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  1696. }
  1697. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  1698. // precisely double when inserting a range that only has a duplicate of
  1699. // "yesterday", but no entry for "today"
  1700. err = thisASR.InsertRange(thatASR)
  1701. if err != nil {
  1702. t.Fatalf("unexpected error: %s", err)
  1703. }
  1704. yAS, err := thisASR.Get(0)
  1705. yAS.Each(func(k string, a *Allocation) {
  1706. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  1707. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1708. }
  1709. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  1710. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1711. }
  1712. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  1713. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1714. }
  1715. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  1716. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1717. }
  1718. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  1719. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1720. }
  1721. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  1722. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1723. }
  1724. if !util.IsApproximately(a.PVByteHours, 2*unit.PVByteHours) {
  1725. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1726. }
  1727. if !util.IsApproximately(a.PVCost, 2*unit.PVCost) {
  1728. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1729. }
  1730. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  1731. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1732. }
  1733. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  1734. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1735. }
  1736. })
  1737. tAS, err := thisASR.Get(1)
  1738. tAS.Each(func(k string, a *Allocation) {
  1739. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1740. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1741. }
  1742. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1743. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1744. }
  1745. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1746. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1747. }
  1748. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1749. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1750. }
  1751. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1752. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1753. }
  1754. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1755. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1756. }
  1757. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1758. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1759. }
  1760. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1761. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1762. }
  1763. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1764. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1765. }
  1766. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1767. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1768. }
  1769. })
  1770. }
  1771. // TODO niko/etl
  1772. // func TestAllocationSetRange_Length(t *testing.T) {}
  1773. // TODO niko/etl
  1774. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1775. // TODO niko/etl
  1776. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1777. // TODO niko/etl
  1778. // func TestAllocationSetRange_Window(t *testing.T) {}