allocation_test.go 94 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850
  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. var disk = PVKey{}
  12. var disk1 = PVKey{
  13. Cluster: "cluster2",
  14. Name: "disk1",
  15. }
  16. var disk2 = PVKey{
  17. Cluster: "cluster2",
  18. Name: "disk2",
  19. }
  20. func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *AllocationProperties) *Allocation {
  21. if name == "" {
  22. name = "cluster1/namespace1/pod1/container1"
  23. }
  24. properties := &AllocationProperties{}
  25. if props == nil {
  26. properties.Cluster = "cluster1"
  27. properties.Node = "node1"
  28. properties.Namespace = "namespace1"
  29. properties.ControllerKind = "deployment"
  30. properties.Controller = "deployment1"
  31. properties.Pod = "pod1"
  32. properties.Container = "container1"
  33. } else {
  34. properties = props
  35. }
  36. end := start.Add(resolution)
  37. alloc := &Allocation{
  38. Name: name,
  39. Properties: properties,
  40. Window: NewWindow(&start, &end).Clone(),
  41. Start: start,
  42. End: end,
  43. CPUCoreHours: 1,
  44. CPUCost: 1,
  45. CPUCoreRequestAverage: 1,
  46. CPUCoreUsageAverage: 1,
  47. GPUHours: 1,
  48. GPUCost: 1,
  49. NetworkCost: 1,
  50. LoadBalancerCost: 1,
  51. PVs: PVAllocations{
  52. disk: {
  53. ByteHours: 1,
  54. Cost: 1,
  55. },
  56. },
  57. RAMByteHours: 1,
  58. RAMCost: 1,
  59. RAMBytesRequestAverage: 1,
  60. RAMBytesUsageAverage: 1,
  61. RawAllocationOnly: &RawAllocationOnlyData{
  62. CPUCoreUsageMax: 1,
  63. RAMBytesUsageMax: 1,
  64. },
  65. }
  66. // If idle allocation, remove non-idle costs, but maintain total cost
  67. if alloc.IsIdle() {
  68. alloc.PVs = nil
  69. alloc.NetworkCost = 0.0
  70. alloc.LoadBalancerCost = 0.0
  71. alloc.CPUCoreHours += 1.0
  72. alloc.CPUCost += 1.0
  73. alloc.RAMByteHours += 1.0
  74. alloc.RAMCost += 1.0
  75. }
  76. return alloc
  77. }
  78. func TestAllocation_Add(t *testing.T) {
  79. var nilAlloc *Allocation
  80. zeroAlloc := &Allocation{}
  81. // nil + nil == nil
  82. nilNilSum, err := nilAlloc.Add(nilAlloc)
  83. if err != nil {
  84. t.Fatalf("Allocation.Add unexpected error: %s", err)
  85. }
  86. if nilNilSum != nil {
  87. t.Fatalf("Allocation.Add failed; exp: nil; act: %s", nilNilSum)
  88. }
  89. // nil + zero == zero
  90. nilZeroSum, err := nilAlloc.Add(zeroAlloc)
  91. if err != nil {
  92. t.Fatalf("Allocation.Add unexpected error: %s", err)
  93. }
  94. if nilZeroSum == nil || nilZeroSum.TotalCost() != 0.0 {
  95. t.Fatalf("Allocation.Add failed; exp: 0.0; act: %s", nilZeroSum)
  96. }
  97. cpuPrice := 0.02
  98. gpuPrice := 2.00
  99. ramPrice := 0.01
  100. pvPrice := 0.00005
  101. gib := 1024.0 * 1024.0 * 1024.0
  102. s1 := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  103. e1 := time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)
  104. hrs1 := e1.Sub(s1).Hours()
  105. a1 := &Allocation{
  106. Start: s1,
  107. End: e1,
  108. Properties: &AllocationProperties{},
  109. CPUCoreHours: 2.0 * hrs1,
  110. CPUCoreRequestAverage: 2.0,
  111. CPUCoreUsageAverage: 1.0,
  112. CPUCost: 2.0 * hrs1 * cpuPrice,
  113. CPUCostAdjustment: 3.0,
  114. GPUHours: 1.0 * hrs1,
  115. GPUCost: 1.0 * hrs1 * gpuPrice,
  116. GPUCostAdjustment: 2.0,
  117. PVs: PVAllocations{
  118. disk: {
  119. ByteHours: 100.0 * gib * hrs1,
  120. Cost: 100.0 * hrs1 * pvPrice,
  121. },
  122. },
  123. PVCostAdjustment: 4.0,
  124. RAMByteHours: 8.0 * gib * hrs1,
  125. RAMBytesRequestAverage: 8.0 * gib,
  126. RAMBytesUsageAverage: 4.0 * gib,
  127. RAMCost: 8.0 * hrs1 * ramPrice,
  128. RAMCostAdjustment: 1.0,
  129. SharedCost: 2.00,
  130. ExternalCost: 1.00,
  131. RawAllocationOnly: &RawAllocationOnlyData{},
  132. }
  133. a1b := a1.Clone()
  134. s2 := time.Date(2021, time.January, 1, 6, 0, 0, 0, time.UTC)
  135. e2 := time.Date(2021, time.January, 1, 24, 0, 0, 0, time.UTC)
  136. hrs2 := e1.Sub(s1).Hours()
  137. a2 := &Allocation{
  138. Start: s2,
  139. End: e2,
  140. Properties: &AllocationProperties{},
  141. CPUCoreHours: 1.0 * hrs2,
  142. CPUCoreRequestAverage: 1.0,
  143. CPUCoreUsageAverage: 1.0,
  144. CPUCost: 1.0 * hrs2 * cpuPrice,
  145. GPUHours: 0.0,
  146. GPUCost: 0.0,
  147. RAMByteHours: 8.0 * gib * hrs2,
  148. RAMBytesRequestAverage: 0.0,
  149. RAMBytesUsageAverage: 8.0 * gib,
  150. RAMCost: 8.0 * hrs2 * ramPrice,
  151. NetworkCost: 0.01,
  152. LoadBalancerCost: 0.05,
  153. SharedCost: 0.00,
  154. ExternalCost: 1.00,
  155. RawAllocationOnly: &RawAllocationOnlyData{},
  156. }
  157. a2b := a2.Clone()
  158. act, err := a1.Add(a2)
  159. if err != nil {
  160. t.Fatalf("Allocation.Add: unexpected error: %s", err)
  161. }
  162. // Neither Allocation should be mutated
  163. if !a1.Equal(a1b) {
  164. t.Fatalf("Allocation.Add: a1 illegally mutated")
  165. }
  166. if !a2.Equal(a2b) {
  167. t.Fatalf("Allocation.Add: a1 illegally mutated")
  168. }
  169. // Costs should be cumulative
  170. if !util.IsApproximately(a1.TotalCost()+a2.TotalCost(), act.TotalCost()) {
  171. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.TotalCost()+a2.TotalCost(), act.TotalCost())
  172. }
  173. if !util.IsApproximately(a1.CPUCost+a2.CPUCost, act.CPUCost) {
  174. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCost+a2.CPUCost, act.CPUCost)
  175. }
  176. if !util.IsApproximately(a1.CPUCostAdjustment+a2.CPUCostAdjustment, act.CPUCostAdjustment) {
  177. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCostAdjustment+a2.CPUCostAdjustment, act.CPUCostAdjustment)
  178. }
  179. if !util.IsApproximately(a1.GPUCost+a2.GPUCost, act.GPUCost) {
  180. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCost+a2.GPUCost, act.GPUCost)
  181. }
  182. if !util.IsApproximately(a1.GPUCostAdjustment+a2.GPUCostAdjustment, act.GPUCostAdjustment) {
  183. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCostAdjustment+a2.GPUCostAdjustment, act.GPUCostAdjustment)
  184. }
  185. if !util.IsApproximately(a1.RAMCost+a2.RAMCost, act.RAMCost) {
  186. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCost+a2.RAMCost, act.RAMCost)
  187. }
  188. if !util.IsApproximately(a1.RAMCostAdjustment+a2.RAMCostAdjustment, act.RAMCostAdjustment) {
  189. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCostAdjustment+a2.RAMCostAdjustment, act.RAMCostAdjustment)
  190. }
  191. if !util.IsApproximately(a1.PVCost()+a2.PVCost(), act.PVCost()) {
  192. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVCost()+a2.PVCost(), act.PVCost())
  193. }
  194. if !util.IsApproximately(a1.NetworkCost+a2.NetworkCost, act.NetworkCost) {
  195. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.NetworkCost+a2.NetworkCost, act.NetworkCost)
  196. }
  197. if !util.IsApproximately(a1.LoadBalancerCost+a2.LoadBalancerCost, act.LoadBalancerCost) {
  198. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.LoadBalancerCost+a2.LoadBalancerCost, act.LoadBalancerCost)
  199. }
  200. if !util.IsApproximately(a1.SharedCost+a2.SharedCost, act.SharedCost) {
  201. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.SharedCost+a2.SharedCost, act.SharedCost)
  202. }
  203. if !util.IsApproximately(a1.ExternalCost+a2.ExternalCost, act.ExternalCost) {
  204. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.ExternalCost+a2.ExternalCost, act.ExternalCost)
  205. }
  206. // ResourceHours should be cumulative
  207. if !util.IsApproximately(a1.CPUCoreHours+a2.CPUCoreHours, act.CPUCoreHours) {
  208. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCoreHours+a2.CPUCoreHours, act.CPUCoreHours)
  209. }
  210. if !util.IsApproximately(a1.RAMByteHours+a2.RAMByteHours, act.RAMByteHours) {
  211. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMByteHours+a2.RAMByteHours, act.RAMByteHours)
  212. }
  213. if !util.IsApproximately(a1.PVByteHours()+a2.PVByteHours(), act.PVByteHours()) {
  214. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVByteHours()+a2.PVByteHours(), act.PVByteHours())
  215. }
  216. // Minutes should be the duration between min(starts) and max(ends)
  217. if !act.Start.Equal(a1.Start) || !act.End.Equal(a2.End) {
  218. t.Fatalf("Allocation.Add: expected %s; actual %s", NewWindow(&a1.Start, &a2.End), NewWindow(&act.Start, &act.End))
  219. }
  220. if act.Minutes() != 1440.0 {
  221. t.Fatalf("Allocation.Add: expected %f; actual %f", 1440.0, act.Minutes())
  222. }
  223. // Requests and Usage should be averaged correctly
  224. // CPU requests = (2.0*12.0 + 1.0*18.0)/(24.0) = 1.75
  225. // CPU usage = (1.0*12.0 + 1.0*18.0)/(24.0) = 1.25
  226. // RAM requests = (8.0*12.0 + 0.0*18.0)/(24.0) = 4.00
  227. // RAM usage = (4.0*12.0 + 8.0*18.0)/(24.0) = 8.00
  228. if !util.IsApproximately(1.75, act.CPUCoreRequestAverage) {
  229. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.75, act.CPUCoreRequestAverage)
  230. }
  231. if !util.IsApproximately(1.25, act.CPUCoreUsageAverage) {
  232. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.25, act.CPUCoreUsageAverage)
  233. }
  234. if !util.IsApproximately(4.00*gib, act.RAMBytesRequestAverage) {
  235. t.Fatalf("Allocation.Add: expected %f; actual %f", 4.00*gib, act.RAMBytesRequestAverage)
  236. }
  237. if !util.IsApproximately(8.00*gib, act.RAMBytesUsageAverage) {
  238. t.Fatalf("Allocation.Add: expected %f; actual %f", 8.00*gib, act.RAMBytesUsageAverage)
  239. }
  240. // Efficiency should be computed accurately from new request/usage
  241. // CPU efficiency = 1.25/1.75 = 0.7142857
  242. // RAM efficiency = 8.00/4.00 = 2.0000000
  243. // Total efficiency = (0.7142857*0.72 + 2.0*1.92)/(2.64) = 1.6493506
  244. if !util.IsApproximately(0.7142857, act.CPUEfficiency()) {
  245. t.Fatalf("Allocation.Add: expected %f; actual %f", 0.7142857, act.CPUEfficiency())
  246. }
  247. if !util.IsApproximately(2.0000000, act.RAMEfficiency()) {
  248. t.Fatalf("Allocation.Add: expected %f; actual %f", 2.0000000, act.RAMEfficiency())
  249. }
  250. if !util.IsApproximately(1.279690, act.TotalEfficiency()) {
  251. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.279690, act.TotalEfficiency())
  252. }
  253. if act.RawAllocationOnly != nil {
  254. t.Errorf("Allocation.Add: Raw only data must be nil after an add")
  255. }
  256. }
  257. func TestAllocation_Share(t *testing.T) {
  258. cpuPrice := 0.02
  259. gpuPrice := 2.00
  260. ramPrice := 0.01
  261. pvPrice := 0.00005
  262. gib := 1024.0 * 1024.0 * 1024.0
  263. s1 := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  264. e1 := time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)
  265. hrs1 := e1.Sub(s1).Hours()
  266. a1 := &Allocation{
  267. Start: s1,
  268. End: e1,
  269. Properties: &AllocationProperties{},
  270. CPUCoreHours: 2.0 * hrs1,
  271. CPUCoreRequestAverage: 2.0,
  272. CPUCoreUsageAverage: 1.0,
  273. CPUCost: 2.0 * hrs1 * cpuPrice,
  274. CPUCostAdjustment: 3.0,
  275. GPUHours: 1.0 * hrs1,
  276. GPUCost: 1.0 * hrs1 * gpuPrice,
  277. GPUCostAdjustment: 2.0,
  278. PVs: PVAllocations{
  279. disk: {
  280. ByteHours: 100.0 * gib * hrs1,
  281. Cost: 100.0 * hrs1 * pvPrice,
  282. },
  283. },
  284. PVCostAdjustment: 4.0,
  285. RAMByteHours: 8.0 * gib * hrs1,
  286. RAMBytesRequestAverage: 8.0 * gib,
  287. RAMBytesUsageAverage: 4.0 * gib,
  288. RAMCost: 8.0 * hrs1 * ramPrice,
  289. RAMCostAdjustment: 1.0,
  290. SharedCost: 2.00,
  291. ExternalCost: 1.00,
  292. }
  293. a1b := a1.Clone()
  294. s2 := time.Date(2021, time.January, 1, 6, 0, 0, 0, time.UTC)
  295. e2 := time.Date(2021, time.January, 1, 24, 0, 0, 0, time.UTC)
  296. hrs2 := e1.Sub(s1).Hours()
  297. a2 := &Allocation{
  298. Start: s2,
  299. End: e2,
  300. Properties: &AllocationProperties{},
  301. CPUCoreHours: 1.0 * hrs2,
  302. CPUCoreRequestAverage: 1.0,
  303. CPUCoreUsageAverage: 1.0,
  304. CPUCost: 1.0 * hrs2 * cpuPrice,
  305. GPUHours: 0.0,
  306. GPUCost: 0.0,
  307. RAMByteHours: 8.0 * gib * hrs2,
  308. RAMBytesRequestAverage: 0.0,
  309. RAMBytesUsageAverage: 8.0 * gib,
  310. RAMCost: 8.0 * hrs2 * ramPrice,
  311. NetworkCost: 0.01,
  312. LoadBalancerCost: 0.05,
  313. SharedCost: 0.00,
  314. ExternalCost: 1.00,
  315. }
  316. a2b := a2.Clone()
  317. act, err := a1.Share(a2)
  318. if err != nil {
  319. t.Fatalf("Allocation.Share: unexpected error: %s", err)
  320. }
  321. // Neither Allocation should be mutated
  322. if !a1.Equal(a1b) {
  323. t.Fatalf("Allocation.Share: a1 illegally mutated")
  324. }
  325. if !a2.Equal(a2b) {
  326. t.Fatalf("Allocation.Share: a1 illegally mutated")
  327. }
  328. // SharedCost and TotalCost should reflect increase by a2.TotalCost
  329. if !util.IsApproximately(a1.TotalCost()+a2.TotalCost(), act.TotalCost()) {
  330. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.TotalCost()+a2.TotalCost(), act.TotalCost())
  331. }
  332. if !util.IsApproximately(a1.SharedCost+a2.TotalCost(), act.SharedCost) {
  333. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.SharedCost+a2.TotalCost(), act.SharedCost)
  334. }
  335. // Costs should match before (expect TotalCost and SharedCost)
  336. if !util.IsApproximately(a1.CPUTotalCost(), act.CPUTotalCost()) {
  337. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUTotalCost(), act.CPUTotalCost())
  338. }
  339. if !util.IsApproximately(a1.GPUTotalCost(), act.GPUTotalCost()) {
  340. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.GPUTotalCost(), act.GPUTotalCost())
  341. }
  342. if !util.IsApproximately(a1.RAMTotalCost(), act.RAMTotalCost()) {
  343. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMTotalCost(), act.RAMTotalCost())
  344. }
  345. if !util.IsApproximately(a1.PVTotalCost(), act.PVTotalCost()) {
  346. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.PVTotalCost(), act.PVTotalCost())
  347. }
  348. if !util.IsApproximately(a1.NetworkCost, act.NetworkCost) {
  349. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.NetworkCost, act.NetworkCost)
  350. }
  351. if !util.IsApproximately(a1.LoadBalancerCost, act.LoadBalancerCost) {
  352. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.LoadBalancerCost, act.LoadBalancerCost)
  353. }
  354. if !util.IsApproximately(a1.ExternalCost, act.ExternalCost) {
  355. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.ExternalCost, act.ExternalCost)
  356. }
  357. // ResourceHours should match before
  358. if !util.IsApproximately(a1.CPUCoreHours, act.CPUCoreHours) {
  359. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreHours, act.CPUCoreHours)
  360. }
  361. if !util.IsApproximately(a1.RAMByteHours, act.RAMByteHours) {
  362. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMByteHours, act.RAMByteHours)
  363. }
  364. if !util.IsApproximately(a1.PVByteHours(), act.PVByteHours()) {
  365. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.PVByteHours(), act.PVByteHours())
  366. }
  367. // Minutes should match before
  368. if !act.Start.Equal(a1.Start) || !act.End.Equal(a1.End) {
  369. t.Fatalf("Allocation.Share: expected %s; actual %s", NewWindow(&a1.Start, &a1.End), NewWindow(&act.Start, &act.End))
  370. }
  371. if act.Minutes() != a1.Minutes() {
  372. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.Minutes(), act.Minutes())
  373. }
  374. // Requests and Usage should match before
  375. if !util.IsApproximately(a1.CPUCoreRequestAverage, act.CPUCoreRequestAverage) {
  376. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreRequestAverage, act.CPUCoreRequestAverage)
  377. }
  378. if !util.IsApproximately(a1.CPUCoreUsageAverage, act.CPUCoreUsageAverage) {
  379. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreUsageAverage, act.CPUCoreUsageAverage)
  380. }
  381. if !util.IsApproximately(a1.RAMBytesRequestAverage, act.RAMBytesRequestAverage) {
  382. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMBytesRequestAverage, act.RAMBytesRequestAverage)
  383. }
  384. if !util.IsApproximately(a1.RAMBytesUsageAverage, act.RAMBytesUsageAverage) {
  385. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMBytesUsageAverage, act.RAMBytesUsageAverage)
  386. }
  387. // Efficiency should match before
  388. if !util.IsApproximately(a1.CPUEfficiency(), act.CPUEfficiency()) {
  389. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUEfficiency(), act.CPUEfficiency())
  390. }
  391. if !util.IsApproximately(a1.RAMEfficiency(), act.RAMEfficiency()) {
  392. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMEfficiency(), act.RAMEfficiency())
  393. }
  394. if !util.IsApproximately(a1.TotalEfficiency(), act.TotalEfficiency()) {
  395. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.TotalEfficiency(), act.TotalEfficiency())
  396. }
  397. }
  398. func TestAllocation_MarshalJSON(t *testing.T) {
  399. start := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  400. end := time.Date(2021, time.January, 2, 0, 0, 0, 0, time.UTC)
  401. hrs := 24.0
  402. gib := 1024.0 * 1024.0 * 1024.0
  403. cpuPrice := 0.02
  404. gpuPrice := 2.00
  405. ramPrice := 0.01
  406. pvPrice := 0.00005
  407. before := &Allocation{
  408. Name: "cluster1/namespace1/node1/pod1/container1",
  409. Properties: &AllocationProperties{
  410. Cluster: "cluster1",
  411. Node: "node1",
  412. Namespace: "namespace1",
  413. Pod: "pod1",
  414. Container: "container1",
  415. },
  416. Window: NewWindow(&start, &end),
  417. Start: start,
  418. End: end,
  419. CPUCoreHours: 2.0 * hrs,
  420. CPUCoreRequestAverage: 2.0,
  421. CPUCoreUsageAverage: 1.0,
  422. CPUCost: 2.0 * hrs * cpuPrice,
  423. CPUCostAdjustment: 3.0,
  424. GPUHours: 1.0 * hrs,
  425. GPUCost: 1.0 * hrs * gpuPrice,
  426. GPUCostAdjustment: 2.0,
  427. NetworkCost: 0.05,
  428. LoadBalancerCost: 0.02,
  429. PVs: PVAllocations{
  430. disk: {
  431. ByteHours: 100.0 * gib * hrs,
  432. Cost: 100.0 * hrs * pvPrice,
  433. },
  434. },
  435. PVCostAdjustment: 4.0,
  436. RAMByteHours: 8.0 * gib * hrs,
  437. RAMBytesRequestAverage: 8.0 * gib,
  438. RAMBytesUsageAverage: 4.0 * gib,
  439. RAMCost: 8.0 * hrs * ramPrice,
  440. RAMCostAdjustment: 1.0,
  441. SharedCost: 2.00,
  442. ExternalCost: 1.00,
  443. RawAllocationOnly: &RawAllocationOnlyData{},
  444. }
  445. data, err := json.Marshal(before)
  446. if err != nil {
  447. t.Fatalf("Allocation.MarshalJSON: unexpected error: %s", err)
  448. }
  449. after := &Allocation{}
  450. err = json.Unmarshal(data, after)
  451. if err != nil {
  452. t.Fatalf("Allocation.UnmarshalJSON: unexpected error: %s", err)
  453. }
  454. // TODO:CLEANUP fix json marshaling of Window so that all of this works.
  455. // In the meantime, just set the Window so that we can test the rest.
  456. after.Window = before.Window.Clone()
  457. // TODO Sean: fix JSON marshaling of PVs
  458. after.PVs = before.PVs
  459. if !after.Equal(before) {
  460. t.Fatalf("Allocation.MarshalJSON: before and after are not equal")
  461. }
  462. }
  463. func TestAllocationSet_generateKey(t *testing.T) {
  464. var alloc *Allocation
  465. var key string
  466. props := []string{
  467. AllocationClusterProp,
  468. }
  469. key = alloc.generateKey(props)
  470. if key != "" {
  471. t.Fatalf("generateKey: expected \"\"; actual \"%s\"", key)
  472. }
  473. alloc = &Allocation{}
  474. alloc.Properties = &AllocationProperties{
  475. Cluster: "cluster1",
  476. Labels: map[string]string{
  477. "app": "app1",
  478. "env": "env1",
  479. },
  480. }
  481. key = alloc.generateKey(props)
  482. if key != "cluster1" {
  483. t.Fatalf("generateKey: expected \"cluster1\"; actual \"%s\"", key)
  484. }
  485. props = []string{
  486. AllocationClusterProp,
  487. AllocationNamespaceProp,
  488. "label:app",
  489. }
  490. key = alloc.generateKey(props)
  491. if key != "cluster1//app=app1" {
  492. t.Fatalf("generateKey: expected \"cluster1//app=app1\"; actual \"%s\"", key)
  493. }
  494. alloc.Properties = &AllocationProperties{
  495. Cluster: "cluster1",
  496. Namespace: "namespace1",
  497. Labels: map[string]string{
  498. "app": "app1",
  499. "env": "env1",
  500. },
  501. }
  502. key = alloc.generateKey(props)
  503. if key != "cluster1/namespace1/app=app1" {
  504. t.Fatalf("generateKey: expected \"cluster1/namespace1/app=app1\"; actual \"%s\"", key)
  505. }
  506. }
  507. func TestNewAllocationSet(t *testing.T) {
  508. // TODO niko/etl
  509. }
  510. func generateAllocationSetClusterIdle(start time.Time) *AllocationSet {
  511. // Cluster Idle allocations
  512. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &AllocationProperties{
  513. Cluster: "cluster1",
  514. })
  515. a1i.CPUCost = 5.0
  516. a1i.RAMCost = 15.0
  517. a1i.GPUCost = 0.0
  518. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &AllocationProperties{
  519. Cluster: "cluster2",
  520. })
  521. a2i.CPUCost = 5.0
  522. a2i.RAMCost = 5.0
  523. a2i.GPUCost = 0.0
  524. as := generateAllocationSet(start)
  525. as.Insert(a1i)
  526. as.Insert(a2i)
  527. return as
  528. }
  529. func generateAllocationSetNodeIdle(start time.Time) *AllocationSet {
  530. // Node Idle allocations
  531. a11i := NewUnitAllocation(fmt.Sprintf("c1nodes/%s", IdleSuffix), start, day, &AllocationProperties{
  532. Cluster: "cluster1",
  533. Node: "c1nodes",
  534. ProviderID: "c1nodes",
  535. })
  536. a11i.CPUCost = 5.0
  537. a11i.RAMCost = 15.0
  538. a11i.GPUCost = 0.0
  539. a21i := NewUnitAllocation(fmt.Sprintf("node1/%s", IdleSuffix), start, day, &AllocationProperties{
  540. Cluster: "cluster2",
  541. Node: "node1",
  542. ProviderID: "node1",
  543. })
  544. a21i.CPUCost = 1.666667
  545. a21i.RAMCost = 1.666667
  546. a21i.GPUCost = 0.0
  547. a22i := NewUnitAllocation(fmt.Sprintf("node2/%s", IdleSuffix), start, day, &AllocationProperties{
  548. Cluster: "cluster2",
  549. Node: "node2",
  550. ProviderID: "node2",
  551. })
  552. a22i.CPUCost = 1.666667
  553. a22i.RAMCost = 1.666667
  554. a22i.GPUCost = 0.0
  555. a23i := NewUnitAllocation(fmt.Sprintf("node3/%s", IdleSuffix), start, day, &AllocationProperties{
  556. Cluster: "cluster2",
  557. Node: "node3",
  558. ProviderID: "node3",
  559. Namespace: "",
  560. })
  561. a23i.CPUCost = 1.666667
  562. a23i.RAMCost = 1.666667
  563. a23i.GPUCost = 0.0
  564. as := generateAllocationSet(start)
  565. as.Insert(a11i)
  566. as.Insert(a21i)
  567. as.Insert(a22i)
  568. as.Insert(a23i)
  569. return as
  570. }
  571. func generateAllocationSet(start time.Time) *AllocationSet {
  572. // Active allocations
  573. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &AllocationProperties{
  574. Cluster: "cluster1",
  575. Namespace: "namespace1",
  576. Pod: "pod1",
  577. Container: "container1",
  578. ProviderID: "c1nodes",
  579. })
  580. a1111.RAMCost = 11.00
  581. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &AllocationProperties{
  582. Cluster: "cluster1",
  583. Namespace: "namespace1",
  584. Pod: "pod-abc",
  585. Container: "container2",
  586. ProviderID: "c1nodes",
  587. })
  588. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &AllocationProperties{
  589. Cluster: "cluster1",
  590. Namespace: "namespace1",
  591. Pod: "pod-def",
  592. Container: "container3",
  593. ProviderID: "c1nodes",
  594. })
  595. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &AllocationProperties{
  596. Cluster: "cluster1",
  597. Namespace: "namespace2",
  598. Pod: "pod-ghi",
  599. Container: "container4",
  600. ProviderID: "c1nodes",
  601. })
  602. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &AllocationProperties{
  603. Cluster: "cluster1",
  604. Namespace: "namespace2",
  605. Pod: "pod-ghi",
  606. Container: "container5",
  607. ProviderID: "c1nodes",
  608. })
  609. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &AllocationProperties{
  610. Cluster: "cluster1",
  611. Namespace: "namespace2",
  612. Pod: "pod-jkl",
  613. Container: "container6",
  614. ProviderID: "c1nodes",
  615. })
  616. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &AllocationProperties{
  617. Cluster: "cluster2",
  618. Namespace: "namespace2",
  619. Pod: "pod-mno",
  620. Container: "container4",
  621. ProviderID: "node1",
  622. })
  623. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &AllocationProperties{
  624. Cluster: "cluster2",
  625. Namespace: "namespace2",
  626. Pod: "pod-mno",
  627. Container: "container5",
  628. ProviderID: "node1",
  629. })
  630. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &AllocationProperties{
  631. Cluster: "cluster2",
  632. Namespace: "namespace2",
  633. Pod: "pod-pqr",
  634. Container: "container6",
  635. ProviderID: "node2",
  636. })
  637. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &AllocationProperties{
  638. Cluster: "cluster2",
  639. Namespace: "namespace3",
  640. Pod: "pod-stu",
  641. Container: "container7",
  642. ProviderID: "node2",
  643. })
  644. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &AllocationProperties{
  645. Cluster: "cluster2",
  646. Namespace: "namespace3",
  647. Pod: "pod-vwx",
  648. Container: "container8",
  649. ProviderID: "node3",
  650. })
  651. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &AllocationProperties{
  652. Cluster: "cluster2",
  653. Namespace: "namespace3",
  654. Pod: "pod-vwx",
  655. Container: "container9",
  656. ProviderID: "node3",
  657. })
  658. // Controllers
  659. a11abc2.Properties.ControllerKind = "deployment"
  660. a11abc2.Properties.Controller = "deployment1"
  661. a11def3.Properties.ControllerKind = "deployment"
  662. a11def3.Properties.Controller = "deployment1"
  663. a12ghi4.Properties.ControllerKind = "deployment"
  664. a12ghi4.Properties.Controller = "deployment2"
  665. a12ghi5.Properties.ControllerKind = "deployment"
  666. a12ghi5.Properties.Controller = "deployment2"
  667. a22mno4.Properties.ControllerKind = "deployment"
  668. a22mno4.Properties.Controller = "deployment2"
  669. a22mno5.Properties.ControllerKind = "deployment"
  670. a22mno5.Properties.Controller = "deployment2"
  671. a23stu7.Properties.ControllerKind = "deployment"
  672. a23stu7.Properties.Controller = "deployment3"
  673. a12jkl6.Properties.ControllerKind = "daemonset"
  674. a12jkl6.Properties.Controller = "daemonset1"
  675. a22pqr6.Properties.ControllerKind = "daemonset"
  676. a22pqr6.Properties.Controller = "daemonset1"
  677. a23vwx8.Properties.ControllerKind = "statefulset"
  678. a23vwx8.Properties.Controller = "statefulset1"
  679. a23vwx9.Properties.ControllerKind = "statefulset"
  680. a23vwx9.Properties.Controller = "statefulset1"
  681. // Labels
  682. a1111.Properties.Labels = map[string]string{"app": "app1", "env": "env1"}
  683. a12ghi4.Properties.Labels = map[string]string{"app": "app2", "env": "env2"}
  684. a12ghi5.Properties.Labels = map[string]string{"app": "app2", "env": "env2"}
  685. a22mno4.Properties.Labels = map[string]string{"app": "app2"}
  686. a22mno5.Properties.Labels = map[string]string{"app": "app2"}
  687. //Annotations
  688. a23stu7.Properties.Annotations = map[string]string{"team": "team1"}
  689. a23vwx8.Properties.Annotations = map[string]string{"team": "team2"}
  690. a23vwx9.Properties.Annotations = map[string]string{"team": "team1"}
  691. // Services
  692. a12jkl6.Properties.Services = []string{"service1"}
  693. a22pqr6.Properties.Services = []string{"service1"}
  694. return NewAllocationSet(start, start.Add(day),
  695. // cluster 1, namespace1
  696. a1111, a11abc2, a11def3,
  697. // cluster 1, namespace 2
  698. a12ghi4, a12ghi5, a12jkl6,
  699. // cluster 2, namespace 2
  700. a22mno4, a22mno5, a22pqr6,
  701. // cluster 2, namespace 3
  702. a23stu7, a23vwx8, a23vwx9,
  703. )
  704. }
  705. func generateAssetSets(start, end time.Time) []*AssetSet {
  706. var assetSets []*AssetSet
  707. // Create an AssetSet representing cluster costs for two clusters (cluster1
  708. // and cluster2). Include Nodes and Disks for both, even though only
  709. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  710. // by type, here we will provide multiple Nodes for one of the clusters to
  711. // make sure the function still holds.
  712. // NOTE: we're re-using generateAllocationSet so this has to line up with
  713. // the allocated node costs from that function. See table above.
  714. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  715. // +-----------------------------------------+------+------+------+------+------------+
  716. // cluster1:
  717. // nodes 100.00 55.00 44.00 11.00 -10.00
  718. // +-----------------------------------------+------+------+------+------+------------+
  719. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  720. // +-----------------------------------------+------+------+------+------+------------+
  721. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  722. // +-----------------------------------------+------+------+------+------+------------+
  723. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  724. // +-----------------------------------------+------+------+------+------+------------+
  725. // cluster2:
  726. // node1 35.00 20.00 15.00 0.00 0.00
  727. // node2 35.00 20.00 15.00 0.00 0.00
  728. // node3 30.00 10.00 10.00 10.00 0.00
  729. // (disks should not matter for idle)
  730. // +-----------------------------------------+------+------+------+------+------------+
  731. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  732. // +-----------------------------------------+------+------+------+------+------------+
  733. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  734. // +-----------------------------------------+------+------+------+------+------------+
  735. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  736. // +-----------------------------------------+------+------+------+------+------------+
  737. cluster1Nodes := NewNode("c1nodes", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
  738. cluster1Nodes.CPUCost = 55.0
  739. cluster1Nodes.RAMCost = 44.0
  740. cluster1Nodes.GPUCost = 11.0
  741. cluster1Nodes.adjustment = -10.00
  742. cluster1Nodes.CPUCoreHours = 8
  743. cluster1Nodes.RAMByteHours = 6
  744. cluster1Nodes.GPUHours = 24
  745. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  746. cluster2Node1.CPUCost = 20.0
  747. cluster2Node1.RAMCost = 15.0
  748. cluster2Node1.GPUCost = 0.0
  749. cluster2Node1.CPUCoreHours = 4
  750. cluster2Node1.RAMByteHours = 3
  751. cluster2Node1.GPUHours = 0
  752. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  753. cluster2Node2.CPUCost = 20.0
  754. cluster2Node2.RAMCost = 15.0
  755. cluster2Node2.GPUCost = 0.0
  756. cluster2Node2.CPUCoreHours = 3
  757. cluster2Node2.RAMByteHours = 2
  758. cluster2Node2.GPUHours = 0
  759. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  760. cluster2Node3.CPUCost = 10.0
  761. cluster2Node3.RAMCost = 10.0
  762. cluster2Node3.GPUCost = 10.0
  763. cluster2Node3.CPUCoreHours = 2
  764. cluster2Node3.RAMByteHours = 2
  765. cluster2Node3.GPUHours = 24
  766. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  767. cluster2Disk1.Cost = 5.0
  768. cluster2Disk1.adjustment = 1.0
  769. cluster2Disk1.ByteHours = 5 * gb
  770. cluster2Disk2 := NewDisk("disk2", "cluster2", "disk2", start, end, NewWindow(&start, &end))
  771. cluster2Disk2.Cost = 10.0
  772. cluster2Disk2.adjustment = 3.0
  773. cluster2Disk2.ByteHours = 10 * gb
  774. assetSet1 := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1, cluster2Disk2)
  775. assetSets = append(assetSets, assetSet1)
  776. // NOTE: we're re-using generateAllocationSet so this has to line up with
  777. // the allocated node costs from that function. See table above.
  778. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  779. // +-----------------------------------------+------+------+------+------+------------+
  780. // cluster1:
  781. // nodes 100.00 5.00 4.00 1.00 90.00
  782. // +-----------------------------------------+------+------+------+------+------------+
  783. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  784. // +-----------------------------------------+------+------+------+------+------------+
  785. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  786. // +-----------------------------------------+------+------+------+------+------------+
  787. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  788. // +-----------------------------------------+------+------+------+------+------------+
  789. // cluster2:
  790. // node1 35.00 20.00 15.00 0.00 0.00
  791. // node2 35.00 20.00 15.00 0.00 0.00
  792. // node3 30.00 10.00 10.00 10.00 0.00
  793. // (disks should not matter for idle)
  794. // +-----------------------------------------+------+------+------+------+------------+
  795. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  796. // +-----------------------------------------+------+------+------+------+------------+
  797. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  798. // +-----------------------------------------+------+------+------+------+------------+
  799. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  800. // +-----------------------------------------+------+------+------+------+------------+
  801. cluster1Nodes = NewNode("", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
  802. cluster1Nodes.CPUCost = 5.0
  803. cluster1Nodes.RAMCost = 4.0
  804. cluster1Nodes.GPUCost = 1.0
  805. cluster1Nodes.adjustment = 90.00
  806. cluster1Nodes.CPUCoreHours = 8
  807. cluster1Nodes.RAMByteHours = 6
  808. cluster1Nodes.GPUHours = 24
  809. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  810. cluster2Node1.CPUCost = 20.0
  811. cluster2Node1.RAMCost = 15.0
  812. cluster2Node1.GPUCost = 0.0
  813. cluster2Node1.CPUCoreHours = 4
  814. cluster2Node1.RAMByteHours = 3
  815. cluster2Node1.GPUHours = 0
  816. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  817. cluster2Node2.CPUCost = 20.0
  818. cluster2Node2.RAMCost = 15.0
  819. cluster2Node2.GPUCost = 0.0
  820. cluster2Node2.CPUCoreHours = 3
  821. cluster2Node2.RAMByteHours = 2
  822. cluster2Node2.GPUHours = 0
  823. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  824. cluster2Node3.CPUCost = 10.0
  825. cluster2Node3.RAMCost = 10.0
  826. cluster2Node3.GPUCost = 10.0
  827. cluster2Node3.CPUCoreHours = 2
  828. cluster2Node3.RAMByteHours = 2
  829. cluster2Node3.GPUHours = 24
  830. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  831. cluster2Disk1.Cost = 5.0
  832. cluster2Disk1.adjustment = 1.0
  833. cluster2Disk1.ByteHours = 5 * gb
  834. cluster2Disk2 = NewDisk("disk2", "cluster2", "disk2", start, end, NewWindow(&start, &end))
  835. cluster2Disk2.Cost = 12.0
  836. cluster2Disk2.adjustment = 4.0
  837. cluster2Disk2.ByteHours = 20 * gb
  838. assetSet2 := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1, cluster2Disk2)
  839. assetSets = append(assetSets, assetSet2)
  840. return assetSets
  841. }
  842. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  843. if err != nil {
  844. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  845. }
  846. if as.Length() != length {
  847. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  848. }
  849. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  850. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  851. }
  852. }
  853. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  854. as.Each(func(k string, a *Allocation) {
  855. if exp, ok := exps[a.Name]; ok {
  856. if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
  857. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %f, actual %f", msg, exp, a.TotalCost())
  858. }
  859. } else {
  860. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  861. }
  862. })
  863. }
  864. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  865. as.Each(func(k string, a *Allocation) {
  866. if !a.Start.Equal(expStart) {
  867. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  868. }
  869. if !a.End.Equal(expEnd) {
  870. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  871. }
  872. if a.Minutes() != expMinutes {
  873. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes())
  874. }
  875. })
  876. }
  877. func printAllocationSet(msg string, as *AllocationSet) {
  878. fmt.Printf("--- %s ---\n", msg)
  879. as.Each(func(k string, a *Allocation) {
  880. fmt.Printf(" > %s\n", a)
  881. })
  882. }
  883. func TestAllocationSet_AggregateBy(t *testing.T) {
  884. // Test AggregateBy against the following workload topology, which is
  885. // generated by generateAllocationSet:
  886. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net | LB |
  887. // +----------------------------------------+------+------+------+------+------+------+------+
  888. // cluster1:
  889. // idle: 20.00 5.00 15.00 0.00 0.00 0.00 0.00
  890. // namespace1:
  891. // pod1:
  892. // container1: [app=app1, env=env1] 16.00 1.00 11.00 1.00 1.00 1.00 1.00
  893. // pod-abc: (deployment1)
  894. // container2: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  895. // pod-def: (deployment1)
  896. // container3: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  897. // namespace2:
  898. // pod-ghi: (deployment2)
  899. // container4: [app=app2, env=env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  900. // container5: [app=app2, env=env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  901. // pod-jkl: (daemonset1)
  902. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  903. // +-----------------------------------------+------+------+------+------+------+------+------+
  904. // cluster1 subtotal 66.00 11.00 31.00 6.00 6.00 6.00 6.00
  905. // +-----------------------------------------+------+------+------+------+------+------+------+
  906. // cluster2:
  907. // idle: 10.00 5.00 5.00 0.00 0.00 0.00 0.00
  908. // namespace2:
  909. // pod-mno: (deployment2)
  910. // container4: [app=app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  911. // container5: [app=app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  912. // pod-pqr: (daemonset1)
  913. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  914. // namespace3:
  915. // pod-stu: (deployment3)
  916. // container7: an[team=team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  917. // pod-vwx: (statefulset1)
  918. // container8: an[team=team2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  919. // container9: an[team=team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  920. // +----------------------------------------+------+------+------+------+------+------+------+
  921. // cluster2 subtotal 46.00 11.00 11.00 6.00 6.00 6.00 6.00
  922. // +----------------------------------------+------+------+------+------+------+------+------+
  923. // total 112.00 22.00 42.00 12.00 12.00 12.00 12.00
  924. // +----------------------------------------+------+------+------+------+------+------+------+
  925. // Scenarios to test:
  926. // 1 Single-aggregation
  927. // 1a AggregationProperties=(Cluster)
  928. // 1b AggregationProperties=(Namespace)
  929. // 1c AggregationProperties=(Pod)
  930. // 1d AggregationProperties=(Container)
  931. // 1e AggregationProperties=(ControllerKind)
  932. // 1f AggregationProperties=(Controller)
  933. // 1g AggregationProperties=(Service)
  934. // 1h AggregationProperties=(Label:app)
  935. // 2 Multi-aggregation
  936. // 2a AggregationProperties=(Cluster, Namespace)
  937. // 2b AggregationProperties=(Namespace, Label:app)
  938. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  939. // 2d AggregationProperties=(Label:app, Label:environment)
  940. // 3 Share idle
  941. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  942. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  943. // 4 Share resources
  944. // 4a Share namespace ShareEven
  945. // 4b Share cluster ShareWeighted
  946. // 4c Share label ShareEven
  947. // 4d Share overhead ShareWeighted
  948. // 5 Filters
  949. // 5a Filter by cluster with separate idle
  950. // 5b Filter by cluster with shared idle
  951. // TODO niko/idle more filter tests
  952. // 6 Combinations and options
  953. // 6a SplitIdle
  954. // 6b Share idle with filters
  955. // 6c Share resources with filters
  956. // 6d Share idle and share resources
  957. // 6e IdleByNode
  958. // 7 Edge cases and errors
  959. // 7a Empty AggregationProperties
  960. // 7b Filter all
  961. // 7c Share all
  962. // 7d Share and filter the same allocations
  963. // Definitions and set-up:
  964. var as *AllocationSet
  965. var err error
  966. endYesterday := time.Now().UTC().Truncate(day)
  967. startYesterday := endYesterday.Add(-day)
  968. numClusters := 2
  969. numNamespaces := 3
  970. numPods := 9
  971. numContainers := 9
  972. numControllerKinds := 3
  973. numControllers := 5
  974. numServices := 1
  975. numLabelApps := 2
  976. // By default, idle is reported as a single, merged allocation
  977. numIdle := 1
  978. // There will only ever be one __unallocated__
  979. numUnallocated := 1
  980. // There are two clusters, so each gets an idle entry when they are split
  981. numSplitIdleCluster := 2
  982. // There are two clusters, so each gets an idle entry when they are split
  983. numSplitIdleNode := 4
  984. activeTotalCost := 82.0
  985. idleTotalCost := 30.0
  986. sharedOverheadHourlyCost := 7.0
  987. // Match Functions
  988. isNamespace3 := func(a *Allocation) bool {
  989. ns := a.Properties.Namespace
  990. return ns == "namespace3"
  991. }
  992. isApp1 := func(a *Allocation) bool {
  993. ls := a.Properties.Labels
  994. if app, ok := ls["app"]; ok && app == "app1" {
  995. return true
  996. }
  997. return false
  998. }
  999. // Filters
  1000. isCluster := func(matchCluster string) func(*Allocation) bool {
  1001. return func(a *Allocation) bool {
  1002. cluster := a.Properties.Cluster
  1003. return cluster == matchCluster
  1004. }
  1005. }
  1006. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  1007. return func(a *Allocation) bool {
  1008. namespace := a.Properties.Namespace
  1009. return namespace == matchNamespace
  1010. }
  1011. }
  1012. end := time.Now().UTC().Truncate(day)
  1013. start := end.Add(-day)
  1014. // Tests:
  1015. cases := map[string]struct {
  1016. start time.Time
  1017. aggBy []string
  1018. aggOpts *AllocationAggregationOptions
  1019. numResults int
  1020. totalCost float64
  1021. results map[string]float64
  1022. windowStart time.Time
  1023. windowEnd time.Time
  1024. expMinutes float64
  1025. }{
  1026. // 1 Single-aggregation
  1027. // 1a AggregationProperties=(Cluster)
  1028. "1a": {
  1029. start: start,
  1030. aggBy: []string{AllocationClusterProp},
  1031. aggOpts: nil,
  1032. numResults: numClusters + numIdle,
  1033. totalCost: activeTotalCost + idleTotalCost,
  1034. results: map[string]float64{
  1035. "cluster1": 46.00,
  1036. "cluster2": 36.00,
  1037. IdleSuffix: 30.00,
  1038. },
  1039. windowStart: startYesterday,
  1040. windowEnd: endYesterday,
  1041. expMinutes: 1440.0,
  1042. },
  1043. // 1b AggregationProperties=(Namespace)
  1044. "1b": {
  1045. start: start,
  1046. aggBy: []string{AllocationNamespaceProp},
  1047. aggOpts: nil,
  1048. numResults: numNamespaces + numIdle,
  1049. totalCost: activeTotalCost + idleTotalCost,
  1050. results: map[string]float64{
  1051. "namespace1": 28.00,
  1052. "namespace2": 36.00,
  1053. "namespace3": 18.00,
  1054. IdleSuffix: 30.00,
  1055. },
  1056. windowStart: startYesterday,
  1057. windowEnd: endYesterday,
  1058. expMinutes: 1440.0,
  1059. },
  1060. // 1c AggregationProperties=(Pod)
  1061. "1c": {
  1062. start: start,
  1063. aggBy: []string{AllocationPodProp},
  1064. aggOpts: nil,
  1065. numResults: numPods + numIdle,
  1066. totalCost: activeTotalCost + idleTotalCost,
  1067. results: map[string]float64{
  1068. "pod-jkl": 6.00,
  1069. "pod-stu": 6.00,
  1070. "pod-abc": 6.00,
  1071. "pod-pqr": 6.00,
  1072. "pod-def": 6.00,
  1073. "pod-vwx": 12.00,
  1074. "pod1": 16.00,
  1075. "pod-mno": 12.00,
  1076. "pod-ghi": 12.00,
  1077. IdleSuffix: 30.00,
  1078. },
  1079. windowStart: startYesterday,
  1080. windowEnd: endYesterday,
  1081. expMinutes: 1440.0,
  1082. },
  1083. // 1d AggregationProperties=(Container)
  1084. "1d": {
  1085. start: start,
  1086. aggBy: []string{AllocationContainerProp},
  1087. aggOpts: nil,
  1088. numResults: numContainers + numIdle,
  1089. totalCost: activeTotalCost + idleTotalCost,
  1090. results: map[string]float64{
  1091. "container2": 6.00,
  1092. "container9": 6.00,
  1093. "container6": 12.00,
  1094. "container3": 6.00,
  1095. "container4": 12.00,
  1096. "container7": 6.00,
  1097. "container8": 6.00,
  1098. "container5": 12.00,
  1099. "container1": 16.00,
  1100. IdleSuffix: 30.00,
  1101. },
  1102. windowStart: startYesterday,
  1103. windowEnd: endYesterday,
  1104. expMinutes: 1440.0,
  1105. },
  1106. // 1e AggregationProperties=(ControllerKind)
  1107. "1e": {
  1108. start: start,
  1109. aggBy: []string{AllocationControllerKindProp},
  1110. aggOpts: nil,
  1111. numResults: numControllerKinds + numIdle + numUnallocated,
  1112. totalCost: activeTotalCost + idleTotalCost,
  1113. results: map[string]float64{
  1114. "daemonset": 12.00,
  1115. "deployment": 42.00,
  1116. "statefulset": 12.00,
  1117. IdleSuffix: 30.00,
  1118. UnallocatedSuffix: 16.00,
  1119. },
  1120. windowStart: startYesterday,
  1121. windowEnd: endYesterday,
  1122. expMinutes: 1440.0,
  1123. },
  1124. // 1f AggregationProperties=(Controller)
  1125. "1f": {
  1126. start: start,
  1127. aggBy: []string{AllocationControllerProp},
  1128. aggOpts: nil,
  1129. numResults: numControllers + numIdle + numUnallocated,
  1130. totalCost: activeTotalCost + idleTotalCost,
  1131. results: map[string]float64{
  1132. "deployment:deployment2": 24.00,
  1133. "daemonset:daemonset1": 12.00,
  1134. "deployment:deployment3": 6.00,
  1135. "statefulset:statefulset1": 12.00,
  1136. "deployment:deployment1": 12.00,
  1137. IdleSuffix: 30.00,
  1138. UnallocatedSuffix: 16.00,
  1139. },
  1140. windowStart: startYesterday,
  1141. windowEnd: endYesterday,
  1142. expMinutes: 1440.0,
  1143. },
  1144. // 1g AggregationProperties=(Service)
  1145. "1g": {
  1146. start: start,
  1147. aggBy: []string{AllocationServiceProp},
  1148. aggOpts: nil,
  1149. numResults: numServices + numIdle + numUnallocated,
  1150. totalCost: activeTotalCost + idleTotalCost,
  1151. results: map[string]float64{
  1152. "service1": 12.00,
  1153. IdleSuffix: 30.00,
  1154. UnallocatedSuffix: 70.00,
  1155. },
  1156. windowStart: startYesterday,
  1157. windowEnd: endYesterday,
  1158. expMinutes: 1440.0,
  1159. },
  1160. // 1h AggregationProperties=(Label:app)
  1161. "1h": {
  1162. start: start,
  1163. aggBy: []string{"label:app"},
  1164. aggOpts: nil,
  1165. numResults: numLabelApps + numIdle + numUnallocated,
  1166. totalCost: activeTotalCost + idleTotalCost,
  1167. results: map[string]float64{
  1168. "app=app1": 16.00,
  1169. "app=app2": 24.00,
  1170. IdleSuffix: 30.00,
  1171. UnallocatedSuffix: 42.00,
  1172. },
  1173. windowStart: startYesterday,
  1174. windowEnd: endYesterday,
  1175. expMinutes: 1440.0,
  1176. },
  1177. // 1i AggregationProperties=(deployment)
  1178. "1i": {
  1179. start: start,
  1180. aggBy: []string{AllocationDeploymentProp},
  1181. aggOpts: nil,
  1182. numResults: 3 + numIdle + numUnallocated,
  1183. totalCost: activeTotalCost + idleTotalCost,
  1184. results: map[string]float64{
  1185. "deployment1": 12.00,
  1186. "deployment2": 24.00,
  1187. "deployment3": 6.00,
  1188. IdleSuffix: 30.00,
  1189. UnallocatedSuffix: 40.00,
  1190. },
  1191. windowStart: startYesterday,
  1192. windowEnd: endYesterday,
  1193. expMinutes: 1440.0,
  1194. },
  1195. // 1j AggregationProperties=(Annotation:team)
  1196. "1j": {
  1197. start: start,
  1198. aggBy: []string{"annotation:team"},
  1199. aggOpts: nil,
  1200. numResults: 2 + numIdle + numUnallocated,
  1201. totalCost: activeTotalCost + idleTotalCost,
  1202. results: map[string]float64{
  1203. "team=team1": 12.00,
  1204. "team=team2": 6.00,
  1205. IdleSuffix: 30.00,
  1206. UnallocatedSuffix: 64.00,
  1207. },
  1208. windowStart: startYesterday,
  1209. windowEnd: endYesterday,
  1210. expMinutes: 1440.0,
  1211. },
  1212. // 1k AggregationProperties=(daemonSet)
  1213. "1k": {
  1214. start: start,
  1215. aggBy: []string{AllocationDaemonSetProp},
  1216. aggOpts: nil,
  1217. numResults: 1 + numIdle + numUnallocated,
  1218. totalCost: activeTotalCost + idleTotalCost,
  1219. results: map[string]float64{
  1220. "daemonset1": 12.00,
  1221. IdleSuffix: 30.00,
  1222. UnallocatedSuffix: 70.00,
  1223. },
  1224. windowStart: startYesterday,
  1225. windowEnd: endYesterday,
  1226. expMinutes: 1440.0,
  1227. },
  1228. // 1l AggregationProperties=(statefulSet)
  1229. "1l": {
  1230. start: start,
  1231. aggBy: []string{AllocationStatefulSetProp},
  1232. aggOpts: nil,
  1233. numResults: 1 + numIdle + numUnallocated,
  1234. totalCost: activeTotalCost + idleTotalCost,
  1235. results: map[string]float64{
  1236. "statefulset1": 12.00,
  1237. IdleSuffix: 30.00,
  1238. UnallocatedSuffix: 70.00,
  1239. },
  1240. windowStart: startYesterday,
  1241. windowEnd: endYesterday,
  1242. expMinutes: 1440.0,
  1243. },
  1244. // 2 Multi-aggregation
  1245. // 2a AggregationProperties=(Cluster, Namespace)
  1246. // 2b AggregationProperties=(Namespace, Label:app)
  1247. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  1248. // 2d AggregationProperties=(Label:app, Label:environment)
  1249. "2d": {
  1250. start: start,
  1251. aggBy: []string{"label:app;env"},
  1252. aggOpts: nil,
  1253. numResults: 3 + numIdle + numUnallocated,
  1254. totalCost: activeTotalCost + idleTotalCost,
  1255. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  1256. results: map[string]float64{
  1257. "app=app1/env=env1": 16.00,
  1258. "app=app2/env=env2": 12.00,
  1259. "app=app2/" + UnallocatedSuffix: 12.00,
  1260. IdleSuffix: 30.00,
  1261. UnallocatedSuffix: 42.00,
  1262. },
  1263. windowStart: startYesterday,
  1264. windowEnd: endYesterday,
  1265. expMinutes: 1440.0,
  1266. },
  1267. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  1268. "2e": {
  1269. start: start,
  1270. aggBy: []string{AllocationClusterProp, "label:app;env"},
  1271. aggOpts: nil,
  1272. numResults: 6,
  1273. totalCost: activeTotalCost + idleTotalCost,
  1274. results: map[string]float64{
  1275. "cluster1/app=app2/env=env2": 12.00,
  1276. "__idle__": 30.00,
  1277. "cluster1/app=app1/env=env1": 16.00,
  1278. "cluster1/" + UnallocatedSuffix: 18.00,
  1279. "cluster2/app=app2/" + UnallocatedSuffix: 12.00,
  1280. "cluster2/" + UnallocatedSuffix: 24.00,
  1281. },
  1282. windowStart: startYesterday,
  1283. windowEnd: endYesterday,
  1284. expMinutes: 1440.0,
  1285. },
  1286. // 2f AggregationProperties=(annotation:team, pod)
  1287. "2f": {
  1288. start: start,
  1289. aggBy: []string{AllocationPodProp, "annotation:team"},
  1290. aggOpts: nil,
  1291. numResults: 11,
  1292. totalCost: activeTotalCost + idleTotalCost,
  1293. results: map[string]float64{
  1294. "pod-jkl/" + UnallocatedSuffix: 6.00,
  1295. "pod-stu/team=team1": 6.00,
  1296. "pod-abc/" + UnallocatedSuffix: 6.00,
  1297. "pod-pqr/" + UnallocatedSuffix: 6.00,
  1298. "pod-def/" + UnallocatedSuffix: 6.00,
  1299. "pod-vwx/team=team1": 6.00,
  1300. "pod-vwx/team=team2": 6.00,
  1301. "pod1/" + UnallocatedSuffix: 16.00,
  1302. "pod-mno/" + UnallocatedSuffix: 12.00,
  1303. "pod-ghi/" + UnallocatedSuffix: 12.00,
  1304. IdleSuffix: 30.00,
  1305. },
  1306. windowStart: startYesterday,
  1307. windowEnd: endYesterday,
  1308. expMinutes: 1440.0,
  1309. },
  1310. // 3 Share idle
  1311. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  1312. // namespace1: 42.6875 = 28.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  1313. // namespace2: 46.3125 = 36.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)
  1314. // namespace3: 23.0000 = 18.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  1315. "3a": {
  1316. start: start,
  1317. aggBy: []string{AllocationNamespaceProp},
  1318. aggOpts: &AllocationAggregationOptions{ShareIdle: ShareWeighted},
  1319. numResults: numNamespaces,
  1320. totalCost: activeTotalCost + idleTotalCost,
  1321. results: map[string]float64{
  1322. "namespace1": 42.69,
  1323. "namespace2": 46.31,
  1324. "namespace3": 23.00,
  1325. },
  1326. windowStart: startYesterday,
  1327. windowEnd: endYesterday,
  1328. expMinutes: 1440.0,
  1329. },
  1330. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  1331. // namespace1: 38.0000 = 28.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  1332. // namespace2: 51.0000 = 36.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)
  1333. // namespace3: 23.0000 = 18.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  1334. "3b": {
  1335. start: start,
  1336. aggBy: []string{AllocationNamespaceProp},
  1337. aggOpts: &AllocationAggregationOptions{ShareIdle: ShareEven},
  1338. numResults: numNamespaces,
  1339. totalCost: activeTotalCost + idleTotalCost,
  1340. results: map[string]float64{
  1341. "namespace1": 38.00,
  1342. "namespace2": 51.00,
  1343. "namespace3": 23.00,
  1344. },
  1345. windowStart: startYesterday,
  1346. windowEnd: endYesterday,
  1347. expMinutes: 1440.0,
  1348. },
  1349. // 4 Share resources
  1350. // 4a Share namespace ShareEven
  1351. // namespace1: 37.5000 = 28.00 + 18.00*(1.0/2.0)
  1352. // namespace2: 45.5000 = 36.00 + 18.00*(1.0/2.0)
  1353. // idle: 30.0000
  1354. "4a": {
  1355. start: start,
  1356. aggBy: []string{AllocationNamespaceProp},
  1357. aggOpts: &AllocationAggregationOptions{
  1358. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  1359. ShareSplit: ShareEven,
  1360. },
  1361. numResults: numNamespaces,
  1362. totalCost: activeTotalCost + idleTotalCost,
  1363. results: map[string]float64{
  1364. "namespace1": 37.00,
  1365. "namespace2": 45.00,
  1366. IdleSuffix: 30.00,
  1367. },
  1368. windowStart: startYesterday,
  1369. windowEnd: endYesterday,
  1370. expMinutes: 1440.0,
  1371. },
  1372. // 4b Share namespace ShareWeighted
  1373. // namespace1: 32.5000 =
  1374. // namespace2: 37.5000 =
  1375. // idle: 30.0000
  1376. "4b": {
  1377. start: start,
  1378. aggBy: []string{AllocationNamespaceProp},
  1379. aggOpts: &AllocationAggregationOptions{
  1380. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  1381. ShareSplit: ShareWeighted,
  1382. },
  1383. numResults: numNamespaces,
  1384. totalCost: activeTotalCost + idleTotalCost,
  1385. results: map[string]float64{
  1386. "namespace1": 35.88,
  1387. "namespace2": 46.125,
  1388. IdleSuffix: 30.00,
  1389. },
  1390. windowStart: startYesterday,
  1391. windowEnd: endYesterday,
  1392. expMinutes: 1440.0,
  1393. },
  1394. // 4c Share label ShareEven
  1395. // namespace1: 17.3333 = 28.00 - 16.00 + 16.00*(1.0/3.0)
  1396. // namespace2: 41.3333 = 36.00 + 16.00*(1.0/3.0)
  1397. // namespace3: 23.3333 = 18.00 + 16.00*(1.0/3.0)
  1398. // idle: 30.0000
  1399. "4c": {
  1400. start: start,
  1401. aggBy: []string{AllocationNamespaceProp},
  1402. aggOpts: &AllocationAggregationOptions{
  1403. ShareFuncs: []AllocationMatchFunc{isApp1},
  1404. ShareSplit: ShareEven,
  1405. },
  1406. numResults: numNamespaces + numIdle,
  1407. totalCost: activeTotalCost + idleTotalCost,
  1408. results: map[string]float64{
  1409. "namespace1": 17.33,
  1410. "namespace2": 41.33,
  1411. "namespace3": 23.33,
  1412. IdleSuffix: 30.00,
  1413. },
  1414. windowStart: startYesterday,
  1415. windowEnd: endYesterday,
  1416. expMinutes: 1440.0,
  1417. },
  1418. // 4d Share overhead ShareWeighted
  1419. // namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
  1420. // namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
  1421. // namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
  1422. // idle: 30.0000
  1423. "4d": {
  1424. start: start,
  1425. aggBy: []string{AllocationNamespaceProp},
  1426. aggOpts: &AllocationAggregationOptions{
  1427. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1428. ShareSplit: ShareWeighted,
  1429. },
  1430. numResults: numNamespaces + numIdle,
  1431. totalCost: activeTotalCost + idleTotalCost + (sharedOverheadHourlyCost * 24.0),
  1432. results: map[string]float64{
  1433. "namespace1": 85.366,
  1434. "namespace2": 109.756,
  1435. "namespace3": 54.878,
  1436. IdleSuffix: 30.00,
  1437. },
  1438. windowStart: startYesterday,
  1439. windowEnd: endYesterday,
  1440. expMinutes: 1440.0,
  1441. },
  1442. // 5 Filters
  1443. // 5a Filter by cluster with separate idle
  1444. "5a": {
  1445. start: start,
  1446. aggBy: []string{AllocationClusterProp},
  1447. aggOpts: &AllocationAggregationOptions{
  1448. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1449. ShareIdle: ShareNone,
  1450. },
  1451. numResults: 1 + numIdle,
  1452. totalCost: 66.0,
  1453. results: map[string]float64{
  1454. "cluster1": 46.00,
  1455. IdleSuffix: 20.00,
  1456. },
  1457. windowStart: startYesterday,
  1458. windowEnd: endYesterday,
  1459. expMinutes: 1440.0,
  1460. },
  1461. // 5b Filter by cluster with shared idle
  1462. "5b": {
  1463. start: start,
  1464. aggBy: []string{AllocationClusterProp},
  1465. aggOpts: &AllocationAggregationOptions{
  1466. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1467. ShareIdle: ShareWeighted,
  1468. },
  1469. numResults: 1,
  1470. totalCost: 66.0,
  1471. results: map[string]float64{
  1472. "cluster1": 66.00,
  1473. },
  1474. windowStart: startYesterday,
  1475. windowEnd: endYesterday,
  1476. expMinutes: 1440.0,
  1477. },
  1478. // 5c Filter by cluster, agg by namespace, with separate idle
  1479. "5c": {
  1480. start: start,
  1481. aggBy: []string{AllocationNamespaceProp},
  1482. aggOpts: &AllocationAggregationOptions{
  1483. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1484. ShareIdle: ShareNone,
  1485. },
  1486. numResults: 2 + numIdle,
  1487. totalCost: 66.0,
  1488. results: map[string]float64{
  1489. "namespace1": 28.00,
  1490. "namespace2": 18.00,
  1491. IdleSuffix: 20.00,
  1492. },
  1493. windowStart: startYesterday,
  1494. windowEnd: endYesterday,
  1495. expMinutes: 1440.0,
  1496. },
  1497. // 5d Filter by namespace, agg by cluster, with separate idle
  1498. "5d": {
  1499. start: start,
  1500. aggBy: []string{AllocationClusterProp},
  1501. aggOpts: &AllocationAggregationOptions{
  1502. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1503. ShareIdle: ShareNone,
  1504. },
  1505. numResults: numClusters + numIdle,
  1506. totalCost: 46.31,
  1507. results: map[string]float64{
  1508. "cluster1": 18.00,
  1509. "cluster2": 18.00,
  1510. IdleSuffix: 10.31,
  1511. },
  1512. windowStart: startYesterday,
  1513. windowEnd: endYesterday,
  1514. expMinutes: 1440.0,
  1515. },
  1516. // 6 Combinations and options
  1517. // 6a SplitIdle
  1518. "6a": {
  1519. start: start,
  1520. aggBy: []string{AllocationNamespaceProp},
  1521. aggOpts: &AllocationAggregationOptions{
  1522. SplitIdle: true,
  1523. },
  1524. numResults: numNamespaces + numSplitIdleCluster,
  1525. totalCost: activeTotalCost + idleTotalCost,
  1526. results: map[string]float64{
  1527. "namespace1": 28.00,
  1528. "namespace2": 36.00,
  1529. "namespace3": 18.00,
  1530. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  1531. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  1532. },
  1533. windowStart: startYesterday,
  1534. windowEnd: endYesterday,
  1535. expMinutes: 1440.0,
  1536. },
  1537. // 6b Share idle weighted with filters
  1538. // Should match values from unfiltered aggregation (3a)
  1539. // namespace2: 46.3125 = 36.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)
  1540. "6b": {
  1541. start: start,
  1542. aggBy: []string{AllocationNamespaceProp},
  1543. aggOpts: &AllocationAggregationOptions{
  1544. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1545. ShareIdle: ShareWeighted,
  1546. },
  1547. numResults: 1,
  1548. totalCost: 46.31,
  1549. results: map[string]float64{
  1550. "namespace2": 46.31,
  1551. },
  1552. windowStart: startYesterday,
  1553. windowEnd: endYesterday,
  1554. expMinutes: 1440.0,
  1555. },
  1556. // 6c Share idle even with filters
  1557. // Should match values from unfiltered aggregation (3b)
  1558. // namespace2: 51.0000 = 36.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)
  1559. "6c": {
  1560. start: start,
  1561. aggBy: []string{AllocationNamespaceProp},
  1562. aggOpts: &AllocationAggregationOptions{
  1563. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1564. ShareIdle: ShareEven,
  1565. },
  1566. numResults: 1,
  1567. totalCost: 51.00,
  1568. results: map[string]float64{
  1569. "namespace2": 51.00,
  1570. },
  1571. windowStart: startYesterday,
  1572. windowEnd: endYesterday,
  1573. expMinutes: 1440.0,
  1574. },
  1575. // 6d Share overhead with filters
  1576. // namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
  1577. // namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
  1578. // namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
  1579. // idle: 30.0000
  1580. // Then namespace 2 is filtered.
  1581. "6d": {
  1582. start: start,
  1583. aggBy: []string{AllocationNamespaceProp},
  1584. aggOpts: &AllocationAggregationOptions{
  1585. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1586. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1587. ShareSplit: ShareWeighted,
  1588. },
  1589. numResults: 1 + numIdle,
  1590. totalCost: 139.756,
  1591. results: map[string]float64{
  1592. "namespace2": 109.756,
  1593. IdleSuffix: 30.00,
  1594. },
  1595. windowStart: startYesterday,
  1596. windowEnd: endYesterday,
  1597. expMinutes: 1440.0,
  1598. },
  1599. // 6e Share resources with filters
  1600. // --- Shared ---
  1601. // namespace1: 28.00 (gets shared among namespace2 and namespace3)
  1602. // --- Filtered ---
  1603. // namespace3: 27.33 = 18.00 + (28.00)*(18.00/54.00) (filtered out)
  1604. // --- Results ---
  1605. // namespace2: 54.667 = 36.00 + (28.00)*(36.00/54.00)
  1606. // idle: 30.0000
  1607. "6e": {
  1608. start: start,
  1609. aggBy: []string{AllocationNamespaceProp},
  1610. aggOpts: &AllocationAggregationOptions{
  1611. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1612. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1613. ShareSplit: ShareWeighted,
  1614. },
  1615. numResults: 1 + numIdle,
  1616. totalCost: 84.667,
  1617. results: map[string]float64{
  1618. "namespace2": 54.667,
  1619. IdleSuffix: 30.00,
  1620. },
  1621. windowStart: startYesterday,
  1622. windowEnd: endYesterday,
  1623. expMinutes: 1440.0,
  1624. },
  1625. // 6f Share idle weighted and share resources weighted
  1626. //
  1627. // First, share idle weighted produces:
  1628. //
  1629. // namespace1: 42.6875
  1630. // initial cost 28.0000
  1631. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1632. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1633. //
  1634. // namespace2: 46.3125
  1635. // initial cost 36.0000
  1636. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1637. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1638. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1639. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1640. //
  1641. // namespace3: 23.0000
  1642. // initial cost 18.0000
  1643. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1644. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1645. //
  1646. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1647. // computed before allocating idle (so that weighting idle differently
  1648. // doesn't adversely affect the sharing mechanism):
  1649. //
  1650. // namespace2: 74.7708
  1651. // initial cost 30.0000
  1652. // idle cost 10.3125
  1653. // shared cost 28.4583 = (42.6875)*(36.0/54.0)
  1654. //
  1655. // namespace3: 37.2292
  1656. // initial cost 18.0000
  1657. // idle cost 5.0000
  1658. // shared cost 14.2292 = (42.6875)*(18.0/54.0)
  1659. "6f": {
  1660. start: start,
  1661. aggBy: []string{AllocationNamespaceProp},
  1662. aggOpts: &AllocationAggregationOptions{
  1663. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1664. ShareSplit: ShareWeighted,
  1665. ShareIdle: ShareWeighted,
  1666. },
  1667. numResults: 2,
  1668. totalCost: activeTotalCost + idleTotalCost,
  1669. results: map[string]float64{
  1670. "namespace2": 74.77,
  1671. "namespace3": 37.23,
  1672. },
  1673. windowStart: startYesterday,
  1674. windowEnd: endYesterday,
  1675. expMinutes: 1440.0,
  1676. },
  1677. // 6g Share idle, share resources, and filter
  1678. //
  1679. // First, share idle weighted produces:
  1680. //
  1681. // namespace1: 42.6875
  1682. // initial cost 28.0000
  1683. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1684. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1685. //
  1686. // namespace2: 46.3125
  1687. // initial cost 36.0000
  1688. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1689. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1690. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1691. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1692. //
  1693. // namespace3: 23.0000
  1694. // initial cost 18.0000
  1695. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1696. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1697. //
  1698. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1699. // computed before allocating idle (so that weighting idle differently
  1700. // doesn't adversely affect the sharing mechanism):
  1701. //
  1702. // namespace2: 74.7708
  1703. // initial cost 36.0000
  1704. // idle cost 10.3125
  1705. // shared cost 28.4583 = (42.6875)*(36.0/54.0)
  1706. //
  1707. // namespace3: 37.2292
  1708. // initial cost 18.0000
  1709. // idle cost 5.0000
  1710. // shared cost 14.2292 = (42.6875)*(18.0/54.0)
  1711. //
  1712. // Then, filter for namespace2: 74.7708
  1713. "6g": {
  1714. start: start,
  1715. aggBy: []string{AllocationNamespaceProp},
  1716. aggOpts: &AllocationAggregationOptions{
  1717. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1718. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1719. ShareSplit: ShareWeighted,
  1720. ShareIdle: ShareWeighted,
  1721. },
  1722. numResults: 1,
  1723. totalCost: 74.77,
  1724. results: map[string]float64{
  1725. "namespace2": 74.77,
  1726. },
  1727. windowStart: startYesterday,
  1728. windowEnd: endYesterday,
  1729. expMinutes: 1440.0,
  1730. },
  1731. // 6h Share idle, share resources, share overhead
  1732. //
  1733. // Share idle weighted:
  1734. //
  1735. // namespace1: 42.6875
  1736. // initial cost 28.0000
  1737. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1738. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1739. //
  1740. // namespace2: 46.3125
  1741. // initial cost 36.0000
  1742. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1743. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1744. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1745. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1746. //
  1747. // namespace3: 23.0000
  1748. // initial cost 18.0000
  1749. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1750. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1751. //
  1752. // Then share overhead:
  1753. //
  1754. // namespace1: 100.0533 = 42.6875 + (7.0*24.0)*(28.00/82.00)
  1755. // namespace2: 120.0686 = 46.3125 + (7.0*24.0)*(36.00/82.00)
  1756. // namespace3: 59.8780 = 23.0000 + (7.0*24.0)*(18.00/82.00)
  1757. //
  1758. // Then namespace 2 is filtered.
  1759. "6h": {
  1760. start: start,
  1761. aggBy: []string{AllocationNamespaceProp},
  1762. aggOpts: &AllocationAggregationOptions{
  1763. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1764. ShareSplit: ShareWeighted,
  1765. ShareIdle: ShareWeighted,
  1766. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1767. },
  1768. numResults: 1,
  1769. totalCost: 120.07,
  1770. results: map[string]float64{
  1771. "namespace2": 120.07,
  1772. },
  1773. windowStart: startYesterday,
  1774. windowEnd: endYesterday,
  1775. expMinutes: 1440.0,
  1776. },
  1777. // 6i Idle by Node
  1778. "6i": {
  1779. start: start,
  1780. aggBy: []string{AllocationNamespaceProp},
  1781. aggOpts: &AllocationAggregationOptions{
  1782. IdleByNode: true,
  1783. },
  1784. numResults: numNamespaces + numIdle,
  1785. totalCost: activeTotalCost + idleTotalCost,
  1786. results: map[string]float64{
  1787. "namespace1": 28.00,
  1788. "namespace2": 36.00,
  1789. "namespace3": 18.00,
  1790. IdleSuffix: 30.00,
  1791. },
  1792. windowStart: startYesterday,
  1793. windowEnd: endYesterday,
  1794. expMinutes: 1440.0,
  1795. },
  1796. // 6j Split Idle, Idle by Node
  1797. "6j": {
  1798. start: start,
  1799. aggBy: []string{AllocationNamespaceProp},
  1800. aggOpts: &AllocationAggregationOptions{
  1801. SplitIdle: true,
  1802. IdleByNode: true,
  1803. },
  1804. numResults: numNamespaces + numSplitIdleNode,
  1805. totalCost: activeTotalCost + idleTotalCost,
  1806. results: map[string]float64{
  1807. "namespace1": 28.00,
  1808. "namespace2": 36.00,
  1809. "namespace3": 18.00,
  1810. fmt.Sprintf("c1nodes/%s", IdleSuffix): 20.00,
  1811. fmt.Sprintf("node1/%s", IdleSuffix): 3.333333,
  1812. fmt.Sprintf("node2/%s", IdleSuffix): 3.333333,
  1813. fmt.Sprintf("node3/%s", IdleSuffix): 3.333333,
  1814. },
  1815. windowStart: startYesterday,
  1816. windowEnd: endYesterday,
  1817. expMinutes: 1440.0,
  1818. },
  1819. // 6k Share idle Even Idle by Node
  1820. // Should match values from unfiltered aggregation (3b)
  1821. "6k": {
  1822. start: start,
  1823. aggBy: []string{AllocationNamespaceProp},
  1824. aggOpts: &AllocationAggregationOptions{
  1825. ShareIdle: ShareEven,
  1826. IdleByNode: true,
  1827. },
  1828. numResults: 3,
  1829. totalCost: 112.00,
  1830. results: map[string]float64{
  1831. "namespace1": 38.00,
  1832. "namespace2": 51.00,
  1833. "namespace3": 23.00,
  1834. },
  1835. windowStart: startYesterday,
  1836. windowEnd: endYesterday,
  1837. expMinutes: 1440.0,
  1838. },
  1839. // 6l Share idle weighted with filters, Idle by Node
  1840. // Should match values from unfiltered aggregation (3a)
  1841. // namespace2: 46.3125 = 36.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)
  1842. "6l": {
  1843. start: start,
  1844. aggBy: []string{AllocationNamespaceProp},
  1845. aggOpts: &AllocationAggregationOptions{
  1846. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1847. ShareIdle: ShareWeighted,
  1848. IdleByNode: true,
  1849. },
  1850. numResults: 1,
  1851. totalCost: 46.31,
  1852. results: map[string]float64{
  1853. "namespace2": 46.31,
  1854. },
  1855. windowStart: startYesterday,
  1856. windowEnd: endYesterday,
  1857. expMinutes: 1440.0,
  1858. },
  1859. // 7 Edge cases and errors
  1860. // 7a Empty AggregationProperties
  1861. // 7b Filter all
  1862. // 7c Share all
  1863. // 7d Share and filter the same allocations
  1864. }
  1865. for name, testcase := range cases {
  1866. t.Run(name, func(t *testing.T) {
  1867. if testcase.aggOpts != nil && testcase.aggOpts.IdleByNode {
  1868. as = generateAllocationSetNodeIdle(testcase.start)
  1869. } else {
  1870. as = generateAllocationSetClusterIdle(testcase.start)
  1871. }
  1872. err = as.AggregateBy(testcase.aggBy, testcase.aggOpts)
  1873. assertAllocationSetTotals(t, as, name, err, testcase.numResults, testcase.totalCost)
  1874. assertAllocationTotals(t, as, name, testcase.results)
  1875. assertAllocationWindow(t, as, name, testcase.windowStart, testcase.windowEnd, testcase.expMinutes)
  1876. })
  1877. }
  1878. }
  1879. // TODO niko/etl
  1880. //func TestAllocationSet_Clone(t *testing.T) {}
  1881. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  1882. var as *AllocationSet
  1883. var err error
  1884. var idles map[string]*Allocation
  1885. end := time.Now().UTC().Truncate(day)
  1886. start := end.Add(-day)
  1887. // Generate AllocationSet without idle allocations
  1888. as = generateAllocationSet(start)
  1889. assetSets := generateAssetSets(start, end)
  1890. cases := map[string]struct {
  1891. allocationSet *AllocationSet
  1892. assetSet *AssetSet
  1893. clusters map[string]Allocation
  1894. }{
  1895. "1a": {
  1896. allocationSet: as,
  1897. assetSet: assetSets[0],
  1898. clusters: map[string]Allocation{
  1899. "cluster1": {
  1900. CPUCost: 44.0,
  1901. RAMCost: 24.0,
  1902. GPUCost: 4.0,
  1903. },
  1904. "cluster2": {
  1905. CPUCost: 44.0,
  1906. RAMCost: 34.0,
  1907. GPUCost: 4.0,
  1908. },
  1909. },
  1910. },
  1911. "1b": {
  1912. allocationSet: as,
  1913. assetSet: assetSets[1],
  1914. clusters: map[string]Allocation{
  1915. "cluster1": {
  1916. CPUCost: 44.0,
  1917. RAMCost: 24.0,
  1918. GPUCost: 4.0,
  1919. },
  1920. "cluster2": {
  1921. CPUCost: 44.0,
  1922. RAMCost: 34.0,
  1923. GPUCost: 4.0,
  1924. },
  1925. },
  1926. },
  1927. }
  1928. for name, testcase := range cases {
  1929. t.Run(name, func(t *testing.T) {
  1930. idles, err = as.ComputeIdleAllocations(testcase.assetSet)
  1931. if err != nil {
  1932. t.Fatalf("unexpected error: %s", err)
  1933. }
  1934. if len(idles) != len(testcase.clusters) {
  1935. t.Fatalf("idles: expected length %d; got length %d", len(testcase.clusters), len(idles))
  1936. }
  1937. for clusterName, cluster := range testcase.clusters {
  1938. if idle, ok := idles[clusterName]; !ok {
  1939. t.Fatalf("expected idle cost for %s", clusterName)
  1940. } else {
  1941. if !util.IsApproximately(idle.TotalCost(), cluster.TotalCost()) {
  1942. t.Fatalf("%s idle: expected total cost %f; got total cost %f", clusterName, cluster.TotalCost(), idle.TotalCost())
  1943. }
  1944. }
  1945. if !util.IsApproximately(idles[clusterName].CPUCost, cluster.CPUCost) {
  1946. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", clusterName, cluster.CPUCost, idles[clusterName].CPUCost)
  1947. }
  1948. if !util.IsApproximately(idles[clusterName].RAMCost, cluster.RAMCost) {
  1949. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", clusterName, cluster.RAMCost, idles[clusterName].RAMCost)
  1950. }
  1951. if !util.IsApproximately(idles[clusterName].GPUCost, cluster.GPUCost) {
  1952. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", clusterName, cluster.GPUCost, idles[clusterName].GPUCost)
  1953. }
  1954. }
  1955. })
  1956. }
  1957. }
  1958. func TestAllocationSet_ComputeIdleAllocationsPerNode(t *testing.T) {
  1959. var as *AllocationSet
  1960. var err error
  1961. var idles map[string]*Allocation
  1962. end := time.Now().UTC().Truncate(day)
  1963. start := end.Add(-day)
  1964. // Generate AllocationSet without idle allocations
  1965. as = generateAllocationSet(start)
  1966. assetSets := generateAssetSets(start, end)
  1967. cases := map[string]struct {
  1968. allocationSet *AllocationSet
  1969. assetSet *AssetSet
  1970. nodes map[string]Allocation
  1971. }{
  1972. "1a": {
  1973. allocationSet: as,
  1974. assetSet: assetSets[0],
  1975. nodes: map[string]Allocation{
  1976. "c1nodes": {
  1977. CPUCost: 44.0,
  1978. RAMCost: 24.0,
  1979. GPUCost: 4.0,
  1980. },
  1981. "node1": {
  1982. CPUCost: 18.0,
  1983. RAMCost: 13.0,
  1984. GPUCost: -2.0,
  1985. },
  1986. "node2": {
  1987. CPUCost: 18.0,
  1988. RAMCost: 13.0,
  1989. GPUCost: -2.0,
  1990. },
  1991. "node3": {
  1992. CPUCost: 8.0,
  1993. RAMCost: 8.0,
  1994. GPUCost: 8.0,
  1995. },
  1996. },
  1997. },
  1998. "1b": {
  1999. allocationSet: as,
  2000. assetSet: assetSets[1],
  2001. nodes: map[string]Allocation{
  2002. "c1nodes": {
  2003. CPUCost: 44.0,
  2004. RAMCost: 24.0,
  2005. GPUCost: 4.0,
  2006. },
  2007. "node1": {
  2008. CPUCost: 18.0,
  2009. RAMCost: 13.0,
  2010. GPUCost: -2.0,
  2011. },
  2012. "node2": {
  2013. CPUCost: 18.0,
  2014. RAMCost: 13.0,
  2015. GPUCost: -2.0,
  2016. },
  2017. "node3": {
  2018. CPUCost: 8.0,
  2019. RAMCost: 8.0,
  2020. GPUCost: 8.0,
  2021. },
  2022. },
  2023. },
  2024. }
  2025. for name, testcase := range cases {
  2026. t.Run(name, func(t *testing.T) {
  2027. idles, err = as.ComputeIdleAllocationsByNode(testcase.assetSet)
  2028. if err != nil {
  2029. t.Fatalf("unexpected error: %s", err)
  2030. }
  2031. if len(idles) != len(testcase.nodes) {
  2032. t.Fatalf("idles: expected length %d; got length %d", len(testcase.nodes), len(idles))
  2033. }
  2034. for nodeName, node := range testcase.nodes {
  2035. if idle, ok := idles[nodeName]; !ok {
  2036. t.Fatalf("expected idle cost for %s", nodeName)
  2037. } else {
  2038. if !util.IsApproximately(idle.TotalCost(), node.TotalCost()) {
  2039. t.Fatalf("%s idle: expected total cost %f; got total cost %f", nodeName, node.TotalCost(), idle.TotalCost())
  2040. }
  2041. }
  2042. if !util.IsApproximately(idles[nodeName].CPUCost, node.CPUCost) {
  2043. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", nodeName, node.CPUCost, idles[nodeName].CPUCost)
  2044. }
  2045. if !util.IsApproximately(idles[nodeName].RAMCost, node.RAMCost) {
  2046. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", nodeName, node.RAMCost, idles[nodeName].RAMCost)
  2047. }
  2048. if !util.IsApproximately(idles[nodeName].GPUCost, node.GPUCost) {
  2049. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", nodeName, node.GPUCost, idles[nodeName].GPUCost)
  2050. }
  2051. }
  2052. })
  2053. }
  2054. }
  2055. func TestAllocationSet_ReconcileAllocations(t *testing.T) {
  2056. var as *AllocationSet
  2057. var err error
  2058. end := time.Now().UTC().Truncate(day)
  2059. start := end.Add(-day)
  2060. // Generate AllocationSet without idle allocations
  2061. as = generateAllocationSet(start)
  2062. // add reconcilable pvs to pod-mno
  2063. for _, a := range as.allocations {
  2064. if a.Properties.Pod == "pod-mno" {
  2065. a.PVs = a.PVs.Add(PVAllocations{
  2066. disk1: {
  2067. Cost: 2.5,
  2068. ByteHours: 2.5 * gb,
  2069. },
  2070. disk2: {
  2071. Cost: 5,
  2072. ByteHours: 5 * gb,
  2073. },
  2074. })
  2075. }
  2076. }
  2077. assetSets := generateAssetSets(start, end)
  2078. cases := map[string]struct {
  2079. allocationSet *AllocationSet
  2080. assetSet *AssetSet
  2081. allocations map[string]Allocation
  2082. }{
  2083. "1a": {
  2084. allocationSet: as,
  2085. assetSet: assetSets[0],
  2086. allocations: map[string]Allocation{
  2087. // Allocation adjustments are found with the formula:
  2088. // ADJUSTMENT_RATE * NODE_COST * (ALLOC_HOURS / NODE_HOURS) - ALLOC_COST
  2089. // ADJUSTMENT_RATE: 0.90909090909
  2090. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2091. // CPU | 55 | 8 | 1 | 1
  2092. // RAM | 44 | 6 | 11 | 1
  2093. // GPU | 11 | 24 | 1 | 1
  2094. "cluster1/namespace1/pod1/container1": {
  2095. CPUCostAdjustment: 5.25,
  2096. RAMCostAdjustment: -4.333333,
  2097. GPUCostAdjustment: -0.583333,
  2098. },
  2099. // ADJUSTMENT_RATE: 0.90909090909
  2100. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2101. // CPU | 55 | 8 | 1 | 1
  2102. // RAM | 44 | 6 | 1 | 1
  2103. // GPU | 11 | 24 | 1 | 1
  2104. "cluster1/namespace1/pod-abc/container2": {
  2105. CPUCostAdjustment: 5.25,
  2106. RAMCostAdjustment: 5.666667,
  2107. GPUCostAdjustment: -0.583333,
  2108. },
  2109. "cluster1/namespace1/pod-def/container3": {
  2110. CPUCostAdjustment: 5.25,
  2111. RAMCostAdjustment: 5.666667,
  2112. GPUCostAdjustment: -0.583333,
  2113. },
  2114. "cluster1/namespace2/pod-ghi/container4": {
  2115. CPUCostAdjustment: 5.25,
  2116. RAMCostAdjustment: 5.666667,
  2117. GPUCostAdjustment: -0.583333,
  2118. },
  2119. "cluster1/namespace2/pod-ghi/container5": {
  2120. CPUCostAdjustment: 5.25,
  2121. RAMCostAdjustment: 5.666667,
  2122. GPUCostAdjustment: -0.583333,
  2123. },
  2124. "cluster1/namespace2/pod-jkl/container6": {
  2125. CPUCostAdjustment: 5.25,
  2126. RAMCostAdjustment: 5.666667,
  2127. GPUCostAdjustment: -0.583333,
  2128. },
  2129. // ADJUSTMENT_RATE: 1.0
  2130. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2131. // CPU | 20 | 4 | 1 | 1
  2132. // RAM | 15 | 3 | 1 | 1
  2133. // GPU | 0 | 0 | 1 | 1
  2134. "cluster2/namespace2/pod-mno/container4": {
  2135. CPUCostAdjustment: 4.0,
  2136. RAMCostAdjustment: 4.0,
  2137. GPUCostAdjustment: -1.0,
  2138. PVCostAdjustment: 2.0,
  2139. },
  2140. "cluster2/namespace2/pod-mno/container5": {
  2141. CPUCostAdjustment: 4.0,
  2142. RAMCostAdjustment: 4.0,
  2143. GPUCostAdjustment: -1.0,
  2144. PVCostAdjustment: 2.0,
  2145. },
  2146. // ADJUSTMENT_RATE: 1.0
  2147. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2148. // CPU | 20 | 3 | 1 | 1
  2149. // RAM | 15 | 2 | 1 | 1
  2150. // GPU | 0 | 0 | 1 | 1
  2151. "cluster2/namespace2/pod-pqr/container6": {
  2152. CPUCostAdjustment: 5.666667,
  2153. RAMCostAdjustment: 6.5,
  2154. GPUCostAdjustment: -1.0,
  2155. },
  2156. "cluster2/namespace3/pod-stu/container7": {
  2157. CPUCostAdjustment: 5.666667,
  2158. RAMCostAdjustment: 6.5,
  2159. GPUCostAdjustment: -1.0,
  2160. },
  2161. // ADJUSTMENT_RATE: 1.0
  2162. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2163. // CPU | 10 | 2 | 1 | 1
  2164. // RAM | 10 | 2 | 1 | 1
  2165. // GPU | 10 | 24 | 1 | 1
  2166. "cluster2/namespace3/pod-vwx/container8": {
  2167. CPUCostAdjustment: 4.0,
  2168. RAMCostAdjustment: 4.0,
  2169. GPUCostAdjustment: -0.583333,
  2170. },
  2171. "cluster2/namespace3/pod-vwx/container9": {
  2172. CPUCostAdjustment: 4.0,
  2173. RAMCostAdjustment: 4.0,
  2174. GPUCostAdjustment: -0.583333,
  2175. },
  2176. },
  2177. },
  2178. "1b": {
  2179. allocationSet: as,
  2180. assetSet: assetSets[1],
  2181. allocations: map[string]Allocation{
  2182. // ADJUSTMENT_RATE: 10
  2183. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2184. // CPU | 5 | 8 | 1 | 1
  2185. // RAM | 4 | 6 | 11 | 1
  2186. // GPU | 1 | 24 | 1 | 1
  2187. "cluster1/namespace1/pod1/container1": {
  2188. CPUCostAdjustment: 5.25,
  2189. RAMCostAdjustment: -4.333333,
  2190. GPUCostAdjustment: -0.583333,
  2191. },
  2192. // ADJUSTMENT_RATE: 10
  2193. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2194. // CPU | 5 | 8 | 1 | 1
  2195. // RAM | 4 | 6 | 1 | 1
  2196. // GPU | 1 | 24 | 1 | 1
  2197. "cluster1/namespace1/pod-abc/container2": {
  2198. CPUCostAdjustment: 5.25,
  2199. RAMCostAdjustment: 5.6666667,
  2200. GPUCostAdjustment: -0.583333,
  2201. },
  2202. "cluster1/namespace1/pod-def/container3": {
  2203. CPUCostAdjustment: 5.25,
  2204. RAMCostAdjustment: 5.6666667,
  2205. GPUCostAdjustment: -0.583333,
  2206. },
  2207. "cluster1/namespace2/pod-ghi/container4": {
  2208. CPUCostAdjustment: 5.25,
  2209. RAMCostAdjustment: 5.6666667,
  2210. GPUCostAdjustment: -0.583333,
  2211. },
  2212. "cluster1/namespace2/pod-ghi/container5": {
  2213. CPUCostAdjustment: 5.25,
  2214. RAMCostAdjustment: 5.6666667,
  2215. GPUCostAdjustment: -0.583333,
  2216. },
  2217. "cluster1/namespace2/pod-jkl/container6": {
  2218. CPUCostAdjustment: 5.25,
  2219. RAMCostAdjustment: 5.6666667,
  2220. GPUCostAdjustment: -0.583333,
  2221. },
  2222. // ADJUSTMENT_RATE: 1.0
  2223. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2224. // CPU | 20 | 4 | 1 | 1
  2225. // RAM | 15 | 3 | 1 | 1
  2226. // GPU | 0 | 0 | 1 | 1
  2227. "cluster2/namespace2/pod-mno/container4": {
  2228. CPUCostAdjustment: 4.0,
  2229. RAMCostAdjustment: 4.0,
  2230. GPUCostAdjustment: -1.0,
  2231. PVCostAdjustment: -0.5,
  2232. },
  2233. "cluster2/namespace2/pod-mno/container5": {
  2234. CPUCostAdjustment: 4.0,
  2235. RAMCostAdjustment: 4.0,
  2236. GPUCostAdjustment: -1.0,
  2237. PVCostAdjustment: -0.5,
  2238. },
  2239. // ADJUSTMENT_RATE: 1.0
  2240. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2241. // CPU | 20 | 3 | 1 | 1
  2242. // RAM | 15 | 2 | 1 | 1
  2243. // GPU | 0 | 0 | 1 | 1
  2244. "cluster2/namespace2/pod-pqr/container6": {
  2245. CPUCostAdjustment: 5.666667,
  2246. RAMCostAdjustment: 6.5,
  2247. GPUCostAdjustment: -1.0,
  2248. },
  2249. "cluster2/namespace3/pod-stu/container7": {
  2250. CPUCostAdjustment: 5.666667,
  2251. RAMCostAdjustment: 6.5,
  2252. GPUCostAdjustment: -1.0,
  2253. },
  2254. // ADJUSTMENT_RATE: 1.0
  2255. // Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
  2256. // CPU | 10 | 2 | 1 | 1
  2257. // RAM | 10 | 2 | 1 | 1
  2258. // GPU | 10 | 24 | 1 | 1
  2259. "cluster2/namespace3/pod-vwx/container8": {
  2260. CPUCostAdjustment: 4.0,
  2261. RAMCostAdjustment: 4.0,
  2262. GPUCostAdjustment: -0.583333,
  2263. },
  2264. "cluster2/namespace3/pod-vwx/container9": {
  2265. CPUCostAdjustment: 4.0,
  2266. RAMCostAdjustment: 4.0,
  2267. GPUCostAdjustment: -0.583333,
  2268. },
  2269. },
  2270. },
  2271. }
  2272. for name, testcase := range cases {
  2273. t.Run(name, func(t *testing.T) {
  2274. err = as.Reconcile(testcase.assetSet)
  2275. reconAllocs := as.allocations
  2276. if err != nil {
  2277. t.Fatalf("unexpected error: %s", err)
  2278. }
  2279. for allocationName, testAlloc := range testcase.allocations {
  2280. if _, ok := reconAllocs[allocationName]; !ok {
  2281. t.Fatalf("expected allocation %s", allocationName)
  2282. }
  2283. if !util.IsApproximately(reconAllocs[allocationName].CPUCostAdjustment, testAlloc.CPUCostAdjustment) {
  2284. t.Fatalf("expected CPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.CPUCostAdjustment, reconAllocs[allocationName].CPUCostAdjustment)
  2285. }
  2286. if !util.IsApproximately(reconAllocs[allocationName].RAMCostAdjustment, testAlloc.RAMCostAdjustment) {
  2287. t.Fatalf("expected RAM Adjustment for %s to be %f; got %f", allocationName, testAlloc.RAMCostAdjustment, reconAllocs[allocationName].RAMCostAdjustment)
  2288. }
  2289. if !util.IsApproximately(reconAllocs[allocationName].GPUCostAdjustment, testAlloc.GPUCostAdjustment) {
  2290. t.Fatalf("expected GPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.GPUCostAdjustment, reconAllocs[allocationName].GPUCostAdjustment)
  2291. }
  2292. if !util.IsApproximately(reconAllocs[allocationName].PVCostAdjustment, testAlloc.PVCostAdjustment) {
  2293. t.Fatalf("expected PV Adjustment for %s to be %f; got %f", allocationName, testAlloc.PVCostAdjustment, reconAllocs[allocationName].PVCostAdjustment)
  2294. }
  2295. }
  2296. })
  2297. }
  2298. }
  2299. // TODO niko/etl
  2300. //func TestAllocationSet_Delete(t *testing.T) {}
  2301. // TODO niko/etl
  2302. //func TestAllocationSet_End(t *testing.T) {}
  2303. // TODO niko/etl
  2304. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  2305. // TODO niko/etl
  2306. //func TestAllocationSet_Insert(t *testing.T) {}
  2307. // TODO niko/etl
  2308. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  2309. // TODO niko/etl
  2310. //func TestAllocationSet_Length(t *testing.T) {}
  2311. // TODO niko/etl
  2312. //func TestAllocationSet_Map(t *testing.T) {}
  2313. // TODO niko/etl
  2314. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  2315. // TODO niko/etl
  2316. //func TestAllocationSet_Resolution(t *testing.T) {}
  2317. // TODO niko/etl
  2318. //func TestAllocationSet_Seconds(t *testing.T) {}
  2319. // TODO niko/etl
  2320. //func TestAllocationSet_Set(t *testing.T) {}
  2321. // TODO niko/etl
  2322. //func TestAllocationSet_Start(t *testing.T) {}
  2323. // TODO niko/etl
  2324. //func TestAllocationSet_TotalCost(t *testing.T) {}
  2325. // TODO niko/etl
  2326. //func TestNewAllocationSetRange(t *testing.T) {}
  2327. func TestAllocationSetRange_Accumulate(t *testing.T) {
  2328. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2329. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2330. today := time.Now().UTC().Truncate(day)
  2331. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2332. // Accumulating any combination of nil and/or empty set should result in empty set
  2333. result, err := NewAllocationSetRange(nil).Accumulate()
  2334. if err != nil {
  2335. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2336. }
  2337. if !result.IsEmpty() {
  2338. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2339. }
  2340. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  2341. if err != nil {
  2342. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2343. }
  2344. if !result.IsEmpty() {
  2345. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2346. }
  2347. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  2348. if err != nil {
  2349. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2350. }
  2351. if !result.IsEmpty() {
  2352. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2353. }
  2354. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  2355. if err != nil {
  2356. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2357. }
  2358. if !result.IsEmpty() {
  2359. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2360. }
  2361. todayAS := NewAllocationSet(today, tomorrow)
  2362. todayAS.Set(NewUnitAllocation("", today, day, nil))
  2363. yesterdayAS := NewAllocationSet(yesterday, today)
  2364. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  2365. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  2366. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  2367. if err != nil {
  2368. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2369. }
  2370. if result == nil {
  2371. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2372. }
  2373. if result.TotalCost() != 6.0 {
  2374. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  2375. }
  2376. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  2377. if err != nil {
  2378. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2379. }
  2380. if result == nil {
  2381. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2382. }
  2383. if result.TotalCost() != 6.0 {
  2384. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  2385. }
  2386. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  2387. if err != nil {
  2388. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2389. }
  2390. if result == nil {
  2391. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2392. }
  2393. if result.TotalCost() != 6.0 {
  2394. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  2395. }
  2396. // Accumulate two non-nil should result in sum of both with appropriate start, end
  2397. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  2398. if err != nil {
  2399. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2400. }
  2401. if result == nil {
  2402. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2403. }
  2404. if result.TotalCost() != 12.0 {
  2405. t.Fatalf("accumulating AllocationSetRange: expected total cost 12.0; actual %f", result.TotalCost())
  2406. }
  2407. allocMap := result.Map()
  2408. if len(allocMap) != 1 {
  2409. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  2410. }
  2411. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  2412. if alloc == nil {
  2413. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  2414. }
  2415. if alloc.CPUCoreHours != 2.0 {
  2416. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  2417. }
  2418. if alloc.CPUCost != 2.0 {
  2419. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  2420. }
  2421. if alloc.CPUEfficiency() != 1.0 {
  2422. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency())
  2423. }
  2424. if alloc.GPUHours != 2.0 {
  2425. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  2426. }
  2427. if alloc.GPUCost != 2.0 {
  2428. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  2429. }
  2430. if alloc.NetworkCost != 2.0 {
  2431. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  2432. }
  2433. if alloc.LoadBalancerCost != 2.0 {
  2434. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.LoadBalancerCost)
  2435. }
  2436. if alloc.PVByteHours() != 2.0 {
  2437. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours())
  2438. }
  2439. if alloc.PVCost() != 2.0 {
  2440. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost())
  2441. }
  2442. if alloc.RAMByteHours != 2.0 {
  2443. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  2444. }
  2445. if alloc.RAMCost != 2.0 {
  2446. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  2447. }
  2448. if alloc.RAMEfficiency() != 1.0 {
  2449. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
  2450. }
  2451. if alloc.TotalCost() != 12.0 {
  2452. t.Fatalf("accumulating AllocationSetRange: expected 12.0; actual %f", alloc.TotalCost())
  2453. }
  2454. if alloc.TotalEfficiency() != 1.0 {
  2455. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
  2456. }
  2457. if !alloc.Start.Equal(yesterday) {
  2458. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  2459. }
  2460. if !alloc.End.Equal(tomorrow) {
  2461. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  2462. }
  2463. if alloc.Minutes() != 2880.0 {
  2464. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes())
  2465. }
  2466. }
  2467. // TODO niko/etl
  2468. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  2469. // TODO niko/etl
  2470. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  2471. // TODO niko/etl
  2472. // func TestAllocationSetRange_Append(t *testing.T) {}
  2473. // TODO niko/etl
  2474. // func TestAllocationSetRange_Each(t *testing.T) {}
  2475. // TODO niko/etl
  2476. // func TestAllocationSetRange_Get(t *testing.T) {}
  2477. func TestAllocationSetRange_InsertRange(t *testing.T) {
  2478. // Set up
  2479. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2480. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2481. today := time.Now().UTC().Truncate(day)
  2482. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2483. unit := NewUnitAllocation("", today, day, nil)
  2484. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2485. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  2486. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  2487. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  2488. yesterdayAS := NewAllocationSet(yesterday, today)
  2489. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  2490. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  2491. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  2492. todayAS := NewAllocationSet(today, tomorrow)
  2493. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  2494. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  2495. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  2496. var nilASR *AllocationSetRange
  2497. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  2498. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  2499. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  2500. var err error
  2501. // Expect an error calling InsertRange on nil
  2502. err = nilASR.InsertRange(thatASR)
  2503. if err == nil {
  2504. t.Fatalf("expected error, got nil")
  2505. }
  2506. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  2507. err = thisASR.InsertRange(nil)
  2508. if err != nil {
  2509. t.Fatalf("unexpected error: %s", err)
  2510. }
  2511. thisASR.Each(func(i int, as *AllocationSet) {
  2512. as.Each(func(k string, a *Allocation) {
  2513. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  2514. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2515. }
  2516. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  2517. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2518. }
  2519. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  2520. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2521. }
  2522. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  2523. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2524. }
  2525. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  2526. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2527. }
  2528. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  2529. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2530. }
  2531. if !util.IsApproximately(a.PVByteHours(), unit.PVByteHours()) {
  2532. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2533. }
  2534. if !util.IsApproximately(a.PVCost(), unit.PVCost()) {
  2535. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2536. }
  2537. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  2538. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2539. }
  2540. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  2541. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2542. }
  2543. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  2544. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2545. }
  2546. })
  2547. })
  2548. // Expect an error calling InsertRange with a range exceeding the receiver
  2549. err = thisASR.InsertRange(longASR)
  2550. if err == nil {
  2551. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  2552. }
  2553. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  2554. // precisely double when inserting a range that only has a duplicate of
  2555. // "yesterday", but no entry for "today"
  2556. err = thisASR.InsertRange(thatASR)
  2557. if err != nil {
  2558. t.Fatalf("unexpected error: %s", err)
  2559. }
  2560. yAS, err := thisASR.Get(0)
  2561. yAS.Each(func(k string, a *Allocation) {
  2562. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  2563. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2564. }
  2565. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  2566. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2567. }
  2568. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  2569. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2570. }
  2571. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  2572. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2573. }
  2574. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  2575. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2576. }
  2577. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  2578. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2579. }
  2580. if !util.IsApproximately(a.PVByteHours(), 2*unit.PVByteHours()) {
  2581. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2582. }
  2583. if !util.IsApproximately(a.PVCost(), 2*unit.PVCost()) {
  2584. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2585. }
  2586. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  2587. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2588. }
  2589. if !util.IsApproximately(a.LoadBalancerCost, 2*unit.LoadBalancerCost) {
  2590. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2591. }
  2592. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  2593. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2594. }
  2595. })
  2596. tAS, err := thisASR.Get(1)
  2597. tAS.Each(func(k string, a *Allocation) {
  2598. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  2599. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2600. }
  2601. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  2602. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2603. }
  2604. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  2605. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2606. }
  2607. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  2608. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2609. }
  2610. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  2611. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2612. }
  2613. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  2614. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2615. }
  2616. if !util.IsApproximately(a.PVByteHours(), unit.PVByteHours()) {
  2617. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2618. }
  2619. if !util.IsApproximately(a.PVCost(), unit.PVCost()) {
  2620. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2621. }
  2622. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  2623. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2624. }
  2625. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  2626. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2627. }
  2628. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  2629. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2630. }
  2631. })
  2632. }
  2633. // TODO niko/etl
  2634. // func TestAllocationSetRange_Length(t *testing.T) {}
  2635. // TODO niko/etl
  2636. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  2637. // TODO niko/etl
  2638. // func TestAllocationSetRange_Slice(t *testing.T) {}
  2639. // TODO niko/etl
  2640. // func TestAllocationSetRange_Window(t *testing.T) {}