allocation_test.go 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971
  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 TestNewAllocationSet(t *testing.T) {
  407. // TODO niko/etl
  408. }
  409. func generateAllocationSet(start time.Time) *AllocationSet {
  410. // Idle allocations
  411. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
  412. ClusterProp: "cluster1",
  413. NodeProp: "node1",
  414. })
  415. a1i.CPUCost = 5.0
  416. a1i.RAMCost = 15.0
  417. a1i.GPUCost = 0.0
  418. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
  419. ClusterProp: "cluster2",
  420. })
  421. a2i.CPUCost = 5.0
  422. a2i.RAMCost = 5.0
  423. a2i.GPUCost = 0.0
  424. // Active allocations
  425. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
  426. ClusterProp: "cluster1",
  427. NamespaceProp: "namespace1",
  428. PodProp: "pod1",
  429. ContainerProp: "container1",
  430. })
  431. a1111.RAMCost = 11.00
  432. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
  433. ClusterProp: "cluster1",
  434. NamespaceProp: "namespace1",
  435. PodProp: "pod-abc",
  436. ContainerProp: "container2",
  437. })
  438. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
  439. ClusterProp: "cluster1",
  440. NamespaceProp: "namespace1",
  441. PodProp: "pod-def",
  442. ContainerProp: "container3",
  443. })
  444. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
  445. ClusterProp: "cluster1",
  446. NamespaceProp: "namespace2",
  447. PodProp: "pod-ghi",
  448. ContainerProp: "container4",
  449. })
  450. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
  451. ClusterProp: "cluster1",
  452. NamespaceProp: "namespace2",
  453. PodProp: "pod-ghi",
  454. ContainerProp: "container5",
  455. })
  456. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
  457. ClusterProp: "cluster1",
  458. NamespaceProp: "namespace2",
  459. PodProp: "pod-jkl",
  460. ContainerProp: "container6",
  461. })
  462. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
  463. ClusterProp: "cluster2",
  464. NamespaceProp: "namespace2",
  465. PodProp: "pod-mno",
  466. ContainerProp: "container4",
  467. })
  468. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
  469. ClusterProp: "cluster2",
  470. NamespaceProp: "namespace2",
  471. PodProp: "pod-mno",
  472. ContainerProp: "container5",
  473. })
  474. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
  475. ClusterProp: "cluster2",
  476. NamespaceProp: "namespace2",
  477. PodProp: "pod-pqr",
  478. ContainerProp: "container6",
  479. })
  480. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
  481. ClusterProp: "cluster2",
  482. NamespaceProp: "namespace3",
  483. PodProp: "pod-stu",
  484. ContainerProp: "container7",
  485. })
  486. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
  487. ClusterProp: "cluster2",
  488. NamespaceProp: "namespace3",
  489. PodProp: "pod-vwx",
  490. ContainerProp: "container8",
  491. })
  492. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
  493. ClusterProp: "cluster2",
  494. NamespaceProp: "namespace3",
  495. PodProp: "pod-vwx",
  496. ContainerProp: "container9",
  497. })
  498. // Controllers
  499. a11abc2.Properties.SetControllerKind("deployment")
  500. a11abc2.Properties.SetController("deployment1")
  501. a11def3.Properties.SetControllerKind("deployment")
  502. a11def3.Properties.SetController("deployment1")
  503. a12ghi4.Properties.SetControllerKind("deployment")
  504. a12ghi4.Properties.SetController("deployment2")
  505. a12ghi5.Properties.SetControllerKind("deployment")
  506. a12ghi5.Properties.SetController("deployment2")
  507. a22mno4.Properties.SetControllerKind("deployment")
  508. a22mno4.Properties.SetController("deployment2")
  509. a22mno5.Properties.SetControllerKind("deployment")
  510. a22mno5.Properties.SetController("deployment2")
  511. a23stu7.Properties.SetControllerKind("deployment")
  512. a23stu7.Properties.SetController("deployment3")
  513. a12jkl6.Properties.SetControllerKind("daemonset")
  514. a12jkl6.Properties.SetController("daemonset1")
  515. a22pqr6.Properties.SetControllerKind("daemonset")
  516. a22pqr6.Properties.SetController("daemonset1")
  517. a23vwx8.Properties.SetControllerKind("statefulset")
  518. a23vwx8.Properties.SetController("statefulset1")
  519. a23vwx9.Properties.SetControllerKind("statefulset")
  520. a23vwx9.Properties.SetController("statefulset1")
  521. // Labels
  522. a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
  523. a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  524. a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  525. a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
  526. a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
  527. //Annotations
  528. a23stu7.Properties.SetAnnotations(map[string]string{"team": "team1"})
  529. a23vwx8.Properties.SetAnnotations(map[string]string{"team": "team2"})
  530. a23vwx9.Properties.SetAnnotations(map[string]string{"team": "team1"})
  531. // Services
  532. a12jkl6.Properties.SetServices([]string{"service1"})
  533. a22pqr6.Properties.SetServices([]string{"service1"})
  534. return NewAllocationSet(start, start.Add(day),
  535. // idle
  536. a1i, a2i,
  537. // cluster 1, namespace1
  538. a1111, a11abc2, a11def3,
  539. // cluster 1, namespace 2
  540. a12ghi4, a12ghi5, a12jkl6,
  541. // cluster 2, namespace 2
  542. a22mno4, a22mno5, a22pqr6,
  543. // cluster 2, namespace 3
  544. a23stu7, a23vwx8, a23vwx9,
  545. )
  546. }
  547. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  548. if err != nil {
  549. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  550. }
  551. if as.Length() != length {
  552. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  553. }
  554. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  555. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  556. }
  557. }
  558. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  559. as.Each(func(k string, a *Allocation) {
  560. if exp, ok := exps[a.Name]; ok {
  561. if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
  562. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %f, actual %f", msg, exp, a.TotalCost())
  563. }
  564. } else {
  565. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  566. }
  567. })
  568. }
  569. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  570. as.Each(func(k string, a *Allocation) {
  571. if !a.Start.Equal(expStart) {
  572. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  573. }
  574. if !a.End.Equal(expEnd) {
  575. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  576. }
  577. if a.Minutes() != expMinutes {
  578. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes())
  579. }
  580. })
  581. }
  582. func printAllocationSet(msg string, as *AllocationSet) {
  583. fmt.Printf("--- %s ---\n", msg)
  584. as.Each(func(k string, a *Allocation) {
  585. fmt.Printf(" > %s\n", a)
  586. })
  587. }
  588. func TestAllocationSet_AggregateBy(t *testing.T) {
  589. // Test AggregateBy against the following workload topology, which is
  590. // generated by generateAllocationSet:
  591. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net | LB |
  592. // +----------------------------------------+------+------+------+------+------+------+------+
  593. // cluster1:
  594. // idle: 20.00 5.00 15.00 0.00 0.00 0.00 0.00
  595. // namespace1:
  596. // pod1:
  597. // container1: [app=app1, env=env1] 16.00 1.00 11.00 1.00 1.00 1.00 1.00
  598. // pod-abc: (deployment1)
  599. // container2: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  600. // pod-def: (deployment1)
  601. // container3: 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  602. // namespace2:
  603. // pod-ghi: (deployment2)
  604. // container4: [app=app2, env=env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  605. // container5: [app=app2, env=env2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  606. // pod-jkl: (daemonset1)
  607. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  608. // +-----------------------------------------+------+------+------+------+------+------+------+
  609. // cluster1 subtotal 66.00 11.00 31.00 6.00 6.00 6.00 6.00
  610. // +-----------------------------------------+------+------+------+------+------+------+------+
  611. // cluster2:
  612. // idle: 10.00 5.00 5.00 0.00 0.00 0.00 0.00
  613. // namespace2:
  614. // pod-mno: (deployment2)
  615. // container4: [app=app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  616. // container5: [app=app2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  617. // pod-pqr: (daemonset1)
  618. // container6: {service1} 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  619. // namespace3:
  620. // pod-stu: (deployment3)
  621. // container7: an[team=team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  622. // pod-vwx: (statefulset1)
  623. // container8: an[team=team2] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  624. // container9: an[team=team1] 6.00 1.00 1.00 1.00 1.00 1.00 1.00
  625. // +----------------------------------------+------+------+------+------+------+------+------+
  626. // cluster2 subtotal 46.00 11.00 11.00 6.00 6.00 6.00 6.00
  627. // +----------------------------------------+------+------+------+------+------+------+------+
  628. // total 112.00 22.00 42.00 12.00 12.00 12.00 12.00
  629. // +----------------------------------------+------+------+------+------+------+------+------+
  630. // Scenarios to test:
  631. // 1 Single-aggregation
  632. // 1a AggregationProperties=(Cluster)
  633. // 1b AggregationProperties=(Namespace)
  634. // 1c AggregationProperties=(Pod)
  635. // 1d AggregationProperties=(Container)
  636. // 1e AggregationProperties=(ControllerKind)
  637. // 1f AggregationProperties=(Controller)
  638. // 1g AggregationProperties=(Service)
  639. // 1h AggregationProperties=(Label:app)
  640. // 2 Multi-aggregation
  641. // 2a AggregationProperties=(Cluster, Namespace)
  642. // 2b AggregationProperties=(Namespace, Label:app)
  643. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  644. // 2d AggregationProperties=(Label:app, Label:environment)
  645. // 3 Share idle
  646. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  647. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  648. // 4 Share resources
  649. // 4a Share namespace ShareEven
  650. // 4b Share cluster ShareWeighted
  651. // 4c Share label ShareEven
  652. // 4d Share overhead ShareWeighted
  653. // 5 Filters
  654. // 5a Filter by cluster with separate idle
  655. // 5b Filter by cluster with shared idle
  656. // TODO niko/idle more filter tests
  657. // 6 Combinations and options
  658. // 6a SplitIdle
  659. // 6b Share idle with filters
  660. // 6c Share resources with filters
  661. // 6d Share idle and share resources
  662. // 7 Edge cases and errors
  663. // 7a Empty AggregationProperties
  664. // 7b Filter all
  665. // 7c Share all
  666. // 7d Share and filter the same allocations
  667. // Definitions and set-up:
  668. var as *AllocationSet
  669. var err error
  670. endYesterday := time.Now().UTC().Truncate(day)
  671. startYesterday := endYesterday.Add(-day)
  672. numClusters := 2
  673. numNamespaces := 3
  674. numPods := 9
  675. numContainers := 9
  676. numControllerKinds := 3
  677. numControllers := 5
  678. numServices := 1
  679. numLabelApps := 2
  680. // By default, idle is reported as a single, merged allocation
  681. numIdle := 1
  682. // There will only ever be one __unallocated__
  683. numUnallocated := 1
  684. // There are two clusters, so each gets an idle entry when they are split
  685. numSplitIdle := 2
  686. activeTotalCost := 82.0
  687. idleTotalCost := 30.0
  688. sharedOverheadHourlyCost := 7.0
  689. isNamespace3 := func(a *Allocation) bool {
  690. ns, err := a.Properties.GetNamespace()
  691. return err == nil && ns == "namespace3"
  692. }
  693. isApp1 := func(a *Allocation) bool {
  694. ls, _ := a.Properties.GetLabels()
  695. if app, ok := ls["app"]; ok && app == "app1" {
  696. return true
  697. }
  698. return false
  699. }
  700. end := time.Now().UTC().Truncate(day)
  701. start := end.Add(-day)
  702. // Tests:
  703. // 1 Single-aggregation
  704. // 1a AggregationProperties=(Cluster)
  705. as = generateAllocationSet(start)
  706. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  707. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  708. assertAllocationTotals(t, as, "1a", map[string]float64{
  709. "cluster1": 46.00,
  710. "cluster2": 36.00,
  711. IdleSuffix: 30.00,
  712. })
  713. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  714. // 1b AggregationProperties=(Namespace)
  715. as = generateAllocationSet(start)
  716. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  717. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  718. assertAllocationTotals(t, as, "1b", map[string]float64{
  719. "namespace1": 28.00,
  720. "namespace2": 36.00,
  721. "namespace3": 18.00,
  722. IdleSuffix: 30.00,
  723. })
  724. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  725. // 1c AggregationProperties=(Pod)
  726. as = generateAllocationSet(start)
  727. err = as.AggregateBy(Properties{PodProp: true}, nil)
  728. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  729. assertAllocationTotals(t, as, "1c", map[string]float64{
  730. "pod-jkl": 6.00,
  731. "pod-stu": 6.00,
  732. "pod-abc": 6.00,
  733. "pod-pqr": 6.00,
  734. "pod-def": 6.00,
  735. "pod-vwx": 12.00,
  736. "pod1": 16.00,
  737. "pod-mno": 12.00,
  738. "pod-ghi": 12.00,
  739. IdleSuffix: 30.00,
  740. })
  741. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  742. // 1d AggregationProperties=(Container)
  743. as = generateAllocationSet(start)
  744. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  745. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  746. assertAllocationTotals(t, as, "1d", map[string]float64{
  747. "container2": 6.00,
  748. "container9": 6.00,
  749. "container6": 12.00,
  750. "container3": 6.00,
  751. "container4": 12.00,
  752. "container7": 6.00,
  753. "container8": 6.00,
  754. "container5": 12.00,
  755. "container1": 16.00,
  756. IdleSuffix: 30.00,
  757. })
  758. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  759. // 1e AggregationProperties=(ControllerKind)
  760. as = generateAllocationSet(start)
  761. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  762. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  763. assertAllocationTotals(t, as, "1e", map[string]float64{
  764. "daemonset": 12.00,
  765. "deployment": 42.00,
  766. "statefulset": 12.00,
  767. IdleSuffix: 30.00,
  768. UnallocatedSuffix: 16.00,
  769. })
  770. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  771. // 1f AggregationProperties=(Controller)
  772. as = generateAllocationSet(start)
  773. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  774. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  775. assertAllocationTotals(t, as, "1f", map[string]float64{
  776. "deployment/deployment2": 24.00,
  777. "daemonset/daemonset1": 12.00,
  778. "deployment/deployment3": 6.00,
  779. "statefulset/statefulset1": 12.00,
  780. "deployment/deployment1": 12.00,
  781. IdleSuffix: 30.00,
  782. UnallocatedSuffix: 16.00,
  783. })
  784. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  785. // 1g AggregationProperties=(Service)
  786. as = generateAllocationSet(start)
  787. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  788. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  789. assertAllocationTotals(t, as, "1g", map[string]float64{
  790. "service1": 12.00,
  791. IdleSuffix: 30.00,
  792. UnallocatedSuffix: 70.00,
  793. })
  794. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  795. // 1h AggregationProperties=(Label:app)
  796. as = generateAllocationSet(start)
  797. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  798. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  799. assertAllocationTotals(t, as, "1h", map[string]float64{
  800. "app=app1": 16.00,
  801. "app=app2": 24.00,
  802. IdleSuffix: 30.00,
  803. UnallocatedSuffix: 42.00,
  804. })
  805. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  806. // 1i AggregationProperties=(ControllerKind:deployment)
  807. as = generateAllocationSet(start)
  808. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  809. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  810. assertAllocationTotals(t, as, "1i", map[string]float64{
  811. "deployment": 42.00,
  812. IdleSuffix: 30.00,
  813. UnallocatedSuffix: 40.00,
  814. })
  815. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  816. // 1j AggregationProperties=(Annotation:team)
  817. as = generateAllocationSet(start)
  818. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
  819. assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  820. assertAllocationTotals(t, as, "1j", map[string]float64{
  821. "team=team1": 12.00,
  822. "team=team2": 6.00,
  823. IdleSuffix: 30.00,
  824. UnallocatedSuffix: 64.00,
  825. })
  826. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  827. // 2 Multi-aggregation
  828. // 2a AggregationProperties=(Cluster, Namespace)
  829. // 2b AggregationProperties=(Namespace, Label:app)
  830. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  831. // 2d AggregationProperties=(Label:app, Label:environment)
  832. as = generateAllocationSet(start)
  833. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  834. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  835. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  836. assertAllocationTotals(t, as, "2d", map[string]float64{
  837. "app=app1/env=env1": 16.00,
  838. "app=app2/env=env2": 12.00,
  839. "app=app2/" + UnallocatedSuffix: 12.00,
  840. IdleSuffix: 30.00,
  841. UnallocatedSuffix: 42.00,
  842. })
  843. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  844. as = generateAllocationSet(start)
  845. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  846. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  847. assertAllocationTotals(t, as, "2e", map[string]float64{
  848. "cluster1/app=app2/env=env2": 12.00,
  849. "__idle__": 30.00,
  850. "cluster1/app=app1/env=env1": 16.00,
  851. "cluster1/" + UnallocatedSuffix: 18.00,
  852. "cluster2/app=app2/" + UnallocatedSuffix: 12.00,
  853. "cluster2/" + UnallocatedSuffix: 24.00,
  854. })
  855. // 2f AggregationProperties=(annotation:team, pod)
  856. as = generateAllocationSet(start)
  857. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
  858. assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
  859. assertAllocationTotals(t, as, "2e", map[string]float64{
  860. "pod-jkl/" + UnallocatedSuffix: 6.00,
  861. "pod-stu/team=team1": 6.00,
  862. "pod-abc/" + UnallocatedSuffix: 6.00,
  863. "pod-pqr/" + UnallocatedSuffix: 6.00,
  864. "pod-def/" + UnallocatedSuffix: 6.00,
  865. "pod-vwx/team=team1": 6.00,
  866. "pod-vwx/team=team2": 6.00,
  867. "pod1/" + UnallocatedSuffix: 16.00,
  868. "pod-mno/" + UnallocatedSuffix: 12.00,
  869. "pod-ghi/" + UnallocatedSuffix: 12.00,
  870. IdleSuffix: 30.00,
  871. })
  872. // // TODO niko/etl
  873. // // 3 Share idle
  874. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  875. // namespace1: 42.6875 = 28.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  876. // 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)
  877. // namespace3: 23.0000 = 18.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  878. as = generateAllocationSet(start)
  879. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  880. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  881. assertAllocationTotals(t, as, "3a", map[string]float64{
  882. "namespace1": 42.69,
  883. "namespace2": 46.31,
  884. "namespace3": 23.00,
  885. })
  886. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  887. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  888. // namespace1: 38.0000 = 28.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  889. // 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)
  890. // namespace3: 23.0000 = 18.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  891. as = generateAllocationSet(start)
  892. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  893. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  894. assertAllocationTotals(t, as, "3a", map[string]float64{
  895. "namespace1": 38.00,
  896. "namespace2": 51.00,
  897. "namespace3": 23.00,
  898. })
  899. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  900. // 4 Share resources
  901. // 4a Share namespace ShareEven
  902. // namespace1: 37.5000 = 28.00 + 18.00*(1.0/2.0)
  903. // namespace2: 45.5000 = 36.00 + 18.00*(1.0/2.0)
  904. // idle: 30.0000
  905. as = generateAllocationSet(start)
  906. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  907. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  908. ShareSplit: ShareEven,
  909. })
  910. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  911. assertAllocationTotals(t, as, "4a", map[string]float64{
  912. "namespace1": 37.00,
  913. "namespace2": 45.00,
  914. IdleSuffix: 30.00,
  915. })
  916. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  917. // 4b Share namespace ShareWeighted
  918. // namespace1: 32.5000 =
  919. // namespace2: 37.5000 =
  920. // idle: 30.0000
  921. as = generateAllocationSet(start)
  922. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  923. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  924. ShareSplit: ShareWeighted,
  925. })
  926. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  927. assertAllocationTotals(t, as, "4b", map[string]float64{
  928. "namespace1": 35.88,
  929. "namespace2": 46.125,
  930. IdleSuffix: 30.00,
  931. })
  932. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  933. // 4c Share label ShareEven
  934. // namespace1: 17.3333 = 28.00 - 16.00 + 16.00*(1.0/3.0)
  935. // namespace2: 41.3333 = 36.00 + 16.00*(1.0/3.0)
  936. // namespace3: 23.3333 = 18.00 + 16.00*(1.0/3.0)
  937. // idle: 30.0000
  938. as = generateAllocationSet(start)
  939. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  940. ShareFuncs: []AllocationMatchFunc{isApp1},
  941. ShareSplit: ShareEven,
  942. })
  943. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  944. assertAllocationTotals(t, as, "4c", map[string]float64{
  945. "namespace1": 17.33,
  946. "namespace2": 41.33,
  947. "namespace3": 23.33,
  948. IdleSuffix: 30.00,
  949. })
  950. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  951. // 4d Share overhead ShareWeighted
  952. // namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
  953. // namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
  954. // namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
  955. // idle: 30.0000
  956. as = generateAllocationSet(start)
  957. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  958. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  959. ShareSplit: ShareWeighted,
  960. })
  961. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  962. assertAllocationTotals(t, as, "4d", map[string]float64{
  963. "namespace1": 85.366,
  964. "namespace2": 109.756,
  965. "namespace3": 54.878,
  966. IdleSuffix: 30.00,
  967. })
  968. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  969. // 5 Filters
  970. isCluster := func(matchCluster string) func(*Allocation) bool {
  971. return func(a *Allocation) bool {
  972. cluster, err := a.Properties.GetCluster()
  973. return err == nil && cluster == matchCluster
  974. }
  975. }
  976. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  977. return func(a *Allocation) bool {
  978. namespace, err := a.Properties.GetNamespace()
  979. return err == nil && namespace == matchNamespace
  980. }
  981. }
  982. // 5a Filter by cluster with separate idle
  983. as = generateAllocationSet(start)
  984. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  985. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  986. ShareIdle: ShareNone,
  987. })
  988. assertAllocationSetTotals(t, as, "5a", err, 2, 66.0)
  989. assertAllocationTotals(t, as, "5a", map[string]float64{
  990. "cluster1": 46.00,
  991. IdleSuffix: 20.00,
  992. })
  993. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  994. // 5b Filter by cluster with shared idle
  995. as = generateAllocationSet(start)
  996. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  997. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  998. ShareIdle: ShareWeighted,
  999. })
  1000. assertAllocationSetTotals(t, as, "5b", err, 1, 66.0)
  1001. assertAllocationTotals(t, as, "5b", map[string]float64{
  1002. "cluster1": 66.00,
  1003. })
  1004. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  1005. // 5c Filter by cluster, agg by namespace, with separate idle
  1006. as = generateAllocationSet(start)
  1007. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1008. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  1009. ShareIdle: ShareNone,
  1010. })
  1011. assertAllocationSetTotals(t, as, "5c", err, 3, 66.0)
  1012. assertAllocationTotals(t, as, "5c", map[string]float64{
  1013. "namespace1": 28.00,
  1014. "namespace2": 18.00,
  1015. IdleSuffix: 20.00,
  1016. })
  1017. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  1018. // 5d Filter by namespace, agg by cluster, with separate idle
  1019. as = generateAllocationSet(start)
  1020. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  1021. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1022. ShareIdle: ShareNone,
  1023. })
  1024. assertAllocationSetTotals(t, as, "5d", err, 3, 46.31)
  1025. assertAllocationTotals(t, as, "5d", map[string]float64{
  1026. "cluster1": 18.00,
  1027. "cluster2": 18.00,
  1028. IdleSuffix: 10.31,
  1029. })
  1030. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  1031. // 6 Combinations and options
  1032. // 6a SplitIdle
  1033. as = generateAllocationSet(start)
  1034. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  1035. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  1036. assertAllocationTotals(t, as, "6a", map[string]float64{
  1037. "namespace1": 28.00,
  1038. "namespace2": 36.00,
  1039. "namespace3": 18.00,
  1040. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  1041. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  1042. })
  1043. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  1044. // 6b Share idle weighted with filters
  1045. // Should match values from unfiltered aggregation (3a)
  1046. // 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)
  1047. as = generateAllocationSet(start)
  1048. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1049. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1050. ShareIdle: ShareWeighted,
  1051. })
  1052. assertAllocationSetTotals(t, as, "6b", err, 1, 46.31)
  1053. assertAllocationTotals(t, as, "6b", map[string]float64{
  1054. "namespace2": 46.31,
  1055. })
  1056. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  1057. // 6c Share idle even with filters
  1058. // Should match values from unfiltered aggregation (3b)
  1059. // 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)
  1060. as = generateAllocationSet(start)
  1061. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1062. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1063. ShareIdle: ShareEven,
  1064. })
  1065. assertAllocationSetTotals(t, as, "6b", err, 1, 51.00)
  1066. assertAllocationTotals(t, as, "6b", map[string]float64{
  1067. "namespace2": 51.00,
  1068. })
  1069. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  1070. // 6d Share overhead with filters
  1071. // namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
  1072. // namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
  1073. // namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
  1074. // idle: 30.0000
  1075. // Then namespace 2 is filtered.
  1076. as = generateAllocationSet(start)
  1077. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1078. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1079. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1080. ShareSplit: ShareWeighted,
  1081. })
  1082. assertAllocationSetTotals(t, as, "6d", err, 2, 139.756)
  1083. assertAllocationTotals(t, as, "6d", map[string]float64{
  1084. "namespace2": 109.756,
  1085. IdleSuffix: 30.00,
  1086. })
  1087. assertAllocationWindow(t, as, "6d", startYesterday, endYesterday, 1440.0)
  1088. // 6e Share resources with filters
  1089. // --- Shared ---
  1090. // namespace1: 28.00 (gets shared among namespace2 and namespace3)
  1091. // --- Filtered ---
  1092. // namespace3: 27.33 = 18.00 + (28.00)*(18.00/54.00) (filtered out)
  1093. // --- Results ---
  1094. // namespace2: 54.667 = 36.00 + (28.00)*(36.00/54.00)
  1095. // idle: 30.0000
  1096. as = generateAllocationSet(start)
  1097. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1098. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1099. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1100. ShareSplit: ShareWeighted,
  1101. })
  1102. assertAllocationSetTotals(t, as, "6e", err, 2, 84.667)
  1103. assertAllocationTotals(t, as, "6e", map[string]float64{
  1104. "namespace2": 54.667,
  1105. IdleSuffix: 30.00,
  1106. })
  1107. assertAllocationWindow(t, as, "6e", startYesterday, endYesterday, 1440.0)
  1108. // 6f Share idle weighted and share resources weighted
  1109. //
  1110. // First, share idle weighted produces:
  1111. //
  1112. // namespace1: 42.6875
  1113. // initial cost 28.0000
  1114. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1115. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1116. //
  1117. // namespace2: 46.3125
  1118. // initial cost 36.0000
  1119. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1120. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1121. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1122. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1123. //
  1124. // namespace3: 23.0000
  1125. // initial cost 18.0000
  1126. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1127. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1128. //
  1129. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1130. // computed before allocating idle (so that weighting idle differently
  1131. // doesn't adversely affect the sharing mechanism):
  1132. //
  1133. // namespace2: 74.7708
  1134. // initial cost 30.0000
  1135. // idle cost 10.3125
  1136. // shared cost 28.4583 = (42.6875)*(36.0/54.0)
  1137. //
  1138. // namespace3: 37.2292
  1139. // initial cost 18.0000
  1140. // idle cost 5.0000
  1141. // shared cost 14.2292 = (42.6875)*(18.0/54.0)
  1142. //
  1143. as = generateAllocationSet(start)
  1144. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1145. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1146. ShareSplit: ShareWeighted,
  1147. ShareIdle: ShareWeighted,
  1148. })
  1149. assertAllocationSetTotals(t, as, "6f", err, 2, activeTotalCost+idleTotalCost)
  1150. assertAllocationTotals(t, as, "6f", map[string]float64{
  1151. "namespace2": 74.77,
  1152. "namespace3": 37.23,
  1153. })
  1154. assertAllocationWindow(t, as, "6f", startYesterday, endYesterday, 1440.0)
  1155. // 6g Share idle, share resources, and filter
  1156. //
  1157. // First, share idle weighted produces:
  1158. //
  1159. // namespace1: 42.6875
  1160. // initial cost 28.0000
  1161. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1162. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1163. //
  1164. // namespace2: 46.3125
  1165. // initial cost 36.0000
  1166. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1167. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1168. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1169. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1170. //
  1171. // namespace3: 23.0000
  1172. // initial cost 18.0000
  1173. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1174. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1175. //
  1176. // Then, sharing namespace1 means sharing 39.6875 according to coefficients
  1177. // computed before allocating idle (so that weighting idle differently
  1178. // doesn't adversely affect the sharing mechanism):
  1179. //
  1180. // namespace2: 74.7708
  1181. // initial cost 36.0000
  1182. // idle cost 10.3125
  1183. // shared cost 28.4583 = (42.6875)*(36.0/54.0)
  1184. //
  1185. // namespace3: 37.2292
  1186. // initial cost 18.0000
  1187. // idle cost 5.0000
  1188. // shared cost 14.2292 = (42.6875)*(18.0/54.0)
  1189. //
  1190. // Then, filter for namespace2: 74.7708
  1191. //
  1192. as = generateAllocationSet(start)
  1193. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1194. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1195. ShareFuncs: []AllocationMatchFunc{isNamespace("namespace1")},
  1196. ShareSplit: ShareWeighted,
  1197. ShareIdle: ShareWeighted,
  1198. })
  1199. assertAllocationSetTotals(t, as, "6g", err, 1, 74.77)
  1200. assertAllocationTotals(t, as, "6g", map[string]float64{
  1201. "namespace2": 74.77,
  1202. })
  1203. assertAllocationWindow(t, as, "6g", startYesterday, endYesterday, 1440.0)
  1204. // 6h Share idle, share resources, share overhead
  1205. //
  1206. // Share idle weighted:
  1207. //
  1208. // namespace1: 42.6875
  1209. // initial cost 28.0000
  1210. // cluster1.cpu 2.5000 = 5.00*(3.00/6.00)
  1211. // cluster1.ram 12.1875 = 15.00*(13.0/16.0)
  1212. //
  1213. // namespace2: 46.3125
  1214. // initial cost 36.0000
  1215. // cluster1.cpu 2.5000 = 5.00*(3.0/6.0)
  1216. // cluster1.ram 2.8125 = 15.00*(3.0/16.0)
  1217. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1218. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1219. //
  1220. // namespace3: 23.0000
  1221. // initial cost 18.0000
  1222. // cluster2.cpu 2.5000 = 5.00*(3.0/6.0)
  1223. // cluster2.ram 2.5000 = 5.00*(3.0/6.0)
  1224. //
  1225. // Then share overhead:
  1226. //
  1227. // namespace1: 100.0533 = 42.6875 + (7.0*24.0)*(28.00/82.00)
  1228. // namespace2: 120.0686 = 46.3125 + (7.0*24.0)*(36.00/82.00)
  1229. // namespace3: 59.8780 = 23.0000 + (7.0*24.0)*(18.00/82.00)
  1230. //
  1231. // Then namespace 2 is filtered.
  1232. as = generateAllocationSet(start)
  1233. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  1234. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  1235. ShareSplit: ShareWeighted,
  1236. ShareIdle: ShareWeighted,
  1237. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  1238. })
  1239. assertAllocationSetTotals(t, as, "6h", err, 1, 120.07)
  1240. assertAllocationTotals(t, as, "6h", map[string]float64{
  1241. "namespace2": 120.07,
  1242. })
  1243. assertAllocationWindow(t, as, "6h", startYesterday, endYesterday, 1440.0)
  1244. // 7 Edge cases and errors
  1245. // 7a Empty AggregationProperties
  1246. // 7b Filter all
  1247. // 7c Share all
  1248. // 7d Share and filter the same allocations
  1249. }
  1250. // TODO niko/etl
  1251. //func TestAllocationSet_Clone(t *testing.T) {}
  1252. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  1253. var as *AllocationSet
  1254. var err error
  1255. var idles map[string]*Allocation
  1256. end := time.Now().UTC().Truncate(day)
  1257. start := end.Add(-day)
  1258. // Generate AllocationSet and strip out any existing idle allocations
  1259. as = generateAllocationSet(start)
  1260. for key := range as.idleKeys {
  1261. as.Delete(key)
  1262. }
  1263. // Create an AssetSet representing cluster costs for two clusters (cluster1
  1264. // and cluster2). Include Nodes and Disks for both, even though only
  1265. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  1266. // by type, here we will provide multiple Nodes for one of the clusters to
  1267. // make sure the function still holds.
  1268. // NOTE: we're re-using generateAllocationSet so this has to line up with
  1269. // the allocated node costs from that function. See table above.
  1270. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  1271. // +-----------------------------------------+------+------+------+------+------------+
  1272. // cluster1:
  1273. // nodes 100.00 55.00 44.00 11.00 -10.00
  1274. // +-----------------------------------------+------+------+------+------+------------+
  1275. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  1276. // +-----------------------------------------+------+------+------+------+------------+
  1277. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  1278. // +-----------------------------------------+------+------+------+------+------------+
  1279. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  1280. // +-----------------------------------------+------+------+------+------+------------+
  1281. // cluster2:
  1282. // node1 35.00 20.00 15.00 0.00 0.00
  1283. // node2 35.00 20.00 15.00 0.00 0.00
  1284. // node3 30.00 10.00 10.00 10.00 0.00
  1285. // (disks should not matter for idle)
  1286. // +-----------------------------------------+------+------+------+------+------------+
  1287. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1288. // +-----------------------------------------+------+------+------+------+------------+
  1289. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1290. // +-----------------------------------------+------+------+------+------+------------+
  1291. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1292. // +-----------------------------------------+------+------+------+------+------------+
  1293. cluster1Nodes := NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1294. cluster1Nodes.CPUCost = 55.0
  1295. cluster1Nodes.RAMCost = 44.0
  1296. cluster1Nodes.GPUCost = 11.0
  1297. cluster1Nodes.adjustment = -10.00
  1298. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1299. cluster2Node1.CPUCost = 20.0
  1300. cluster2Node1.RAMCost = 15.0
  1301. cluster2Node1.GPUCost = 0.0
  1302. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1303. cluster2Node2.CPUCost = 20.0
  1304. cluster2Node2.RAMCost = 15.0
  1305. cluster2Node2.GPUCost = 0.0
  1306. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1307. cluster2Node3.CPUCost = 10.0
  1308. cluster2Node3.RAMCost = 10.0
  1309. cluster2Node3.GPUCost = 10.0
  1310. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1311. cluster2Disk1.Cost = 5.0
  1312. assetSet := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1313. idles, err = as.ComputeIdleAllocations(assetSet)
  1314. if err != nil {
  1315. t.Fatalf("unexpected error: %s", err)
  1316. }
  1317. if len(idles) != 2 {
  1318. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1319. }
  1320. if idle, ok := idles["cluster1"]; !ok {
  1321. t.Fatalf("expected idle cost for %s", "cluster1")
  1322. } else {
  1323. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  1324. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  1325. }
  1326. }
  1327. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1328. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1329. }
  1330. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1331. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1332. }
  1333. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1334. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1335. }
  1336. if idle, ok := idles["cluster2"]; !ok {
  1337. t.Fatalf("expected idle cost for %s", "cluster2")
  1338. } else {
  1339. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  1340. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  1341. }
  1342. }
  1343. // NOTE: we're re-using generateAllocationSet so this has to line up with
  1344. // the allocated node costs from that function. See table above.
  1345. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  1346. // +-----------------------------------------+------+------+------+------+------------+
  1347. // cluster1:
  1348. // nodes 100.00 5.00 4.00 1.00 90.00
  1349. // +-----------------------------------------+------+------+------+------+------------+
  1350. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  1351. // +-----------------------------------------+------+------+------+------+------------+
  1352. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  1353. // +-----------------------------------------+------+------+------+------+------------+
  1354. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  1355. // +-----------------------------------------+------+------+------+------+------------+
  1356. // cluster2:
  1357. // node1 35.00 20.00 15.00 0.00 0.00
  1358. // node2 35.00 20.00 15.00 0.00 0.00
  1359. // node3 30.00 10.00 10.00 10.00 0.00
  1360. // (disks should not matter for idle)
  1361. // +-----------------------------------------+------+------+------+------+------------+
  1362. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  1363. // +-----------------------------------------+------+------+------+------+------------+
  1364. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  1365. // +-----------------------------------------+------+------+------+------+------------+
  1366. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  1367. // +-----------------------------------------+------+------+------+------+------------+
  1368. cluster1Nodes = NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  1369. cluster1Nodes.CPUCost = 5.0
  1370. cluster1Nodes.RAMCost = 4.0
  1371. cluster1Nodes.GPUCost = 1.0
  1372. cluster1Nodes.adjustment = 90.00
  1373. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  1374. cluster2Node1.CPUCost = 20.0
  1375. cluster2Node1.RAMCost = 15.0
  1376. cluster2Node1.GPUCost = 0.0
  1377. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  1378. cluster2Node2.CPUCost = 20.0
  1379. cluster2Node2.RAMCost = 15.0
  1380. cluster2Node2.GPUCost = 0.0
  1381. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  1382. cluster2Node3.CPUCost = 10.0
  1383. cluster2Node3.RAMCost = 10.0
  1384. cluster2Node3.GPUCost = 10.0
  1385. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  1386. cluster2Disk1.Cost = 5.0
  1387. assetSet = NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  1388. idles, err = as.ComputeIdleAllocations(assetSet)
  1389. if err != nil {
  1390. t.Fatalf("unexpected error: %s", err)
  1391. }
  1392. if len(idles) != 2 {
  1393. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  1394. }
  1395. if idle, ok := idles["cluster1"]; !ok {
  1396. t.Fatalf("expected idle cost for %s", "cluster1")
  1397. } else {
  1398. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  1399. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  1400. }
  1401. }
  1402. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  1403. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  1404. }
  1405. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  1406. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  1407. }
  1408. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  1409. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  1410. }
  1411. if idle, ok := idles["cluster2"]; !ok {
  1412. t.Fatalf("expected idle cost for %s", "cluster2")
  1413. } else {
  1414. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  1415. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  1416. }
  1417. }
  1418. // TODO assert value of each resource cost precisely
  1419. }
  1420. // TODO niko/etl
  1421. //func TestAllocationSet_Delete(t *testing.T) {}
  1422. // TODO niko/etl
  1423. //func TestAllocationSet_End(t *testing.T) {}
  1424. // TODO niko/etl
  1425. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  1426. // TODO niko/etl
  1427. //func TestAllocationSet_Insert(t *testing.T) {}
  1428. // TODO niko/etl
  1429. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  1430. // TODO niko/etl
  1431. //func TestAllocationSet_Length(t *testing.T) {}
  1432. // TODO niko/etl
  1433. //func TestAllocationSet_Map(t *testing.T) {}
  1434. // TODO niko/etl
  1435. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  1436. // TODO niko/etl
  1437. //func TestAllocationSet_Resolution(t *testing.T) {}
  1438. // TODO niko/etl
  1439. //func TestAllocationSet_Seconds(t *testing.T) {}
  1440. // TODO niko/etl
  1441. //func TestAllocationSet_Set(t *testing.T) {}
  1442. // TODO niko/etl
  1443. //func TestAllocationSet_Start(t *testing.T) {}
  1444. // TODO niko/etl
  1445. //func TestAllocationSet_TotalCost(t *testing.T) {}
  1446. // TODO niko/etl
  1447. //func TestNewAllocationSetRange(t *testing.T) {}
  1448. func TestAllocationSetRange_Accumulate(t *testing.T) {
  1449. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1450. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1451. today := time.Now().UTC().Truncate(day)
  1452. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1453. // Accumulating any combination of nil and/or empty set should result in empty set
  1454. result, err := NewAllocationSetRange(nil).Accumulate()
  1455. if err != nil {
  1456. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1457. }
  1458. if !result.IsEmpty() {
  1459. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1460. }
  1461. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  1462. if err != nil {
  1463. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1464. }
  1465. if !result.IsEmpty() {
  1466. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1467. }
  1468. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  1469. if err != nil {
  1470. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1471. }
  1472. if !result.IsEmpty() {
  1473. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1474. }
  1475. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  1476. if err != nil {
  1477. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1478. }
  1479. if !result.IsEmpty() {
  1480. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1481. }
  1482. todayAS := NewAllocationSet(today, tomorrow)
  1483. todayAS.Set(NewUnitAllocation("", today, day, nil))
  1484. yesterdayAS := NewAllocationSet(yesterday, today)
  1485. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  1486. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  1487. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  1488. if err != nil {
  1489. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1490. }
  1491. if result == nil {
  1492. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1493. }
  1494. if result.TotalCost() != 6.0 {
  1495. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  1496. }
  1497. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  1498. if err != nil {
  1499. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1500. }
  1501. if result == nil {
  1502. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1503. }
  1504. if result.TotalCost() != 6.0 {
  1505. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  1506. }
  1507. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  1508. if err != nil {
  1509. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1510. }
  1511. if result == nil {
  1512. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1513. }
  1514. if result.TotalCost() != 6.0 {
  1515. t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
  1516. }
  1517. // Accumulate two non-nil should result in sum of both with appropriate start, end
  1518. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  1519. if err != nil {
  1520. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1521. }
  1522. if result == nil {
  1523. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1524. }
  1525. if result.TotalCost() != 12.0 {
  1526. t.Fatalf("accumulating AllocationSetRange: expected total cost 12.0; actual %f", result.TotalCost())
  1527. }
  1528. allocMap := result.Map()
  1529. if len(allocMap) != 1 {
  1530. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  1531. }
  1532. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  1533. if alloc == nil {
  1534. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  1535. }
  1536. if alloc.CPUCoreHours != 2.0 {
  1537. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  1538. }
  1539. if alloc.CPUCost != 2.0 {
  1540. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  1541. }
  1542. if alloc.CPUEfficiency() != 1.0 {
  1543. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency())
  1544. }
  1545. if alloc.GPUHours != 2.0 {
  1546. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  1547. }
  1548. if alloc.GPUCost != 2.0 {
  1549. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  1550. }
  1551. if alloc.NetworkCost != 2.0 {
  1552. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  1553. }
  1554. if alloc.LoadBalancerCost != 2.0 {
  1555. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.LoadBalancerCost)
  1556. }
  1557. if alloc.PVByteHours != 2.0 {
  1558. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  1559. }
  1560. if alloc.PVCost != 2.0 {
  1561. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  1562. }
  1563. if alloc.RAMByteHours != 2.0 {
  1564. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  1565. }
  1566. if alloc.RAMCost != 2.0 {
  1567. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  1568. }
  1569. if alloc.RAMEfficiency() != 1.0 {
  1570. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
  1571. }
  1572. if alloc.TotalCost() != 12.0 {
  1573. t.Fatalf("accumulating AllocationSetRange: expected 12.0; actual %f", alloc.TotalCost())
  1574. }
  1575. if alloc.TotalEfficiency() != 1.0 {
  1576. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
  1577. }
  1578. if !alloc.Start.Equal(yesterday) {
  1579. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1580. }
  1581. if !alloc.End.Equal(tomorrow) {
  1582. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1583. }
  1584. if alloc.Minutes() != 2880.0 {
  1585. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes())
  1586. }
  1587. }
  1588. // TODO niko/etl
  1589. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1590. // TODO niko/etl
  1591. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1592. // TODO niko/etl
  1593. // func TestAllocationSetRange_Append(t *testing.T) {}
  1594. // TODO niko/etl
  1595. // func TestAllocationSetRange_Each(t *testing.T) {}
  1596. // TODO niko/etl
  1597. // func TestAllocationSetRange_Get(t *testing.T) {}
  1598. func TestAllocationSetRange_InsertRange(t *testing.T) {
  1599. // Set up
  1600. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1601. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1602. today := time.Now().UTC().Truncate(day)
  1603. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1604. unit := NewUnitAllocation("", today, day, nil)
  1605. ago2dAS := NewAllocationSet(ago2d, yesterday)
  1606. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  1607. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  1608. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  1609. yesterdayAS := NewAllocationSet(yesterday, today)
  1610. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  1611. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  1612. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  1613. todayAS := NewAllocationSet(today, tomorrow)
  1614. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  1615. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  1616. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  1617. var nilASR *AllocationSetRange
  1618. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  1619. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  1620. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  1621. var err error
  1622. // Expect an error calling InsertRange on nil
  1623. err = nilASR.InsertRange(thatASR)
  1624. if err == nil {
  1625. t.Fatalf("expected error, got nil")
  1626. }
  1627. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  1628. err = thisASR.InsertRange(nil)
  1629. if err != nil {
  1630. t.Fatalf("unexpected error: %s", err)
  1631. }
  1632. thisASR.Each(func(i int, as *AllocationSet) {
  1633. as.Each(func(k string, a *Allocation) {
  1634. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1635. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1636. }
  1637. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1638. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1639. }
  1640. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1641. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1642. }
  1643. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1644. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1645. }
  1646. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1647. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1648. }
  1649. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1650. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1651. }
  1652. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1653. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1654. }
  1655. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1656. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1657. }
  1658. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1659. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1660. }
  1661. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  1662. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  1663. }
  1664. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1665. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1666. }
  1667. })
  1668. })
  1669. // Expect an error calling InsertRange with a range exceeding the receiver
  1670. err = thisASR.InsertRange(longASR)
  1671. if err == nil {
  1672. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  1673. }
  1674. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  1675. // precisely double when inserting a range that only has a duplicate of
  1676. // "yesterday", but no entry for "today"
  1677. err = thisASR.InsertRange(thatASR)
  1678. if err != nil {
  1679. t.Fatalf("unexpected error: %s", err)
  1680. }
  1681. yAS, err := thisASR.Get(0)
  1682. yAS.Each(func(k string, a *Allocation) {
  1683. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  1684. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1685. }
  1686. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  1687. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1688. }
  1689. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  1690. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1691. }
  1692. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  1693. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1694. }
  1695. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  1696. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1697. }
  1698. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  1699. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1700. }
  1701. if !util.IsApproximately(a.PVByteHours, 2*unit.PVByteHours) {
  1702. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1703. }
  1704. if !util.IsApproximately(a.PVCost, 2*unit.PVCost) {
  1705. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1706. }
  1707. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  1708. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1709. }
  1710. if !util.IsApproximately(a.LoadBalancerCost, 2*unit.LoadBalancerCost) {
  1711. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  1712. }
  1713. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  1714. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1715. }
  1716. })
  1717. tAS, err := thisASR.Get(1)
  1718. tAS.Each(func(k string, a *Allocation) {
  1719. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1720. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1721. }
  1722. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1723. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1724. }
  1725. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1726. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1727. }
  1728. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1729. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1730. }
  1731. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1732. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1733. }
  1734. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1735. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1736. }
  1737. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1738. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1739. }
  1740. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1741. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1742. }
  1743. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1744. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1745. }
  1746. if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
  1747. t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
  1748. }
  1749. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1750. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1751. }
  1752. })
  1753. }
  1754. // TODO niko/etl
  1755. // func TestAllocationSetRange_Length(t *testing.T) {}
  1756. // TODO niko/etl
  1757. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1758. // TODO niko/etl
  1759. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1760. // TODO niko/etl
  1761. // func TestAllocationSetRange_Window(t *testing.T) {}