allocation_test.go 87 KB

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