allocation_test.go 127 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926
  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/etl
  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/etl)
  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/etl
  1935. //func TestAllocationSet_Clone(t *testing.T) {}
  1936. // TODO niko/etl
  1937. //func TestAllocationSet_Delete(t *testing.T) {}
  1938. // TODO niko/etl
  1939. //func TestAllocationSet_End(t *testing.T) {}
  1940. // TODO niko/etl
  1941. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  1942. // TODO niko/etl
  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/etl
  2162. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  2163. // TODO niko/etl
  2164. //func TestAllocationSet_Length(t *testing.T) {}
  2165. // TODO niko/etl
  2166. //func TestAllocationSet_Map(t *testing.T) {}
  2167. // TODO niko/etl
  2168. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  2169. // TODO niko/etl
  2170. //func TestAllocationSet_Resolution(t *testing.T) {}
  2171. // TODO niko/etl
  2172. //func TestAllocationSet_Seconds(t *testing.T) {}
  2173. // TODO niko/etl
  2174. //func TestAllocationSet_Set(t *testing.T) {}
  2175. // TODO niko/etl
  2176. //func TestAllocationSet_Start(t *testing.T) {}
  2177. // TODO niko/etl
  2178. //func TestAllocationSet_TotalCost(t *testing.T) {}
  2179. // TODO niko/etl
  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. // TODO niko/etl
  2580. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  2581. // TODO niko/etl
  2582. // func TestAllocationSetRange_Append(t *testing.T) {}
  2583. // TODO niko/etl
  2584. // func TestAllocationSetRange_Each(t *testing.T) {}
  2585. // TODO niko/etl
  2586. // func TestAllocationSetRange_Get(t *testing.T) {}
  2587. func TestAllocationSetRange_InsertRange(t *testing.T) {
  2588. // Set up
  2589. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  2590. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  2591. today := time.Now().UTC().Truncate(day)
  2592. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  2593. unit := NewMockUnitAllocation("", today, day, nil)
  2594. ago2dAS := NewAllocationSet(ago2d, yesterday)
  2595. ago2dAS.Set(NewMockUnitAllocation("a", ago2d, day, nil))
  2596. ago2dAS.Set(NewMockUnitAllocation("b", ago2d, day, nil))
  2597. ago2dAS.Set(NewMockUnitAllocation("c", ago2d, day, nil))
  2598. yesterdayAS := NewAllocationSet(yesterday, today)
  2599. yesterdayAS.Set(NewMockUnitAllocation("a", yesterday, day, nil))
  2600. yesterdayAS.Set(NewMockUnitAllocation("b", yesterday, day, nil))
  2601. yesterdayAS.Set(NewMockUnitAllocation("c", yesterday, day, nil))
  2602. todayAS := NewAllocationSet(today, tomorrow)
  2603. todayAS.Set(NewMockUnitAllocation("a", today, day, nil))
  2604. todayAS.Set(NewMockUnitAllocation("b", today, day, nil))
  2605. todayAS.Set(NewMockUnitAllocation("c", today, day, nil))
  2606. var nilASR *AllocationSetRange
  2607. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  2608. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  2609. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  2610. var err error
  2611. // Expect an error calling InsertRange on nil
  2612. err = nilASR.InsertRange(thatASR)
  2613. if err == nil {
  2614. t.Fatalf("expected error, got nil")
  2615. }
  2616. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  2617. err = thisASR.InsertRange(nil)
  2618. if err != nil {
  2619. t.Fatalf("unexpected error: %s", err)
  2620. }
  2621. for _, as := range thisASR.Allocations {
  2622. for k, a := range as.Allocations {
  2623. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  2624. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2625. }
  2626. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  2627. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2628. }
  2629. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  2630. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2631. }
  2632. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  2633. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2634. }
  2635. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  2636. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2637. }
  2638. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  2639. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2640. }
  2641. if !util.IsApproximately(a.PVByteHours(), unit.PVByteHours()) {
  2642. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2643. }
  2644. if !util.IsApproximately(a.PVCost(), unit.PVCost()) {
  2645. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2646. }
  2647. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  2648. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2649. }
  2650. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  2651. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2652. }
  2653. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  2654. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2655. }
  2656. }
  2657. }
  2658. // Expect an error calling InsertRange with a range exceeding the receiver
  2659. err = thisASR.InsertRange(longASR)
  2660. if err == nil {
  2661. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  2662. }
  2663. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  2664. // precisely double when inserting a range that only has a duplicate of
  2665. // "yesterday", but no entry for "today"
  2666. err = thisASR.InsertRange(thatASR)
  2667. if err != nil {
  2668. t.Fatalf("unexpected error: %s", err)
  2669. }
  2670. yAS, err := thisASR.Get(0)
  2671. for k, a := range yAS.Allocations {
  2672. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  2673. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2674. }
  2675. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  2676. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2677. }
  2678. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  2679. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2680. }
  2681. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  2682. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2683. }
  2684. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  2685. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2686. }
  2687. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  2688. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2689. }
  2690. if !util.IsApproximately(a.PVByteHours(), 2*unit.PVByteHours()) {
  2691. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2692. }
  2693. if !util.IsApproximately(a.PVCost(), 2*unit.PVCost()) {
  2694. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2695. }
  2696. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  2697. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2698. }
  2699. if !util.IsApproximately(a.LoadBalancerCost, 2*unit.LoadBalancerCost) {
  2700. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2701. }
  2702. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  2703. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2704. }
  2705. }
  2706. tAS, err := thisASR.Get(1)
  2707. for k, a := range tAS.Allocations {
  2708. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  2709. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  2710. }
  2711. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  2712. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  2713. }
  2714. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  2715. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  2716. }
  2717. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  2718. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  2719. }
  2720. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  2721. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  2722. }
  2723. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  2724. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  2725. }
  2726. if !util.IsApproximately(a.PVByteHours(), unit.PVByteHours()) {
  2727. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours(), a.PVByteHours())
  2728. }
  2729. if !util.IsApproximately(a.PVCost(), unit.PVCost()) {
  2730. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost(), a.PVCost())
  2731. }
  2732. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  2733. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  2734. }
  2735. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  2736. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  2737. }
  2738. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  2739. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  2740. }
  2741. }
  2742. }
  2743. // TODO niko/etl
  2744. // func TestAllocationSetRange_Length(t *testing.T) {}
  2745. func TestAllocationSetRange_MarshalJSON(t *testing.T) {
  2746. tests := []struct {
  2747. name string
  2748. arg *AllocationSetRange
  2749. expected *AllocationSetRange
  2750. }{
  2751. {
  2752. name: "Nil ASR",
  2753. arg: nil,
  2754. },
  2755. {
  2756. name: "Nil AS in ASR",
  2757. arg: NewAllocationSetRange(nil),
  2758. },
  2759. {
  2760. name: "Normal ASR",
  2761. arg: &AllocationSetRange{
  2762. Allocations: []*AllocationSet{
  2763. {
  2764. Allocations: map[string]*Allocation{
  2765. "a": {
  2766. Start: time.Now().UTC().Truncate(day),
  2767. },
  2768. },
  2769. },
  2770. },
  2771. },
  2772. },
  2773. }
  2774. for _, test := range tests {
  2775. bytes, err := json.Marshal(test.arg)
  2776. if err != nil {
  2777. t.Fatalf("ASR Marshal: test %s, unexpected error: %s", test.name, err)
  2778. }
  2779. var testASR []*AllocationSet
  2780. marshaled := &testASR
  2781. err = json.Unmarshal(bytes, marshaled)
  2782. if err != nil {
  2783. t.Fatalf("ASR Unmarshal: test %s: unexpected error: %s", test.name, err)
  2784. }
  2785. if test.arg.Length() != len(testASR) {
  2786. t.Fatalf("ASR Unmarshal: test %s: length mutated in encoding: expected %d but got %d", test.name, test.arg.Length(), len(testASR))
  2787. }
  2788. // Allocations don't unmarshal back from json
  2789. }
  2790. }
  2791. // TODO niko/etl
  2792. // func TestAllocationSetRange_Slice(t *testing.T) {}
  2793. // TODO niko/etl
  2794. // func TestAllocationSetRange_Window(t *testing.T) {}
  2795. func TestAllocationSetRange_Start(t *testing.T) {
  2796. tests := []struct {
  2797. name string
  2798. arg *AllocationSetRange
  2799. expectError bool
  2800. expected time.Time
  2801. }{
  2802. {
  2803. name: "Empty ASR",
  2804. arg: nil,
  2805. expectError: true,
  2806. },
  2807. {
  2808. name: "Single allocation",
  2809. arg: &AllocationSetRange{
  2810. Allocations: []*AllocationSet{
  2811. {
  2812. Allocations: map[string]*Allocation{
  2813. "a": {
  2814. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2815. },
  2816. },
  2817. },
  2818. },
  2819. },
  2820. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2821. },
  2822. {
  2823. name: "Two allocations",
  2824. arg: &AllocationSetRange{
  2825. Allocations: []*AllocationSet{
  2826. {
  2827. Allocations: map[string]*Allocation{
  2828. "a": {
  2829. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2830. },
  2831. "b": {
  2832. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2833. },
  2834. },
  2835. },
  2836. },
  2837. },
  2838. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2839. },
  2840. {
  2841. name: "Two AllocationSets",
  2842. arg: &AllocationSetRange{
  2843. Allocations: []*AllocationSet{
  2844. {
  2845. Allocations: map[string]*Allocation{
  2846. "a": {
  2847. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2848. },
  2849. },
  2850. },
  2851. {
  2852. Allocations: map[string]*Allocation{
  2853. "b": {
  2854. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2855. },
  2856. },
  2857. },
  2858. },
  2859. },
  2860. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2861. },
  2862. }
  2863. for _, test := range tests {
  2864. result, err := test.arg.Start()
  2865. if test.expectError && err != nil {
  2866. continue
  2867. }
  2868. if test.expectError && err == nil {
  2869. t.Errorf("%s: expected error and got none", test.name)
  2870. } else if result != test.expected {
  2871. t.Errorf("%s: expected %s but got %s", test.name, test.expected, result)
  2872. }
  2873. }
  2874. }
  2875. func TestAllocationSetRange_End(t *testing.T) {
  2876. tests := []struct {
  2877. name string
  2878. arg *AllocationSetRange
  2879. expectError bool
  2880. expected time.Time
  2881. }{
  2882. {
  2883. name: "Empty ASR",
  2884. arg: nil,
  2885. expectError: true,
  2886. },
  2887. {
  2888. name: "Single allocation",
  2889. arg: &AllocationSetRange{
  2890. Allocations: []*AllocationSet{
  2891. {
  2892. Allocations: map[string]*Allocation{
  2893. "a": {
  2894. End: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2895. },
  2896. },
  2897. },
  2898. },
  2899. },
  2900. expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2901. },
  2902. {
  2903. name: "Two allocations",
  2904. arg: &AllocationSetRange{
  2905. Allocations: []*AllocationSet{
  2906. {
  2907. Allocations: map[string]*Allocation{
  2908. "a": {
  2909. End: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2910. },
  2911. "b": {
  2912. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2913. },
  2914. },
  2915. },
  2916. },
  2917. },
  2918. expected: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2919. },
  2920. {
  2921. name: "Two AllocationSets",
  2922. arg: &AllocationSetRange{
  2923. Allocations: []*AllocationSet{
  2924. {
  2925. Allocations: map[string]*Allocation{
  2926. "a": {
  2927. End: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2928. },
  2929. },
  2930. },
  2931. {
  2932. Allocations: map[string]*Allocation{
  2933. "b": {
  2934. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2935. },
  2936. },
  2937. },
  2938. },
  2939. },
  2940. expected: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2941. },
  2942. }
  2943. for _, test := range tests {
  2944. result, err := test.arg.End()
  2945. if test.expectError && err != nil {
  2946. continue
  2947. }
  2948. if test.expectError && err == nil {
  2949. t.Errorf("%s: expected error and got none", test.name)
  2950. } else if result != test.expected {
  2951. t.Errorf("%s: expected %s but got %s", test.name, test.expected, result)
  2952. }
  2953. }
  2954. }
  2955. func TestAllocationSetRange_Minutes(t *testing.T) {
  2956. tests := []struct {
  2957. name string
  2958. arg *AllocationSetRange
  2959. expected float64
  2960. }{
  2961. {
  2962. name: "Empty ASR",
  2963. arg: nil,
  2964. expected: 0,
  2965. },
  2966. {
  2967. name: "Single allocation",
  2968. arg: &AllocationSetRange{
  2969. Allocations: []*AllocationSet{
  2970. {
  2971. Allocations: map[string]*Allocation{
  2972. "a": {
  2973. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2974. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2975. },
  2976. },
  2977. },
  2978. },
  2979. },
  2980. expected: 24 * 60,
  2981. },
  2982. {
  2983. name: "Two allocations",
  2984. arg: &AllocationSetRange{
  2985. Allocations: []*AllocationSet{
  2986. {
  2987. Allocations: map[string]*Allocation{
  2988. "a": {
  2989. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  2990. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2991. },
  2992. "b": {
  2993. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  2994. End: time.Date(1970, 1, 3, 0, 0, 0, 0, time.UTC),
  2995. },
  2996. },
  2997. },
  2998. },
  2999. },
  3000. expected: 2 * 24 * 60,
  3001. },
  3002. {
  3003. name: "Two AllocationSets",
  3004. arg: &AllocationSetRange{
  3005. Allocations: []*AllocationSet{
  3006. {
  3007. Allocations: map[string]*Allocation{
  3008. "a": {
  3009. Start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
  3010. End: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  3011. },
  3012. },
  3013. },
  3014. {
  3015. Allocations: map[string]*Allocation{
  3016. "b": {
  3017. Start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
  3018. End: time.Date(1970, 1, 3, 0, 0, 0, 0, time.UTC),
  3019. },
  3020. },
  3021. },
  3022. },
  3023. },
  3024. expected: 2 * 24 * 60,
  3025. },
  3026. }
  3027. for _, test := range tests {
  3028. result := test.arg.Minutes()
  3029. if result != test.expected {
  3030. t.Errorf("%s: expected %f but got %f", test.name, test.expected, result)
  3031. }
  3032. }
  3033. }
  3034. func TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate(t *testing.T) {
  3035. today := time.Now().Round(day)
  3036. start := today.AddDate(0, 0, -4)
  3037. var allocationSets []*AllocationSet
  3038. for i := 0; i < 4; i++ {
  3039. allocationSets = append(allocationSets, GenerateMockAllocationSet(start))
  3040. start = start.AddDate(0, 0, 1)
  3041. }
  3042. var originalAllocationSets []*AllocationSet
  3043. for _, as := range allocationSets {
  3044. originalAllocationSets = append(originalAllocationSets, as.Clone())
  3045. }
  3046. asr := NewAllocationSetRange()
  3047. for _, as := range allocationSets {
  3048. asr.Append(as.Clone())
  3049. }
  3050. expected, err := asr.accumulate()
  3051. if err != nil {
  3052. t.Errorf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: AllocationSetRange.Accumulate() returned an error\n")
  3053. }
  3054. var got *AllocationSet
  3055. for i := 0; i < len(allocationSets); i++ {
  3056. got, err = got.Accumulate(allocationSets[i])
  3057. if err != nil {
  3058. t.Errorf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: got.Accumulate(allocationSets[%d]) returned an error\n", i)
  3059. }
  3060. }
  3061. // compare the got and expected Allocation sets, ensure that they match
  3062. if len(got.Allocations) != len(expected.Allocations) {
  3063. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: length of got.Allocations does not match length of expected.Allocations\n")
  3064. }
  3065. for key, a := range got.Allocations {
  3066. if _, ok := expected.Allocations[key]; !ok {
  3067. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: got.Allocations[%s] not found in expected.Allocations\n", key)
  3068. }
  3069. if !a.Equal(expected.Allocations[key]) {
  3070. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: got.Allocations[%s] did not match expected.Allocations[%[1]s]", key)
  3071. }
  3072. }
  3073. if len(got.ExternalKeys) != len(expected.ExternalKeys) {
  3074. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: length of got.ExternalKeys does not match length of expected.ExternalKeys\n")
  3075. }
  3076. if len(got.IdleKeys) != len(expected.IdleKeys) {
  3077. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: length of got.IdleKeys does not match length of expected.IdleKeys\n")
  3078. }
  3079. if !got.Window.Start().UTC().Equal(expected.Window.Start().UTC()) {
  3080. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: Window.start: got:%s, expected:%s\n", got.Window.Start(), expected.Window.Start())
  3081. }
  3082. if !got.Window.End().UTC().Equal(expected.Window.End().UTC()) {
  3083. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: Window.end: got:%s, expected:%s\n", got.Window.End(), expected.Window.End())
  3084. }
  3085. for i := range allocationSets {
  3086. for key, allocation := range allocationSets[i].Allocations {
  3087. if !allocation.Equal(originalAllocationSets[i].Allocations[key]) {
  3088. t.Fatalf("TestAllocationSet_Accumulate_Equals_AllocationSetRange_Accumulate: allocationSet has been mutated in Accumulate; allocationSet: %d, allocation: %s\n", i, key)
  3089. }
  3090. }
  3091. }
  3092. }
  3093. func Test_AggregateByService_UnmountedLBs(t *testing.T) {
  3094. end := time.Now().UTC().Truncate(day)
  3095. start := end.Add(-day)
  3096. normalProps := &AllocationProperties{
  3097. Cluster: "cluster-one",
  3098. Container: "nginx-plus-nginx-ingress",
  3099. Controller: "nginx-plus-nginx-ingress",
  3100. ControllerKind: "deployment",
  3101. Namespace: "nginx-plus",
  3102. Pod: "nginx-plus-nginx-ingress-123a4b5678-ab12c",
  3103. ProviderID: "test",
  3104. Node: "testnode",
  3105. Services: []string{
  3106. "nginx-plus-nginx-ingress",
  3107. },
  3108. }
  3109. problematicProps := &AllocationProperties{
  3110. Cluster: "cluster-one",
  3111. Container: UnmountedSuffix,
  3112. Namespace: UnmountedSuffix,
  3113. Pod: UnmountedSuffix,
  3114. ProviderID: "test",
  3115. Node: "testnode",
  3116. Services: []string{
  3117. "nginx-plus-nginx-ingress",
  3118. "ingress-nginx-controller",
  3119. "pacman",
  3120. },
  3121. }
  3122. idle := NewMockUnitAllocation(fmt.Sprintf("cluster-one/%s", IdleSuffix), start, day, &AllocationProperties{
  3123. Cluster: "cluster-one",
  3124. })
  3125. // this allocation is the main point of the test; an unmounted LB that has services
  3126. problematicAllocation := NewMockUnitAllocation("cluster-one//__unmounted__/__unmounted__/__unmounted__", start, day, problematicProps)
  3127. two := NewMockUnitAllocation("cluster-one//nginx-plus/nginx-plus-nginx-ingress-123a4b5678-ab12c/nginx-plus-nginx-ingress", start, day, normalProps)
  3128. three := NewMockUnitAllocation("cluster-one//nginx-plus/nginx-plus-nginx-ingress-123a4b5678-ab12c/nginx-plus-nginx-ingress", start, day, normalProps)
  3129. four := NewMockUnitAllocation("cluster-one//nginx-plus/nginx-plus-nginx-ingress-123a4b5678-ab12c/nginx-plus-nginx-ingress", start, day, normalProps)
  3130. problematicAllocation.ExternalCost = 2.35
  3131. two.ExternalCost = 1.35
  3132. three.ExternalCost = 2.60
  3133. four.ExternalCost = 4.30
  3134. set := NewAllocationSet(start, start.Add(day), problematicAllocation, two, three, four)
  3135. set.Insert(idle)
  3136. set.AggregateBy([]string{AllocationServiceProp}, &AllocationAggregationOptions{
  3137. Filter: ops.Contains(allocation.FieldServices, "nginx-plus-nginx-ingress"),
  3138. })
  3139. for _, alloc := range set.Allocations {
  3140. if !strings.Contains(UnmountedSuffix, alloc.Name) {
  3141. props := alloc.Properties
  3142. if props.Cluster == UnmountedSuffix {
  3143. t.Error("cluster unmounted")
  3144. }
  3145. if props.Container == UnmountedSuffix {
  3146. t.Error("container unmounted")
  3147. }
  3148. if props.Namespace == UnmountedSuffix {
  3149. t.Error("namespace unmounted")
  3150. }
  3151. if props.Pod == UnmountedSuffix {
  3152. t.Error("pod unmounted")
  3153. }
  3154. if props.Controller == UnmountedSuffix {
  3155. t.Error("controller unmounted")
  3156. }
  3157. }
  3158. }
  3159. spew.Config.DisableMethods = true
  3160. t.Logf("%s", spew.Sdump(set.Allocations))
  3161. }
  3162. func Test_DetermineSharingName(t *testing.T) {
  3163. var alloc *Allocation
  3164. var name string
  3165. var err error
  3166. // test nil allocation with nil options
  3167. name, err = alloc.determineSharingName(nil)
  3168. if err == nil {
  3169. t.Fatalf("determineSharingName: expected error; actual nil")
  3170. }
  3171. // test nil with non-nil options
  3172. name, err = alloc.determineSharingName(&AllocationAggregationOptions{})
  3173. if err == nil {
  3174. t.Fatalf("determineSharingName: expected error; actual nil")
  3175. }
  3176. alloc = &Allocation{}
  3177. alloc.Properties = &AllocationProperties{
  3178. Cluster: "cluster1",
  3179. Labels: map[string]string{
  3180. "app": "app1",
  3181. "env": "env1",
  3182. },
  3183. Namespace: "namespace1",
  3184. }
  3185. // test non-nil allocation with nil options
  3186. name, err = alloc.determineSharingName(nil)
  3187. if err != nil {
  3188. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3189. } else if name != "unknown" {
  3190. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3191. }
  3192. // test non-nil allocation with empty options
  3193. options := &AllocationAggregationOptions{}
  3194. name, err = alloc.determineSharingName(options)
  3195. if err != nil {
  3196. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3197. } else if name != "unknown" {
  3198. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3199. }
  3200. // test non-nil allocation with matching namespace options
  3201. options.SharedNamespaces = []string{"namespace1"}
  3202. name, err = alloc.determineSharingName(options)
  3203. if err != nil {
  3204. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3205. } else if name != "namespace1" {
  3206. t.Fatalf("determineSharingName: expected \"namespace1\"; actual \"%s\"", name)
  3207. }
  3208. // test non-nil allocation with non-matching namespace options
  3209. options.SharedNamespaces = []string{"namespace2"}
  3210. name, err = alloc.determineSharingName(options)
  3211. if err != nil {
  3212. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3213. } else if name != "unknown" {
  3214. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3215. }
  3216. // test non-nil allocation with matching label options
  3217. options.SharedNamespaces = nil
  3218. options.SharedLabels = map[string][]string{
  3219. "app": {"app1"},
  3220. }
  3221. name, err = alloc.determineSharingName(options)
  3222. if err != nil {
  3223. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3224. } else if name != "app1" {
  3225. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3226. }
  3227. // test non-nil allocation with partial-matching label options
  3228. options.SharedLabels = map[string][]string{
  3229. "app": {"app1", "app2"},
  3230. }
  3231. name, err = alloc.determineSharingName(options)
  3232. if err != nil {
  3233. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3234. } else if name != "app1" {
  3235. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3236. }
  3237. // test non-nil allocation with non-matching label options
  3238. options.SharedLabels = map[string][]string{
  3239. "app": {"app2"},
  3240. }
  3241. name, err = alloc.determineSharingName(options)
  3242. if err != nil {
  3243. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3244. } else if name != "unknown" {
  3245. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3246. }
  3247. // test non-nil allocation with matching namespace and label options
  3248. options.SharedNamespaces = []string{"namespace1"}
  3249. options.SharedLabels = map[string][]string{
  3250. "app": {"app1"},
  3251. }
  3252. name, err = alloc.determineSharingName(options)
  3253. if err != nil {
  3254. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3255. } else if name != "app1" {
  3256. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3257. }
  3258. // test non-nil allocation with non-matching namespace and matching label options
  3259. options.SharedNamespaces = []string{"namespace2"}
  3260. options.SharedLabels = map[string][]string{
  3261. "app": {"app1"},
  3262. }
  3263. name, err = alloc.determineSharingName(options)
  3264. if err != nil {
  3265. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3266. } else if name != "app1" {
  3267. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3268. }
  3269. // test non-nil allocation with non-matching namespace and non-matching label options
  3270. options.SharedNamespaces = []string{"namespace2"}
  3271. options.SharedLabels = map[string][]string{
  3272. "app": {"app2"},
  3273. }
  3274. name, err = alloc.determineSharingName(options)
  3275. if err != nil {
  3276. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3277. } else if name != "unknown" {
  3278. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3279. }
  3280. // test non-nil allocation with multiple matching label options
  3281. alloc.Properties.Labels = map[string]string{
  3282. "app": "app1",
  3283. "env": "env1",
  3284. }
  3285. options.SharedNamespaces = nil
  3286. options.SharedLabels = map[string][]string{
  3287. "app": {"app1"},
  3288. "env": {"env1"},
  3289. }
  3290. name, err = alloc.determineSharingName(options)
  3291. if err != nil {
  3292. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3293. } else if name != "app1" {
  3294. t.Fatalf("determineSharingName: expected \"app1\"; actual \"%s\"", name)
  3295. }
  3296. // test non-nil allocation with one matching label option
  3297. alloc.Properties.Labels = map[string]string{
  3298. "app": "app2",
  3299. "env": "env1",
  3300. }
  3301. options.SharedNamespaces = nil
  3302. options.SharedLabels = map[string][]string{
  3303. "app": {"app1"},
  3304. "env": {"env1"},
  3305. }
  3306. name, err = alloc.determineSharingName(options)
  3307. if err != nil {
  3308. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3309. } else if name != "env1" {
  3310. t.Fatalf("determineSharingName: expected \"env1\"; actual \"%s\"", name)
  3311. }
  3312. // test non-nil allocation with one matching namespace option
  3313. alloc.Properties.Namespace = "namespace1"
  3314. options.SharedNamespaces = []string{"namespace1", "namespace2"}
  3315. options.SharedLabels = nil
  3316. name, err = alloc.determineSharingName(options)
  3317. if err != nil {
  3318. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3319. } else if name != "namespace1" {
  3320. t.Fatalf("determineSharingName: expected \"namespace1\"; actual \"%s\"", name)
  3321. }
  3322. // test non-nil allocation with another one matching namespace option
  3323. alloc.Properties.Namespace = "namespace2"
  3324. options.SharedNamespaces = []string{"namespace1", "namespace2"}
  3325. options.SharedLabels = nil
  3326. name, err = alloc.determineSharingName(options)
  3327. if err != nil {
  3328. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3329. } else if name != "namespace2" {
  3330. t.Fatalf("determineSharingName: expected \"namespace2\"; actual \"%s\"", name)
  3331. }
  3332. // test non-nil allocation with non-matching namespace options
  3333. alloc.Properties.Namespace = "namespace3"
  3334. options.SharedNamespaces = []string{"namespace1", "namespace2"}
  3335. name, err = alloc.determineSharingName(options)
  3336. if err != nil {
  3337. t.Fatalf("determineSharingName: expected no error; actual \"%s\"", err)
  3338. } else if name != "unknown" {
  3339. t.Fatalf("determineSharingName: expected \"unknown\"; actual \"%s\"", name)
  3340. }
  3341. }
  3342. func TestIsFilterEmptyTrue(t *testing.T) {
  3343. compiler := NewAllocationMatchCompiler(nil)
  3344. matcher, err := compiler.Compile(nil)
  3345. if err != nil {
  3346. t.Fatalf("compiling nil filter: %s", err)
  3347. }
  3348. result := isFilterEmpty(matcher)
  3349. if !result {
  3350. t.Errorf("matcher '%+v' should be reported empty but wasn't", matcher)
  3351. }
  3352. }
  3353. func TestIsFilterEmptyFalse(t *testing.T) {
  3354. compiler := NewAllocationMatchCompiler(nil)
  3355. matcher, err := compiler.Compile(ops.Eq(allocation.FieldClusterID, "test"))
  3356. if err != nil {
  3357. t.Fatalf("compiling nil filter: %s", err)
  3358. }
  3359. result := isFilterEmpty(matcher)
  3360. if result {
  3361. t.Errorf("matcher '%+v' should be not be reported empty but was", matcher)
  3362. }
  3363. }
  3364. func TestAllocation_SanitizeNaN(t *testing.T) {
  3365. tcName := "TestAllocation_SanitizeNaN"
  3366. alloc := getMockAllocation(math.NaN())
  3367. alloc.SanitizeNaN()
  3368. checkAllocation(t, tcName, alloc)
  3369. }
  3370. func checkAllocation(t *testing.T, tcName string, alloc Allocation) {
  3371. v := reflect.ValueOf(alloc)
  3372. checkAllFloat64sForNaN(t, v, tcName)
  3373. vRaw := reflect.ValueOf(*alloc.RawAllocationOnly)
  3374. checkAllFloat64sForNaN(t, vRaw, tcName)
  3375. for _, pv := range alloc.PVs {
  3376. vPV := reflect.ValueOf(*pv)
  3377. checkAllFloat64sForNaN(t, vPV, tcName)
  3378. }
  3379. for _, parc := range alloc.ProportionalAssetResourceCosts {
  3380. vParc := reflect.ValueOf(parc)
  3381. checkAllFloat64sForNaN(t, vParc, tcName)
  3382. }
  3383. for _, scb := range alloc.SharedCostBreakdown {
  3384. vScb := reflect.ValueOf(scb)
  3385. checkAllFloat64sForNaN(t, vScb, tcName)
  3386. }
  3387. for _, lb := range alloc.LoadBalancers {
  3388. vLb := reflect.ValueOf(*lb)
  3389. checkAllFloat64sForNaN(t, vLb, tcName)
  3390. }
  3391. }
  3392. func TestAllocationSet_SanitizeNaN(t *testing.T) {
  3393. allocNaN := getMockAllocation(math.NaN())
  3394. allocNotNaN := getMockAllocation(1.2)
  3395. allocSet := AllocationSet{
  3396. Allocations: map[string]*Allocation{"NaN": &allocNaN, "notNaN": &allocNotNaN},
  3397. }
  3398. allocSet.SanitizeNaN()
  3399. for _, a := range allocSet.Allocations {
  3400. checkAllocation(t, "TestAllocationSet_SanitizeNaN", *a)
  3401. }
  3402. }
  3403. func getMockAllocation(f float64) Allocation {
  3404. alloc := Allocation{
  3405. Name: "mockAllocation",
  3406. Properties: nil,
  3407. Window: Window{},
  3408. Start: time.Time{},
  3409. End: time.Time{},
  3410. CPUCoreHours: f,
  3411. CPUCoreRequestAverage: f,
  3412. CPUCoreUsageAverage: f,
  3413. CPUCost: f,
  3414. CPUCostAdjustment: f,
  3415. GPUHours: f,
  3416. GPUCost: f,
  3417. GPUCostAdjustment: f,
  3418. NetworkTransferBytes: f,
  3419. NetworkReceiveBytes: f,
  3420. NetworkCost: f,
  3421. NetworkCrossZoneCost: f,
  3422. NetworkCrossRegionCost: f,
  3423. NetworkInternetCost: f,
  3424. NetworkCostAdjustment: f,
  3425. LoadBalancerCost: f,
  3426. LoadBalancerCostAdjustment: f,
  3427. PVs: PVAllocations{{Cluster: "testPV", Name: "PVName"}: getMockPVAllocation(math.NaN())},
  3428. PVCostAdjustment: f,
  3429. RAMByteHours: f,
  3430. RAMBytesRequestAverage: f,
  3431. RAMBytesUsageAverage: f,
  3432. RAMCost: f,
  3433. RAMCostAdjustment: f,
  3434. SharedCost: f,
  3435. ExternalCost: f,
  3436. RawAllocationOnly: getMockRawAllocationOnlyData(f),
  3437. ProportionalAssetResourceCosts: ProportionalAssetResourceCosts{"NaN": *getMockPARC(f)},
  3438. SharedCostBreakdown: SharedCostBreakdowns{"NaN": *getMockSharedCostBreakdown(f)},
  3439. LoadBalancers: LbAllocations{"NaN": getMockLbAllocation(f)},
  3440. }
  3441. return alloc
  3442. }
  3443. func TestPVAllocation_SanitizeNaN(t *testing.T) {
  3444. pva := getMockPVAllocation(math.NaN())
  3445. pva.SanitizeNaN()
  3446. v := reflect.ValueOf(*pva)
  3447. checkAllFloat64sForNaN(t, v, "TestPVAllocation_SanitizeNaN")
  3448. }
  3449. func TestPVAllocations_SanitizeNaN(t *testing.T) {
  3450. pvaNaN := getMockPVAllocation(math.NaN())
  3451. pvaNotNaN := getMockPVAllocation(1.2)
  3452. pvs := PVAllocations{{Cluster: "testPV", Name: "PVName1"}: pvaNaN, {Cluster: "testPV", Name: "PVName2"}: pvaNotNaN}
  3453. pvs.SanitizeNaN()
  3454. for _, pv := range pvs {
  3455. v := reflect.ValueOf(*pv)
  3456. checkAllFloat64sForNaN(t, v, "TestPVAllocations_SanitizeNaN")
  3457. }
  3458. }
  3459. func getMockPVAllocation(f float64) *PVAllocation {
  3460. return &PVAllocation{
  3461. ByteHours: f,
  3462. Cost: f,
  3463. }
  3464. }
  3465. func TestRawAllocationOnlyData_SanitizeNaN(t *testing.T) {
  3466. raw := getMockRawAllocationOnlyData(math.NaN())
  3467. raw.SanitizeNaN()
  3468. v := reflect.ValueOf(*raw)
  3469. checkAllFloat64sForNaN(t, v, "TestRawAllocationOnlyData_SanitizeNaN")
  3470. nan := math.NaN()
  3471. nilRawAllocation := &RawAllocationOnlyData{
  3472. CPUCoreUsageMax: nan,
  3473. RAMBytesUsageMax: nan,
  3474. GPUUsageMax: &nan,
  3475. }
  3476. nilRawAllocation.SanitizeNaN()
  3477. // SanitizeNaN allocates nil if NaN is passed
  3478. if nilRawAllocation.GPUUsageMax != nil {
  3479. t.Fatalf("want: nil, got: %v", nilRawAllocation.GPUUsageMax)
  3480. }
  3481. // SanitizeNaN allocates 0.0 if NaN is passed
  3482. if nilRawAllocation.CPUCoreUsageMax != 0.0 {
  3483. t.Fatalf("want: 0.0, got: %v", nilRawAllocation.CPUCoreUsageMax)
  3484. }
  3485. // SanitizeNaN allocates 0.0 if NaN is passed
  3486. if nilRawAllocation.RAMBytesUsageMax != 0.0 {
  3487. t.Fatalf("want: 0.0, got: %v", nilRawAllocation.RAMBytesUsageMax)
  3488. }
  3489. }
  3490. func getMockRawAllocationOnlyData(f float64) *RawAllocationOnlyData {
  3491. return &RawAllocationOnlyData{
  3492. CPUCoreUsageMax: f,
  3493. RAMBytesUsageMax: f,
  3494. GPUUsageMax: &f,
  3495. }
  3496. }
  3497. func TestLbAllocation_SanitizeNaN(t *testing.T) {
  3498. lbaNaN := getMockLbAllocation(math.NaN())
  3499. lbaNaN.SanitizeNaN()
  3500. v := reflect.ValueOf(*lbaNaN)
  3501. checkAllFloat64sForNaN(t, v, "TestLbAllocation_SanitizeNaN")
  3502. }
  3503. func TestLbAllocations_SanitizeNaN(t *testing.T) {
  3504. lbaNaN := getMockLbAllocation(math.NaN())
  3505. lbaValid := getMockLbAllocation(1.2)
  3506. lbas := LbAllocations{"NaN": lbaNaN, "notNaN": lbaValid}
  3507. lbas.SanitizeNaN()
  3508. for _, lba := range lbas {
  3509. v := reflect.ValueOf(*lba)
  3510. checkAllFloat64sForNaN(t, v, "TestLbAllocations_SanitizeNaN")
  3511. }
  3512. }
  3513. func getMockLbAllocation(f float64) *LbAllocation {
  3514. return &LbAllocation{
  3515. Service: "testLoadBalancer",
  3516. Cost: f,
  3517. Private: false,
  3518. }
  3519. }
  3520. func TestProportionalAssetResourceCosts_SanitizeNaN(t *testing.T) {
  3521. parcAllNaN := getMockPARC(math.NaN())
  3522. parcNotNaN := getMockPARC(1.2)
  3523. parcs := ProportionalAssetResourceCosts{"NaN": *parcAllNaN, "notNaN": *parcNotNaN}
  3524. parcs.SanitizeNaN()
  3525. for _, parc := range parcs {
  3526. v := reflect.ValueOf(parc)
  3527. checkAllFloat64sForNaN(t, v, "TestProportionalAssetResourceCosts_SanitizeNaN")
  3528. }
  3529. }
  3530. func getMockPARC(f float64) *ProportionalAssetResourceCost {
  3531. return &ProportionalAssetResourceCost{
  3532. Cluster: "testCluster",
  3533. Name: "testName",
  3534. Type: "testType",
  3535. ProviderID: "testProvider",
  3536. CPUPercentage: f,
  3537. GPUPercentage: f,
  3538. RAMPercentage: f,
  3539. LoadBalancerPercentage: f,
  3540. PVPercentage: f,
  3541. NodeResourceCostPercentage: f,
  3542. GPUTotalCost: f,
  3543. GPUProportionalCost: f,
  3544. CPUTotalCost: f,
  3545. CPUProportionalCost: f,
  3546. RAMTotalCost: f,
  3547. RAMProportionalCost: f,
  3548. LoadBalancerProportionalCost: f,
  3549. LoadBalancerTotalCost: f,
  3550. PVProportionalCost: f,
  3551. PVTotalCost: f,
  3552. }
  3553. }
  3554. func TestSharedCostBreakdowns_SanitizeNaN(t *testing.T) {
  3555. scbNaN := getMockSharedCostBreakdown(math.NaN())
  3556. scbNotNaN := getMockSharedCostBreakdown(1.2)
  3557. scbs := SharedCostBreakdowns{"NaN": *scbNaN, "notNaN": *scbNotNaN}
  3558. scbs.SanitizeNaN()
  3559. for _, scb := range scbs {
  3560. v := reflect.ValueOf(scb)
  3561. checkAllFloat64sForNaN(t, v, "TestSharedCostBreakdowns_SanitizeNaN")
  3562. }
  3563. }
  3564. func getMockSharedCostBreakdown(f float64) *SharedCostBreakdown {
  3565. return &SharedCostBreakdown{
  3566. Name: "testBreakdown",
  3567. TotalCost: f,
  3568. CPUCost: f,
  3569. GPUCost: f,
  3570. RAMCost: f,
  3571. PVCost: f,
  3572. NetworkCost: f,
  3573. LBCost: f,
  3574. ExternalCost: f,
  3575. }
  3576. }
  3577. func checkAllFloat64sForNaN(t *testing.T, v reflect.Value, testCaseName string) {
  3578. vType := v.Type()
  3579. // go through each field on the struct
  3580. for i := 0; i < v.NumField(); i++ {
  3581. // Check if field is public and can be converted to a float
  3582. if v.Field(i).CanInterface() && v.Field(i).CanFloat() {
  3583. f := v.Field(i).Float()
  3584. if math.IsNaN(f) {
  3585. t.Fatalf("%s: expected not NaN for field: %s, got:NaN", testCaseName, vType.Field(i).Name)
  3586. }
  3587. }
  3588. }
  3589. }