allocation_test.go 75 KB

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