allocation_test.go 103 KB

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