allocation_test.go 129 KB

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