allocation_test.go 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502
  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. // TODO niko/etl more
  80. }
  81. func TestAllocation_MarshalJSON(t *testing.T) {
  82. start := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
  83. end := time.Date(2021, time.January, 2, 0, 0, 0, 0, time.UTC)
  84. hrs := 24.0
  85. gib := 1024.0 * 1024.0 * 1024.0
  86. cpuPrice := 0.02
  87. gpuPrice := 2.00
  88. ramPrice := 0.01
  89. pvPrice := 0.00005
  90. before := &Allocation{
  91. Name: "cluster1/namespace1/node1/pod1/container1",
  92. Properties: Properties{
  93. ClusterProp: "cluster1",
  94. NodeProp: "node1",
  95. NamespaceProp: "namespace1",
  96. PodProp: "pod1",
  97. ContainerProp: "container1",
  98. },
  99. Window: NewWindow(&start, &end),
  100. Start: start,
  101. End: end,
  102. CPUCoreHours: 2.0 * hrs,
  103. CPUCoreRequestAverage: 2.0,
  104. CPUCoreUsageAverage: 1.0,
  105. CPUCost: 2.0 * hrs * cpuPrice,
  106. GPUHours: 1.0 * hrs,
  107. GPUCost: 1.0 * hrs * gpuPrice,
  108. NetworkCost: 0.05,
  109. PVByteHours: 100.0 * gib * hrs,
  110. PVCost: 100.0 * hrs * pvPrice,
  111. RAMByteHours: 8.0 * gib * hrs,
  112. RAMBytesRequestAverage: 8.0 * gib,
  113. RAMBytesUsageAverage: 4.0 * gib,
  114. RAMCost: 8.0 * hrs * ramPrice,
  115. SharedCost: 2.00,
  116. ExternalCost: 1.00,
  117. }
  118. data, err := json.Marshal(before)
  119. if err != nil {
  120. t.Fatalf("Allocation.MarshalJSON: unexpected error: %s", err)
  121. }
  122. after := &Allocation{}
  123. err = json.Unmarshal(data, after)
  124. if err != nil {
  125. t.Fatalf("Allocation.UnmarshalJSON: unexpected error: %s", err)
  126. }
  127. // TODO:CLEANUP fix json marshaling of Window so that all of this works.
  128. // In the meantime, just set the Window so that we can test the rest.
  129. after.Window = before.Window.Clone()
  130. fmt.Println(*before)
  131. fmt.Println(*after)
  132. if !after.Equal(before) {
  133. t.Fatalf("Allocation.MarshalJSON: before and after are not equal")
  134. }
  135. }
  136. func TestNewAllocationSet(t *testing.T) {
  137. // TODO niko/etl
  138. }
  139. func generateAllocationSet(start time.Time) *AllocationSet {
  140. // Idle allocations
  141. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
  142. ClusterProp: "cluster1",
  143. NodeProp: "node1",
  144. })
  145. a1i.CPUCost = 5.0
  146. a1i.RAMCost = 15.0
  147. a1i.GPUCost = 0.0
  148. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
  149. ClusterProp: "cluster2",
  150. })
  151. a2i.CPUCost = 5.0
  152. a2i.RAMCost = 5.0
  153. a2i.GPUCost = 0.0
  154. // Active allocations
  155. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
  156. ClusterProp: "cluster1",
  157. NamespaceProp: "namespace1",
  158. PodProp: "pod1",
  159. ContainerProp: "container1",
  160. })
  161. a1111.RAMCost = 11.00
  162. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
  163. ClusterProp: "cluster1",
  164. NamespaceProp: "namespace1",
  165. PodProp: "pod-abc",
  166. ContainerProp: "container2",
  167. })
  168. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
  169. ClusterProp: "cluster1",
  170. NamespaceProp: "namespace1",
  171. PodProp: "pod-def",
  172. ContainerProp: "container3",
  173. })
  174. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
  175. ClusterProp: "cluster1",
  176. NamespaceProp: "namespace2",
  177. PodProp: "pod-ghi",
  178. ContainerProp: "container4",
  179. })
  180. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
  181. ClusterProp: "cluster1",
  182. NamespaceProp: "namespace2",
  183. PodProp: "pod-ghi",
  184. ContainerProp: "container5",
  185. })
  186. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
  187. ClusterProp: "cluster1",
  188. NamespaceProp: "namespace2",
  189. PodProp: "pod-jkl",
  190. ContainerProp: "container6",
  191. })
  192. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
  193. ClusterProp: "cluster2",
  194. NamespaceProp: "namespace2",
  195. PodProp: "pod-mno",
  196. ContainerProp: "container4",
  197. })
  198. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
  199. ClusterProp: "cluster2",
  200. NamespaceProp: "namespace2",
  201. PodProp: "pod-mno",
  202. ContainerProp: "container5",
  203. })
  204. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
  205. ClusterProp: "cluster2",
  206. NamespaceProp: "namespace2",
  207. PodProp: "pod-pqr",
  208. ContainerProp: "container6",
  209. })
  210. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
  211. ClusterProp: "cluster2",
  212. NamespaceProp: "namespace3",
  213. PodProp: "pod-stu",
  214. ContainerProp: "container7",
  215. })
  216. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
  217. ClusterProp: "cluster2",
  218. NamespaceProp: "namespace3",
  219. PodProp: "pod-vwx",
  220. ContainerProp: "container8",
  221. })
  222. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
  223. ClusterProp: "cluster2",
  224. NamespaceProp: "namespace3",
  225. PodProp: "pod-vwx",
  226. ContainerProp: "container9",
  227. })
  228. // Controllers
  229. a11abc2.Properties.SetControllerKind("deployment")
  230. a11abc2.Properties.SetController("deployment1")
  231. a11def3.Properties.SetControllerKind("deployment")
  232. a11def3.Properties.SetController("deployment1")
  233. a12ghi4.Properties.SetControllerKind("deployment")
  234. a12ghi4.Properties.SetController("deployment2")
  235. a12ghi5.Properties.SetControllerKind("deployment")
  236. a12ghi5.Properties.SetController("deployment2")
  237. a22mno4.Properties.SetControllerKind("deployment")
  238. a22mno4.Properties.SetController("deployment2")
  239. a22mno5.Properties.SetControllerKind("deployment")
  240. a22mno5.Properties.SetController("deployment2")
  241. a23stu7.Properties.SetControllerKind("deployment")
  242. a23stu7.Properties.SetController("deployment3")
  243. a12jkl6.Properties.SetControllerKind("daemonset")
  244. a12jkl6.Properties.SetController("daemonset1")
  245. a22pqr6.Properties.SetControllerKind("daemonset")
  246. a22pqr6.Properties.SetController("daemonset1")
  247. a23vwx8.Properties.SetControllerKind("statefulset")
  248. a23vwx8.Properties.SetController("statefulset1")
  249. a23vwx9.Properties.SetControllerKind("statefulset")
  250. a23vwx9.Properties.SetController("statefulset1")
  251. // Labels
  252. a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
  253. a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  254. a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  255. a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
  256. a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
  257. //Annotations
  258. a23stu7.Properties.SetAnnotations(map[string]string{"team": "team1"})
  259. a23vwx8.Properties.SetAnnotations(map[string]string{"team": "team2"})
  260. a23vwx9.Properties.SetAnnotations(map[string]string{"team": "team1"})
  261. // Services
  262. a12jkl6.Properties.SetServices([]string{"service1"})
  263. a22pqr6.Properties.SetServices([]string{"service1"})
  264. return NewAllocationSet(start, start.Add(day),
  265. // idle
  266. a1i, a2i,
  267. // cluster 1, namespace1
  268. a1111, a11abc2, a11def3,
  269. // cluster 1, namespace 2
  270. a12ghi4, a12ghi5, a12jkl6,
  271. // cluster 2, namespace 2
  272. a22mno4, a22mno5, a22pqr6,
  273. // cluster 2, namespace 3
  274. a23stu7, a23vwx8, a23vwx9,
  275. )
  276. }
  277. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  278. if err != nil {
  279. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  280. }
  281. if as.Length() != length {
  282. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  283. }
  284. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  285. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  286. }
  287. }
  288. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  289. as.Each(func(k string, a *Allocation) {
  290. if exp, ok := exps[a.Name]; ok {
  291. if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
  292. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost())
  293. }
  294. } else {
  295. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  296. }
  297. })
  298. }
  299. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  300. as.Each(func(k string, a *Allocation) {
  301. if !a.Start.Equal(expStart) {
  302. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  303. }
  304. if !a.End.Equal(expEnd) {
  305. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  306. }
  307. if a.Minutes() != expMinutes {
  308. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes())
  309. }
  310. })
  311. }
  312. func printAllocationSet(msg string, as *AllocationSet) {
  313. fmt.Printf("--- %s ---\n", msg)
  314. as.Each(func(k string, a *Allocation) {
  315. fmt.Printf(" > %s\n", a)
  316. })
  317. }
  318. func TestAllocationSet_AggregateBy(t *testing.T) {
  319. // Test AggregateBy against the following workload topology, which is
  320. // generated by generateAllocationSet:
  321. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
  322. // +----------------------------------------+------+------+------+------+------+------+
  323. // cluster1:
  324. // idle: 20.00 5.00 15.00 0.00 0.00 0.00
  325. // namespace1:
  326. // pod1:
  327. // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
  328. // pod-abc: (deployment1)
  329. // container2: 5.00 1.00 1.00 1.00 1.00 1.00
  330. // pod-def: (deployment1)
  331. // container3: 5.00 1.00 1.00 1.00 1.00 1.00
  332. // namespace2:
  333. // pod-ghi: (deployment2)
  334. // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  335. // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  336. // pod-jkl: (daemonset1)
  337. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  338. // +-----------------------------------------+------+------+------+------+------+------+
  339. // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
  340. // +-----------------------------------------+------+------+------+------+------+------+
  341. // cluster2:
  342. // idle: 10.00 5.00 5.00 0.00 0.00 0.00
  343. // namespace2:
  344. // pod-mno: (deployment2)
  345. // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  346. // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  347. // pod-pqr: (daemonset1)
  348. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  349. // namespace3:
  350. // pod-stu: (deployment3)
  351. // container7: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  352. // pod-vwx: (statefulset1)
  353. // container8: an[team=team2] 5.00 1.00 1.00 1.00 1.00 1.00
  354. // container9: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  355. // +----------------------------------------+------+------+------+------+------+------+
  356. // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
  357. // +----------------------------------------+------+------+------+------+------+------+
  358. // total 100.00 22.00 42.00 12.00 12.00 12.00
  359. // +----------------------------------------+------+------+------+------+------+------+
  360. // Scenarios to test:
  361. // 1 Single-aggregation
  362. // 1a AggregationProperties=(Cluster)
  363. // 1b AggregationProperties=(Namespace)
  364. // 1c AggregationProperties=(Pod)
  365. // 1d AggregationProperties=(Container)
  366. // 1e AggregationProperties=(ControllerKind)
  367. // 1f AggregationProperties=(Controller)
  368. // 1g AggregationProperties=(Service)
  369. // 1h AggregationProperties=(Label:app)
  370. // 2 Multi-aggregation
  371. // 2a AggregationProperties=(Cluster, Namespace)
  372. // 2b AggregationProperties=(Namespace, Label:app)
  373. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  374. // 2d AggregationProperties=(Label:app, Label:environment)
  375. // 3 Share idle
  376. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  377. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  378. // 4 Share resources
  379. // 4a Share namespace ShareEven
  380. // 4b Share cluster ShareWeighted
  381. // 4c Share label ShareEven
  382. // 4d Share overhead ShareWeighted
  383. // 5 Filters
  384. // 5a Filter by cluster with separate idle
  385. // 5b Filter by cluster with shared idle
  386. // TODO niko/idle more filter tests
  387. // 6 Combinations and options
  388. // 6a SplitIdle
  389. // 6b Share idle with filters
  390. // 6c Share resources with filters
  391. // 6d Share idle and share resources
  392. // 7 Edge cases and errors
  393. // 7a Empty AggregationProperties
  394. // 7b Filter all
  395. // 7c Share all
  396. // 7d Share and filter the same allocations
  397. // Definitions and set-up:
  398. var as *AllocationSet
  399. var err error
  400. endYesterday := time.Now().UTC().Truncate(day)
  401. startYesterday := endYesterday.Add(-day)
  402. numClusters := 2
  403. numNamespaces := 3
  404. numPods := 9
  405. numContainers := 9
  406. numControllerKinds := 3
  407. numControllers := 5
  408. numServices := 1
  409. numLabelApps := 2
  410. // By default, idle is reported as a single, merged allocation
  411. numIdle := 1
  412. // There will only ever be one __unallocated__
  413. numUnallocated := 1
  414. // There are two clusters, so each gets an idle entry when they are split
  415. numSplitIdle := 2
  416. activeTotalCost := 70.0
  417. idleTotalCost := 30.0
  418. sharedOverheadHourlyCost := 7.0
  419. isNamespace3 := func(a *Allocation) bool {
  420. ns, err := a.Properties.GetNamespace()
  421. return err == nil && ns == "namespace3"
  422. }
  423. isApp1 := func(a *Allocation) bool {
  424. ls, _ := a.Properties.GetLabels()
  425. if app, ok := ls["app"]; ok && app == "app1" {
  426. return true
  427. }
  428. return false
  429. }
  430. end := time.Now().UTC().Truncate(day)
  431. start := end.Add(-day)
  432. // Tests:
  433. // 1 Single-aggregation
  434. // 1a AggregationProperties=(Cluster)
  435. as = generateAllocationSet(start)
  436. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  437. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  438. assertAllocationTotals(t, as, "1a", map[string]float64{
  439. "cluster1": 40.00,
  440. "cluster2": 30.00,
  441. IdleSuffix: 30.00,
  442. })
  443. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  444. // 1b AggregationProperties=(Namespace)
  445. as = generateAllocationSet(start)
  446. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  447. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  448. assertAllocationTotals(t, as, "1b", map[string]float64{
  449. "namespace1": 25.00,
  450. "namespace2": 30.00,
  451. "namespace3": 15.00,
  452. IdleSuffix: 30.00,
  453. })
  454. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  455. // 1c AggregationProperties=(Pod)
  456. as = generateAllocationSet(start)
  457. err = as.AggregateBy(Properties{PodProp: true}, nil)
  458. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  459. assertAllocationTotals(t, as, "1c", map[string]float64{
  460. "pod-jkl": 5.00,
  461. "pod-stu": 5.00,
  462. "pod-abc": 5.00,
  463. "pod-pqr": 5.00,
  464. "pod-def": 5.00,
  465. "pod-vwx": 10.00,
  466. "pod1": 15.00,
  467. "pod-mno": 10.00,
  468. "pod-ghi": 10.00,
  469. IdleSuffix: 30.00,
  470. })
  471. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  472. // 1d AggregationProperties=(Container)
  473. as = generateAllocationSet(start)
  474. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  475. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  476. assertAllocationTotals(t, as, "1d", map[string]float64{
  477. "container2": 5.00,
  478. "container9": 5.00,
  479. "container6": 10.00,
  480. "container3": 5.00,
  481. "container4": 10.00,
  482. "container7": 5.00,
  483. "container8": 5.00,
  484. "container5": 10.00,
  485. "container1": 15.00,
  486. IdleSuffix: 30.00,
  487. })
  488. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  489. // 1e AggregationProperties=(ControllerKind)
  490. as = generateAllocationSet(start)
  491. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  492. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  493. assertAllocationTotals(t, as, "1e", map[string]float64{
  494. "daemonset": 10.00,
  495. "deployment": 35.00,
  496. "statefulset": 10.00,
  497. IdleSuffix: 30.00,
  498. UnallocatedSuffix: 15.00,
  499. })
  500. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  501. // 1f AggregationProperties=(Controller)
  502. as = generateAllocationSet(start)
  503. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  504. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  505. assertAllocationTotals(t, as, "1f", map[string]float64{
  506. "deployment/deployment2": 20.00,
  507. "daemonset/daemonset1": 10.00,
  508. "deployment/deployment3": 5.00,
  509. "statefulset/statefulset1": 10.00,
  510. "deployment/deployment1": 10.00,
  511. IdleSuffix: 30.00,
  512. UnallocatedSuffix: 15.00,
  513. })
  514. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  515. // 1g AggregationProperties=(Service)
  516. as = generateAllocationSet(start)
  517. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  518. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  519. assertAllocationTotals(t, as, "1g", map[string]float64{
  520. "service1": 10.00,
  521. IdleSuffix: 30.00,
  522. UnallocatedSuffix: 60.00,
  523. })
  524. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  525. // 1h AggregationProperties=(Label:app)
  526. as = generateAllocationSet(start)
  527. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  528. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  529. assertAllocationTotals(t, as, "1h", map[string]float64{
  530. "app=app1": 15.00,
  531. "app=app2": 20.00,
  532. IdleSuffix: 30.00,
  533. UnallocatedSuffix: 35.00,
  534. })
  535. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  536. // 1i AggregationProperties=(ControllerKind:deployment)
  537. as = generateAllocationSet(start)
  538. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  539. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  540. assertAllocationTotals(t, as, "1i", map[string]float64{
  541. "deployment": 35.00,
  542. IdleSuffix: 30.00,
  543. UnallocatedSuffix: 35.00,
  544. })
  545. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  546. // 1j AggregationProperties=(Annotation:team)
  547. as = generateAllocationSet(start)
  548. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
  549. assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  550. assertAllocationTotals(t, as, "1j", map[string]float64{
  551. "team=team1": 10.00,
  552. "team=team2": 5.00,
  553. IdleSuffix: 30.00,
  554. UnallocatedSuffix: 55.00,
  555. })
  556. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  557. // 2 Multi-aggregation
  558. // 2a AggregationProperties=(Cluster, Namespace)
  559. // 2b AggregationProperties=(Namespace, Label:app)
  560. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  561. // 2d AggregationProperties=(Label:app, Label:environment)
  562. as = generateAllocationSet(start)
  563. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  564. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  565. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  566. assertAllocationTotals(t, as, "2d", map[string]float64{
  567. "app=app1/env=env1": 15.00,
  568. "app=app2/env=env2": 10.00,
  569. "app=app2/" + UnallocatedSuffix: 10.00,
  570. IdleSuffix: 30.00,
  571. UnallocatedSuffix: 35.00,
  572. })
  573. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  574. as = generateAllocationSet(start)
  575. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  576. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  577. assertAllocationTotals(t, as, "2e", map[string]float64{
  578. "cluster1/app=app2/env=env2": 10.00,
  579. "__idle__": 30.00,
  580. "cluster1/app=app1/env=env1": 15.00,
  581. "cluster1/" + UnallocatedSuffix: 15.00,
  582. "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
  583. "cluster2/" + UnallocatedSuffix: 20.00,
  584. })
  585. // 2f AggregationProperties=(annotation:team, pod)
  586. as = generateAllocationSet(start)
  587. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
  588. assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
  589. assertAllocationTotals(t, as, "2e", map[string]float64{
  590. "pod-jkl/" + UnallocatedSuffix: 5.00,
  591. "pod-stu/team=team1": 5.00,
  592. "pod-abc/" + UnallocatedSuffix: 5.00,
  593. "pod-pqr/" + UnallocatedSuffix: 5.00,
  594. "pod-def/" + UnallocatedSuffix: 5.00,
  595. "pod-vwx/team=team1": 5.00,
  596. "pod-vwx/team=team2": 5.00,
  597. "pod1/" + UnallocatedSuffix: 15.00,
  598. "pod-mno/" + UnallocatedSuffix: 10.00,
  599. "pod-ghi/" + UnallocatedSuffix: 10.00,
  600. IdleSuffix: 30.00,
  601. })
  602. // // TODO niko/etl
  603. // // 3 Share idle
  604. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  605. // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  606. // 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)
  607. // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  608. as = generateAllocationSet(start)
  609. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  610. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  611. assertAllocationTotals(t, as, "3a", map[string]float64{
  612. "namespace1": 39.69,
  613. "namespace2": 40.31,
  614. "namespace3": 20.00,
  615. })
  616. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  617. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  618. // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  619. // 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)
  620. // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  621. as = generateAllocationSet(start)
  622. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  623. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  624. assertAllocationTotals(t, as, "3a", map[string]float64{
  625. "namespace1": 35.00,
  626. "namespace2": 45.00,
  627. "namespace3": 20.00,
  628. })
  629. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  630. // 4 Share resources
  631. // 4a Share namespace ShareEven
  632. // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
  633. // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
  634. // idle: 30.0000
  635. as = generateAllocationSet(start)
  636. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  637. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  638. ShareSplit: ShareEven,
  639. })
  640. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  641. assertAllocationTotals(t, as, "4a", map[string]float64{
  642. "namespace1": 32.50,
  643. "namespace2": 37.50,
  644. IdleSuffix: 30.00,
  645. })
  646. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  647. // 4b Share namespace ShareWeighted
  648. // namespace1: 32.5000 =
  649. // namespace2: 37.5000 =
  650. // idle: 30.0000
  651. as = generateAllocationSet(start)
  652. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  653. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  654. ShareSplit: ShareWeighted,
  655. })
  656. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  657. assertAllocationTotals(t, as, "4b", map[string]float64{
  658. "namespace1": 31.82,
  659. "namespace2": 38.18,
  660. IdleSuffix: 30.00,
  661. })
  662. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  663. // 4c Share label ShareEven
  664. // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
  665. // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
  666. // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
  667. // idle: 30.0000
  668. as = generateAllocationSet(start)
  669. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  670. ShareFuncs: []AllocationMatchFunc{isApp1},
  671. ShareSplit: ShareEven,
  672. })
  673. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  674. assertAllocationTotals(t, as, "4c", map[string]float64{
  675. "namespace1": 15.00,
  676. "namespace2": 35.00,
  677. "namespace3": 20.00,
  678. IdleSuffix: 30.00,
  679. })
  680. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  681. // 4d Share overhead ShareWeighted
  682. // namespace1: 37.5000 = 25.00 + (7.0*24.0)*(25.00/70.00)
  683. // namespace2: 45.0000 = 30.00 + (7.0*24.0)*(30.00/70.00)
  684. // namespace3: 22.5000 = 15.00 + (7.0*24.0)*(15.00/70.00)
  685. // idle: 30.0000
  686. as = generateAllocationSet(start)
  687. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  688. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  689. ShareSplit: ShareWeighted,
  690. })
  691. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  692. assertAllocationTotals(t, as, "4d", map[string]float64{
  693. "namespace1": 85.00,
  694. "namespace2": 102.00,
  695. "namespace3": 51.00,
  696. IdleSuffix: 30.00,
  697. })
  698. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  699. // 5 Filters
  700. isCluster := func(matchCluster string) func(*Allocation) bool {
  701. return func(a *Allocation) bool {
  702. cluster, err := a.Properties.GetCluster()
  703. return err == nil && cluster == matchCluster
  704. }
  705. }
  706. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  707. return func(a *Allocation) bool {
  708. namespace, err := a.Properties.GetNamespace()
  709. return err == nil && namespace == matchNamespace
  710. }
  711. }
  712. // 5a Filter by cluster with separate idle
  713. as = generateAllocationSet(start)
  714. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  715. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  716. ShareIdle: ShareNone,
  717. })
  718. assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
  719. assertAllocationTotals(t, as, "5a", map[string]float64{
  720. "cluster1": 40.00,
  721. IdleSuffix: 20.00,
  722. })
  723. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  724. // 5b Filter by cluster with shared idle
  725. as = generateAllocationSet(start)
  726. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  727. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  728. ShareIdle: ShareWeighted,
  729. })
  730. assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
  731. assertAllocationTotals(t, as, "5b", map[string]float64{
  732. "cluster1": 60.00,
  733. })
  734. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  735. // 5c Filter by cluster, agg by namespace, with separate idle
  736. as = generateAllocationSet(start)
  737. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  738. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  739. ShareIdle: ShareNone,
  740. })
  741. assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
  742. assertAllocationTotals(t, as, "5c", map[string]float64{
  743. "namespace1": 25.00,
  744. "namespace2": 15.00,
  745. IdleSuffix: 20.00,
  746. })
  747. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  748. // 5d Filter by namespace, agg by cluster, with separate idle
  749. as = generateAllocationSet(start)
  750. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  751. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  752. ShareIdle: ShareNone,
  753. })
  754. assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
  755. assertAllocationTotals(t, as, "5d", map[string]float64{
  756. "cluster1": 15.00,
  757. "cluster2": 15.00,
  758. IdleSuffix: 10.31,
  759. })
  760. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  761. // 6 Combinations and options
  762. // 6a SplitIdle
  763. as = generateAllocationSet(start)
  764. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  765. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  766. assertAllocationTotals(t, as, "6a", map[string]float64{
  767. "namespace1": 25.00,
  768. "namespace2": 30.00,
  769. "namespace3": 15.00,
  770. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  771. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  772. })
  773. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  774. // 6b Share idle weighted with filters
  775. // Should match values from unfiltered aggregation
  776. // as = generateAllocationSet(start)
  777. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  778. // printAllocationSet("6b unfiltered", as)
  779. as = generateAllocationSet(start)
  780. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  781. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  782. ShareIdle: ShareWeighted,
  783. })
  784. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  785. assertAllocationTotals(t, as, "6b", map[string]float64{
  786. "namespace2": 40.31,
  787. })
  788. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  789. // 6c Share idle even with filters
  790. // Should match values from unfiltered aggregation
  791. // as = generateAllocationSet(start)
  792. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  793. // printAllocationSet("6c unfiltered", as)
  794. as = generateAllocationSet(start)
  795. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  796. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  797. ShareIdle: ShareEven,
  798. })
  799. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  800. assertAllocationTotals(t, as, "6b", map[string]float64{
  801. "namespace2": 45.00,
  802. })
  803. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  804. // 6d Share resources with filters
  805. // 6e Share idle and share resources
  806. // 7 Edge cases and errors
  807. // 7a Empty AggregationProperties
  808. // 7b Filter all
  809. // 7c Share all
  810. // 7d Share and filter the same allocations
  811. }
  812. // TODO niko/etl
  813. //func TestAllocationSet_Clone(t *testing.T) {}
  814. func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
  815. var as *AllocationSet
  816. var err error
  817. var idles map[string]*Allocation
  818. end := time.Now().UTC().Truncate(day)
  819. start := end.Add(-day)
  820. // Generate AllocationSet and strip out any existing idle allocations
  821. as = generateAllocationSet(start)
  822. for key := range as.idleKeys {
  823. as.Delete(key)
  824. }
  825. // Create an AssetSet representing cluster costs for two clusters (cluster1
  826. // and cluster2). Include Nodes and Disks for both, even though only
  827. // Nodes will be counted. Whereas in practice, Assets should be aggregated
  828. // by type, here we will provide multiple Nodes for one of the clusters to
  829. // make sure the function still holds.
  830. // NOTE: we're re-using generateAllocationSet so this has to line up with
  831. // the allocated node costs from that function. See table above.
  832. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  833. // +-----------------------------------------+------+------+------+------+------------+
  834. // cluster1:
  835. // nodes 100.00 55.00 44.00 11.00 -10.00
  836. // +-----------------------------------------+------+------+------+------+------------+
  837. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  838. // +-----------------------------------------+------+------+------+------+------------+
  839. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  840. // +-----------------------------------------+------+------+------+------+------------+
  841. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  842. // +-----------------------------------------+------+------+------+------+------------+
  843. // cluster2:
  844. // node1 35.00 20.00 15.00 0.00 0.00
  845. // node2 35.00 20.00 15.00 0.00 0.00
  846. // node3 30.00 10.00 10.00 10.00 0.00
  847. // (disks should not matter for idle)
  848. // +-----------------------------------------+------+------+------+------+------------+
  849. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  850. // +-----------------------------------------+------+------+------+------+------------+
  851. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  852. // +-----------------------------------------+------+------+------+------+------------+
  853. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  854. // +-----------------------------------------+------+------+------+------+------------+
  855. cluster1Nodes := NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  856. cluster1Nodes.CPUCost = 55.0
  857. cluster1Nodes.RAMCost = 44.0
  858. cluster1Nodes.GPUCost = 11.0
  859. cluster1Nodes.adjustment = -10.00
  860. cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  861. cluster2Node1.CPUCost = 20.0
  862. cluster2Node1.RAMCost = 15.0
  863. cluster2Node1.GPUCost = 0.0
  864. cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  865. cluster2Node2.CPUCost = 20.0
  866. cluster2Node2.RAMCost = 15.0
  867. cluster2Node2.GPUCost = 0.0
  868. cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  869. cluster2Node3.CPUCost = 10.0
  870. cluster2Node3.RAMCost = 10.0
  871. cluster2Node3.GPUCost = 10.0
  872. cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  873. cluster2Disk1.Cost = 5.0
  874. assetSet := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  875. idles, err = as.ComputeIdleAllocations(assetSet)
  876. if err != nil {
  877. t.Fatalf("unexpected error: %s", err)
  878. }
  879. if len(idles) != 2 {
  880. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  881. }
  882. if idle, ok := idles["cluster1"]; !ok {
  883. t.Fatalf("expected idle cost for %s", "cluster1")
  884. } else {
  885. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  886. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  887. }
  888. }
  889. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  890. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  891. }
  892. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  893. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  894. }
  895. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  896. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  897. }
  898. if idle, ok := idles["cluster2"]; !ok {
  899. t.Fatalf("expected idle cost for %s", "cluster2")
  900. } else {
  901. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  902. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  903. }
  904. }
  905. // NOTE: we're re-using generateAllocationSet so this has to line up with
  906. // the allocated node costs from that function. See table above.
  907. // | Hierarchy | Cost | CPU | RAM | GPU | Adjustment |
  908. // +-----------------------------------------+------+------+------+------+------------+
  909. // cluster1:
  910. // nodes 100.00 5.00 4.00 1.00 90.00
  911. // +-----------------------------------------+------+------+------+------+------------+
  912. // cluster1 subtotal (adjusted) 100.00 50.00 40.00 10.00 0.00
  913. // +-----------------------------------------+------+------+------+------+------------+
  914. // cluster1 allocated 48.00 6.00 16.00 6.00 0.00
  915. // +-----------------------------------------+------+------+------+------+------------+
  916. // cluster1 idle 72.00 44.00 24.00 4.00 0.00
  917. // +-----------------------------------------+------+------+------+------+------------+
  918. // cluster2:
  919. // node1 35.00 20.00 15.00 0.00 0.00
  920. // node2 35.00 20.00 15.00 0.00 0.00
  921. // node3 30.00 10.00 10.00 10.00 0.00
  922. // (disks should not matter for idle)
  923. // +-----------------------------------------+------+------+------+------+------------+
  924. // cluster2 subtotal 100.00 50.00 40.00 10.00 0.00
  925. // +-----------------------------------------+------+------+------+------+------------+
  926. // cluster2 allocated 28.00 6.00 6.00 6.00 0.00
  927. // +-----------------------------------------+------+------+------+------+------------+
  928. // cluster2 idle 82.00 44.00 34.00 4.00 0.00
  929. // +-----------------------------------------+------+------+------+------+------------+
  930. cluster1Nodes = NewNode("", "cluster1", "", start, end, NewWindow(&start, &end))
  931. cluster1Nodes.CPUCost = 5.0
  932. cluster1Nodes.RAMCost = 4.0
  933. cluster1Nodes.GPUCost = 1.0
  934. cluster1Nodes.adjustment = 90.00
  935. cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
  936. cluster2Node1.CPUCost = 20.0
  937. cluster2Node1.RAMCost = 15.0
  938. cluster2Node1.GPUCost = 0.0
  939. cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
  940. cluster2Node2.CPUCost = 20.0
  941. cluster2Node2.RAMCost = 15.0
  942. cluster2Node2.GPUCost = 0.0
  943. cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
  944. cluster2Node3.CPUCost = 10.0
  945. cluster2Node3.RAMCost = 10.0
  946. cluster2Node3.GPUCost = 10.0
  947. cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
  948. cluster2Disk1.Cost = 5.0
  949. assetSet = NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1)
  950. idles, err = as.ComputeIdleAllocations(assetSet)
  951. if err != nil {
  952. t.Fatalf("unexpected error: %s", err)
  953. }
  954. if len(idles) != 2 {
  955. t.Fatalf("idles: expected length %d; got length %d", 2, len(idles))
  956. }
  957. if idle, ok := idles["cluster1"]; !ok {
  958. t.Fatalf("expected idle cost for %s", "cluster1")
  959. } else {
  960. if !util.IsApproximately(idle.TotalCost(), 72.0) {
  961. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster1", 72.0, idle.TotalCost())
  962. }
  963. }
  964. if !util.IsApproximately(idles["cluster1"].CPUCost, 44.0) {
  965. t.Fatalf("expected idle CPU cost for %s to be %.2f; got %.2f", "cluster1", 44.0, idles["cluster1"].CPUCost)
  966. }
  967. if !util.IsApproximately(idles["cluster1"].RAMCost, 24.0) {
  968. t.Fatalf("expected idle RAM cost for %s to be %.2f; got %.2f", "cluster1", 24.0, idles["cluster1"].RAMCost)
  969. }
  970. if !util.IsApproximately(idles["cluster1"].GPUCost, 4.0) {
  971. t.Fatalf("expected idle GPU cost for %s to be %.2f; got %.2f", "cluster1", 4.0, idles["cluster1"].GPUCost)
  972. }
  973. if idle, ok := idles["cluster2"]; !ok {
  974. t.Fatalf("expected idle cost for %s", "cluster2")
  975. } else {
  976. if !util.IsApproximately(idle.TotalCost(), 82.0) {
  977. t.Fatalf("%s idle: expected total cost %f; got total cost %f", "cluster2", 82.0, idle.TotalCost())
  978. }
  979. }
  980. // TODO assert value of each resource cost precisely
  981. }
  982. // TODO niko/etl
  983. //func TestAllocationSet_Delete(t *testing.T) {}
  984. // TODO niko/etl
  985. //func TestAllocationSet_End(t *testing.T) {}
  986. // TODO niko/etl
  987. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  988. // TODO niko/etl
  989. //func TestAllocationSet_Insert(t *testing.T) {}
  990. // TODO niko/etl
  991. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  992. // TODO niko/etl
  993. //func TestAllocationSet_Length(t *testing.T) {}
  994. // TODO niko/etl
  995. //func TestAllocationSet_Map(t *testing.T) {}
  996. // TODO niko/etl
  997. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  998. // TODO niko/etl
  999. //func TestAllocationSet_Resolution(t *testing.T) {}
  1000. // TODO niko/etl
  1001. //func TestAllocationSet_Seconds(t *testing.T) {}
  1002. // TODO niko/etl
  1003. //func TestAllocationSet_Set(t *testing.T) {}
  1004. // TODO niko/etl
  1005. //func TestAllocationSet_Start(t *testing.T) {}
  1006. // TODO niko/etl
  1007. //func TestAllocationSet_TotalCost(t *testing.T) {}
  1008. // TODO niko/etl
  1009. //func TestNewAllocationSetRange(t *testing.T) {}
  1010. func TestAllocationSetRange_Accumulate(t *testing.T) {
  1011. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1012. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1013. today := time.Now().UTC().Truncate(day)
  1014. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1015. // Accumulating any combination of nil and/or empty set should result in empty set
  1016. result, err := NewAllocationSetRange(nil).Accumulate()
  1017. if err != nil {
  1018. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1019. }
  1020. if !result.IsEmpty() {
  1021. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1022. }
  1023. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  1024. if err != nil {
  1025. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1026. }
  1027. if !result.IsEmpty() {
  1028. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1029. }
  1030. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  1031. if err != nil {
  1032. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1033. }
  1034. if !result.IsEmpty() {
  1035. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1036. }
  1037. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  1038. if err != nil {
  1039. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  1040. }
  1041. if !result.IsEmpty() {
  1042. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  1043. }
  1044. todayAS := NewAllocationSet(today, tomorrow)
  1045. todayAS.Set(NewUnitAllocation("", today, day, nil))
  1046. yesterdayAS := NewAllocationSet(yesterday, today)
  1047. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  1048. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  1049. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  1050. if err != nil {
  1051. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1052. }
  1053. if result == nil {
  1054. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1055. }
  1056. if result.TotalCost() != 5.0 {
  1057. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1058. }
  1059. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  1060. if err != nil {
  1061. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1062. }
  1063. if result == nil {
  1064. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1065. }
  1066. if result.TotalCost() != 5.0 {
  1067. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1068. }
  1069. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  1070. if err != nil {
  1071. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1072. }
  1073. if result == nil {
  1074. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1075. }
  1076. if result.TotalCost() != 5.0 {
  1077. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  1078. }
  1079. // Accumulate two non-nil should result in sum of both with appropriate start, end
  1080. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  1081. if err != nil {
  1082. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  1083. }
  1084. if result == nil {
  1085. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  1086. }
  1087. if result.TotalCost() != 10.0 {
  1088. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  1089. }
  1090. allocMap := result.Map()
  1091. if len(allocMap) != 1 {
  1092. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  1093. }
  1094. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  1095. if alloc == nil {
  1096. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  1097. }
  1098. if alloc.CPUCoreHours != 2.0 {
  1099. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  1100. }
  1101. if alloc.CPUCost != 2.0 {
  1102. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  1103. }
  1104. if alloc.CPUEfficiency() != 1.0 {
  1105. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency())
  1106. }
  1107. if alloc.GPUHours != 2.0 {
  1108. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  1109. }
  1110. if alloc.GPUCost != 2.0 {
  1111. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  1112. }
  1113. if alloc.NetworkCost != 2.0 {
  1114. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  1115. }
  1116. if alloc.PVByteHours != 2.0 {
  1117. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  1118. }
  1119. if alloc.PVCost != 2.0 {
  1120. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  1121. }
  1122. if alloc.RAMByteHours != 2.0 {
  1123. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  1124. }
  1125. if alloc.RAMCost != 2.0 {
  1126. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  1127. }
  1128. if alloc.RAMEfficiency() != 1.0 {
  1129. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
  1130. }
  1131. if alloc.TotalCost() != 10.0 {
  1132. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost())
  1133. }
  1134. if alloc.TotalEfficiency() != 1.0 {
  1135. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
  1136. }
  1137. if !alloc.Start.Equal(yesterday) {
  1138. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1139. }
  1140. if !alloc.End.Equal(tomorrow) {
  1141. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1142. }
  1143. if alloc.Minutes() != 2880.0 {
  1144. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes())
  1145. }
  1146. }
  1147. // TODO niko/etl
  1148. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1149. // TODO niko/etl
  1150. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1151. // TODO niko/etl
  1152. // func TestAllocationSetRange_Append(t *testing.T) {}
  1153. // TODO niko/etl
  1154. // func TestAllocationSetRange_Each(t *testing.T) {}
  1155. // TODO niko/etl
  1156. // func TestAllocationSetRange_Get(t *testing.T) {}
  1157. func TestAllocationSetRange_InsertRange(t *testing.T) {
  1158. // Set up
  1159. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  1160. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  1161. today := time.Now().UTC().Truncate(day)
  1162. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  1163. unit := NewUnitAllocation("", today, day, nil)
  1164. ago2dAS := NewAllocationSet(ago2d, yesterday)
  1165. ago2dAS.Set(NewUnitAllocation("a", ago2d, day, nil))
  1166. ago2dAS.Set(NewUnitAllocation("b", ago2d, day, nil))
  1167. ago2dAS.Set(NewUnitAllocation("c", ago2d, day, nil))
  1168. yesterdayAS := NewAllocationSet(yesterday, today)
  1169. yesterdayAS.Set(NewUnitAllocation("a", yesterday, day, nil))
  1170. yesterdayAS.Set(NewUnitAllocation("b", yesterday, day, nil))
  1171. yesterdayAS.Set(NewUnitAllocation("c", yesterday, day, nil))
  1172. todayAS := NewAllocationSet(today, tomorrow)
  1173. todayAS.Set(NewUnitAllocation("a", today, day, nil))
  1174. todayAS.Set(NewUnitAllocation("b", today, day, nil))
  1175. todayAS.Set(NewUnitAllocation("c", today, day, nil))
  1176. var nilASR *AllocationSetRange
  1177. thisASR := NewAllocationSetRange(yesterdayAS.Clone(), todayAS.Clone())
  1178. thatASR := NewAllocationSetRange(yesterdayAS.Clone())
  1179. longASR := NewAllocationSetRange(ago2dAS.Clone(), yesterdayAS.Clone(), todayAS.Clone())
  1180. var err error
  1181. // Expect an error calling InsertRange on nil
  1182. err = nilASR.InsertRange(thatASR)
  1183. if err == nil {
  1184. t.Fatalf("expected error, got nil")
  1185. }
  1186. // Expect nothing to happen calling InsertRange(nil) on non-nil ASR
  1187. err = thisASR.InsertRange(nil)
  1188. if err != nil {
  1189. t.Fatalf("unexpected error: %s", err)
  1190. }
  1191. thisASR.Each(func(i int, as *AllocationSet) {
  1192. as.Each(func(k string, a *Allocation) {
  1193. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1194. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1195. }
  1196. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1197. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1198. }
  1199. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1200. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1201. }
  1202. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1203. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1204. }
  1205. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1206. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1207. }
  1208. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1209. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1210. }
  1211. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1212. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1213. }
  1214. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1215. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1216. }
  1217. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1218. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1219. }
  1220. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1221. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1222. }
  1223. })
  1224. })
  1225. // Expect an error calling InsertRange with a range exceeding the receiver
  1226. err = thisASR.InsertRange(longASR)
  1227. if err == nil {
  1228. t.Fatalf("expected error calling InsertRange with a range exceeding the receiver")
  1229. }
  1230. // Expect each Allocation in "today" to stay the same, but "yesterday" to
  1231. // precisely double when inserting a range that only has a duplicate of
  1232. // "yesterday", but no entry for "today"
  1233. err = thisASR.InsertRange(thatASR)
  1234. if err != nil {
  1235. t.Fatalf("unexpected error: %s", err)
  1236. }
  1237. yAS, err := thisASR.Get(0)
  1238. yAS.Each(func(k string, a *Allocation) {
  1239. if !util.IsApproximately(a.CPUCoreHours, 2*unit.CPUCoreHours) {
  1240. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1241. }
  1242. if !util.IsApproximately(a.CPUCost, 2*unit.CPUCost) {
  1243. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1244. }
  1245. if !util.IsApproximately(a.RAMByteHours, 2*unit.RAMByteHours) {
  1246. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1247. }
  1248. if !util.IsApproximately(a.RAMCost, 2*unit.RAMCost) {
  1249. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1250. }
  1251. if !util.IsApproximately(a.GPUHours, 2*unit.GPUHours) {
  1252. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1253. }
  1254. if !util.IsApproximately(a.GPUCost, 2*unit.GPUCost) {
  1255. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1256. }
  1257. if !util.IsApproximately(a.PVByteHours, 2*unit.PVByteHours) {
  1258. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1259. }
  1260. if !util.IsApproximately(a.PVCost, 2*unit.PVCost) {
  1261. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1262. }
  1263. if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
  1264. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1265. }
  1266. if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
  1267. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1268. }
  1269. })
  1270. tAS, err := thisASR.Get(1)
  1271. tAS.Each(func(k string, a *Allocation) {
  1272. if !util.IsApproximately(a.CPUCoreHours, unit.CPUCoreHours) {
  1273. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCoreHours, a.CPUCoreHours)
  1274. }
  1275. if !util.IsApproximately(a.CPUCost, unit.CPUCost) {
  1276. t.Fatalf("allocation %s: expected %f; got %f", k, unit.CPUCost, a.CPUCost)
  1277. }
  1278. if !util.IsApproximately(a.RAMByteHours, unit.RAMByteHours) {
  1279. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMByteHours, a.RAMByteHours)
  1280. }
  1281. if !util.IsApproximately(a.RAMCost, unit.RAMCost) {
  1282. t.Fatalf("allocation %s: expected %f; got %f", k, unit.RAMCost, a.RAMCost)
  1283. }
  1284. if !util.IsApproximately(a.GPUHours, unit.GPUHours) {
  1285. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUHours, a.GPUHours)
  1286. }
  1287. if !util.IsApproximately(a.GPUCost, unit.GPUCost) {
  1288. t.Fatalf("allocation %s: expected %f; got %f", k, unit.GPUCost, a.GPUCost)
  1289. }
  1290. if !util.IsApproximately(a.PVByteHours, unit.PVByteHours) {
  1291. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVByteHours, a.PVByteHours)
  1292. }
  1293. if !util.IsApproximately(a.PVCost, unit.PVCost) {
  1294. t.Fatalf("allocation %s: expected %f; got %f", k, unit.PVCost, a.PVCost)
  1295. }
  1296. if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
  1297. t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
  1298. }
  1299. if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
  1300. t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
  1301. }
  1302. })
  1303. }
  1304. // TODO niko/etl
  1305. // func TestAllocationSetRange_Length(t *testing.T) {}
  1306. // TODO niko/etl
  1307. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1308. // TODO niko/etl
  1309. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1310. // TODO niko/etl
  1311. // func TestAllocationSetRange_Window(t *testing.T) {}