2
0

allocation_test.go 127 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901
  1. package kubecost
  2. import (
  3. "fmt"
  4. "math"
  5. "reflect"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/davecgh/go-spew/spew"
  10. filter21 "github.com/opencost/opencost/pkg/filter21"
  11. "github.com/opencost/opencost/pkg/filter21/allocation"
  12. afilter "github.com/opencost/opencost/pkg/filter21/allocation"
  13. "github.com/opencost/opencost/pkg/filter21/ast"
  14. "github.com/opencost/opencost/pkg/filter21/ops"
  15. "github.com/opencost/opencost/pkg/log"
  16. "github.com/opencost/opencost/pkg/util"
  17. "github.com/opencost/opencost/pkg/util/json"
  18. "github.com/opencost/opencost/pkg/util/timeutil"
  19. )
  20. var filterParser = afilter.NewAllocationFilterParser()
  21. var matcherCompiler = NewAllocationMatchCompiler(nil)
  22. // useful for creating filters on the fly when testing. panics
  23. // on parse errors!
  24. func mustParseFilter(s string) filter21.Filter {
  25. filter, err := filterParser.Parse(s)
  26. if err != nil {
  27. panic(err)
  28. }
  29. return filter
  30. }
  31. // useful for creating filters on the fly when testing. panics
  32. // on parse or compile errors!
  33. func mustCompileFilter(s string) AllocationMatcher {
  34. filter := mustParseFilter(s)
  35. m, err := matcherCompiler.Compile(filter)
  36. if err != nil {
  37. panic(err)
  38. }
  39. return m
  40. }
  41. func TestAllocation_Add(t *testing.T) {
  42. var nilAlloc *Allocation
  43. zeroAlloc := &Allocation{}
  44. // nil + nil == nil
  45. nilNilSum, err := nilAlloc.Add(nilAlloc)
  46. if err != nil {
  47. t.Fatalf("Allocation.Add unexpected error: %s", err)
  48. }
  49. if nilNilSum != nil {
  50. t.Fatalf("Allocation.Add failed; exp: nil; act: %s", nilNilSum)
  51. }
  52. // nil + zero == zero
  53. nilZeroSum, err := nilAlloc.Add(zeroAlloc)
  54. if err != nil {
  55. t.Fatalf("Allocation.Add unexpected error: %s", err)
  56. }
  57. if nilZeroSum == nil || nilZeroSum.TotalCost() != 0.0 {
  58. t.Fatalf("Allocation.Add failed; exp: 0.0; act: %s", nilZeroSum)
  59. }
  60. cpuPrice := 0.02
  61. gpuPrice := 2.00
  62. ramPrice := 0.01
  63. pvPrice := 0.00005
  64. gib := 1024.0 * 1024.0 * 1024.0
  65. s1 := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  66. e1 := time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)
  67. hrs1 := e1.Sub(s1).Hours()
  68. a1 := &Allocation{
  69. Start: s1,
  70. End: e1,
  71. Properties: &AllocationProperties{},
  72. CPUCoreHours: 2.0 * hrs1,
  73. CPUCoreRequestAverage: 2.0,
  74. CPUCoreUsageAverage: 1.0,
  75. CPUCost: 2.0 * hrs1 * cpuPrice,
  76. CPUCostAdjustment: 3.0,
  77. GPUHours: 1.0 * hrs1,
  78. GPUCost: 1.0 * hrs1 * gpuPrice,
  79. GPUCostAdjustment: 2.0,
  80. PVs: PVAllocations{
  81. disk: {
  82. ByteHours: 100.0 * gib * hrs1,
  83. Cost: 100.0 * hrs1 * pvPrice,
  84. },
  85. },
  86. PVCostAdjustment: 4.0,
  87. RAMByteHours: 8.0 * gib * hrs1,
  88. RAMBytesRequestAverage: 8.0 * gib,
  89. RAMBytesUsageAverage: 4.0 * gib,
  90. RAMCost: 8.0 * hrs1 * ramPrice,
  91. RAMCostAdjustment: 1.0,
  92. SharedCost: 2.00,
  93. ExternalCost: 1.00,
  94. RawAllocationOnly: &RawAllocationOnlyData{},
  95. }
  96. a1b := a1.Clone()
  97. s2 := time.Date(2021, time.January, 1, 6, 0, 0, 0, time.UTC)
  98. e2 := time.Date(2021, time.January, 1, 24, 0, 0, 0, time.UTC)
  99. hrs2 := e1.Sub(s1).Hours()
  100. a2 := &Allocation{
  101. Start: s2,
  102. End: e2,
  103. Properties: &AllocationProperties{},
  104. CPUCoreHours: 1.0 * hrs2,
  105. CPUCoreRequestAverage: 1.0,
  106. CPUCoreUsageAverage: 1.0,
  107. CPUCost: 1.0 * hrs2 * cpuPrice,
  108. GPUHours: 0.0,
  109. GPUCost: 0.0,
  110. RAMByteHours: 8.0 * gib * hrs2,
  111. RAMBytesRequestAverage: 0.0,
  112. RAMBytesUsageAverage: 8.0 * gib,
  113. RAMCost: 8.0 * hrs2 * ramPrice,
  114. NetworkCost: 0.01,
  115. LoadBalancerCost: 0.05,
  116. SharedCost: 0.00,
  117. ExternalCost: 1.00,
  118. RawAllocationOnly: &RawAllocationOnlyData{},
  119. }
  120. a2b := a2.Clone()
  121. act, err := a1.Add(a2)
  122. if err != nil {
  123. t.Fatalf("Allocation.Add: unexpected error: %s", err)
  124. }
  125. // Neither Allocation should be mutated
  126. if !a1.Equal(a1b) {
  127. t.Fatalf("Allocation.Add: a1 illegally mutated")
  128. }
  129. if !a2.Equal(a2b) {
  130. t.Fatalf("Allocation.Add: a1 illegally mutated")
  131. }
  132. // Costs should be cumulative
  133. if !util.IsApproximately(a1.TotalCost()+a2.TotalCost(), act.TotalCost()) {
  134. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.TotalCost()+a2.TotalCost(), act.TotalCost())
  135. }
  136. if !util.IsApproximately(a1.CPUCost+a2.CPUCost, act.CPUCost) {
  137. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCost+a2.CPUCost, act.CPUCost)
  138. }
  139. if !util.IsApproximately(a1.CPUCostAdjustment+a2.CPUCostAdjustment, act.CPUCostAdjustment) {
  140. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCostAdjustment+a2.CPUCostAdjustment, act.CPUCostAdjustment)
  141. }
  142. if !util.IsApproximately(a1.GPUCost+a2.GPUCost, act.GPUCost) {
  143. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCost+a2.GPUCost, act.GPUCost)
  144. }
  145. if !util.IsApproximately(a1.GPUCostAdjustment+a2.GPUCostAdjustment, act.GPUCostAdjustment) {
  146. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCostAdjustment+a2.GPUCostAdjustment, act.GPUCostAdjustment)
  147. }
  148. if !util.IsApproximately(a1.RAMCost+a2.RAMCost, act.RAMCost) {
  149. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCost+a2.RAMCost, act.RAMCost)
  150. }
  151. if !util.IsApproximately(a1.RAMCostAdjustment+a2.RAMCostAdjustment, act.RAMCostAdjustment) {
  152. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCostAdjustment+a2.RAMCostAdjustment, act.RAMCostAdjustment)
  153. }
  154. if !util.IsApproximately(a1.PVCost()+a2.PVCost(), act.PVCost()) {
  155. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVCost()+a2.PVCost(), act.PVCost())
  156. }
  157. if !util.IsApproximately(a1.NetworkCost+a2.NetworkCost, act.NetworkCost) {
  158. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.NetworkCost+a2.NetworkCost, act.NetworkCost)
  159. }
  160. if !util.IsApproximately(a1.LoadBalancerCost+a2.LoadBalancerCost, act.LoadBalancerCost) {
  161. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.LoadBalancerCost+a2.LoadBalancerCost, act.LoadBalancerCost)
  162. }
  163. if !util.IsApproximately(a1.SharedCost+a2.SharedCost, act.SharedCost) {
  164. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.SharedCost+a2.SharedCost, act.SharedCost)
  165. }
  166. if !util.IsApproximately(a1.ExternalCost+a2.ExternalCost, act.ExternalCost) {
  167. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.ExternalCost+a2.ExternalCost, act.ExternalCost)
  168. }
  169. // ResourceHours should be cumulative
  170. if !util.IsApproximately(a1.CPUCoreHours+a2.CPUCoreHours, act.CPUCoreHours) {
  171. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCoreHours+a2.CPUCoreHours, act.CPUCoreHours)
  172. }
  173. if !util.IsApproximately(a1.RAMByteHours+a2.RAMByteHours, act.RAMByteHours) {
  174. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMByteHours+a2.RAMByteHours, act.RAMByteHours)
  175. }
  176. if !util.IsApproximately(a1.PVByteHours()+a2.PVByteHours(), act.PVByteHours()) {
  177. t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVByteHours()+a2.PVByteHours(), act.PVByteHours())
  178. }
  179. // Minutes should be the duration between min(starts) and max(ends)
  180. if !act.Start.Equal(a1.Start) || !act.End.Equal(a2.End) {
  181. t.Fatalf("Allocation.Add: expected %s; actual %s", NewWindow(&a1.Start, &a2.End), NewWindow(&act.Start, &act.End))
  182. }
  183. if act.Minutes() != 1440.0 {
  184. t.Fatalf("Allocation.Add: expected %f; actual %f", 1440.0, act.Minutes())
  185. }
  186. // Requests and Usage should be averaged correctly
  187. // CPU requests = (2.0*12.0 + 1.0*18.0)/(24.0) = 1.75
  188. // CPU usage = (1.0*12.0 + 1.0*18.0)/(24.0) = 1.25
  189. // RAM requests = (8.0*12.0 + 0.0*18.0)/(24.0) = 4.00
  190. // RAM usage = (4.0*12.0 + 8.0*18.0)/(24.0) = 8.00
  191. if !util.IsApproximately(1.75, act.CPUCoreRequestAverage) {
  192. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.75, act.CPUCoreRequestAverage)
  193. }
  194. if !util.IsApproximately(1.25, act.CPUCoreUsageAverage) {
  195. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.25, act.CPUCoreUsageAverage)
  196. }
  197. if !util.IsApproximately(4.00*gib, act.RAMBytesRequestAverage) {
  198. t.Fatalf("Allocation.Add: expected %f; actual %f", 4.00*gib, act.RAMBytesRequestAverage)
  199. }
  200. if !util.IsApproximately(8.00*gib, act.RAMBytesUsageAverage) {
  201. t.Fatalf("Allocation.Add: expected %f; actual %f", 8.00*gib, act.RAMBytesUsageAverage)
  202. }
  203. // Efficiency should be computed accurately from new request/usage
  204. // CPU efficiency = 1.25/1.75 = 0.7142857
  205. // RAM efficiency = 8.00/4.00 = 2.0000000
  206. // Total efficiency = (0.7142857*0.72 + 2.0*1.92)/(2.64) = 1.6493506
  207. if !util.IsApproximately(0.7142857, act.CPUEfficiency()) {
  208. t.Fatalf("Allocation.Add: expected %f; actual %f", 0.7142857, act.CPUEfficiency())
  209. }
  210. if !util.IsApproximately(2.0000000, act.RAMEfficiency()) {
  211. t.Fatalf("Allocation.Add: expected %f; actual %f", 2.0000000, act.RAMEfficiency())
  212. }
  213. if !util.IsApproximately(1.279690, act.TotalEfficiency()) {
  214. t.Fatalf("Allocation.Add: expected %f; actual %f", 1.279690, act.TotalEfficiency())
  215. }
  216. if act.RawAllocationOnly != nil {
  217. t.Errorf("Allocation.Add: Raw only data must be nil after an add")
  218. }
  219. }
  220. func TestAllocation_Share(t *testing.T) {
  221. cpuPrice := 0.02
  222. gpuPrice := 2.00
  223. ramPrice := 0.01
  224. pvPrice := 0.00005
  225. gib := 1024.0 * 1024.0 * 1024.0
  226. s1 := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  227. e1 := time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)
  228. hrs1 := e1.Sub(s1).Hours()
  229. a1 := &Allocation{
  230. Start: s1,
  231. End: e1,
  232. Properties: &AllocationProperties{},
  233. CPUCoreHours: 2.0 * hrs1,
  234. CPUCoreRequestAverage: 2.0,
  235. CPUCoreUsageAverage: 1.0,
  236. CPUCost: 2.0 * hrs1 * cpuPrice,
  237. CPUCostAdjustment: 3.0,
  238. GPUHours: 1.0 * hrs1,
  239. GPUCost: 1.0 * hrs1 * gpuPrice,
  240. GPUCostAdjustment: 2.0,
  241. PVs: PVAllocations{
  242. disk: {
  243. ByteHours: 100.0 * gib * hrs1,
  244. Cost: 100.0 * hrs1 * pvPrice,
  245. },
  246. },
  247. PVCostAdjustment: 4.0,
  248. RAMByteHours: 8.0 * gib * hrs1,
  249. RAMBytesRequestAverage: 8.0 * gib,
  250. RAMBytesUsageAverage: 4.0 * gib,
  251. RAMCost: 8.0 * hrs1 * ramPrice,
  252. RAMCostAdjustment: 1.0,
  253. SharedCost: 2.00,
  254. ExternalCost: 1.00,
  255. }
  256. a1b := a1.Clone()
  257. s2 := time.Date(2021, time.January, 1, 6, 0, 0, 0, time.UTC)
  258. e2 := time.Date(2021, time.January, 1, 24, 0, 0, 0, time.UTC)
  259. hrs2 := e1.Sub(s1).Hours()
  260. a2 := &Allocation{
  261. Start: s2,
  262. End: e2,
  263. Properties: &AllocationProperties{},
  264. CPUCoreHours: 1.0 * hrs2,
  265. CPUCoreRequestAverage: 1.0,
  266. CPUCoreUsageAverage: 1.0,
  267. CPUCost: 1.0 * hrs2 * cpuPrice,
  268. GPUHours: 0.0,
  269. GPUCost: 0.0,
  270. RAMByteHours: 8.0 * gib * hrs2,
  271. RAMBytesRequestAverage: 0.0,
  272. RAMBytesUsageAverage: 8.0 * gib,
  273. RAMCost: 8.0 * hrs2 * ramPrice,
  274. NetworkCost: 0.01,
  275. LoadBalancerCost: 0.05,
  276. SharedCost: 0.00,
  277. ExternalCost: 1.00,
  278. }
  279. a2b := a2.Clone()
  280. act, err := a1.Share(a2)
  281. if err != nil {
  282. t.Fatalf("Allocation.Share: unexpected error: %s", err)
  283. }
  284. // Neither Allocation should be mutated
  285. if !a1.Equal(a1b) {
  286. t.Fatalf("Allocation.Share: a1 illegally mutated")
  287. }
  288. if !a2.Equal(a2b) {
  289. t.Fatalf("Allocation.Share: a1 illegally mutated")
  290. }
  291. // SharedCost and TotalCost should reflect increase by a2.TotalCost
  292. if !util.IsApproximately(a1.TotalCost()+a2.TotalCost(), act.TotalCost()) {
  293. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.TotalCost()+a2.TotalCost(), act.TotalCost())
  294. }
  295. if !util.IsApproximately(a1.SharedCost+a2.TotalCost(), act.SharedCost) {
  296. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.SharedCost+a2.TotalCost(), act.SharedCost)
  297. }
  298. // Costs should match before (expect TotalCost and SharedCost)
  299. if !util.IsApproximately(a1.CPUTotalCost(), act.CPUTotalCost()) {
  300. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUTotalCost(), act.CPUTotalCost())
  301. }
  302. if !util.IsApproximately(a1.GPUTotalCost(), act.GPUTotalCost()) {
  303. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.GPUTotalCost(), act.GPUTotalCost())
  304. }
  305. if !util.IsApproximately(a1.RAMTotalCost(), act.RAMTotalCost()) {
  306. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMTotalCost(), act.RAMTotalCost())
  307. }
  308. if !util.IsApproximately(a1.PVTotalCost(), act.PVTotalCost()) {
  309. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.PVTotalCost(), act.PVTotalCost())
  310. }
  311. if !util.IsApproximately(a1.NetworkCost, act.NetworkCost) {
  312. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.NetworkCost, act.NetworkCost)
  313. }
  314. if !util.IsApproximately(a1.LoadBalancerCost, act.LoadBalancerCost) {
  315. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.LoadBalancerCost, act.LoadBalancerCost)
  316. }
  317. if !util.IsApproximately(a1.ExternalCost, act.ExternalCost) {
  318. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.ExternalCost, act.ExternalCost)
  319. }
  320. // ResourceHours should match before
  321. if !util.IsApproximately(a1.CPUCoreHours, act.CPUCoreHours) {
  322. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreHours, act.CPUCoreHours)
  323. }
  324. if !util.IsApproximately(a1.RAMByteHours, act.RAMByteHours) {
  325. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMByteHours, act.RAMByteHours)
  326. }
  327. if !util.IsApproximately(a1.PVByteHours(), act.PVByteHours()) {
  328. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.PVByteHours(), act.PVByteHours())
  329. }
  330. // Minutes should match before
  331. if !act.Start.Equal(a1.Start) || !act.End.Equal(a1.End) {
  332. t.Fatalf("Allocation.Share: expected %s; actual %s", NewWindow(&a1.Start, &a1.End), NewWindow(&act.Start, &act.End))
  333. }
  334. if act.Minutes() != a1.Minutes() {
  335. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.Minutes(), act.Minutes())
  336. }
  337. // Requests and Usage should match before
  338. if !util.IsApproximately(a1.CPUCoreRequestAverage, act.CPUCoreRequestAverage) {
  339. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreRequestAverage, act.CPUCoreRequestAverage)
  340. }
  341. if !util.IsApproximately(a1.CPUCoreUsageAverage, act.CPUCoreUsageAverage) {
  342. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUCoreUsageAverage, act.CPUCoreUsageAverage)
  343. }
  344. if !util.IsApproximately(a1.RAMBytesRequestAverage, act.RAMBytesRequestAverage) {
  345. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMBytesRequestAverage, act.RAMBytesRequestAverage)
  346. }
  347. if !util.IsApproximately(a1.RAMBytesUsageAverage, act.RAMBytesUsageAverage) {
  348. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMBytesUsageAverage, act.RAMBytesUsageAverage)
  349. }
  350. // Efficiency should match before
  351. if !util.IsApproximately(a1.CPUEfficiency(), act.CPUEfficiency()) {
  352. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.CPUEfficiency(), act.CPUEfficiency())
  353. }
  354. if !util.IsApproximately(a1.RAMEfficiency(), act.RAMEfficiency()) {
  355. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.RAMEfficiency(), act.RAMEfficiency())
  356. }
  357. if !util.IsApproximately(a1.TotalEfficiency(), act.TotalEfficiency()) {
  358. t.Fatalf("Allocation.Share: expected %f; actual %f", a1.TotalEfficiency(), act.TotalEfficiency())
  359. }
  360. }
  361. func TestAllocation_AddDifferentController(t *testing.T) {
  362. a1 := &Allocation{
  363. Properties: &AllocationProperties{
  364. Container: "container",
  365. Pod: "pod",
  366. Namespace: "ns",
  367. Cluster: "cluster",
  368. Controller: "controller 1",
  369. },
  370. }
  371. a2 := a1.Clone()
  372. a2.Properties.Controller = "controller 2"
  373. result, err := a1.Add(a2)
  374. if err != nil {
  375. t.Fatalf("Allocation.Add: unexpected error: %s", err)
  376. }
  377. if result.Properties.Controller == "" {
  378. t.Errorf("Adding allocations whose properties only differ in controller name should not result in an empty string controller name.")
  379. }
  380. }
  381. func TestAllocationSet_generateKey(t *testing.T) {
  382. var alloc *Allocation
  383. var key string
  384. props := []string{
  385. AllocationClusterProp,
  386. }
  387. key = alloc.generateKey(props, nil)
  388. if key != "" {
  389. t.Fatalf("generateKey: expected \"\"; actual \"%s\"", key)
  390. }
  391. alloc = &Allocation{}
  392. alloc.Properties = &AllocationProperties{
  393. Cluster: "cluster1",
  394. Labels: map[string]string{
  395. "app": "app1",
  396. "env": "env1",
  397. },
  398. }
  399. key = alloc.generateKey(props, nil)
  400. if key != "cluster1" {
  401. t.Fatalf("generateKey: expected \"cluster1\"; actual \"%s\"", key)
  402. }
  403. props = []string{
  404. AllocationClusterProp,
  405. AllocationNamespaceProp,
  406. "label:app",
  407. }
  408. key = alloc.generateKey(props, nil)
  409. if key != "cluster1//app1" {
  410. t.Fatalf("generateKey: expected \"cluster1//app1\"; actual \"%s\"", key)
  411. }
  412. alloc.Properties = &AllocationProperties{
  413. Cluster: "cluster1",
  414. Namespace: "namespace1",
  415. Labels: map[string]string{
  416. "app": "app1",
  417. "env": "env1",
  418. },
  419. }
  420. key = alloc.generateKey(props, nil)
  421. if key != "cluster1/namespace1/app1" {
  422. t.Fatalf("generateKey: expected \"cluster1/namespace1/app1\"; actual \"%s\"", key)
  423. }
  424. props = []string{
  425. AllocationDepartmentProp,
  426. AllocationEnvironmentProp,
  427. AllocationOwnerProp,
  428. AllocationProductProp,
  429. AllocationTeamProp,
  430. }
  431. labelConfig := NewLabelConfig()
  432. alloc.Properties = &AllocationProperties{
  433. Cluster: "cluster1",
  434. Namespace: "namespace1",
  435. Labels: map[string]string{
  436. labelConfig.DepartmentLabel: "dept1",
  437. labelConfig.EnvironmentLabel: "envt1",
  438. labelConfig.OwnerLabel: "ownr1",
  439. labelConfig.ProductLabel: "prod1",
  440. labelConfig.TeamLabel: "team1",
  441. },
  442. }
  443. key = alloc.generateKey(props, nil)
  444. if key != "dept1/envt1/ownr1/prod1/team1" {
  445. t.Fatalf("generateKey: expected \"dept1/envt1/ownr1/prod1/team1\"; actual \"%s\"", key)
  446. }
  447. // Ensure that labels with illegal Prometheus characters in LabelConfig
  448. // still match their sanitized values. Ensure also that multiple comma-
  449. // separated values work.
  450. labelConfig.DepartmentLabel = "prom/illegal-department"
  451. labelConfig.EnvironmentLabel = " env "
  452. labelConfig.OwnerLabel = "$owner%"
  453. labelConfig.ProductLabel = "app.kubernetes.io/app"
  454. labelConfig.TeamLabel = "team,app.kubernetes.io/team,k8s-team"
  455. alloc.Properties = &AllocationProperties{
  456. Cluster: "cluster1",
  457. Namespace: "namespace1",
  458. Labels: map[string]string{
  459. "prom_illegal_department": "dept1",
  460. "env": "envt1",
  461. "_owner_": "ownr1",
  462. "team": "team1",
  463. "app_kubernetes_io_app": "prod1",
  464. "app_kubernetes_io_team": "team2",
  465. },
  466. }
  467. props = []string{
  468. AllocationDepartmentProp,
  469. AllocationEnvironmentProp,
  470. AllocationOwnerProp,
  471. AllocationProductProp,
  472. AllocationTeamProp,
  473. }
  474. key = alloc.generateKey(props, labelConfig)
  475. if key != "dept1/envt1/ownr1/prod1/team1/team2/__unallocated__" {
  476. t.Fatalf("generateKey: expected \"dept1/envt1/ownr1/prod1/team1/team2/__unallocated__\"; actual \"%s\"", key)
  477. }
  478. }
  479. func TestNewAllocationSet(t *testing.T) {
  480. // TODO niko/etl
  481. }
  482. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  483. if err != nil {
  484. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  485. }
  486. if as.Length() != length {
  487. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  488. }
  489. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  490. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  491. }
  492. }
  493. func assertParcResults(t *testing.T, as *AllocationSet, msg string, exps map[string]ProportionalAssetResourceCosts) {
  494. for allocKey, a := range as.Allocations {
  495. for key, actualParc := range a.ProportionalAssetResourceCosts {
  496. expectedParcs := exps[allocKey]
  497. // round to prevent floating point issues from failing tests at ultra high precision
  498. actualParc.NodeResourceCostPercentage = roundFloat(actualParc.NodeResourceCostPercentage)
  499. actualParc.CPUPercentage = roundFloat(actualParc.CPUPercentage)
  500. actualParc.RAMPercentage = roundFloat(actualParc.RAMPercentage)
  501. actualParc.GPUPercentage = roundFloat(actualParc.GPUPercentage)
  502. actualParc.PVPercentage = roundFloat(actualParc.PVPercentage)
  503. if !reflect.DeepEqual(expectedParcs[key], actualParc) {
  504. t.Fatalf("actual PARC %+v did not match expected PARC %+v", actualParc, expectedParcs[key])
  505. }
  506. }
  507. }
  508. }
  509. func roundFloat(val float64) float64 {
  510. ratio := math.Pow(10, float64(5))
  511. return math.Round(val*ratio) / ratio
  512. }
  513. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  514. for _, a := range as.Allocations {
  515. if exp, ok := exps[a.Name]; ok {
  516. if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
  517. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %f, actual %f", msg, exp, a.TotalCost())
  518. }
  519. } else {
  520. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  521. }
  522. }
  523. }
  524. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  525. for _, a := range as.Allocations {
  526. if !a.Start.Equal(expStart) {
  527. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  528. }
  529. if !a.End.Equal(expEnd) {
  530. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  531. }
  532. if a.Minutes() != expMinutes {
  533. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes())
  534. }
  535. }
  536. }
  537. func printAllocationSet(msg string, as *AllocationSet) {
  538. fmt.Printf("--- %s ---\n", msg)
  539. for _, a := range as.Allocations {
  540. fmt.Printf(" > %s\n", a)
  541. }
  542. }
  543. func TestAllocationSet_AggregateBy(t *testing.T) {
  544. // Test AggregateBy against the following workload topology, which is
  545. // generated by GenerateMockAllocationSet:
  546. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net | LB |
  547. // +----------------------------------------+------+------+------+------+------+------+------+
  548. // cluster1:
  549. // idle: 20.00 5.00 15.00 0.00 0.00 0.00 0.00
  550. // namespace1:
  551. // pod1:
  552. // container1: [app1, env1] 16.00 1.00 11.00 1.00 1.00 1.00 1.00
  553. // pod-abc: (deployment1)
  554. // container2: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  555. // pod-def: (deployment1)
  556. // container3: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  557. // namespace2:
  558. // pod-ghi: (deployment2)
  559. // container4: [app2, env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  560. // container5: [app2, env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  561. // pod-jkl: (daemonset1)
  562. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  563. // +-----------------------------------------+------+------+------+------+------+------+------+
  564. // cluster1 subtotal 66.00 11.00 31.00 6.00 6.00 6.00 6.00
  565. // +-----------------------------------------+------+------+------+------+------+------+------+
  566. // cluster2:
  567. // idle: 10.00 5.00 5.00 0.00 0.00 0.00 0.00
  568. // namespace2:
  569. // pod-mno: (deployment2)
  570. // container4: [app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  571. // container5: [app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  572. // pod-pqr: (daemonset1)
  573. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  574. // namespace3:
  575. // pod-stu: (deployment3)
  576. // container7: an[team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  577. // pod-vwx: (statefulset1)
  578. // container8: an[team2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  579. // container9: an[team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  580. // +----------------------------------------+------+------+------+------+------+------+------+
  581. // cluster2 subtotal 46.00 11.00 11.00 6.00 6.00 6.00 6.00
  582. // +----------------------------------------+------+------+------+------+------+------+------+
  583. // total 112.00 22.00 42.00 12.00 12.00 12.00 12.00
  584. // +----------------------------------------+------+------+------+------+------+------+------+
  585. // Scenarios to test:
  586. // 1 Single-aggregation
  587. // 1a AggregationProperties=(Cluster)
  588. // 1b AggregationProperties=(Namespace)
  589. // 1c AggregationProperties=(Pod)
  590. // 1d AggregationProperties=(Container)
  591. // 1e AggregationProperties=(ControllerKind)
  592. // 1f AggregationProperties=(Controller)
  593. // 1g AggregationProperties=(Service)
  594. // 1h AggregationProperties=(Label:app)
  595. // 2 Multi-aggregation
  596. // 2a AggregationProperties=(Cluster, Namespace)
  597. // 2b AggregationProperties=(Namespace, Label:app)
  598. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  599. // 2d AggregationProperties=(Label:app, Label:environment)
  600. // 3 Share idle
  601. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  602. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  603. // 4 Share resources
  604. // 4a Share namespace ShareEven
  605. // 4b Share cluster ShareWeighted
  606. // 4c Share label ShareEven
  607. // 4d Share overhead ShareWeighted
  608. // 5 Filters
  609. // 5a Filter by cluster with separate idle
  610. // 5b Filter by cluster with shared idle
  611. // TODO niko/idle more filter tests
  612. // 6 Combinations and options
  613. // 6a SplitIdle
  614. // 6b Share idle with filters
  615. // 6c Share resources with filters
  616. // 6d Share idle and share resources
  617. // 6e IdleByNode
  618. // 7 Edge cases and errors
  619. // 7a Empty AggregationProperties
  620. // 7b Filter all
  621. // 7c Share all
  622. // 7d Share and filter the same allocations
  623. // Definitions and set-up:
  624. var as *AllocationSet
  625. var err error
  626. endYesterday := time.Now().UTC().Truncate(day)
  627. startYesterday := endYesterday.Add(-day)
  628. numClusters := 2
  629. numNamespaces := 3
  630. numPods := 9
  631. numContainers := 9
  632. numControllerKinds := 3
  633. numControllers := 5
  634. numServices := 1
  635. numLabelApps := 2
  636. // By default, idle is reported as a single, merged allocation
  637. numIdle := 1
  638. // There will only ever be one __unallocated__
  639. numUnallocated := 1
  640. // There are two clusters, so each gets an idle entry when they are split
  641. numSplitIdleCluster := 2
  642. // There are two clusters, so each gets an idle entry when they are split
  643. numSplitIdleNode := 4
  644. activeTotalCost := 82.0
  645. idleTotalCost := 30.0
  646. sharedOverheadHourlyCost := 7.0
  647. // Match filters
  648. // This is ugly, but required because cannot import filterutil due to import cycle
  649. namespaceEquals := func(ns string) *ast.EqualOp {
  650. return &ast.EqualOp{
  651. Left: ast.Identifier{
  652. Field: ast.NewField(allocation.FieldNamespace),
  653. Key: "",
  654. },
  655. Right: ns,
  656. }
  657. }
  658. // This is ugly, but required because cannot import filterutil due to import cycle
  659. labelEquals := func(name, value string) *ast.EqualOp {
  660. return &ast.EqualOp{
  661. Left: ast.Identifier{
  662. Field: ast.NewField(allocation.FieldLabel),
  663. Key: name,
  664. },
  665. Right: value,
  666. }
  667. }
  668. end := time.Now().UTC().Truncate(day)
  669. start := end.Add(-day)
  670. // Tests:
  671. cases := map[string]struct {
  672. start time.Time
  673. aggBy []string
  674. aggOpts *AllocationAggregationOptions
  675. numResults int
  676. totalCost float64
  677. results map[string]float64
  678. windowStart time.Time
  679. windowEnd time.Time
  680. expMinutes float64
  681. expectedParcResults map[string]ProportionalAssetResourceCosts
  682. }{
  683. // 1 Single-aggregation
  684. // 1a AggregationProperties=(Cluster)
  685. "1a": {
  686. start: start,
  687. aggBy: []string{AllocationClusterProp},
  688. aggOpts: &AllocationAggregationOptions{
  689. IncludeProportionalAssetResourceCosts: true,
  690. },
  691. numResults: numClusters + numIdle,
  692. totalCost: activeTotalCost + idleTotalCost,
  693. results: map[string]float64{
  694. "cluster1": 46.00,
  695. "cluster2": 36.00,
  696. IdleSuffix: 30.00,
  697. },
  698. windowStart: startYesterday,
  699. windowEnd: endYesterday,
  700. expMinutes: 1440.0,
  701. expectedParcResults: map[string]ProportionalAssetResourceCosts{
  702. "cluster1": {
  703. "cluster1": ProportionalAssetResourceCost{
  704. Cluster: "cluster1",
  705. Name: "",
  706. Type: "",
  707. ProviderID: "",
  708. GPUProportionalCost: 6.0,
  709. CPUProportionalCost: 6.0,
  710. RAMProportionalCost: 16.0,
  711. PVProportionalCost: 6.0,
  712. },
  713. },
  714. "cluster2": {
  715. "cluster2": ProportionalAssetResourceCost{
  716. Cluster: "cluster2",
  717. Name: "",
  718. Type: "",
  719. ProviderID: "",
  720. GPUProportionalCost: 6,
  721. CPUProportionalCost: 6,
  722. RAMProportionalCost: 6,
  723. PVProportionalCost: 6,
  724. },
  725. },
  726. },
  727. },
  728. // 1b AggregationProperties=(Namespace)
  729. "1b": {
  730. start: start,
  731. aggBy: []string{AllocationNamespaceProp},
  732. aggOpts: nil,
  733. numResults: numNamespaces + numIdle,
  734. totalCost: activeTotalCost + idleTotalCost,
  735. results: map[string]float64{
  736. "namespace1": 28.00,
  737. "namespace2": 36.00,
  738. "namespace3": 18.00,
  739. IdleSuffix: 30.00,
  740. },
  741. windowStart: startYesterday,
  742. windowEnd: endYesterday,
  743. expMinutes: 1440.0,
  744. },
  745. // 1c AggregationProperties=(Pod)
  746. "1c": {
  747. start: start,
  748. aggBy: []string{AllocationPodProp},
  749. aggOpts: &AllocationAggregationOptions{
  750. IncludeProportionalAssetResourceCosts: true,
  751. },
  752. numResults: numPods + numIdle,
  753. totalCost: activeTotalCost + idleTotalCost,
  754. results: map[string]float64{
  755. "pod-jkl": 6.00,
  756. "pod-stu": 6.00,
  757. "pod-abc": 6.00,
  758. "pod-pqr": 6.00,
  759. "pod-def": 6.00,
  760. "pod-vwx": 12.00,
  761. "pod1": 16.00,
  762. "pod-mno": 12.00,
  763. "pod-ghi": 12.00,
  764. IdleSuffix: 30.00,
  765. },
  766. windowStart: startYesterday,
  767. windowEnd: endYesterday,
  768. expMinutes: 1440.0,
  769. expectedParcResults: map[string]ProportionalAssetResourceCosts{
  770. "pod1": {
  771. "cluster1": ProportionalAssetResourceCost{
  772. Cluster: "cluster1",
  773. Name: "",
  774. Type: "",
  775. ProviderID: "",
  776. GPUProportionalCost: 1.0,
  777. CPUProportionalCost: 1.0,
  778. RAMProportionalCost: 11.0,
  779. PVProportionalCost: 1.0,
  780. },
  781. },
  782. "pod-abc": {
  783. "cluster1": ProportionalAssetResourceCost{
  784. Cluster: "cluster1",
  785. Name: "",
  786. Type: "",
  787. ProviderID: "",
  788. GPUProportionalCost: 1.0,
  789. CPUProportionalCost: 1.0,
  790. RAMProportionalCost: 1.0,
  791. PVProportionalCost: 1.0,
  792. },
  793. },
  794. "pod-def": {
  795. "cluster1": ProportionalAssetResourceCost{
  796. Cluster: "cluster1",
  797. Name: "",
  798. Type: "",
  799. ProviderID: "",
  800. GPUProportionalCost: 1.0,
  801. CPUProportionalCost: 1.0,
  802. RAMProportionalCost: 1.0,
  803. PVProportionalCost: 1.0,
  804. },
  805. },
  806. "pod-ghi": {
  807. "cluster1": ProportionalAssetResourceCost{
  808. Cluster: "cluster1",
  809. Name: "",
  810. Type: "",
  811. ProviderID: "",
  812. GPUProportionalCost: 2.0,
  813. CPUProportionalCost: 2.0,
  814. RAMProportionalCost: 2.0,
  815. PVProportionalCost: 2.0,
  816. },
  817. },
  818. "pod-jkl": {
  819. "cluster1": ProportionalAssetResourceCost{
  820. Cluster: "cluster1",
  821. Name: "",
  822. Type: "",
  823. ProviderID: "",
  824. GPUProportionalCost: 1.0,
  825. CPUProportionalCost: 1.0,
  826. RAMProportionalCost: 1.0,
  827. PVProportionalCost: 1.0,
  828. },
  829. },
  830. "pod-mno": {
  831. "cluster2": ProportionalAssetResourceCost{
  832. Cluster: "cluster2",
  833. Name: "",
  834. Type: "",
  835. ProviderID: "",
  836. GPUProportionalCost: 2.0,
  837. CPUProportionalCost: 2.0,
  838. RAMProportionalCost: 2.0,
  839. PVProportionalCost: 2.0,
  840. },
  841. },
  842. "pod-pqr": {
  843. "cluster2": ProportionalAssetResourceCost{
  844. Cluster: "cluster2",
  845. Name: "",
  846. Type: "",
  847. ProviderID: "",
  848. GPUProportionalCost: 1.0,
  849. CPUProportionalCost: 1.0,
  850. RAMProportionalCost: 1.0,
  851. PVProportionalCost: 1.0,
  852. },
  853. },
  854. "pod-stu": {
  855. "cluster2": ProportionalAssetResourceCost{
  856. Cluster: "cluster2",
  857. Name: "",
  858. Type: "",
  859. ProviderID: "",
  860. GPUProportionalCost: 1.0,
  861. CPUProportionalCost: 1.0,
  862. RAMProportionalCost: 1.0,
  863. PVProportionalCost: 1.0,
  864. },
  865. },
  866. "pod-vwx": {
  867. "cluster2": ProportionalAssetResourceCost{
  868. Cluster: "cluster2",
  869. Name: "",
  870. Type: "",
  871. ProviderID: "",
  872. GPUProportionalCost: 2.0,
  873. CPUProportionalCost: 2.0,
  874. RAMProportionalCost: 2.0,
  875. PVProportionalCost: 2.0,
  876. },
  877. },
  878. },
  879. },
  880. // 1d AggregationProperties=(Container)
  881. "1d": {
  882. start: start,
  883. aggBy: []string{AllocationContainerProp},
  884. aggOpts: nil,
  885. numResults: numContainers + numIdle,
  886. totalCost: activeTotalCost + idleTotalCost,
  887. results: map[string]float64{
  888. "container2": 6.00,
  889. "container9": 6.00,
  890. "container6": 12.00,
  891. "container3": 6.00,
  892. "container4": 12.00,
  893. "container7": 6.00,
  894. "container8": 6.00,
  895. "container5": 12.00,
  896. "container1": 16.00,
  897. IdleSuffix: 30.00,
  898. },
  899. windowStart: startYesterday,
  900. windowEnd: endYesterday,
  901. expMinutes: 1440.0,
  902. },
  903. // 1e AggregationProperties=(ControllerKind)
  904. "1e": {
  905. start: start,
  906. aggBy: []string{AllocationControllerKindProp},
  907. aggOpts: nil,
  908. numResults: numControllerKinds + numIdle + numUnallocated,
  909. totalCost: activeTotalCost + idleTotalCost,
  910. results: map[string]float64{
  911. "daemonset": 12.00,
  912. "deployment": 42.00,
  913. "statefulset": 12.00,
  914. IdleSuffix: 30.00,
  915. UnallocatedSuffix: 16.00,
  916. },
  917. windowStart: startYesterday,
  918. windowEnd: endYesterday,
  919. expMinutes: 1440.0,
  920. },
  921. // 1f AggregationProperties=(Controller)
  922. "1f": {
  923. start: start,
  924. aggBy: []string{AllocationControllerProp},
  925. aggOpts: nil,
  926. numResults: numControllers + numIdle + numUnallocated,
  927. totalCost: activeTotalCost + idleTotalCost,
  928. results: map[string]float64{
  929. "deployment:deployment2": 24.00,
  930. "daemonset:daemonset1": 12.00,
  931. "deployment:deployment3": 6.00,
  932. "statefulset:statefulset1": 12.00,
  933. "deployment:deployment1": 12.00,
  934. IdleSuffix: 30.00,
  935. UnallocatedSuffix: 16.00,
  936. },
  937. windowStart: startYesterday,
  938. windowEnd: endYesterday,
  939. expMinutes: 1440.0,
  940. },
  941. // 1g AggregationProperties=(Service)
  942. "1g": {
  943. start: start,
  944. aggBy: []string{AllocationServiceProp},
  945. aggOpts: nil,
  946. numResults: numServices + numIdle + numUnallocated,
  947. totalCost: activeTotalCost + idleTotalCost,
  948. results: map[string]float64{
  949. "service1": 12.00,
  950. IdleSuffix: 30.00,
  951. UnallocatedSuffix: 70.00,
  952. },
  953. windowStart: startYesterday,
  954. windowEnd: endYesterday,
  955. expMinutes: 1440.0,
  956. },
  957. // 1h AggregationProperties=(Label:app)
  958. "1h": {
  959. start: start,
  960. aggBy: []string{"label:app"},
  961. aggOpts: nil,
  962. numResults: numLabelApps + numIdle + numUnallocated,
  963. totalCost: activeTotalCost + idleTotalCost,
  964. results: map[string]float64{
  965. "app1": 16.00,
  966. "app2": 24.00,
  967. IdleSuffix: 30.00,
  968. UnallocatedSuffix: 42.00,
  969. },
  970. windowStart: startYesterday,
  971. windowEnd: endYesterday,
  972. expMinutes: 1440.0,
  973. },
  974. // 1i AggregationProperties=(deployment)
  975. "1i": {
  976. start: start,
  977. aggBy: []string{AllocationDeploymentProp},
  978. aggOpts: nil,
  979. numResults: 3 + numIdle + numUnallocated,
  980. totalCost: activeTotalCost + idleTotalCost,
  981. results: map[string]float64{
  982. "deployment1": 12.00,
  983. "deployment2": 24.00,
  984. "deployment3": 6.00,
  985. IdleSuffix: 30.00,
  986. UnallocatedSuffix: 40.00,
  987. },
  988. windowStart: startYesterday,
  989. windowEnd: endYesterday,
  990. expMinutes: 1440.0,
  991. },
  992. // 1j AggregationProperties=(Annotation:team)
  993. "1j": {
  994. start: start,
  995. aggBy: []string{"annotation:team"},
  996. aggOpts: nil,
  997. numResults: 2 + numIdle + numUnallocated,
  998. totalCost: activeTotalCost + idleTotalCost,
  999. results: map[string]float64{
  1000. "team1": 12.00,
  1001. "team2": 6.00,
  1002. IdleSuffix: 30.00,
  1003. UnallocatedSuffix: 64.00,
  1004. },
  1005. windowStart: startYesterday,
  1006. windowEnd: endYesterday,
  1007. expMinutes: 1440.0,
  1008. },
  1009. // 1k AggregationProperties=(daemonSet)
  1010. "1k": {
  1011. start: start,
  1012. aggBy: []string{AllocationDaemonSetProp},
  1013. aggOpts: nil,
  1014. numResults: 1 + numIdle + numUnallocated,
  1015. totalCost: activeTotalCost + idleTotalCost,
  1016. results: map[string]float64{
  1017. "daemonset1": 12.00,
  1018. IdleSuffix: 30.00,
  1019. UnallocatedSuffix: 70.00,
  1020. },
  1021. windowStart: startYesterday,
  1022. windowEnd: endYesterday,
  1023. expMinutes: 1440.0,
  1024. },
  1025. // 1l AggregationProperties=(statefulSet)
  1026. "1l": {
  1027. start: start,
  1028. aggBy: []string{AllocationStatefulSetProp},
  1029. aggOpts: nil,
  1030. numResults: 1 + numIdle + numUnallocated,
  1031. totalCost: activeTotalCost + idleTotalCost,
  1032. results: map[string]float64{
  1033. "statefulset1": 12.00,
  1034. IdleSuffix: 30.00,
  1035. UnallocatedSuffix: 70.00,
  1036. },
  1037. windowStart: startYesterday,
  1038. windowEnd: endYesterday,
  1039. expMinutes: 1440.0,
  1040. },
  1041. // 2 Multi-aggregation
  1042. // 2a AggregationProperties=(Cluster, Namespace)
  1043. // 2b AggregationProperties=(Namespace, Label:app)
  1044. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  1045. // 2d AggregationProperties=(Label:app, Label:environment)
  1046. "2d": {
  1047. start: start,
  1048. aggBy: []string{"label:app", "label:env"},
  1049. aggOpts: nil,
  1050. numResults: 3 + numIdle + numUnallocated,
  1051. totalCost: activeTotalCost + idleTotalCost,
  1052. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  1053. results: map[string]float64{
  1054. "app1/env1": 16.00,
  1055. "app2/env2": 12.00,
  1056. "app2/" + UnallocatedSuffix: 12.00,
  1057. IdleSuffix: 30.00,
  1058. UnallocatedSuffix + "/" + UnallocatedSuffix: 42.00,
  1059. },
  1060. windowStart: startYesterday,
  1061. windowEnd: endYesterday,
  1062. expMinutes: 1440.0,
  1063. },
  1064. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  1065. "2e": {
  1066. start: start,
  1067. aggBy: []string{AllocationClusterProp, "label:app", "label:env"},
  1068. aggOpts: nil,
  1069. numResults: 6,
  1070. totalCost: activeTotalCost + idleTotalCost,
  1071. results: map[string]float64{
  1072. "cluster1/app2/env2": 12.00,
  1073. "__idle__": 30.00,
  1074. "cluster1/app1/env1": 16.00,
  1075. "cluster1/" + UnallocatedSuffix + "/" + UnallocatedSuffix: 18.00,
  1076. "cluster2/app2/" + UnallocatedSuffix: 12.00,
  1077. "cluster2/" + UnallocatedSuffix + "/" + UnallocatedSuffix: 24.00,
  1078. },
  1079. windowStart: startYesterday,
  1080. windowEnd: endYesterday,
  1081. expMinutes: 1440.0,
  1082. },
  1083. // 2f AggregationProperties=(annotation:team, pod)
  1084. "2f": {
  1085. start: start,
  1086. aggBy: []string{AllocationPodProp, "annotation:team"},
  1087. aggOpts: nil,
  1088. numResults: 11,
  1089. totalCost: activeTotalCost + idleTotalCost,
  1090. results: map[string]float64{
  1091. "pod-jkl/" + UnallocatedSuffix: 6.00,
  1092. "pod-stu/team1": 6.00,
  1093. "pod-abc/" + UnallocatedSuffix: 6.00,
  1094. "pod-pqr/" + UnallocatedSuffix: 6.00,
  1095. "pod-def/" + UnallocatedSuffix: 6.00,
  1096. "pod-vwx/team1": 6.00,
  1097. "pod-vwx/team2": 6.00,
  1098. "pod1/" + UnallocatedSuffix: 16.00,
  1099. "pod-mno/" + UnallocatedSuffix: 12.00,
  1100. "pod-ghi/" + UnallocatedSuffix: 12.00,
  1101. IdleSuffix: 30.00,
  1102. },
  1103. windowStart: startYesterday,
  1104. windowEnd: endYesterday,
  1105. expMinutes: 1440.0,
  1106. },
  1107. // 3 Share idle
  1108. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  1109. // namespace1: 42.6875 = 28.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  1110. // 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)
  1111. // namespace3: 23.0000 = 18.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  1112. "3a": {
  1113. start: start,
  1114. aggBy: []string{AllocationNamespaceProp},
  1115. aggOpts: &AllocationAggregationOptions{ShareIdle: ShareWeighted},
  1116. numResults: numNamespaces,
  1117. totalCost: activeTotalCost + idleTotalCost,
  1118. results: map[string]float64{
  1119. "namespace1": 42.69,
  1120. "namespace2": 46.31,
  1121. "namespace3": 23.00,
  1122. },
  1123. windowStart: startYesterday,
  1124. windowEnd: endYesterday,
  1125. expMinutes: 1440.0,
  1126. },
  1127. // 3b: sharing idle evenly is deprecated
  1128. // 4 Share resources
  1129. // 4a Share namespace ShareEven
  1130. // namespace1: 37.5000 = 28.00 + 18.00*(1.0/2.0)
  1131. // namespace2: 45.5000 = 36.00 + 18.00*(1.0/2.0)
  1132. // idle: 30.0000
  1133. "4a": {
  1134. start: start,
  1135. aggBy: []string{AllocationNamespaceProp},
  1136. aggOpts: &AllocationAggregationOptions{
  1137. Share: namespaceEquals("namespace3"),
  1138. ShareSplit: ShareEven,
  1139. },
  1140. numResults: numNamespaces,
  1141. totalCost: activeTotalCost + idleTotalCost,
  1142. results: map[string]float64{
  1143. "namespace1": 37.00,
  1144. "namespace2": 45.00,
  1145. IdleSuffix: 30.00,
  1146. },
  1147. windowStart: startYesterday,
  1148. windowEnd: endYesterday,
  1149. expMinutes: 1440.0,
  1150. },
  1151. // 4b Share namespace ShareWeighted
  1152. // namespace1: 32.5000 =
  1153. // namespace2: 37.5000 =
  1154. // idle: 30.0000
  1155. "4b": {
  1156. start: start,
  1157. aggBy: []string{AllocationNamespaceProp},
  1158. aggOpts: &AllocationAggregationOptions{
  1159. Share: namespaceEquals("namespace3"),
  1160. ShareSplit: ShareWeighted,
  1161. IncludeProportionalAssetResourceCosts: true,
  1162. },
  1163. numResults: numNamespaces,
  1164. totalCost: activeTotalCost + idleTotalCost,
  1165. results: map[string]float64{
  1166. "namespace1": 35.88,
  1167. "namespace2": 46.125,
  1168. IdleSuffix: 30.00,
  1169. },
  1170. windowStart: startYesterday,
  1171. windowEnd: endYesterday,
  1172. expMinutes: 1440.0,
  1173. expectedParcResults: map[string]ProportionalAssetResourceCosts{
  1174. "namespace1": {
  1175. "cluster1": ProportionalAssetResourceCost{
  1176. Cluster: "cluster1",
  1177. Name: "",
  1178. Type: "",
  1179. ProviderID: "",
  1180. GPUProportionalCost: 3,
  1181. CPUProportionalCost: 3,
  1182. RAMProportionalCost: 13,
  1183. PVProportionalCost: 3,
  1184. },
  1185. },
  1186. "namespace2": {
  1187. "cluster1": ProportionalAssetResourceCost{
  1188. Cluster: "cluster1",
  1189. Name: "",
  1190. Type: "",
  1191. ProviderID: "",
  1192. GPUProportionalCost: 3,
  1193. CPUProportionalCost: 3,
  1194. RAMProportionalCost: 3,
  1195. PVProportionalCost: 3,
  1196. },
  1197. "cluster2": ProportionalAssetResourceCost{
  1198. Cluster: "cluster2",
  1199. Name: "",
  1200. Type: "",
  1201. ProviderID: "",
  1202. GPUProportionalCost: 3,
  1203. CPUProportionalCost: 3,
  1204. RAMProportionalCost: 3,
  1205. PVProportionalCost: 3,
  1206. },
  1207. },
  1208. },
  1209. },
  1210. // 4c Share label ShareEven
  1211. // namespace1: 17.3333 = 28.00 - 16.00 + 16.00*(1.0/3.0)
  1212. // namespace2: 41.3333 = 36.00 + 16.00*(1.0/3.0)
  1213. // namespace3: 23.3333 = 18.00 + 16.00*(1.0/3.0)
  1214. // idle: 30.0000
  1215. "4c": {
  1216. start: start,
  1217. aggBy: []string{AllocationNamespaceProp},
  1218. aggOpts: &AllocationAggregationOptions{
  1219. Share: labelEquals("app", "app1"),
  1220. ShareSplit: ShareEven,
  1221. },
  1222. numResults: numNamespaces + numIdle,
  1223. totalCost: activeTotalCost + idleTotalCost,
  1224. results: map[string]float64{
  1225. "namespace1": 17.33,
  1226. "namespace2": 41.33,
  1227. "namespace3": 23.33,
  1228. IdleSuffix: 30.00,
  1229. },
  1230. windowStart: startYesterday,
  1231. windowEnd: endYesterday,
  1232. expMinutes: 1440.0,
  1233. },
  1234. // 4d Share overhead ShareWeighted
  1235. // namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
  1236. // namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
  1237. // namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
  1238. // idle: 30.0000
  1239. "4d": {
  1240. start: start,
  1241. aggBy: []string{AllocationNamespaceProp},
  1242. aggOpts: &AllocationAggregationOptions{
  1243. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1244. ShareSplit: ShareWeighted,
  1245. },
  1246. numResults: numNamespaces + numIdle,
  1247. totalCost: activeTotalCost + idleTotalCost + (sharedOverheadHourlyCost * 24.0),
  1248. results: map[string]float64{
  1249. "namespace1": 85.366,
  1250. "namespace2": 109.756,
  1251. "namespace3": 54.878,
  1252. IdleSuffix: 30.00,
  1253. },
  1254. windowStart: startYesterday,
  1255. windowEnd: endYesterday,
  1256. expMinutes: 1440.0,
  1257. },
  1258. // 5 Filters
  1259. // 5a Filter by cluster with separate idle
  1260. "5a": {
  1261. start: start,
  1262. aggBy: []string{AllocationClusterProp},
  1263. aggOpts: &AllocationAggregationOptions{
  1264. Filter: mustParseFilter(`cluster:"cluster1"`),
  1265. ShareIdle: ShareNone,
  1266. },
  1267. numResults: 1 + numIdle,
  1268. totalCost: 66.0,
  1269. results: map[string]float64{
  1270. "cluster1": 46.00,
  1271. IdleSuffix: 20.00,
  1272. },
  1273. windowStart: startYesterday,
  1274. windowEnd: endYesterday,
  1275. expMinutes: 1440.0,
  1276. },
  1277. // 5b Filter by cluster with shared idle
  1278. "5b": {
  1279. start: start,
  1280. aggBy: []string{AllocationClusterProp},
  1281. aggOpts: &AllocationAggregationOptions{
  1282. Filter: mustParseFilter(`cluster:"cluster1"`),
  1283. ShareIdle: ShareWeighted,
  1284. },
  1285. numResults: 1,
  1286. totalCost: 66.0,
  1287. results: map[string]float64{
  1288. "cluster1": 66.00,
  1289. },
  1290. windowStart: startYesterday,
  1291. windowEnd: endYesterday,
  1292. expMinutes: 1440.0,
  1293. },
  1294. // 5c Filter by cluster, agg by namespace, with separate idle
  1295. "5c": {
  1296. start: start,
  1297. aggBy: []string{AllocationNamespaceProp},
  1298. aggOpts: &AllocationAggregationOptions{
  1299. Filter: mustParseFilter(`cluster:"cluster1"`),
  1300. ShareIdle: ShareNone,
  1301. },
  1302. numResults: 2 + numIdle,
  1303. totalCost: 66.0,
  1304. results: map[string]float64{
  1305. "namespace1": 28.00,
  1306. "namespace2": 18.00,
  1307. IdleSuffix: 20.00,
  1308. },
  1309. windowStart: startYesterday,
  1310. windowEnd: endYesterday,
  1311. expMinutes: 1440.0,
  1312. },
  1313. // 5d Filter by namespace, agg by cluster, with separate idle
  1314. "5d": {
  1315. start: start,
  1316. aggBy: []string{AllocationClusterProp},
  1317. aggOpts: &AllocationAggregationOptions{
  1318. Filter: mustParseFilter(`namespace:"namespace2"`),
  1319. ShareIdle: ShareNone,
  1320. },
  1321. numResults: numClusters + numIdle,
  1322. totalCost: 46.31,
  1323. results: map[string]float64{
  1324. "cluster1": 18.00,
  1325. "cluster2": 18.00,
  1326. IdleSuffix: 10.31,
  1327. },
  1328. windowStart: startYesterday,
  1329. windowEnd: endYesterday,
  1330. expMinutes: 1440.0,
  1331. },
  1332. // 6 Combinations and options
  1333. // 6a SplitIdle
  1334. "6a": {
  1335. start: start,
  1336. aggBy: []string{AllocationNamespaceProp},
  1337. aggOpts: &AllocationAggregationOptions{
  1338. SplitIdle: true,
  1339. },
  1340. numResults: numNamespaces + numSplitIdleCluster,
  1341. totalCost: activeTotalCost + idleTotalCost,
  1342. results: map[string]float64{
  1343. "namespace1": 28.00,
  1344. "namespace2": 36.00,
  1345. "namespace3": 18.00,
  1346. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  1347. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  1348. },
  1349. windowStart: startYesterday,
  1350. windowEnd: endYesterday,
  1351. expMinutes: 1440.0,
  1352. },
  1353. // 6b Share idle weighted with filters
  1354. // Should match values from unfiltered aggregation (3a)
  1355. // 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)
  1356. "6b": {
  1357. start: start,
  1358. aggBy: []string{AllocationNamespaceProp},
  1359. aggOpts: &AllocationAggregationOptions{
  1360. Filter: mustParseFilter(`namespace:"namespace2"`),
  1361. ShareIdle: ShareWeighted,
  1362. },
  1363. numResults: 1,
  1364. totalCost: 46.31,
  1365. results: map[string]float64{
  1366. "namespace2": 46.31,
  1367. },
  1368. windowStart: startYesterday,
  1369. windowEnd: endYesterday,
  1370. expMinutes: 1440.0,
  1371. },
  1372. // 6c Share idle even with filters (share idle even is deprecated)
  1373. // 6d Share overhead with filters
  1374. // namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
  1375. // namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
  1376. // namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
  1377. // idle: 10.3125 = % of idle paired with namespace2
  1378. // Then namespace 2 is filtered.
  1379. "6d": {
  1380. start: start,
  1381. aggBy: []string{AllocationNamespaceProp},
  1382. aggOpts: &AllocationAggregationOptions{
  1383. Filter: mustParseFilter(`namespace:"namespace2"`),
  1384. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1385. ShareSplit: ShareWeighted,
  1386. },
  1387. numResults: 1 + numIdle,
  1388. totalCost: 120.0686,
  1389. results: map[string]float64{
  1390. "namespace2": 109.7561,
  1391. IdleSuffix: 10.3125,
  1392. },
  1393. windowStart: startYesterday,
  1394. windowEnd: endYesterday,
  1395. expMinutes: 1440.0,
  1396. },
  1397. // 6e Share resources with filters
  1398. "6e": {
  1399. start: start,
  1400. aggBy: []string{AllocationNamespaceProp},
  1401. aggOpts: &AllocationAggregationOptions{
  1402. Filter: mustParseFilter(`namespace:"namespace2"`),
  1403. Share: namespaceEquals("namespace1"),
  1404. ShareSplit: ShareWeighted,
  1405. },
  1406. numResults: 1 + numIdle,
  1407. totalCost: 79.6667, // should be 74.7708, but I'm punting -- too difficult (NK)
  1408. results: map[string]float64{
  1409. "namespace2": 54.6667,
  1410. IdleSuffix: 25.000, // should be 20.1042, but I'm punting -- too difficult (NK)
  1411. },
  1412. windowStart: startYesterday,
  1413. windowEnd: endYesterday,
  1414. expMinutes: 1440.0,
  1415. },
  1416. // 6f Share resources with filters and share idle
  1417. "6f": {
  1418. start: start,
  1419. aggBy: []string{AllocationNamespaceProp},
  1420. aggOpts: &AllocationAggregationOptions{
  1421. Filter: mustParseFilter(`namespace:"namespace2"`),
  1422. Share: namespaceEquals("namespace1"),
  1423. ShareSplit: ShareWeighted,
  1424. ShareIdle: ShareWeighted,
  1425. },
  1426. numResults: 1,
  1427. totalCost: 74.77083,
  1428. results: map[string]float64{
  1429. "namespace2": 74.77083,
  1430. },
  1431. windowStart: startYesterday,
  1432. windowEnd: endYesterday,
  1433. expMinutes: 1440.0,
  1434. },
  1435. // 6g Share idle weighted and share resources weighted
  1436. //
  1437. // First, share idle weighted produces:
  1438. //
  1439. // namespace1: 42.6875
  1440. // initial cost 28.0000
  1441. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1442. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1443. //
  1444. // namespace2: 46.3125
  1445. // initial cost 36.0000
  1446. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1447. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1448. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1449. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1450. //
  1451. // namespace3: 23.0000
  1452. // initial cost 18.0000
  1453. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1454. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1455. //
  1456. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1457. // computed before allocating idle (so that weighting idle differently
  1458. // doesn't adversely affect the sharing mechanism):
  1459. //
  1460. // namespace2: 74.7708
  1461. // initial cost 30.0000
  1462. // idle cost 10.3125
  1463. // shared cost 28.4583 = (42.6875)*(36.0/54.0)
  1464. //
  1465. // namespace3: 37.2292
  1466. // initial cost 18.0000
  1467. // idle cost 5.0000
  1468. // shared cost 14.2292 = (42.6875)*(18.0/54.0)
  1469. "6g": {
  1470. start: start,
  1471. aggBy: []string{AllocationNamespaceProp},
  1472. aggOpts: &AllocationAggregationOptions{
  1473. Share: namespaceEquals("namespace1"),
  1474. ShareSplit: ShareWeighted,
  1475. ShareIdle: ShareWeighted,
  1476. },
  1477. numResults: 2,
  1478. totalCost: activeTotalCost + idleTotalCost,
  1479. results: map[string]float64{
  1480. "namespace2": 74.77,
  1481. "namespace3": 37.23,
  1482. },
  1483. windowStart: startYesterday,
  1484. windowEnd: endYesterday,
  1485. expMinutes: 1440.0,
  1486. },
  1487. // 6h Share idle, share resources, and filter
  1488. //
  1489. // First, share idle weighted produces:
  1490. //
  1491. // namespace1: 42.6875
  1492. // initial cost 28.0000
  1493. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1494. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1495. //
  1496. // namespace2: 46.3125
  1497. // initial cost 36.0000
  1498. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1499. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1500. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1501. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1502. //
  1503. // namespace3: 23.0000
  1504. // initial cost 18.0000
  1505. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1506. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1507. //
  1508. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1509. // computed before allocating idle (so that weighting idle differently
  1510. // doesn't adversely affect the sharing mechanism):
  1511. //
  1512. // namespace2: 74.7708
  1513. // initial cost 36.0000
  1514. // idle cost 10.3125
  1515. // shared cost 28.4583 = (42.6875)*(36.0/54.0)
  1516. //
  1517. // namespace3: 37.2292
  1518. // initial cost 18.0000
  1519. // idle cost 5.0000
  1520. // shared cost 14.2292 = (42.6875)*(18.0/54.0)
  1521. //
  1522. // Then, filter for namespace2: 74.7708
  1523. "6h": {
  1524. start: start,
  1525. aggBy: []string{AllocationNamespaceProp},
  1526. aggOpts: &AllocationAggregationOptions{
  1527. Filter: mustParseFilter(`namespace:"namespace2"`),
  1528. Share: namespaceEquals("namespace1"),
  1529. ShareSplit: ShareWeighted,
  1530. ShareIdle: ShareWeighted,
  1531. },
  1532. numResults: 1,
  1533. totalCost: 74.77,
  1534. results: map[string]float64{
  1535. "namespace2": 74.77,
  1536. },
  1537. windowStart: startYesterday,
  1538. windowEnd: endYesterday,
  1539. expMinutes: 1440.0,
  1540. },
  1541. // 6i Share idle, share resources, share overhead
  1542. //
  1543. // Share idle weighted:
  1544. //
  1545. // namespace1: 42.6875
  1546. // initial cost 28.0000
  1547. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1548. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1549. //
  1550. // namespace2: 46.3125
  1551. // initial cost 36.0000
  1552. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1553. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1554. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1555. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1556. //
  1557. // namespace3: 23.0000
  1558. // initial cost 18.0000
  1559. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1560. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1561. //
  1562. // Then share overhead:
  1563. //
  1564. // namespace1: 100.0533 = 42.6875 + (7.0*24.0)*(28.00/82.00)
  1565. // namespace2: 120.0686 = 46.3125 + (7.0*24.0)*(36.00/82.00)
  1566. // namespace3: 59.8780 = 23.0000 + (7.0*24.0)*(18.00/82.00)
  1567. //
  1568. // Then namespace 2 is filtered.
  1569. "6i": {
  1570. start: start,
  1571. aggBy: []string{AllocationNamespaceProp},
  1572. aggOpts: &AllocationAggregationOptions{
  1573. Filter: mustParseFilter(`namespace:"namespace2"`),
  1574. ShareSplit: ShareWeighted,
  1575. ShareIdle: ShareWeighted,
  1576. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1577. },
  1578. numResults: 1,
  1579. totalCost: 120.07,
  1580. results: map[string]float64{
  1581. "namespace2": 120.07,
  1582. },
  1583. windowStart: startYesterday,
  1584. windowEnd: endYesterday,
  1585. expMinutes: 1440.0,
  1586. },
  1587. // 6j Idle by Node
  1588. "6j": {
  1589. start: start,
  1590. aggBy: []string{AllocationNamespaceProp},
  1591. aggOpts: &AllocationAggregationOptions{
  1592. IdleByNode: true,
  1593. IncludeProportionalAssetResourceCosts: true,
  1594. },
  1595. numResults: numNamespaces + numIdle,
  1596. totalCost: activeTotalCost + idleTotalCost,
  1597. results: map[string]float64{
  1598. "namespace1": 28.00,
  1599. "namespace2": 36.00,
  1600. "namespace3": 18.00,
  1601. IdleSuffix: 30.00,
  1602. },
  1603. windowStart: startYesterday,
  1604. windowEnd: endYesterday,
  1605. expMinutes: 1440.0,
  1606. expectedParcResults: map[string]ProportionalAssetResourceCosts{
  1607. "namespace1": {
  1608. "cluster1,c1nodes": ProportionalAssetResourceCost{
  1609. Cluster: "cluster1",
  1610. Name: "c1nodes",
  1611. Type: "Node",
  1612. ProviderID: "c1nodes",
  1613. GPUProportionalCost: 3,
  1614. CPUProportionalCost: 3,
  1615. RAMProportionalCost: 13,
  1616. },
  1617. "cluster2,node2": ProportionalAssetResourceCost{
  1618. Cluster: "cluster2",
  1619. Name: "node2",
  1620. Type: "Node",
  1621. ProviderID: "node2",
  1622. GPUProportionalCost: 3,
  1623. CPUProportionalCost: 3,
  1624. RAMProportionalCost: 3,
  1625. },
  1626. "cluster1,pv-a1111": {
  1627. Cluster: "cluster1",
  1628. Name: "pv-a1111",
  1629. Type: "PV",
  1630. PVProportionalCost: 1,
  1631. },
  1632. "cluster1,pv-a11abc2": {
  1633. Cluster: "cluster1",
  1634. Name: "pv-a11abc2",
  1635. Type: "PV",
  1636. PVProportionalCost: 1,
  1637. },
  1638. "cluster1,pv-a11def3": {
  1639. Cluster: "cluster1",
  1640. Name: "pv-a11def3",
  1641. Type: "PV",
  1642. PVProportionalCost: 1,
  1643. },
  1644. },
  1645. "namespace2": {
  1646. "cluster1,c1nodes": ProportionalAssetResourceCost{
  1647. Cluster: "cluster1",
  1648. Name: "c1nodes",
  1649. Type: "Node",
  1650. ProviderID: "c1nodes",
  1651. GPUProportionalCost: 3,
  1652. CPUProportionalCost: 3,
  1653. RAMProportionalCost: 3,
  1654. },
  1655. "cluster2,node1": ProportionalAssetResourceCost{
  1656. Cluster: "cluster2",
  1657. Name: "node1",
  1658. Type: "Node",
  1659. ProviderID: "node1",
  1660. GPUProportionalCost: 2,
  1661. CPUProportionalCost: 2,
  1662. RAMProportionalCost: 2,
  1663. },
  1664. "cluster2,node2": ProportionalAssetResourceCost{
  1665. Cluster: "cluster2",
  1666. Name: "node2",
  1667. Type: "Node",
  1668. ProviderID: "node2",
  1669. GPUProportionalCost: 1,
  1670. CPUProportionalCost: 1,
  1671. RAMProportionalCost: 1,
  1672. },
  1673. "cluster1,pv-a12ghi4": {
  1674. Cluster: "cluster1",
  1675. Name: "pv-a12ghi4",
  1676. Type: "PV",
  1677. PVProportionalCost: 1,
  1678. },
  1679. "cluster1,pv-a12ghi5": {
  1680. Cluster: "cluster1",
  1681. Name: "pv-a12ghi5",
  1682. Type: "PV",
  1683. PVProportionalCost: 1,
  1684. },
  1685. "cluster1,pv-a12jkl6": {
  1686. Cluster: "cluster1",
  1687. Name: "pv-a12jkl6",
  1688. Type: "PV",
  1689. PVProportionalCost: 1,
  1690. },
  1691. "cluster2,pv-a22mno4": {
  1692. Cluster: "cluster2",
  1693. Name: "pv-a22mno4",
  1694. Type: "PV",
  1695. PVProportionalCost: 1,
  1696. },
  1697. "cluster2,pv-a22mno5": {
  1698. Cluster: "cluster2",
  1699. Name: "pv-a22mno5",
  1700. Type: "PV",
  1701. PVProportionalCost: 1,
  1702. },
  1703. "cluster2,pv-a22pqr6": {
  1704. Cluster: "cluster2",
  1705. Name: "pv-a22pqr6",
  1706. Type: "PV",
  1707. PVProportionalCost: 1,
  1708. },
  1709. },
  1710. "namespace3": {
  1711. "cluster2,node3": ProportionalAssetResourceCost{
  1712. Cluster: "cluster2",
  1713. Name: "node3",
  1714. Type: "Node",
  1715. ProviderID: "node3",
  1716. GPUProportionalCost: 2,
  1717. CPUProportionalCost: 2,
  1718. RAMProportionalCost: 2,
  1719. },
  1720. "cluster2,node2": ProportionalAssetResourceCost{
  1721. Cluster: "cluster2",
  1722. Name: "node2",
  1723. Type: "Node",
  1724. ProviderID: "node2",
  1725. GPUProportionalCost: 1,
  1726. CPUProportionalCost: 1,
  1727. RAMProportionalCost: 1,
  1728. },
  1729. "cluster2,pv-a23stu7": {
  1730. Cluster: "cluster2",
  1731. Name: "pv-a23stu7",
  1732. Type: "PV",
  1733. PVProportionalCost: 1,
  1734. },
  1735. "cluster2,pv-a23vwx8": {
  1736. Cluster: "cluster2",
  1737. Name: "pv-a23vwx8",
  1738. Type: "PV",
  1739. PVProportionalCost: 1,
  1740. },
  1741. "cluster2,pv-a23vwx9": {
  1742. Cluster: "cluster2",
  1743. Name: "pv-a23vwx9",
  1744. Type: "PV",
  1745. PVProportionalCost: 1,
  1746. },
  1747. },
  1748. },
  1749. },
  1750. // 6k Split Idle, Idle by Node
  1751. "6k": {
  1752. start: start,
  1753. aggBy: []string{AllocationNamespaceProp},
  1754. aggOpts: &AllocationAggregationOptions{
  1755. SplitIdle: true,
  1756. IdleByNode: true,
  1757. },
  1758. numResults: numNamespaces + numSplitIdleNode,
  1759. totalCost: activeTotalCost + idleTotalCost,
  1760. results: map[string]float64{
  1761. "namespace1": 28.00,
  1762. "namespace2": 36.00,
  1763. "namespace3": 18.00,
  1764. fmt.Sprintf("c1nodes/%s", IdleSuffix): 20.00,
  1765. fmt.Sprintf("node1/%s", IdleSuffix): 3.333333,
  1766. fmt.Sprintf("node2/%s", IdleSuffix): 3.333333,
  1767. fmt.Sprintf("node3/%s", IdleSuffix): 3.333333,
  1768. },
  1769. windowStart: startYesterday,
  1770. windowEnd: endYesterday,
  1771. expMinutes: 1440.0,
  1772. },
  1773. // Old 6k Share idle Even Idle by Node (share idle even deprecated)
  1774. // 6l Share idle weighted with filters, Idle by Node
  1775. // Should match values from unfiltered aggregation (3a)
  1776. // 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)
  1777. "6l": {
  1778. start: start,
  1779. aggBy: []string{AllocationNamespaceProp},
  1780. aggOpts: &AllocationAggregationOptions{
  1781. Filter: mustParseFilter(`namespace:"namespace2"`),
  1782. ShareIdle: ShareWeighted,
  1783. IdleByNode: true,
  1784. },
  1785. numResults: 1,
  1786. totalCost: 46.31,
  1787. results: map[string]float64{
  1788. "namespace2": 46.31,
  1789. },
  1790. windowStart: startYesterday,
  1791. windowEnd: endYesterday,
  1792. expMinutes: 1440.0,
  1793. },
  1794. // 7 Edge cases and errors
  1795. // 7a Empty AggregationProperties
  1796. // 7b Filter all
  1797. // 7c Share all
  1798. // 7d Share and filter the same allocations
  1799. }
  1800. for name, testcase := range cases {
  1801. if name != "4a" {
  1802. continue
  1803. }
  1804. t.Run(name, func(t *testing.T) {
  1805. if testcase.aggOpts != nil && testcase.aggOpts.IdleByNode {
  1806. as = GenerateMockAllocationSetNodeIdle(testcase.start)
  1807. } else {
  1808. as = GenerateMockAllocationSetClusterIdle(testcase.start)
  1809. }
  1810. err = as.AggregateBy(testcase.aggBy, testcase.aggOpts)
  1811. log.Infof("RESULTS")
  1812. for name, alloc := range as.Allocations {
  1813. log.Infof(" %s = %f", name, alloc.TotalCost())
  1814. }
  1815. assertAllocationSetTotals(t, as, name, err, testcase.numResults, testcase.totalCost)
  1816. assertAllocationTotals(t, as, name, testcase.results)
  1817. assertParcResults(t, as, name, testcase.expectedParcResults)
  1818. assertAllocationWindow(t, as, name, testcase.windowStart, testcase.windowEnd, testcase.expMinutes)
  1819. })
  1820. }
  1821. }
  1822. func TestAllocationSet_AggregateBy_SharedCostBreakdown(t *testing.T) {
  1823. // Set generated by GenerateMockAllocationSet
  1824. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net | LB |
  1825. // +----------------------------------------+------+------+------+------+------+------+------+
  1826. // cluster1:
  1827. // idle: 20.00 5.00 15.00 0.00 0.00 0.00 0.00
  1828. // namespace1:
  1829. // pod1:
  1830. // container1: [app1, env1] 16.00 1.00 11.00 1.00 1.00 1.00 1.00
  1831. // pod-abc: (deployment1)
  1832. // container2: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1833. // pod-def: (deployment1)
  1834. // container3: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1835. // namespace2:
  1836. // pod-ghi: (deployment2)
  1837. // container4: [app2, env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1838. // container5: [app2, env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1839. // pod-jkl: (daemonset1)
  1840. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1841. // +-----------------------------------------+------+------+------+------+------+------+------+
  1842. // cluster1 subtotal 66.00 11.00 31.00 6.00 6.00 6.00 6.00
  1843. // +-----------------------------------------+------+------+------+------+------+------+------+
  1844. // cluster2:
  1845. // idle: 10.00 5.00 5.00 0.00 0.00 0.00 0.00
  1846. // namespace2:
  1847. // pod-mno: (deployment2)
  1848. // container4: [app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1849. // container5: [app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1850. // pod-pqr: (daemonset1)
  1851. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1852. // namespace3:
  1853. // pod-stu: (deployment3)
  1854. // container7: an[team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1855. // pod-vwx: (statefulset1)
  1856. // container8: an[team2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1857. // container9: an[team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  1858. // +----------------------------------------+------+------+------+------+------+------+------+
  1859. // cluster2 subtotal 46.00 11.00 11.00 6.00 6.00 6.00 6.00
  1860. // +----------------------------------------+------+------+------+------+------+------+------+
  1861. // total 112.00 22.00 42.00 12.00 12.00 12.00 12.00
  1862. // +----------------------------------------+------+------+------+------+------+------+------+
  1863. end := time.Now().UTC().Truncate(day)
  1864. start := end.Add(-day)
  1865. // This is ugly, but required because cannot import filterutil due to import cycle
  1866. namespaceEquals := func(ns string) *ast.EqualOp {
  1867. return &ast.EqualOp{
  1868. Left: ast.Identifier{
  1869. Field: ast.NewField(allocation.FieldNamespace),
  1870. Key: "",
  1871. },
  1872. Right: ns,
  1873. }
  1874. }
  1875. cases := map[string]struct {
  1876. start time.Time
  1877. aggBy []string
  1878. aggOpts *AllocationAggregationOptions
  1879. }{
  1880. "agg cluster, flat shared cost": {
  1881. start: start,
  1882. aggBy: []string{"cluster"},
  1883. aggOpts: &AllocationAggregationOptions{
  1884. SharedHourlyCosts: map[string]float64{"share_hourly": 10.0 / timeutil.HoursPerDay},
  1885. IncludeSharedCostBreakdown: true,
  1886. },
  1887. },
  1888. "agg namespace, shared namespace: namespace1": {
  1889. start: start,
  1890. aggBy: []string{"namespace"},
  1891. aggOpts: &AllocationAggregationOptions{
  1892. Share: namespaceEquals("namespace1"),
  1893. IncludeSharedCostBreakdown: true,
  1894. },
  1895. },
  1896. "agg namespace, shared namespace: namespace3": {
  1897. start: start,
  1898. aggBy: []string{"namespace"},
  1899. aggOpts: &AllocationAggregationOptions{
  1900. Share: namespaceEquals("namespace3"),
  1901. IncludeSharedCostBreakdown: true,
  1902. },
  1903. },
  1904. }
  1905. for name, tc := range cases {
  1906. t.Run(name, func(t *testing.T) {
  1907. as := GenerateMockAllocationSetClusterIdle(tc.start)
  1908. err := as.AggregateBy(tc.aggBy, tc.aggOpts)
  1909. if err != nil {
  1910. t.Fatalf("error aggregating: %s", err)
  1911. }
  1912. for _, alloc := range as.Allocations {
  1913. var breakdownTotal float64
  1914. // ignore idle since it should never have shared costs
  1915. if strings.Contains(alloc.Name, IdleSuffix) {
  1916. continue
  1917. }
  1918. for _, sharedAlloc := range alloc.SharedCostBreakdown {
  1919. breakdownTotal += sharedAlloc.TotalCost
  1920. totalInternal := sharedAlloc.CPUCost + sharedAlloc.GPUCost + sharedAlloc.RAMCost + sharedAlloc.NetworkCost + sharedAlloc.LBCost + sharedAlloc.PVCost + sharedAlloc.ExternalCost
  1921. // check that the total cost of a single item in the breakdown equals the sum of its parts
  1922. // we can ignore the overheadCost breakdown since it only has a total
  1923. if totalInternal != sharedAlloc.TotalCost && sharedAlloc.Name != "overheadCost" {
  1924. t.Errorf("expected internal total: %f; got %f", sharedAlloc.TotalCost, totalInternal)
  1925. }
  1926. }
  1927. // check that the totals of all shared cost breakdowns equal the allocation's SharedCost
  1928. if breakdownTotal != alloc.SharedCost {
  1929. t.Errorf("expected breakdown total: %f; got %f", alloc.SharedCost, breakdownTotal)
  1930. }
  1931. }
  1932. })
  1933. }
  1934. }
  1935. // TODO niko/etl
  1936. //func TestAllocationSet_Clone(t *testing.T) {}
  1937. // TODO niko/etl
  1938. //func TestAllocationSet_Delete(t *testing.T) {}
  1939. // TODO niko/etl
  1940. //func TestAllocationSet_End(t *testing.T) {}
  1941. // TODO niko/etl
  1942. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  1943. // TODO niko/etl
  1944. //func TestAllocationSet_Insert(t *testing.T) {}
  1945. // Asserts that all Allocations within an AllocationSet have a Window that
  1946. // matches that of the AllocationSet.
  1947. func TestAllocationSet_insertMatchingWindow(t *testing.T) {
  1948. setStart := time.Now().Round(time.Hour)
  1949. setEnd := setStart.Add(1 * time.Hour)
  1950. a1WindowStart := setStart.Add(5 * time.Minute)
  1951. a1WindowEnd := setStart.Add(50 * time.Minute)
  1952. a2WindowStart := setStart.Add(17 * time.Minute)
  1953. a2WindowEnd := setStart.Add(34 * time.Minute)
  1954. a1 := &Allocation{
  1955. Name: "allocation-1",
  1956. Window: Window(NewClosedWindow(a1WindowStart, a1WindowEnd)),
  1957. }
  1958. a2 := &Allocation{
  1959. Name: "allocation-2",
  1960. Window: Window(NewClosedWindow(a2WindowStart, a2WindowEnd)),
  1961. }
  1962. as := NewAllocationSet(setStart, setEnd)
  1963. as.Insert(a1)
  1964. as.Insert(a2)
  1965. if as.Length() != 2 {
  1966. t.Errorf("AS length got %d, expected %d", as.Length(), 2)
  1967. }
  1968. for _, a := range as.Allocations {
  1969. if !(*a.Window.Start()).Equal(setStart) {
  1970. t.Errorf("Allocation %s window start is %s, expected %s", a.Name, *a.Window.Start(), setStart)
  1971. }
  1972. if !(*a.Window.End()).Equal(setEnd) {
  1973. t.Errorf("Allocation %s window end is %s, expected %s", a.Name, *a.Window.End(), setEnd)
  1974. }
  1975. }
  1976. }
  1977. // This tests PARC accumulation. Assuming Node cost is $1 per core per hour
  1978. // From https://github.com/opencost/opencost/pull/1867#discussion_r1174109388:
  1979. // Over the span of hour 1:
  1980. // Pod 1 runs for 30 minutes, consuming 1 CPU while alive. PARC: 12.5% (0.5 core-hours / 4 available core-hours)
  1981. // Pod 2 runs for 1 hour, consuming 2 CPU while alive. PARC: 50% (2 core-hours)
  1982. // Pod 3 runs for 1 hour, consuming 1 CPU while alive. PARC: 25% (1 core-hour)
  1983. // Over the span of hour 2:
  1984. // Pod 1 does not run. PARC: 0% (0 core-hours / 4 available core-hours)
  1985. // Pod 2 runs for 30 minutes, consuming 2 CPU while active. PARC: 25% (1 core-hour)
  1986. // Pod 3 runs for 1 hour, consuming 1 CPU while active. PARC: 25% (1 core-hour)
  1987. // Over the span of hour 3:
  1988. // Pod 1 does not run. PARC: 0% (0 core-hours / 4 available)
  1989. // Pod 2 runs for 30 minutes, consuming 3 CPU while active. PARC: 37.5% (1.5 core-hours)
  1990. // Pod 3 runs for 1 hour, consuming 1 CPU while active. PARC: 25% (1 core-hour)
  1991. // We expect the following accumulated PARC:
  1992. // Pod 1: (0.5 + 0 + 0) core-hours used / (4 + 4 + 4) core-hours available = 0.5/12 = 4.16%
  1993. // Pod 2: (2 + 1 + 1.5) / (4 + 4 + 4) = 4.5/12 = 37.5%
  1994. // Pod 3: (1 + 1 + 1) / (4 + 4 + 4) = 3/12 = 25%
  1995. func TestParcInsert(t *testing.T) {
  1996. pod1_hour1 := ProportionalAssetResourceCost{
  1997. Cluster: "cluster1",
  1998. Name: "node1",
  1999. Type: "Node",
  2000. ProviderID: "i-1234",
  2001. CPUPercentage: 0.125,
  2002. GPUPercentage: 0,
  2003. RAMPercentage: 0,
  2004. NodeResourceCostPercentage: 0,
  2005. CPUTotalCost: 4,
  2006. CPUProportionalCost: 0.5,
  2007. }
  2008. pod1_hour2 := ProportionalAssetResourceCost{
  2009. Cluster: "cluster1",
  2010. Name: "node1",
  2011. Type: "Node",
  2012. ProviderID: "i-1234",
  2013. CPUPercentage: 0.0,
  2014. GPUPercentage: 0,
  2015. RAMPercentage: 0,
  2016. NodeResourceCostPercentage: 0,
  2017. CPUTotalCost: 4,
  2018. }
  2019. pod1_hour3 := ProportionalAssetResourceCost{
  2020. Cluster: "cluster1",
  2021. Name: "node1",
  2022. Type: "Node",
  2023. ProviderID: "i-1234",
  2024. CPUPercentage: 0.0,
  2025. GPUPercentage: 0,
  2026. RAMPercentage: 0,
  2027. NodeResourceCostPercentage: 0,
  2028. CPUTotalCost: 4,
  2029. }
  2030. pod2_hour1 := ProportionalAssetResourceCost{
  2031. Cluster: "cluster1",
  2032. Name: "node2",
  2033. Type: "Node",
  2034. ProviderID: "i-1234",
  2035. CPUPercentage: 0.0,
  2036. GPUPercentage: 0,
  2037. RAMPercentage: 0,
  2038. NodeResourceCostPercentage: 0,
  2039. CPUTotalCost: 4,
  2040. CPUProportionalCost: 2,
  2041. }
  2042. pod2_hour2 := ProportionalAssetResourceCost{
  2043. Cluster: "cluster1",
  2044. Name: "node2",
  2045. Type: "Node",
  2046. ProviderID: "i-1234",
  2047. CPUPercentage: 0.0,
  2048. GPUPercentage: 0,
  2049. RAMPercentage: 0,
  2050. NodeResourceCostPercentage: 0,
  2051. CPUTotalCost: 4,
  2052. CPUProportionalCost: 1,
  2053. }
  2054. pod2_hour3 := ProportionalAssetResourceCost{
  2055. Cluster: "cluster1",
  2056. Name: "node2",
  2057. Type: "Node",
  2058. ProviderID: "i-1234",
  2059. CPUPercentage: 0.0,
  2060. GPUPercentage: 0,
  2061. RAMPercentage: 0,
  2062. NodeResourceCostPercentage: 0,
  2063. CPUTotalCost: 4,
  2064. CPUProportionalCost: 1.5,
  2065. }
  2066. pod3_hour1 := ProportionalAssetResourceCost{
  2067. Cluster: "cluster1",
  2068. Name: "node3",
  2069. Type: "Node",
  2070. ProviderID: "i-1234",
  2071. CPUPercentage: 0.0,
  2072. GPUPercentage: 0,
  2073. RAMPercentage: 0,
  2074. NodeResourceCostPercentage: 0,
  2075. CPUTotalCost: 4,
  2076. CPUProportionalCost: 1,
  2077. }
  2078. pod3_hour2 := ProportionalAssetResourceCost{
  2079. Cluster: "cluster1",
  2080. Name: "node3",
  2081. Type: "Node",
  2082. ProviderID: "i-1234",
  2083. CPUPercentage: 0.0,
  2084. GPUPercentage: 0,
  2085. RAMPercentage: 0,
  2086. NodeResourceCostPercentage: 0,
  2087. CPUTotalCost: 4,
  2088. CPUProportionalCost: 1,
  2089. }
  2090. pod3_hour3 := ProportionalAssetResourceCost{
  2091. Cluster: "cluster1",
  2092. Name: "node3",
  2093. Type: "Node",
  2094. ProviderID: "i-1234",
  2095. CPUPercentage: 0.0,
  2096. GPUPercentage: 0,
  2097. RAMPercentage: 0,
  2098. NodeResourceCostPercentage: 0,
  2099. CPUTotalCost: 4,
  2100. CPUProportionalCost: 1,
  2101. }
  2102. parcs := ProportionalAssetResourceCosts{}
  2103. parcs.Insert(pod1_hour1, true)
  2104. parcs.Insert(pod1_hour2, true)
  2105. parcs.Insert(pod1_hour3, true)
  2106. parcs.Insert(pod2_hour1, true)
  2107. parcs.Insert(pod2_hour2, true)
  2108. parcs.Insert(pod2_hour3, true)
  2109. parcs.Insert(pod3_hour1, true)
  2110. parcs.Insert(pod3_hour2, true)
  2111. parcs.Insert(pod3_hour3, true)
  2112. log.Debug("added all parcs")
  2113. // set totals, compute percentaves
  2114. parc1, ok := parcs["cluster1,node1"]
  2115. if !ok {
  2116. t.Fatalf("parc1 not found")
  2117. }
  2118. parc1.CPUTotalCost = 12
  2119. parc2, ok := parcs["cluster1,node2"]
  2120. if !ok {
  2121. t.Fatalf("parc2 not found")
  2122. }
  2123. parc2.CPUTotalCost = 12
  2124. parc3, ok := parcs["cluster1,node3"]
  2125. if !ok {
  2126. t.Fatalf("parc1 not found")
  2127. }
  2128. parc3.CPUTotalCost = 12
  2129. ComputePercentages(&parc1)
  2130. ComputePercentages(&parc2)
  2131. ComputePercentages(&parc3)
  2132. parcs["cluster1,node1"] = parc1
  2133. parcs["cluster1,node2"] = parc2
  2134. parcs["cluster1,node3"] = parc3
  2135. expectedParcs := ProportionalAssetResourceCosts{
  2136. "cluster1,node1": ProportionalAssetResourceCost{
  2137. CPUPercentage: 0.041666666666666664,
  2138. NodeResourceCostPercentage: 0.041666666666666664,
  2139. },
  2140. "cluster1,node2": ProportionalAssetResourceCost{
  2141. CPUPercentage: 0.375,
  2142. NodeResourceCostPercentage: 0.375,
  2143. },
  2144. "cluster1,node3": ProportionalAssetResourceCost{
  2145. CPUPercentage: 0.25,
  2146. NodeResourceCostPercentage: 0.25,
  2147. },
  2148. }
  2149. for key, expectedParc := range expectedParcs {
  2150. actualParc, ok := parcs[key]
  2151. if !ok {
  2152. t.Fatalf("did not find expected PARC: %s", key)
  2153. }
  2154. if actualParc.CPUPercentage != expectedParc.CPUPercentage {
  2155. t.Fatalf("actual parc cpu percentage: %f did not match expected: %f", actualParc.CPUPercentage, expectedParc.CPUPercentage)
  2156. }
  2157. if actualParc.NodeResourceCostPercentage != expectedParc.NodeResourceCostPercentage {
  2158. t.Fatalf("actual parc node percentage: %f did not match expected: %f", actualParc.NodeResourceCostPercentage, expectedParc.NodeResourceCostPercentage)
  2159. }
  2160. }
  2161. }
  2162. // TODO niko/etl
  2163. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  2164. // TODO niko/etl
  2165. //func TestAllocationSet_Length(t *testing.T) {}
  2166. // TODO niko/etl
  2167. //func TestAllocationSet_Map(t *testing.T) {}
  2168. // TODO niko/etl
  2169. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  2170. // TODO niko/etl
  2171. //func TestAllocationSet_Resolution(t *testing.T) {}
  2172. // TODO niko/etl
  2173. //func TestAllocationSet_Seconds(t *testing.T) {}
  2174. // TODO niko/etl
  2175. //func TestAllocationSet_Set(t *testing.T) {}
  2176. // TODO niko/etl
  2177. //func TestAllocationSet_Start(t *testing.T) {}
  2178. // TODO niko/etl
  2179. //func TestAllocationSet_TotalCost(t *testing.T) {}
  2180. // TODO niko/etl
  2181. //func TestNewAllocationSetRange(t *testing.T) {}
  2182. func TestAllocationSetRange_AccumulateRepeat(t *testing.T) {
  2183. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2184. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2185. today := time.Now().UTC().Truncate(day)
  2186. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2187. a := GenerateMockAllocationSet(ago2d)
  2188. b := GenerateMockAllocationSet(yesterday)
  2189. c := GenerateMockAllocationSet(today)
  2190. d := GenerateMockAllocationSet(tomorrow)
  2191. asr := NewAllocationSetRange(a, b, c, d)
  2192. // Take Total Cost
  2193. totalCost := asr.TotalCost()
  2194. // NewAccumulation does not mutate
  2195. result, err := asr.newAccumulation()
  2196. if err != nil {
  2197. t.Fatal(err)
  2198. }
  2199. asr2 := NewAllocationSetRange(result)
  2200. // Ensure Costs Match
  2201. if totalCost != asr2.TotalCost() {
  2202. t.Fatalf("Accumulated Total Cost does not match original Total Cost")
  2203. }
  2204. // Next NewAccumulation() call should prove that there is no mutation of inner data
  2205. result, err = asr.newAccumulation()
  2206. if err != nil {
  2207. t.Fatal(err)
  2208. }
  2209. asr3 := NewAllocationSetRange(result)
  2210. // Costs should be correct, as multiple calls to NewAccumulation() should not alter
  2211. // the internals of the AllocationSetRange
  2212. if totalCost != asr3.TotalCost() {
  2213. t.Fatalf("Accumulated Total Cost does not match original Total Cost. %f != %f", totalCost, asr3.TotalCost())
  2214. }
  2215. }
  2216. func TestAllocationSetRange_Accumulate(t *testing.T) {
  2217. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2218. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2219. today := time.Now().UTC().Truncate(day)
  2220. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2221. // Accumulating any combination of nil and/or empty set should result in empty set
  2222. result, err := NewAllocationSetRange(nil).accumulate()
  2223. if err != nil {
  2224. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2225. }
  2226. if !result.IsEmpty() {
  2227. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2228. }
  2229. result, err = NewAllocationSetRange(nil, nil).accumulate()
  2230. if err != nil {
  2231. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2232. }
  2233. if !result.IsEmpty() {
  2234. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2235. }
  2236. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).accumulate()
  2237. if err != nil {
  2238. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2239. }
  2240. if !result.IsEmpty() {
  2241. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2242. }
  2243. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).accumulate()
  2244. if err != nil {
  2245. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  2246. }
  2247. if !result.IsEmpty() {
  2248. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  2249. }
  2250. todayAS := NewAllocationSet(today, tomorrow)
  2251. todayAS.Set(NewMockUnitAllocation("", today, day, nil))
  2252. yesterdayAS := NewAllocationSet(yesterday, today)
  2253. yesterdayAS.Set(NewMockUnitAllocation("", yesterday, day, nil))
  2254. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  2255. result, err = NewAllocationSetRange(nil, todayAS).accumulate()
  2256. if err != nil {
  2257. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2258. }
  2259. if result == nil {
  2260. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2261. }
  2262. if result.TotalCost() != 6.0 {
  2263. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  2264. }
  2265. result, err = NewAllocationSetRange(todayAS, nil).accumulate()
  2266. if err != nil {
  2267. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2268. }
  2269. if result == nil {
  2270. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2271. }
  2272. if result.TotalCost() != 6.0 {
  2273. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  2274. }
  2275. result, err = NewAllocationSetRange(nil, todayAS, nil).accumulate()
  2276. if err != nil {
  2277. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2278. }
  2279. if result == nil {
  2280. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2281. }
  2282. if result.TotalCost() != 6.0 {
  2283. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  2284. }
  2285. // Accumulate two non-nil should result in sum of both with appropriate start, end
  2286. result, err = NewAllocationSetRange(yesterdayAS, todayAS).accumulate()
  2287. if err != nil {
  2288. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  2289. }
  2290. if result == nil {
  2291. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  2292. }
  2293. if result.TotalCost() != 12.0 {
  2294. t.Fatalf("accumulating AllocationSetRange: expected total cost 12.0; actual %f", result.TotalCost())
  2295. }
  2296. allocMap := result.Allocations
  2297. if len(allocMap) != 1 {
  2298. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  2299. }
  2300. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  2301. if alloc == nil {
  2302. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  2303. }
  2304. if alloc.CPUCoreHours != 2.0 {
  2305. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  2306. }
  2307. if alloc.CPUCost != 2.0 {
  2308. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  2309. }
  2310. if alloc.CPUEfficiency() != 1.0 {
  2311. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency())
  2312. }
  2313. if alloc.GPUHours != 2.0 {
  2314. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  2315. }
  2316. if alloc.GPUCost != 2.0 {
  2317. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  2318. }
  2319. if alloc.NetworkCost != 2.0 {
  2320. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  2321. }
  2322. if alloc.LoadBalancerCost != 2.0 {
  2323. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.LoadBalancerCost)
  2324. }
  2325. if alloc.PVByteHours() != 2.0 {
  2326. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours())
  2327. }
  2328. if alloc.PVCost() != 2.0 {
  2329. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost())
  2330. }
  2331. if alloc.RAMByteHours != 2.0 {
  2332. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  2333. }
  2334. if alloc.RAMCost != 2.0 {
  2335. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  2336. }
  2337. if alloc.RAMEfficiency() != 1.0 {
  2338. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
  2339. }
  2340. if alloc.TotalCost() != 12.0 {
  2341. t.Fatalf("accumulating AllocationSetRange: expected 12.0; actual %f", alloc.TotalCost())
  2342. }
  2343. if alloc.TotalEfficiency() != 1.0 {
  2344. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
  2345. }
  2346. if !alloc.Start.Equal(yesterday) {
  2347. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  2348. }
  2349. if !alloc.End.Equal(tomorrow) {
  2350. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  2351. }
  2352. if alloc.Minutes() != 2880.0 {
  2353. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes())
  2354. }
  2355. }
  2356. func TestAllocationSetRange_AccumulateBy_None(t *testing.T) {
  2357. ago4d := time.Now().UTC().Truncate(day).Add(-4 * day)
  2358. ago3d := time.Now().UTC().Truncate(day).Add(-3 * day)
  2359. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2360. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2361. today := time.Now().UTC().Truncate(day)
  2362. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2363. ago4dAS := NewAllocationSet(ago4d, ago3d)
  2364. ago4dAS.Set(NewMockUnitAllocation("4", ago4d, day, nil))
  2365. ago3dAS := NewAllocationSet(ago3d, ago2d)
  2366. ago3dAS.Set(NewMockUnitAllocation("a", ago3d, day, nil))
  2367. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2368. ago2dAS.Set(NewMockUnitAllocation("", ago2d, day, nil))
  2369. yesterdayAS := NewAllocationSet(yesterday, today)
  2370. yesterdayAS.Set(NewMockUnitAllocation("", yesterday, day, nil))
  2371. todayAS := NewAllocationSet(today, tomorrow)
  2372. todayAS.Set(NewMockUnitAllocation("", today, day, nil))
  2373. asr := NewAllocationSetRange(ago4dAS, ago3dAS, ago2dAS, yesterdayAS, todayAS)
  2374. asr, err := asr.Accumulate(AccumulateOptionNone)
  2375. if err != nil {
  2376. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2377. }
  2378. if len(asr.Allocations) != 5 {
  2379. t.Fatalf("expected 5 allocation sets, got:%d", len(asr.Allocations))
  2380. }
  2381. }
  2382. func TestAllocationSetRange_AccumulateBy_All(t *testing.T) {
  2383. ago4d := time.Now().UTC().Truncate(day).Add(-4 * day)
  2384. ago3d := time.Now().UTC().Truncate(day).Add(-3 * day)
  2385. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2386. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2387. today := time.Now().UTC().Truncate(day)
  2388. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2389. ago4dAS := NewAllocationSet(ago4d, ago3d)
  2390. ago4dAS.Set(NewMockUnitAllocation("4", ago4d, day, nil))
  2391. ago3dAS := NewAllocationSet(ago3d, ago2d)
  2392. ago3dAS.Set(NewMockUnitAllocation("a", ago3d, day, nil))
  2393. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2394. ago2dAS.Set(NewMockUnitAllocation("", ago2d, day, nil))
  2395. yesterdayAS := NewAllocationSet(yesterday, today)
  2396. yesterdayAS.Set(NewMockUnitAllocation("", yesterday, day, nil))
  2397. todayAS := NewAllocationSet(today, tomorrow)
  2398. todayAS.Set(NewMockUnitAllocation("", today, day, nil))
  2399. asr := NewAllocationSetRange(ago4dAS, ago3dAS, ago2dAS, yesterdayAS, todayAS)
  2400. asr, err := asr.Accumulate(AccumulateOptionAll)
  2401. if err != nil {
  2402. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2403. }
  2404. if len(asr.Allocations) != 1 {
  2405. t.Fatalf("expected 1 allocation set, got:%d", len(asr.Allocations))
  2406. }
  2407. allocMap := asr.Allocations[0].Allocations
  2408. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  2409. if alloc.Minutes() != 4320.0 {
  2410. t.Errorf("accumulating AllocationSetRange: expected %f minutes; actual %f", 4320.0, alloc.Minutes())
  2411. }
  2412. }
  2413. func TestAllocationSetRange_AccumulateBy_Hour(t *testing.T) {
  2414. ago4h := time.Now().UTC().Truncate(time.Hour).Add(-4 * time.Hour)
  2415. ago3h := time.Now().UTC().Truncate(time.Hour).Add(-3 * time.Hour)
  2416. ago2h := time.Now().UTC().Truncate(time.Hour).Add(-2 * time.Hour)
  2417. ago1h := time.Now().UTC().Truncate(time.Hour).Add(-time.Hour)
  2418. currentHour := time.Now().UTC().Truncate(time.Hour)
  2419. nextHour := time.Now().UTC().Truncate(time.Hour).Add(time.Hour)
  2420. ago4hAS := NewAllocationSet(ago4h, ago3h)
  2421. ago4hAS.Set(NewMockUnitAllocation("4", ago4h, time.Hour, nil))
  2422. ago3hAS := NewAllocationSet(ago3h, ago2h)
  2423. ago3hAS.Set(NewMockUnitAllocation("a", ago3h, time.Hour, nil))
  2424. ago2hAS := NewAllocationSet(ago2h, ago1h)
  2425. ago2hAS.Set(NewMockUnitAllocation("", ago2h, time.Hour, nil))
  2426. ago1hAS := NewAllocationSet(ago1h, currentHour)
  2427. ago1hAS.Set(NewMockUnitAllocation("", ago1h, time.Hour, nil))
  2428. currentHourAS := NewAllocationSet(currentHour, nextHour)
  2429. currentHourAS.Set(NewMockUnitAllocation("", currentHour, time.Hour, nil))
  2430. asr := NewAllocationSetRange(ago4hAS, ago3hAS, ago2hAS, ago1hAS, currentHourAS)
  2431. asr, err := asr.Accumulate(AccumulateOptionHour)
  2432. if err != nil {
  2433. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2434. }
  2435. if len(asr.Allocations) != 5 {
  2436. t.Fatalf("expected 5 allocation sets, got:%d", len(asr.Allocations))
  2437. }
  2438. allocMap := asr.Allocations[0].Allocations
  2439. alloc := allocMap["4"]
  2440. if alloc.Minutes() != 60.0 {
  2441. t.Errorf("accumulating AllocationSetRange: expected %f minutes; actual %f", 60.0, alloc.Minutes())
  2442. }
  2443. }
  2444. func TestAllocationSetRange_AccumulateBy_Day_From_Day(t *testing.T) {
  2445. ago4d := time.Now().UTC().Truncate(day).Add(-4 * day)
  2446. ago3d := time.Now().UTC().Truncate(day).Add(-3 * day)
  2447. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2448. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2449. today := time.Now().UTC().Truncate(day)
  2450. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2451. ago4dAS := NewAllocationSet(ago4d, ago3d)
  2452. ago4dAS.Set(NewMockUnitAllocation("4", ago4d, day, nil))
  2453. ago3dAS := NewAllocationSet(ago3d, ago2d)
  2454. ago3dAS.Set(NewMockUnitAllocation("a", ago3d, day, nil))
  2455. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2456. ago2dAS.Set(NewMockUnitAllocation("", ago2d, day, nil))
  2457. yesterdayAS := NewAllocationSet(yesterday, today)
  2458. yesterdayAS.Set(NewMockUnitAllocation("", yesterday, day, nil))
  2459. todayAS := NewAllocationSet(today, tomorrow)
  2460. todayAS.Set(NewMockUnitAllocation("", today, day, nil))
  2461. asr := NewAllocationSetRange(ago4dAS, ago3dAS, ago2dAS, yesterdayAS, todayAS)
  2462. asr, err := asr.Accumulate(AccumulateOptionDay)
  2463. if err != nil {
  2464. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2465. }
  2466. if len(asr.Allocations) != 5 {
  2467. t.Fatalf("expected 5 allocation sets, got:%d", len(asr.Allocations))
  2468. }
  2469. allocMap := asr.Allocations[0].Allocations
  2470. alloc := allocMap["4"]
  2471. if alloc.Minutes() != 1440.0 {
  2472. t.Errorf("accumulating AllocationSetRange: expected %f minutes; actual %f", 1440.0, alloc.Minutes())
  2473. }
  2474. }
  2475. func TestAllocationSetRange_AccumulateBy_Day_From_Hours(t *testing.T) {
  2476. ago4h := time.Now().UTC().Truncate(time.Hour).Add(-4 * time.Hour)
  2477. ago3h := time.Now().UTC().Truncate(time.Hour).Add(-3 * time.Hour)
  2478. ago2h := time.Now().UTC().Truncate(time.Hour).Add(-2 * time.Hour)
  2479. ago1h := time.Now().UTC().Truncate(time.Hour).Add(-time.Hour)
  2480. currentHour := time.Now().UTC().Truncate(time.Hour)
  2481. nextHour := time.Now().UTC().Truncate(time.Hour).Add(time.Hour)
  2482. ago4hAS := NewAllocationSet(ago4h, ago3h)
  2483. ago4hAS.Set(NewMockUnitAllocation("", ago4h, time.Hour, nil))
  2484. ago3hAS := NewAllocationSet(ago3h, ago2h)
  2485. ago3hAS.Set(NewMockUnitAllocation("", ago3h, time.Hour, nil))
  2486. ago2hAS := NewAllocationSet(ago2h, ago1h)
  2487. ago2hAS.Set(NewMockUnitAllocation("", ago2h, time.Hour, nil))
  2488. ago1hAS := NewAllocationSet(ago1h, currentHour)
  2489. ago1hAS.Set(NewMockUnitAllocation("", ago1h, time.Hour, nil))
  2490. currentHourAS := NewAllocationSet(currentHour, nextHour)
  2491. currentHourAS.Set(NewMockUnitAllocation("", currentHour, time.Hour, nil))
  2492. asr := NewAllocationSetRange(ago4hAS, ago3hAS, ago2hAS, ago1hAS, currentHourAS)
  2493. asr, err := asr.Accumulate(AccumulateOptionDay)
  2494. if err != nil {
  2495. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2496. }
  2497. if len(asr.Allocations) != 1 && len(asr.Allocations) != 2 {
  2498. t.Fatalf("expected 1 allocation set, got:%d", len(asr.Allocations))
  2499. }
  2500. allocMap := asr.Allocations[0].Allocations
  2501. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  2502. if alloc.Minutes() > 300.0 {
  2503. t.Errorf("accumulating AllocationSetRange: expected %f or less minutes; actual %f", 300.0, alloc.Minutes())
  2504. }
  2505. }
  2506. func TestAllocationSetRange_AccumulateBy_Week(t *testing.T) {
  2507. ago9d := time.Now().UTC().Truncate(day).Add(-9 * day)
  2508. ago8d := time.Now().UTC().Truncate(day).Add(-8 * day)
  2509. ago7d := time.Now().UTC().Truncate(day).Add(-7 * day)
  2510. ago6d := time.Now().UTC().Truncate(day).Add(-6 * day)
  2511. ago5d := time.Now().UTC().Truncate(day).Add(-5 * day)
  2512. ago4d := time.Now().UTC().Truncate(day).Add(-4 * day)
  2513. ago3d := time.Now().UTC().Truncate(day).Add(-3 * day)
  2514. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2515. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2516. today := time.Now().UTC().Truncate(day)
  2517. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2518. ago9dAS := NewAllocationSet(ago9d, ago8d)
  2519. ago9dAS.Set(NewMockUnitAllocation("4", ago9d, day, nil))
  2520. ago8dAS := NewAllocationSet(ago8d, ago7d)
  2521. ago8dAS.Set(NewMockUnitAllocation("4", ago8d, day, nil))
  2522. ago7dAS := NewAllocationSet(ago7d, ago6d)
  2523. ago7dAS.Set(NewMockUnitAllocation("4", ago7d, day, nil))
  2524. ago6dAS := NewAllocationSet(ago6d, ago5d)
  2525. ago6dAS.Set(NewMockUnitAllocation("4", ago6d, day, nil))
  2526. ago5dAS := NewAllocationSet(ago5d, ago4d)
  2527. ago5dAS.Set(NewMockUnitAllocation("4", ago5d, day, nil))
  2528. ago4dAS := NewAllocationSet(ago4d, ago3d)
  2529. ago4dAS.Set(NewMockUnitAllocation("4", ago4d, day, nil))
  2530. ago3dAS := NewAllocationSet(ago3d, ago2d)
  2531. ago3dAS.Set(NewMockUnitAllocation("a", ago3d, day, nil))
  2532. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2533. ago2dAS.Set(NewMockUnitAllocation("", ago2d, day, nil))
  2534. yesterdayAS := NewAllocationSet(yesterday, today)
  2535. yesterdayAS.Set(NewMockUnitAllocation("", yesterday, day, nil))
  2536. todayAS := NewAllocationSet(today, tomorrow)
  2537. todayAS.Set(NewMockUnitAllocation("", today, day, nil))
  2538. asr := NewAllocationSetRange(ago9dAS, ago8dAS, ago7dAS, ago6dAS, ago5dAS, ago4dAS, ago3dAS, ago2dAS, yesterdayAS, todayAS)
  2539. asr, err := asr.Accumulate(AccumulateOptionWeek)
  2540. if err != nil {
  2541. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2542. }
  2543. if len(asr.Allocations) != 2 && len(asr.Allocations) != 3 {
  2544. t.Fatalf("expected 2 or 3 allocation sets, got:%d", len(asr.Allocations))
  2545. }
  2546. for _, as := range asr.Allocations {
  2547. if as.Window.Duration() < time.Hour*24 || as.Window.Duration() > time.Hour*24*7 {
  2548. t.Fatalf("expected window duration to be between 1 and 7 days, got:%s", as.Window.Duration().String())
  2549. }
  2550. }
  2551. }
  2552. func TestAllocationSetRange_AccumulateBy_Month(t *testing.T) {
  2553. prevMonth1stDay := time.Date(2020, 01, 29, 0, 0, 0, 0, time.UTC)
  2554. prevMonth2ndDay := time.Date(2020, 01, 30, 0, 0, 0, 0, time.UTC)
  2555. prevMonth3ndDay := time.Date(2020, 01, 31, 0, 0, 0, 0, time.UTC)
  2556. nextMonth1stDay := time.Date(2020, 02, 01, 0, 0, 0, 0, time.UTC)
  2557. nextMonth2ndDay := time.Date(2020, 02, 02, 0, 0, 0, 0, time.UTC)
  2558. prev1AS := NewAllocationSet(prevMonth1stDay, prevMonth2ndDay)
  2559. prev1AS.Set(NewMockUnitAllocation("", prevMonth1stDay, day, nil))
  2560. prev2AS := NewAllocationSet(prevMonth2ndDay, prevMonth3ndDay)
  2561. prev2AS.Set(NewMockUnitAllocation("", prevMonth2ndDay, day, nil))
  2562. prev3AS := NewAllocationSet(prevMonth3ndDay, nextMonth1stDay)
  2563. prev3AS.Set(NewMockUnitAllocation("", prevMonth3ndDay, day, nil))
  2564. nextAS := NewAllocationSet(nextMonth1stDay, nextMonth2ndDay)
  2565. nextAS.Set(NewMockUnitAllocation("", nextMonth1stDay, day, nil))
  2566. asr := NewAllocationSetRange(prev1AS, prev2AS, prev3AS, nextAS)
  2567. asr, err := asr.Accumulate(AccumulateOptionMonth)
  2568. if err != nil {
  2569. t.Fatalf("unexpected error calling accumulateBy: %s", err)
  2570. }
  2571. if len(asr.Allocations) != 2 {
  2572. t.Fatalf("expected 2 allocation sets, got:%d", len(asr.Allocations))
  2573. }
  2574. for _, as := range asr.Allocations {
  2575. if as.Window.Duration() < time.Hour*24 || as.Window.Duration() > time.Hour*24*31 {
  2576. t.Fatalf("expected window duration to be between 1 and 7 days, got:%s", as.Window.Duration().String())
  2577. }
  2578. }
  2579. }
  2580. // TODO niko/etl
  2581. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  2582. // TODO niko/etl
  2583. // func TestAllocationSetRange_Append(t *testing.T) {}
  2584. // TODO niko/etl
  2585. // func TestAllocationSetRange_Each(t *testing.T) {}
  2586. // TODO niko/etl
  2587. // func TestAllocationSetRange_Get(t *testing.T) {}
  2588. func TestAllocationSetRange_InsertRange(t *testing.T) {
  2589. // Set up
  2590. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2591. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2592. today := time.Now().UTC().Truncate(day)
  2593. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2594. unit := NewMockUnitAllocation("", today, day, nil)
  2595. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2596. ago2dAS.Set(NewMockUnitAllocation("a", ago2d, day, nil))
  2597. ago2dAS.Set(NewMockUnitAllocation("b", ago2d, day, nil))
  2598. ago2dAS.Set(NewMockUnitAllocation("c", ago2d, day, nil))
  2599. yesterdayAS := NewAllocationSet(yesterday, today)
  2600. yesterdayAS.Set(NewMockUnitAllocation("a", yesterday, day, nil))
  2601. yesterdayAS.Set(NewMockUnitAllocation("b", yesterday, day, nil))
  2602. yesterdayAS.Set(NewMockUnitAllocation("c", yesterday, day, nil))
  2603. todayAS := NewAllocationSet(today, tomorrow)
  2604. todayAS.Set(NewMockUnitAllocation("a", today, day, nil))
  2605. todayAS.Set(NewMockUnitAllocation("b", today, day, nil))
  2606. todayAS.Set(NewMockUnitAllocation("c", today, day, nil))
  2607. var nilASR *AllocationSetRange
  2608. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  2609. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  2610. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  2611. var err error
  2612. // Expect an error calling InsertRange on nil
  2613. err = nilASR.InsertRange(thatASR)
  2614. if err == nil {
  2615. t.Fatalf("expected error, got nil")
  2616. }
  2617. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  2618. err = thisASR.InsertRange(nil)
  2619. if err != nil {
  2620. t.Fatalf("unexpected error: %s", err)
  2621. }
  2622. for _, as := range thisASR.Allocations {
  2623. for k, a := range as.Allocations {
  2624. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  2625. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2626. }
  2627. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  2628. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2629. }
  2630. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  2631. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2632. }
  2633. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  2634. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2635. }
  2636. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  2637. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2638. }
  2639. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  2640. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2641. }
  2642. if !util.IsApproximately(a.PVByteHours(), unit.PVByteHours()) {
  2643. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2644. }
  2645. if !util.IsApproximately(a.PVCost(), unit.PVCost()) {
  2646. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2647. }
  2648. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  2649. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2650. }
  2651. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  2652. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2653. }
  2654. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  2655. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2656. }
  2657. }
  2658. }
  2659. // Expect an error calling InsertRange with a range exceeding the receiver
  2660. err = thisASR.InsertRange(longASR)
  2661. if err == nil {
  2662. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  2663. }
  2664. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  2665. // precisely double when inserting a range that only has a duplicate of
  2666. // "yesterday", but no entry for "today"
  2667. err = thisASR.InsertRange(thatASR)
  2668. if err != nil {
  2669. t.Fatalf("unexpected error: %s", err)
  2670. }
  2671. yAS, err := thisASR.Get(0)
  2672. for k, a := range yAS.Allocations {
  2673. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  2674. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2675. }
  2676. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  2677. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2678. }
  2679. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  2680. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2681. }
  2682. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  2683. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2684. }
  2685. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  2686. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2687. }
  2688. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  2689. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2690. }
  2691. if !util.IsApproximately(a.PVByteHours(), 2*unit.PVByteHours()) {
  2692. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2693. }
  2694. if !util.IsApproximately(a.PVCost(), 2*unit.PVCost()) {
  2695. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2696. }
  2697. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  2698. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2699. }
  2700. if !util.IsApproximately(a.LoadBalancerCost, 2*unit.LoadBalancerCost) {
  2701. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2702. }
  2703. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  2704. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2705. }
  2706. }
  2707. tAS, err := thisASR.Get(1)
  2708. for k, a := range tAS.Allocations {
  2709. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  2710. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2711. }
  2712. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  2713. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2714. }
  2715. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  2716. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2717. }
  2718. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  2719. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2720. }
  2721. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  2722. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2723. }
  2724. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  2725. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2726. }
  2727. if !util.IsApproximately(a.PVByteHours(), unit.PVByteHours()) {
  2728. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2729. }
  2730. if !util.IsApproximately(a.PVCost(), unit.PVCost()) {
  2731. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2732. }
  2733. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  2734. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2735. }
  2736. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  2737. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2738. }
  2739. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  2740. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2741. }
  2742. }
  2743. }
  2744. // TODO niko/etl
  2745. // func TestAllocationSetRange_Length(t *testing.T) {}
  2746. func TestAllocationSetRange_MarshalJSON(t *testing.T) {
  2747. tests := []struct {
  2748. name string
  2749. arg *AllocationSetRange
  2750. expected *AllocationSetRange
  2751. }{
  2752. {
  2753. name: "Nil ASR",
  2754. arg: nil,
  2755. },
  2756. {
  2757. name: "Nil AS in ASR",
  2758. arg: NewAllocationSetRange(nil),
  2759. },
  2760. {
  2761. name: "Normal ASR",
  2762. arg: &AllocationSetRange{
  2763. Allocations: []*AllocationSet{
  2764. {
  2765. Allocations: map[string]*Allocation{
  2766. "a": {
  2767. Start: time.Now().UTC().Truncate(day),
  2768. },
  2769. },
  2770. },
  2771. },
  2772. },
  2773. },
  2774. }
  2775. for _, test := range tests {
  2776. bytes, err := json.Marshal(test.arg)
  2777. if err != nil {
  2778. t.Fatalf("ASR Marshal: test %s, unexpected error: %s", test.name, err)
  2779. }
  2780. var testASR []*AllocationSet
  2781. marshaled := &testASR
  2782. err = json.Unmarshal(bytes, marshaled)
  2783. if err != nil {
  2784. t.Fatalf("ASR Unmarshal: test %s: unexpected error: %s", test.name, err)
  2785. }
  2786. if test.arg.Length() != len(testASR) {
  2787. t.Fatalf("ASR Unmarshal: test %s: length mutated in encoding: expected %d but got %d", test.name, test.arg.Length(), len(testASR))
  2788. }
  2789. // Allocations don't unmarshal back from json
  2790. }
  2791. }
  2792. // TODO niko/etl
  2793. // func TestAllocationSetRange_Slice(t *testing.T) {}
  2794. // TODO niko/etl
  2795. // func TestAllocationSetRange_Window(t *testing.T) {}
  2796. func TestAllocationSetRange_Start(t *testing.T) {
  2797. tests := []struct {
  2798. name string
  2799. arg *AllocationSetRange
  2800. expectError bool
  2801. expected time.Time
  2802. }{
  2803. {
  2804. name: "Empty ASR",
  2805. arg: nil,
  2806. expectError: true,
  2807. },
  2808. {
  2809. name: "Single allocation",
  2810. arg: &AllocationSetRange{
  2811. Allocations: []*AllocationSet{
  2812. {
  2813. Allocations: map[string]*Allocation{
  2814. "a": {
  2815. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2816. },
  2817. },
  2818. },
  2819. },
  2820. },
  2821. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2822. },
  2823. {
  2824. name: "Two allocations",
  2825. arg: &AllocationSetRange{
  2826. Allocations: []*AllocationSet{
  2827. {
  2828. Allocations: map[string]*Allocation{
  2829. "a": {
  2830. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2831. },
  2832. "b": {
  2833. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2834. },
  2835. },
  2836. },
  2837. },
  2838. },
  2839. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2840. },
  2841. {
  2842. name: "Two AllocationSets",
  2843. arg: &AllocationSetRange{
  2844. Allocations: []*AllocationSet{
  2845. {
  2846. Allocations: map[string]*Allocation{
  2847. "a": {
  2848. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2849. },
  2850. },
  2851. },
  2852. {
  2853. Allocations: map[string]*Allocation{
  2854. "b": {
  2855. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2856. },
  2857. },
  2858. },
  2859. },
  2860. },
  2861. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2862. },
  2863. }
  2864. for _, test := range tests {
  2865. result, err := test.arg.Start()
  2866. if test.expectError && err != nil {
  2867. continue
  2868. }
  2869. if test.expectError && err == nil {
  2870. t.Errorf("%s: expected error and got none", test.name)
  2871. } else if result != test.expected {
  2872. t.Errorf("%s: expected %s but got %s", test.name, test.expected, result)
  2873. }
  2874. }
  2875. }
  2876. func TestAllocationSetRange_End(t *testing.T) {
  2877. tests := []struct {
  2878. name string
  2879. arg *AllocationSetRange
  2880. expectError bool
  2881. expected time.Time
  2882. }{
  2883. {
  2884. name: "Empty ASR",
  2885. arg: nil,
  2886. expectError: true,
  2887. },
  2888. {
  2889. name: "Single allocation",
  2890. arg: &AllocationSetRange{
  2891. Allocations: []*AllocationSet{
  2892. {
  2893. Allocations: map[string]*Allocation{
  2894. "a": {
  2895. End: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2896. },
  2897. },
  2898. },
  2899. },
  2900. },
  2901. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2902. },
  2903. {
  2904. name: "Two allocations",
  2905. arg: &AllocationSetRange{
  2906. Allocations: []*AllocationSet{
  2907. {
  2908. Allocations: map[string]*Allocation{
  2909. "a": {
  2910. End: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2911. },
  2912. "b": {
  2913. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2914. },
  2915. },
  2916. },
  2917. },
  2918. },
  2919. expected: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2920. },
  2921. {
  2922. name: "Two AllocationSets",
  2923. arg: &AllocationSetRange{
  2924. Allocations: []*AllocationSet{
  2925. {
  2926. Allocations: map[string]*Allocation{
  2927. "a": {
  2928. End: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2929. },
  2930. },
  2931. },
  2932. {
  2933. Allocations: map[string]*Allocation{
  2934. "b": {
  2935. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2936. },
  2937. },
  2938. },
  2939. },
  2940. },
  2941. expected: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2942. },
  2943. }
  2944. for _, test := range tests {
  2945. result, err := test.arg.End()
  2946. if test.expectError && err != nil {
  2947. continue
  2948. }
  2949. if test.expectError && err == nil {
  2950. t.Errorf("%s: expected error and got none", test.name)
  2951. } else if result != test.expected {
  2952. t.Errorf("%s: expected %s but got %s", test.name, test.expected, result)
  2953. }
  2954. }
  2955. }
  2956. func TestAllocationSetRange_Minutes(t *testing.T) {
  2957. tests := []struct {
  2958. name string
  2959. arg *AllocationSetRange
  2960. expected float64
  2961. }{
  2962. {
  2963. name: "Empty ASR",
  2964. arg: nil,
  2965. expected: 0,
  2966. },
  2967. {
  2968. name: "Single allocation",
  2969. arg: &AllocationSetRange{
  2970. Allocations: []*AllocationSet{
  2971. {
  2972. Allocations: map[string]*Allocation{
  2973. "a": {
  2974. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2975. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2976. },
  2977. },
  2978. },
  2979. },
  2980. },
  2981. expected: 24 * 60,
  2982. },
  2983. {
  2984. name: "Two allocations",
  2985. arg: &AllocationSetRange{
  2986. Allocations: []*AllocationSet{
  2987. {
  2988. Allocations: map[string]*Allocation{
  2989. "a": {
  2990. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2991. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2992. },
  2993. "b": {
  2994. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2995. End: time.Date(1970, 1, 3, 0, 0, 0, 0, time.UTC),
  2996. },
  2997. },
  2998. },
  2999. },
  3000. },
  3001. expected: 2 * 24 * 60,
  3002. },
  3003. {
  3004. name: "Two AllocationSets",
  3005. arg: &AllocationSetRange{
  3006. Allocations: []*AllocationSet{
  3007. {
  3008. Allocations: map[string]*Allocation{
  3009. "a": {
  3010. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  3011. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  3012. },
  3013. },
  3014. },
  3015. {
  3016. Allocations: map[string]*Allocation{
  3017. "b": {
  3018. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  3019. End: time.Date(1970, 1, 3, 0, 0, 0, 0, time.UTC),
  3020. },
  3021. },
  3022. },
  3023. },
  3024. },
  3025. expected: 2 * 24 * 60,
  3026. },
  3027. }
  3028. for _, test := range tests {
  3029. result := test.arg.Minutes()
  3030. if result != test.expected {
  3031. t.Errorf("%s: expected %f but got %f", test.name, test.expected, result)
  3032. }
  3033. }
  3034. }
  3035. func TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate(t *testing.T) {
  3036. today := time.Now().Round(day)
  3037. start := today.AddDate(0, 0, -4)
  3038. var allocationSets []*AllocationSet
  3039. for i := 0; i < 4; i++ {
  3040. allocationSets = append(allocationSets, GenerateMockAllocationSet(start))
  3041. start = start.AddDate(0, 0, 1)
  3042. }
  3043. var originalAllocationSets []*AllocationSet
  3044. for _, as := range allocationSets {
  3045. originalAllocationSets = append(originalAllocationSets, as.Clone())
  3046. }
  3047. asr := NewAllocationSetRange()
  3048. for _, as := range allocationSets {
  3049. asr.Append(as.Clone())
  3050. }
  3051. expected, err := asr.accumulate()
  3052. if err != nil {
  3053. t.Errorf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: AllocationSetRange.Accumulate() returned an error\n")
  3054. }
  3055. var got *AllocationSet
  3056. for i := 0; i < len(allocationSets); i++ {
  3057. got, err = got.Accumulate(allocationSets[i])
  3058. if err != nil {
  3059. t.Errorf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: got.Accumulate(allocationSets[%d]) returned an error\n", i)
  3060. }
  3061. }
  3062. // compare the got and expected Allocation sets, ensure that they match
  3063. if len(got.Allocations) != len(expected.Allocations) {
  3064. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: length of got.Allocations does not match length of expected.Allocations\n")
  3065. }
  3066. for key, a := range got.Allocations {
  3067. if _, ok := expected.Allocations[key]; !ok {
  3068. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: got.Allocations[%s] not found in expected.Allocations\n", key)
  3069. }
  3070. if !a.Equal(expected.Allocations[key]) {
  3071. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: got.Allocations[%s] did not match expected.Allocations[%[1]s]", key)
  3072. }
  3073. }
  3074. if len(got.ExternalKeys) != len(expected.ExternalKeys) {
  3075. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: length of got.ExternalKeys does not match length of expected.ExternalKeys\n")
  3076. }
  3077. if len(got.IdleKeys) != len(expected.IdleKeys) {
  3078. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: length of got.IdleKeys does not match length of expected.IdleKeys\n")
  3079. }
  3080. if !got.Window.Start().UTC().Equal(expected.Window.Start().UTC()) {
  3081. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: Window.start: got:%s, expected:%s\n", got.Window.Start(), expected.Window.Start())
  3082. }
  3083. if !got.Window.End().UTC().Equal(expected.Window.End().UTC()) {
  3084. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: Window.end: got:%s, expected:%s\n", got.Window.End(), expected.Window.End())
  3085. }
  3086. for i := range allocationSets {
  3087. for key, allocation := range allocationSets[i].Allocations {
  3088. if !allocation.Equal(originalAllocationSets[i].Allocations[key]) {
  3089. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: allocationSet has been mutated in Accumulate; allocationSet: %d, allocation: %s\n", i, key)
  3090. }
  3091. }
  3092. }
  3093. }
  3094. func Test_AggregateByService_UnmountedLBs(t *testing.T) {
  3095. end := time.Now().UTC().Truncate(day)
  3096. start := end.Add(-day)
  3097. normalProps := &AllocationProperties{
  3098. Cluster: "cluster-one",
  3099. Container: "nginx-plus-nginx-ingress",
  3100. Controller: "nginx-plus-nginx-ingress",
  3101. ControllerKind: "deployment",
  3102. Namespace: "nginx-plus",
  3103. Pod: "nginx-plus-nginx-ingress-123a4b5678-ab12c",
  3104. ProviderID: "test",
  3105. Node: "testnode",
  3106. Services: []string{
  3107. "nginx-plus-nginx-ingress",
  3108. },
  3109. }
  3110. problematicProps := &AllocationProperties{
  3111. Cluster: "cluster-one",
  3112. Container: UnmountedSuffix,
  3113. Namespace: UnmountedSuffix,
  3114. Pod: UnmountedSuffix,
  3115. ProviderID: "test",
  3116. Node: "testnode",
  3117. Services: []string{
  3118. "nginx-plus-nginx-ingress",
  3119. "ingress-nginx-controller",
  3120. "pacman",
  3121. },
  3122. }
  3123. idle := NewMockUnitAllocation(fmt.Sprintf("cluster-one/%s", IdleSuffix), start, day, &AllocationProperties{
  3124. Cluster: "cluster-one",
  3125. })
  3126. // this allocation is the main point of the test; an unmounted LB that has services
  3127. problematicAllocation := NewMockUnitAllocation("cluster-one//__unmounted__/__unmounted__/__unmounted__", start, day, problematicProps)
  3128. two := NewMockUnitAllocation("cluster-one//nginx-plus/nginx-plus-nginx-ingress-123a4b5678-ab12c/nginx-plus-nginx-ingress", start, day, normalProps)
  3129. three := NewMockUnitAllocation("cluster-one//nginx-plus/nginx-plus-nginx-ingress-123a4b5678-ab12c/nginx-plus-nginx-ingress", start, day, normalProps)
  3130. four := NewMockUnitAllocation("cluster-one//nginx-plus/nginx-plus-nginx-ingress-123a4b5678-ab12c/nginx-plus-nginx-ingress", start, day, normalProps)
  3131. problematicAllocation.ExternalCost = 2.35
  3132. two.ExternalCost = 1.35
  3133. three.ExternalCost = 2.60
  3134. four.ExternalCost = 4.30
  3135. set := NewAllocationSet(start, start.Add(day), problematicAllocation, two, three, four)
  3136. set.Insert(idle)
  3137. set.AggregateBy([]string{AllocationServiceProp}, &AllocationAggregationOptions{
  3138. Filter: ops.Contains(afilter.FieldServices, "nginx-plus-nginx-ingress"),
  3139. })
  3140. for _, alloc := range set.Allocations {
  3141. if !strings.Contains(UnmountedSuffix, alloc.Name) {
  3142. props := alloc.Properties
  3143. if props.Cluster == UnmountedSuffix {
  3144. t.Error("cluster unmounted")
  3145. }
  3146. if props.Container == UnmountedSuffix {
  3147. t.Error("container unmounted")
  3148. }
  3149. if props.Namespace == UnmountedSuffix {
  3150. t.Error("namespace unmounted")
  3151. }
  3152. if props.Pod == UnmountedSuffix {
  3153. t.Error("pod unmounted")
  3154. }
  3155. if props.Controller == UnmountedSuffix {
  3156. t.Error("controller unmounted")
  3157. }
  3158. }
  3159. }
  3160. spew.Config.DisableMethods = true
  3161. t.Logf("%s", spew.Sdump(set.Allocations))
  3162. }
  3163. func Test_DetermineSharingName(t *testing.T) {
  3164. var alloc *Allocation
  3165. var name string
  3166. var err error
  3167. // test nil allocation with nil options
  3168. name, err = alloc.determineSharingName(nil)
  3169. if err == nil {
  3170. t.Fatalf("determineSharingName: expected error; actual nil")
  3171. }
  3172. // test nil with non-nil options
  3173. name, err = alloc.determineSharingName(&AllocationAggregationOptions{})
  3174. if err == nil {
  3175. t.Fatalf("determineSharingName: expected error; actual nil")
  3176. }
  3177. alloc = &Allocation{}
  3178. alloc.Properties = &AllocationProperties{
  3179. Cluster: "cluster1",
  3180. Labels: map[string]string{
  3181. "app": "app1",
  3182. "env": "env1",
  3183. },
  3184. Namespace: "namespace1",
  3185. }
  3186. // test non-nil allocation with nil options
  3187. name, err = alloc.determineSharingName(nil)
  3188. if err != nil {
  3189. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3190. } else if err != nil || name != "unknown" {
  3191. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3192. }
  3193. // test non-nil allocation with empty options
  3194. options := &AllocationAggregationOptions{}
  3195. name, err = alloc.determineSharingName(options)
  3196. if err != nil {
  3197. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3198. } else if err != nil || name != "unknown" {
  3199. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3200. }
  3201. // test non-nil allocation with matching namespace options
  3202. options.SharedNamespaces = []string{"namespace1"}
  3203. name, err = alloc.determineSharingName(options)
  3204. if err != nil {
  3205. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3206. } else if err != nil || name != "namespace1" {
  3207. t.Fatalf("determineSharingName: expected \"namespace1\"; actual \"%s\"", name)
  3208. }
  3209. // test non-nil allocation with non-matching namespace options
  3210. options.SharedNamespaces = []string{"namespace2"}
  3211. name, err = alloc.determineSharingName(options)
  3212. if err != nil {
  3213. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3214. } else if err != nil || name != "unknown" {
  3215. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3216. }
  3217. // test non-nil allocation with matching label options
  3218. options.SharedNamespaces = nil
  3219. options.SharedLabels = map[string][]string{
  3220. "app": {"app1"},
  3221. }
  3222. name, err = alloc.determineSharingName(options)
  3223. if err != nil {
  3224. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3225. } else if err != nil || name != "app1" {
  3226. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3227. }
  3228. // test non-nil allocation with partial-matching label options
  3229. options.SharedLabels = map[string][]string{
  3230. "app": {"app1", "app2"},
  3231. }
  3232. name, err = alloc.determineSharingName(options)
  3233. if err != nil {
  3234. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3235. } else if err != nil || name != "app1" {
  3236. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3237. }
  3238. // test non-nil allocation with non-matching label options
  3239. options.SharedLabels = map[string][]string{
  3240. "app": {"app2"},
  3241. }
  3242. name, err = alloc.determineSharingName(options)
  3243. if err != nil {
  3244. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3245. } else if err != nil || name != "unknown" {
  3246. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3247. }
  3248. // test non-nil allocation with matching namespace and label options
  3249. options.SharedNamespaces = []string{"namespace1"}
  3250. options.SharedLabels = map[string][]string{
  3251. "app": {"app1"},
  3252. }
  3253. name, err = alloc.determineSharingName(options)
  3254. if err != nil {
  3255. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3256. } else if err != nil || name != "app1" {
  3257. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3258. }
  3259. // test non-nil allocation with non-matching namespace and matching label options
  3260. options.SharedNamespaces = []string{"namespace2"}
  3261. options.SharedLabels = map[string][]string{
  3262. "app": {"app1"},
  3263. }
  3264. name, err = alloc.determineSharingName(options)
  3265. if err != nil {
  3266. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3267. } else if err != nil || name != "app1" {
  3268. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3269. }
  3270. // test non-nil allocation with non-matching namespace and non-matching label options
  3271. options.SharedNamespaces = []string{"namespace2"}
  3272. options.SharedLabels = map[string][]string{
  3273. "app": {"app2"},
  3274. }
  3275. name, err = alloc.determineSharingName(options)
  3276. if err != nil {
  3277. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3278. } else if err != nil || name != "unknown" {
  3279. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3280. }
  3281. // test non-nil allocation with multiple matching label options
  3282. alloc.Properties.Labels = map[string]string{
  3283. "app": "app1",
  3284. "env": "env1",
  3285. }
  3286. options.SharedNamespaces = nil
  3287. options.SharedLabels = map[string][]string{
  3288. "app": {"app1"},
  3289. "env": {"env1"},
  3290. }
  3291. name, err = alloc.determineSharingName(options)
  3292. if err != nil {
  3293. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3294. } else if err != nil || name != "app1" {
  3295. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3296. }
  3297. // test non-nil allocation with one matching label option
  3298. alloc.Properties.Labels = map[string]string{
  3299. "app": "app2",
  3300. "env": "env1",
  3301. }
  3302. options.SharedNamespaces = nil
  3303. options.SharedLabels = map[string][]string{
  3304. "app": {"app1"},
  3305. "env": {"env1"},
  3306. }
  3307. name, err = alloc.determineSharingName(options)
  3308. if err != nil {
  3309. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3310. } else if err != nil || name != "env1" {
  3311. t.Fatalf("determineSharingName: expected \"env1\"; actual \"%s\"", name)
  3312. }
  3313. // test non-nil allocation with one matching namespace option
  3314. alloc.Properties.Namespace = "namespace1"
  3315. options.SharedNamespaces = []string{"namespace1", "namespace2"}
  3316. options.SharedLabels = nil
  3317. name, err = alloc.determineSharingName(options)
  3318. if err != nil {
  3319. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3320. } else if err != nil || name != "namespace1" {
  3321. t.Fatalf("determineSharingName: expected \"namespace1\"; actual \"%s\"", name)
  3322. }
  3323. // test non-nil allocation with another one matching namespace option
  3324. alloc.Properties.Namespace = "namespace2"
  3325. options.SharedNamespaces = []string{"namespace1", "namespace2"}
  3326. options.SharedLabels = nil
  3327. name, err = alloc.determineSharingName(options)
  3328. if err != nil {
  3329. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3330. } else if err != nil || name != "namespace2" {
  3331. t.Fatalf("determineSharingName: expected \"namespace2\"; actual \"%s\"", name)
  3332. }
  3333. // test non-nil allocation with non-matching namespace options
  3334. alloc.Properties.Namespace = "namespace3"
  3335. options.SharedNamespaces = []string{"namespace1", "namespace2"}
  3336. name, err = alloc.determineSharingName(options)
  3337. if err != nil {
  3338. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3339. } else if err != nil || name != "unknown" {
  3340. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3341. }
  3342. }
  3343. func TestIsFilterEmptyTrue(t *testing.T) {
  3344. compiler := NewAllocationMatchCompiler(nil)
  3345. matcher, err := compiler.Compile(nil)
  3346. if err != nil {
  3347. t.Fatalf("compiling nil filter: %s", err)
  3348. }
  3349. result := isFilterEmpty(matcher)
  3350. if !result {
  3351. t.Errorf("matcher '%+v' should be reported empty but wasn't", matcher)
  3352. }
  3353. }
  3354. func TestIsFilterEmptyFalse(t *testing.T) {
  3355. compiler := NewAllocationMatchCompiler(nil)
  3356. matcher, err := compiler.Compile(ops.Eq(afilter.FieldClusterID, "test"))
  3357. if err != nil {
  3358. t.Fatalf("compiling nil filter: %s", err)
  3359. }
  3360. result := isFilterEmpty(matcher)
  3361. if result {
  3362. t.Errorf("matcher '%+v' should be not be reported empty but was", matcher)
  3363. }
  3364. }
  3365. func TestAllocation_SanitizeNaN(t *testing.T) {
  3366. tcName := "TestAllocation_SanitizeNaN"
  3367. alloc := getMockAllocation(math.NaN())
  3368. alloc.SanitizeNaN()
  3369. checkAllocation(t, tcName, alloc)
  3370. }
  3371. func checkAllocation(t *testing.T, tcName string, alloc Allocation) {
  3372. v := reflect.ValueOf(alloc)
  3373. checkAllFloat64sForNaN(t, v, tcName)
  3374. vRaw := reflect.ValueOf(*alloc.RawAllocationOnly)
  3375. checkAllFloat64sForNaN(t, vRaw, tcName)
  3376. for _, pv := range alloc.PVs {
  3377. vPV := reflect.ValueOf(*pv)
  3378. checkAllFloat64sForNaN(t, vPV, tcName)
  3379. }
  3380. for _, parc := range alloc.ProportionalAssetResourceCosts {
  3381. vParc := reflect.ValueOf(parc)
  3382. checkAllFloat64sForNaN(t, vParc, tcName)
  3383. }
  3384. for _, scb := range alloc.SharedCostBreakdown {
  3385. vScb := reflect.ValueOf(scb)
  3386. checkAllFloat64sForNaN(t, vScb, tcName)
  3387. }
  3388. for _, lb := range alloc.LoadBalancers {
  3389. vLb := reflect.ValueOf(*lb)
  3390. checkAllFloat64sForNaN(t, vLb, tcName)
  3391. }
  3392. }
  3393. func TestAllocationSet_SanitizeNaN(t *testing.T) {
  3394. allocNaN := getMockAllocation(math.NaN())
  3395. allocNotNaN := getMockAllocation(1.2)
  3396. allocSet := AllocationSet{
  3397. Allocations: map[string]*Allocation{"NaN": &allocNaN, "notNaN": &allocNotNaN},
  3398. }
  3399. allocSet.SanitizeNaN()
  3400. for _, a := range allocSet.Allocations {
  3401. checkAllocation(t, "TestAllocationSet_SanitizeNaN", *a)
  3402. }
  3403. }
  3404. func getMockAllocation(f float64) Allocation {
  3405. alloc := Allocation{
  3406. Name: "mockAllocation",
  3407. Properties: nil,
  3408. Window: Window{},
  3409. Start: time.Time{},
  3410. End: time.Time{},
  3411. CPUCoreHours: f,
  3412. CPUCoreRequestAverage: f,
  3413. CPUCoreUsageAverage: f,
  3414. CPUCost: f,
  3415. CPUCostAdjustment: f,
  3416. GPUHours: f,
  3417. GPUCost: f,
  3418. GPUCostAdjustment: f,
  3419. NetworkTransferBytes: f,
  3420. NetworkReceiveBytes: f,
  3421. NetworkCost: f,
  3422. NetworkCrossZoneCost: f,
  3423. NetworkCrossRegionCost: f,
  3424. NetworkInternetCost: f,
  3425. NetworkCostAdjustment: f,
  3426. LoadBalancerCost: f,
  3427. LoadBalancerCostAdjustment: f,
  3428. PVs: PVAllocations{{Cluster: "testPV", Name: "PVName"}: getMockPVAllocation(math.NaN())},
  3429. PVCostAdjustment: f,
  3430. RAMByteHours: f,
  3431. RAMBytesRequestAverage: f,
  3432. RAMBytesUsageAverage: f,
  3433. RAMCost: f,
  3434. RAMCostAdjustment: f,
  3435. SharedCost: f,
  3436. ExternalCost: f,
  3437. RawAllocationOnly: getMockRawAllocationOnlyData(f),
  3438. ProportionalAssetResourceCosts: ProportionalAssetResourceCosts{"NaN": *getMockPARC(f)},
  3439. SharedCostBreakdown: SharedCostBreakdowns{"NaN": *getMockSharedCostBreakdown(f)},
  3440. LoadBalancers: LbAllocations{"NaN": getMockLbAllocation(f)},
  3441. }
  3442. return alloc
  3443. }
  3444. func TestPVAllocation_SanitizeNaN(t *testing.T) {
  3445. pva := getMockPVAllocation(math.NaN())
  3446. pva.SanitizeNaN()
  3447. v := reflect.ValueOf(*pva)
  3448. checkAllFloat64sForNaN(t, v, "TestPVAllocation_SanitizeNaN")
  3449. }
  3450. func TestPVAllocations_SanitizeNaN(t *testing.T) {
  3451. pvaNaN := getMockPVAllocation(math.NaN())
  3452. pvaNotNaN := getMockPVAllocation(1.2)
  3453. pvs := PVAllocations{{Cluster: "testPV", Name: "PVName1"}: pvaNaN, {Cluster: "testPV", Name: "PVName2"}: pvaNotNaN}
  3454. pvs.SanitizeNaN()
  3455. for _, pv := range pvs {
  3456. v := reflect.ValueOf(*pv)
  3457. checkAllFloat64sForNaN(t, v, "TestPVAllocations_SanitizeNaN")
  3458. }
  3459. }
  3460. func getMockPVAllocation(f float64) *PVAllocation {
  3461. return &PVAllocation{
  3462. ByteHours: f,
  3463. Cost: f,
  3464. }
  3465. }
  3466. func TestRawAllocationOnlyData_SanitizeNaN(t *testing.T) {
  3467. raw := getMockRawAllocationOnlyData(math.NaN())
  3468. raw.SanitizeNaN()
  3469. v := reflect.ValueOf(*raw)
  3470. checkAllFloat64sForNaN(t, v, "TestRawAllocationOnlyData_SanitizeNaN")
  3471. }
  3472. func getMockRawAllocationOnlyData(f float64) *RawAllocationOnlyData {
  3473. return &RawAllocationOnlyData{
  3474. CPUCoreUsageMax: f,
  3475. RAMBytesUsageMax: f,
  3476. }
  3477. }
  3478. func TestLbAllocation_SanitizeNaN(t *testing.T) {
  3479. lbaNaN := getMockLbAllocation(math.NaN())
  3480. lbaNaN.SanitizeNaN()
  3481. v := reflect.ValueOf(*lbaNaN)
  3482. checkAllFloat64sForNaN(t, v, "TestLbAllocation_SanitizeNaN")
  3483. }
  3484. func TestLbAllocations_SanitizeNaN(t *testing.T) {
  3485. lbaNaN := getMockLbAllocation(math.NaN())
  3486. lbaValid := getMockLbAllocation(1.2)
  3487. lbas := LbAllocations{"NaN": lbaNaN, "notNaN": lbaValid}
  3488. lbas.SanitizeNaN()
  3489. for _, lba := range lbas {
  3490. v := reflect.ValueOf(*lba)
  3491. checkAllFloat64sForNaN(t, v, "TestLbAllocations_SanitizeNaN")
  3492. }
  3493. }
  3494. func getMockLbAllocation(f float64) *LbAllocation {
  3495. return &LbAllocation{
  3496. Service: "testLoadBalancer",
  3497. Cost: f,
  3498. Private: false,
  3499. }
  3500. }
  3501. func TestProportionalAssetResourceCosts_SanitizeNaN(t *testing.T) {
  3502. parcAllNaN := getMockPARC(math.NaN())
  3503. parcNotNaN := getMockPARC(1.2)
  3504. parcs := ProportionalAssetResourceCosts{"NaN": *parcAllNaN, "notNaN": *parcNotNaN}
  3505. parcs.SanitizeNaN()
  3506. for _, parc := range parcs {
  3507. v := reflect.ValueOf(parc)
  3508. checkAllFloat64sForNaN(t, v, "TestProportionalAssetResourceCosts_SanitizeNaN")
  3509. }
  3510. }
  3511. func getMockPARC(f float64) *ProportionalAssetResourceCost {
  3512. return &ProportionalAssetResourceCost{
  3513. Cluster: "testCluster",
  3514. Name: "testName",
  3515. Type: "testType",
  3516. ProviderID: "testProvider",
  3517. CPUPercentage: f,
  3518. GPUPercentage: f,
  3519. RAMPercentage: f,
  3520. LoadBalancerPercentage: f,
  3521. PVPercentage: f,
  3522. NodeResourceCostPercentage: f,
  3523. GPUTotalCost: f,
  3524. GPUProportionalCost: f,
  3525. CPUTotalCost: f,
  3526. CPUProportionalCost: f,
  3527. RAMTotalCost: f,
  3528. RAMProportionalCost: f,
  3529. LoadBalancerProportionalCost: f,
  3530. LoadBalancerTotalCost: f,
  3531. PVProportionalCost: f,
  3532. PVTotalCost: f,
  3533. }
  3534. }
  3535. func TestSharedCostBreakdowns_SanitizeNaN(t *testing.T) {
  3536. scbNaN := getMockSharedCostBreakdown(math.NaN())
  3537. scbNotNaN := getMockSharedCostBreakdown(1.2)
  3538. scbs := SharedCostBreakdowns{"NaN": *scbNaN, "notNaN": *scbNotNaN}
  3539. scbs.SanitizeNaN()
  3540. for _, scb := range scbs {
  3541. v := reflect.ValueOf(scb)
  3542. checkAllFloat64sForNaN(t, v, "TestSharedCostBreakdowns_SanitizeNaN")
  3543. }
  3544. }
  3545. func getMockSharedCostBreakdown(f float64) *SharedCostBreakdown {
  3546. return &SharedCostBreakdown{
  3547. Name: "testBreakdown",
  3548. TotalCost: f,
  3549. CPUCost: f,
  3550. GPUCost: f,
  3551. RAMCost: f,
  3552. PVCost: f,
  3553. NetworkCost: f,
  3554. LBCost: f,
  3555. ExternalCost: f,
  3556. }
  3557. }
  3558. func checkAllFloat64sForNaN(t *testing.T, v reflect.Value, testCaseName string) {
  3559. vType := v.Type()
  3560. // go through each field on the struct
  3561. for i := 0; i < v.NumField(); i++ {
  3562. // Check if field is public and can be converted to a float
  3563. if v.Field(i).CanInterface() && v.Field(i).CanFloat() {
  3564. f := v.Field(i).Float()
  3565. if math.IsNaN(f) {
  3566. t.Fatalf("%s: expected not NaN for field: %s, got:NaN", testCaseName, vType.Field(i).Name)
  3567. }
  3568. }
  3569. }
  3570. }