allocation_test.go 71 KB

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