allocation_test.go 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  1. package kubecost
  2. import (
  3. "fmt"
  4. "math"
  5. "testing"
  6. "time"
  7. )
  8. const day = 24 * time.Hour
  9. func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *Properties) *Allocation {
  10. if name == "" {
  11. name = "cluster1/namespace1/pod1/container1"
  12. }
  13. properties := &Properties{}
  14. if props == nil {
  15. properties.SetCluster("cluster1")
  16. properties.SetNode("node1")
  17. properties.SetNamespace("namespace1")
  18. properties.SetControllerKind("deployment")
  19. properties.SetController("deployment1")
  20. properties.SetPod("pod1")
  21. properties.SetContainer("container1")
  22. } else {
  23. properties = props
  24. }
  25. end := start.Add(resolution)
  26. alloc := &Allocation{
  27. Name: name,
  28. Properties: *properties,
  29. Start: start,
  30. End: end,
  31. Minutes: 1440,
  32. CPUCoreHours: 1,
  33. CPUCost: 1,
  34. CPUEfficiency: 1,
  35. GPUHours: 1,
  36. GPUCost: 1,
  37. NetworkCost: 1,
  38. PVByteHours: 1,
  39. PVCost: 1,
  40. RAMByteHours: 1,
  41. RAMCost: 1,
  42. RAMEfficiency: 1,
  43. TotalCost: 5,
  44. TotalEfficiency: 1,
  45. }
  46. // If idle allocation, remove non-idle costs, but maintain total cost
  47. if alloc.IsIdle() {
  48. alloc.PVByteHours = 0.0
  49. alloc.PVCost = 0.0
  50. alloc.NetworkCost = 0.0
  51. alloc.CPUCoreHours += 1.0
  52. alloc.CPUCost += 1.0
  53. alloc.RAMByteHours += 1.0
  54. alloc.RAMCost += 1.0
  55. }
  56. return alloc
  57. }
  58. func TestAllocation_Add(t *testing.T) {
  59. var nilAlloc *Allocation
  60. zeroAlloc := &Allocation{}
  61. // nil + nil == nil
  62. nilNilSum, err := nilAlloc.Add(nilAlloc)
  63. if err != nil {
  64. t.Fatalf("Allocation.Add unexpected error: %s", err)
  65. }
  66. if nilNilSum != nil {
  67. t.Fatalf("Allocation.Add failed; exp: nil; act: %s", nilNilSum)
  68. }
  69. // nil + zero == zero
  70. nilZeroSum, err := nilAlloc.Add(zeroAlloc)
  71. if err != nil {
  72. t.Fatalf("Allocation.Add unexpected error: %s", err)
  73. }
  74. if nilZeroSum == nil || nilZeroSum.TotalCost != 0.0 {
  75. t.Fatalf("Allocation.Add failed; exp: 0.0; act: %s", nilZeroSum)
  76. }
  77. // TODO niko/etl more
  78. }
  79. // TODO niko/etl
  80. // func TestAllocation_Clone(t *testing.T) {}
  81. // TODO niko/etl
  82. // func TestAllocation_IsIdle(t *testing.T) {}
  83. func TestAllocation_MatchesAll(t *testing.T) {
  84. var alloc *Allocation
  85. // nil Allocations never match
  86. if alloc.MatchesAll() {
  87. t.Fatalf("Allocation.MatchesAll: expected no match on nil allocation")
  88. }
  89. today := time.Now().UTC().Truncate(day)
  90. alloc = NewUnitAllocation("", today, day, nil)
  91. // Matches when no Properties are given
  92. if !alloc.MatchesAll() {
  93. t.Fatalf("Allocation.MatchesAll: expected match on no conditions")
  94. }
  95. // Matches when all Properties match
  96. if !alloc.MatchesAll(Properties{
  97. NamespaceProp: "namespace1",
  98. }, Properties{
  99. ClusterProp: "cluster1",
  100. ControllerKindProp: "deployment",
  101. }, Properties{
  102. NodeProp: "node1",
  103. }) {
  104. t.Fatalf("Allocation.MatchesAll: expected match when all Properties are met")
  105. }
  106. // Doesn't match when one Property doesn't match
  107. if alloc.MatchesAll(Properties{
  108. NamespaceProp: "namespace1",
  109. ServiceProp: []string{"missing"},
  110. }, Properties{
  111. ClusterProp: "cluster1",
  112. ControllerKindProp: "deployment",
  113. }) {
  114. t.Fatalf("Allocation.MatchesAll: expected no match when one Properties is not met")
  115. }
  116. // Doesn't match when no Properties are met
  117. if alloc.MatchesAll(Properties{
  118. NamespaceProp: "namespace1",
  119. ServiceProp: []string{"missing"},
  120. }, Properties{
  121. ClusterProp: "cluster2",
  122. ControllerKindProp: "deployment",
  123. }) {
  124. t.Fatalf("Allocation.MatchesAll: expected no match when no Properties are met")
  125. }
  126. }
  127. func TestAllocation_MatchesOne(t *testing.T) {
  128. var alloc *Allocation
  129. // nil Allocations never match
  130. if alloc.MatchesOne() {
  131. t.Fatalf("Allocation.MatchesOne: expected no match on nil allocation")
  132. }
  133. today := time.Now().UTC().Truncate(day)
  134. alloc = NewUnitAllocation("", today, day, nil)
  135. // Doesn't match when no Properties are given
  136. if alloc.MatchesOne() {
  137. t.Fatalf("Allocation.MatchesOne: expected no match on no conditions")
  138. }
  139. // Matches when all Properties match
  140. if !alloc.MatchesOne(Properties{
  141. NamespaceProp: "namespace1",
  142. }, Properties{
  143. ClusterProp: "cluster1",
  144. ControllerKindProp: "deployment",
  145. }) {
  146. t.Fatalf("Allocation.MatchesOne: expected match when all Properties are met")
  147. }
  148. // Matches when one Property doesn't match
  149. if !alloc.MatchesOne(Properties{
  150. NamespaceProp: "namespace1",
  151. ServiceProp: []string{"missing"},
  152. }, Properties{
  153. ClusterProp: "cluster1",
  154. ControllerKindProp: "deployment",
  155. }) {
  156. t.Fatalf("Allocation.MatchesOne: expected match when one Properties is met")
  157. }
  158. // Doesn't match when no Properties are met
  159. if alloc.MatchesOne(Properties{
  160. NamespaceProp: "namespace1",
  161. ServiceProp: []string{"missing"},
  162. }, Properties{
  163. ClusterProp: "cluster2",
  164. ControllerKindProp: "deployment",
  165. }) {
  166. t.Fatalf("Allocation.MatchesOne: expected no match when no Properties are met")
  167. }
  168. }
  169. func TestAllocation_String(t *testing.T) {
  170. // TODO niko/etl
  171. }
  172. func TestNewAllocationSet(t *testing.T) {
  173. // TODO niko/etl
  174. }
  175. func generateAllocationSet(start time.Time) *AllocationSet {
  176. // Idle allocations
  177. a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
  178. ClusterProp: "cluster1",
  179. NodeProp: "node1",
  180. })
  181. a1i.CPUCost = 5.0
  182. a1i.RAMCost = 15.0
  183. a1i.GPUCost = 0.0
  184. a1i.TotalCost = 20.0
  185. a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
  186. ClusterProp: "cluster2",
  187. })
  188. a2i.CPUCost = 5.0
  189. a2i.RAMCost = 5.0
  190. a2i.GPUCost = 0.0
  191. a2i.TotalCost = 10.0
  192. // Active allocations
  193. a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
  194. ClusterProp: "cluster1",
  195. NamespaceProp: "namespace1",
  196. PodProp: "pod1",
  197. ContainerProp: "container1",
  198. })
  199. a1111.RAMCost = 11.00
  200. a1111.TotalCost = 15.00
  201. a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
  202. ClusterProp: "cluster1",
  203. NamespaceProp: "namespace1",
  204. PodProp: "pod-abc",
  205. ContainerProp: "container2",
  206. })
  207. a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
  208. ClusterProp: "cluster1",
  209. NamespaceProp: "namespace1",
  210. PodProp: "pod-def",
  211. ContainerProp: "container3",
  212. })
  213. a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
  214. ClusterProp: "cluster1",
  215. NamespaceProp: "namespace2",
  216. PodProp: "pod-ghi",
  217. ContainerProp: "container4",
  218. })
  219. a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
  220. ClusterProp: "cluster1",
  221. NamespaceProp: "namespace2",
  222. PodProp: "pod-ghi",
  223. ContainerProp: "container5",
  224. })
  225. a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
  226. ClusterProp: "cluster1",
  227. NamespaceProp: "namespace2",
  228. PodProp: "pod-jkl",
  229. ContainerProp: "container6",
  230. })
  231. a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
  232. ClusterProp: "cluster2",
  233. NamespaceProp: "namespace2",
  234. PodProp: "pod-mno",
  235. ContainerProp: "container4",
  236. })
  237. a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
  238. ClusterProp: "cluster2",
  239. NamespaceProp: "namespace2",
  240. PodProp: "pod-mno",
  241. ContainerProp: "container5",
  242. })
  243. a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
  244. ClusterProp: "cluster2",
  245. NamespaceProp: "namespace2",
  246. PodProp: "pod-pqr",
  247. ContainerProp: "container6",
  248. })
  249. a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
  250. ClusterProp: "cluster2",
  251. NamespaceProp: "namespace3",
  252. PodProp: "pod-stu",
  253. ContainerProp: "container7",
  254. })
  255. a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
  256. ClusterProp: "cluster2",
  257. NamespaceProp: "namespace3",
  258. PodProp: "pod-vwx",
  259. ContainerProp: "container8",
  260. })
  261. a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
  262. ClusterProp: "cluster2",
  263. NamespaceProp: "namespace3",
  264. PodProp: "pod-vwx",
  265. ContainerProp: "container9",
  266. })
  267. // Controllers
  268. a11abc2.Properties.SetControllerKind("deployment")
  269. a11abc2.Properties.SetController("deployment1")
  270. a11def3.Properties.SetControllerKind("deployment")
  271. a11def3.Properties.SetController("deployment1")
  272. a12ghi4.Properties.SetControllerKind("deployment")
  273. a12ghi4.Properties.SetController("deployment2")
  274. a12ghi5.Properties.SetControllerKind("deployment")
  275. a12ghi5.Properties.SetController("deployment2")
  276. a22mno4.Properties.SetControllerKind("deployment")
  277. a22mno4.Properties.SetController("deployment2")
  278. a22mno5.Properties.SetControllerKind("deployment")
  279. a22mno5.Properties.SetController("deployment2")
  280. a23stu7.Properties.SetControllerKind("deployment")
  281. a23stu7.Properties.SetController("deployment3")
  282. a12jkl6.Properties.SetControllerKind("daemonset")
  283. a12jkl6.Properties.SetController("daemonset1")
  284. a22pqr6.Properties.SetControllerKind("daemonset")
  285. a22pqr6.Properties.SetController("daemonset1")
  286. a23vwx8.Properties.SetControllerKind("statefulset")
  287. a23vwx8.Properties.SetController("statefulset1")
  288. a23vwx9.Properties.SetControllerKind("statefulset")
  289. a23vwx9.Properties.SetController("statefulset1")
  290. // Labels
  291. a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
  292. a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  293. a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
  294. a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
  295. a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
  296. // Services
  297. a12jkl6.Properties.SetServices([]string{"service1"})
  298. a22pqr6.Properties.SetServices([]string{"service1"})
  299. return NewAllocationSet(start, start.Add(day),
  300. // idle
  301. a1i, a2i,
  302. // cluster 1, namespace1
  303. a1111, a11abc2, a11def3,
  304. // cluster 1, namespace 2
  305. a12ghi4, a12ghi5, a12jkl6,
  306. // cluster 2, namespace 2
  307. a22mno4, a22mno5, a22pqr6,
  308. // cluster 2, namespace 3
  309. a23stu7, a23vwx8, a23vwx9,
  310. )
  311. }
  312. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  313. if err != nil {
  314. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  315. }
  316. if as.Length() != length {
  317. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  318. }
  319. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  320. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  321. }
  322. }
  323. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  324. as.Each(func(k string, a *Allocation) {
  325. if exp, ok := exps[a.Name]; ok {
  326. if math.Round(a.TotalCost*100) != math.Round(exp*100) {
  327. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost)
  328. }
  329. } else {
  330. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  331. }
  332. })
  333. }
  334. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  335. as.Each(func(k string, a *Allocation) {
  336. if !a.Start.Equal(expStart) {
  337. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  338. }
  339. if !a.End.Equal(expEnd) {
  340. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  341. }
  342. if a.Minutes != expMinutes {
  343. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes)
  344. }
  345. })
  346. }
  347. func printAllocationSet(msg string, as *AllocationSet) {
  348. fmt.Printf("--- %s ---\n", msg)
  349. as.Each(func(k string, a *Allocation) {
  350. fmt.Printf(" > %s\n", a)
  351. })
  352. }
  353. func TestAllocationSet_AggregateBy(t *testing.T) {
  354. // Test AggregateBy against the following workload topology, which is
  355. // generated by generateAllocationSet:
  356. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
  357. // +----------------------------------------+------+------+------+------+------+------+
  358. // cluster1:
  359. // idle: 20.00 5.00 15.00 0.00 0.00 0.00
  360. // namespace1:
  361. // pod1:
  362. // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
  363. // pod-abc: (deployment1)
  364. // container2: 5.00 1.00 1.00 1.00 1.00 1.00
  365. // pod-def: (deployment1)
  366. // container3: 5.00 1.00 1.00 1.00 1.00 1.00
  367. // namespace2:
  368. // pod-ghi: (deployment2)
  369. // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  370. // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  371. // pod-jkl: (daemonset1)
  372. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  373. // +-----------------------------------------+------+------+------+------+------+------+
  374. // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
  375. // +-----------------------------------------+------+------+------+------+------+------+
  376. // cluster2:
  377. // idle: 10.00 5.00 5.00 0.00 0.00 0.00
  378. // namespace2:
  379. // pod-mno: (deployment2)
  380. // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  381. // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  382. // pod-pqr: (daemonset1)
  383. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  384. // namespace3:
  385. // pod-stu: (deployment3)
  386. // container7: 5.00 1.00 1.00 1.00 1.00 1.00
  387. // pod-vwx: (statefulset1)
  388. // container8: 5.00 1.00 1.00 1.00 1.00 1.00
  389. // container9: 5.00 1.00 1.00 1.00 1.00 1.00
  390. // +----------------------------------------+------+------+------+------+------+------+
  391. // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
  392. // +----------------------------------------+------+------+------+------+------+------+
  393. // total 100.00 22.00 42.00 12.00 12.00 12.00
  394. // +----------------------------------------+------+------+------+------+------+------+
  395. // Scenarios to test:
  396. // 1 Single-aggregation
  397. // 1a AggregationProperties=(Cluster)
  398. // 1b AggregationProperties=(Namespace)
  399. // 1c AggregationProperties=(Pod)
  400. // 1d AggregationProperties=(Container)
  401. // 1e AggregationProperties=(ControllerKind)
  402. // 1f AggregationProperties=(Controller)
  403. // 1g AggregationProperties=(Service)
  404. // 1h AggregationProperties=(Label:app)
  405. // 2 Multi-aggregation
  406. // 2a AggregationProperties=(Cluster, Namespace)
  407. // 2b AggregationProperties=(Namespace, Label:app)
  408. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  409. // 2d AggregationProperties=(Label:app, Label:environment)
  410. // 3 Share idle
  411. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  412. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  413. // 4 Share resources
  414. // 4a Share namespace ShareEven
  415. // 4b Share cluster ShareWeighted
  416. // 4c Share label ShareEven
  417. // 4d Share overhead ShareWeighted
  418. // 5 Filters
  419. // 5a Filter by cluster with separate idle
  420. // 5b Filter by cluster with shared idle
  421. // TODO niko/idle more filter tests
  422. // 6 Combinations and options
  423. // 6a SplitIdle
  424. // 6b Share idle with filters
  425. // 6c Share resources with filters
  426. // 6d Share idle and share resources
  427. // 7 Edge cases and errors
  428. // 7a Empty AggregationProperties
  429. // 7b Filter all
  430. // 7c Share all
  431. // 7d Share and filter the same allocations
  432. // Definitions and set-up:
  433. var as *AllocationSet
  434. var err error
  435. endYesterday := time.Now().UTC().Truncate(day)
  436. startYesterday := endYesterday.Add(-day)
  437. numClusters := 2
  438. numNamespaces := 3
  439. numPods := 9
  440. numContainers := 9
  441. numControllerKinds := 3
  442. numControllers := 5
  443. numServices := 1
  444. numLabelApps := 2
  445. // By default, idle is reported as a single, merged allocation
  446. numIdle := 1
  447. // There will only ever be one __unallocated__
  448. numUnallocated := 1
  449. // There are two clusters, so each gets an idle entry when they are split
  450. numSplitIdle := 2
  451. activeTotalCost := 70.0
  452. idleTotalCost := 30.0
  453. sharedOverheadHourlyCost := 7.0
  454. isNamespace3 := func(a *Allocation) bool {
  455. ns, err := a.Properties.GetNamespace()
  456. return err == nil && ns == "namespace3"
  457. }
  458. isApp1 := func(a *Allocation) bool {
  459. ls, _ := a.Properties.GetLabels()
  460. if app, ok := ls["app"]; ok && app == "app1" {
  461. return true
  462. }
  463. return false
  464. }
  465. end := time.Now().UTC().Truncate(day)
  466. start := end.Add(-day)
  467. // Tests:
  468. // 1 Single-aggregation
  469. // 1a AggregationProperties=(Cluster)
  470. as = generateAllocationSet(start)
  471. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  472. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  473. assertAllocationTotals(t, as, "1a", map[string]float64{
  474. "cluster1": 40.00,
  475. "cluster2": 30.00,
  476. IdleSuffix: 30.00,
  477. })
  478. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  479. // 1b AggregationProperties=(Namespace)
  480. as = generateAllocationSet(start)
  481. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  482. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  483. assertAllocationTotals(t, as, "1b", map[string]float64{
  484. "namespace1": 25.00,
  485. "namespace2": 30.00,
  486. "namespace3": 15.00,
  487. IdleSuffix: 30.00,
  488. })
  489. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  490. // 1c AggregationProperties=(Pod)
  491. as = generateAllocationSet(start)
  492. err = as.AggregateBy(Properties{PodProp: true}, nil)
  493. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  494. assertAllocationTotals(t, as, "1c", map[string]float64{
  495. "pod-jkl": 5.00,
  496. "pod-stu": 5.00,
  497. "pod-abc": 5.00,
  498. "pod-pqr": 5.00,
  499. "pod-def": 5.00,
  500. "pod-vwx": 10.00,
  501. "pod1": 15.00,
  502. "pod-mno": 10.00,
  503. "pod-ghi": 10.00,
  504. IdleSuffix: 30.00,
  505. })
  506. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  507. // 1d AggregationProperties=(Container)
  508. as = generateAllocationSet(start)
  509. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  510. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  511. assertAllocationTotals(t, as, "1d", map[string]float64{
  512. "container2": 5.00,
  513. "container9": 5.00,
  514. "container6": 10.00,
  515. "container3": 5.00,
  516. "container4": 10.00,
  517. "container7": 5.00,
  518. "container8": 5.00,
  519. "container5": 10.00,
  520. "container1": 15.00,
  521. IdleSuffix: 30.00,
  522. })
  523. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  524. // 1e AggregationProperties=(ControllerKind)
  525. as = generateAllocationSet(start)
  526. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  527. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  528. assertAllocationTotals(t, as, "1e", map[string]float64{
  529. "daemonset": 10.00,
  530. "deployment": 35.00,
  531. "statefulset": 10.00,
  532. IdleSuffix: 30.00,
  533. UnallocatedSuffix: 15.00,
  534. })
  535. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  536. // 1f AggregationProperties=(Controller)
  537. as = generateAllocationSet(start)
  538. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  539. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  540. assertAllocationTotals(t, as, "1f", map[string]float64{
  541. "deployment/deployment2": 20.00,
  542. "daemonset/daemonset1": 10.00,
  543. "deployment/deployment3": 5.00,
  544. "statefulset/statefulset1": 10.00,
  545. "deployment/deployment1": 10.00,
  546. IdleSuffix: 30.00,
  547. UnallocatedSuffix: 15.00,
  548. })
  549. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  550. // 1g AggregationProperties=(Service)
  551. as = generateAllocationSet(start)
  552. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  553. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  554. assertAllocationTotals(t, as, "1g", map[string]float64{
  555. "service1": 10.00,
  556. IdleSuffix: 30.00,
  557. UnallocatedSuffix: 60.00,
  558. })
  559. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  560. // 1h AggregationProperties=(Label:app)
  561. as = generateAllocationSet(start)
  562. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  563. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  564. assertAllocationTotals(t, as, "1h", map[string]float64{
  565. "app=app1": 15.00,
  566. "app=app2": 20.00,
  567. IdleSuffix: 30.00,
  568. UnallocatedSuffix: 35.00,
  569. })
  570. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  571. // 1i AggregationProperties=(ControllerKind:deployment)
  572. as = generateAllocationSet(start)
  573. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  574. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  575. assertAllocationTotals(t, as, "1i", map[string]float64{
  576. "deployment": 35.00,
  577. IdleSuffix: 30.00,
  578. UnallocatedSuffix: 35.00,
  579. })
  580. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  581. // 2 Multi-aggregation
  582. // 2a AggregationProperties=(Cluster, Namespace)
  583. // 2b AggregationProperties=(Namespace, Label:app)
  584. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  585. // 2d AggregationProperties=(Label:app, Label:environment)
  586. as = generateAllocationSet(start)
  587. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  588. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  589. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  590. assertAllocationTotals(t, as, "2d", map[string]float64{
  591. "app=app1/env=env1": 15.00,
  592. "app=app2/env=env2": 10.00,
  593. "app=app2/" + UnallocatedSuffix: 10.00,
  594. IdleSuffix: 30.00,
  595. UnallocatedSuffix: 35.00,
  596. })
  597. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  598. as = generateAllocationSet(start)
  599. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  600. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  601. assertAllocationTotals(t, as, "2e", map[string]float64{
  602. "cluster1/app=app2/env=env2": 10.00,
  603. "__idle__": 30.00,
  604. "cluster1/app=app1/env=env1": 15.00,
  605. "cluster1/" + UnallocatedSuffix: 15.00,
  606. "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
  607. "cluster2/" + UnallocatedSuffix: 20.00,
  608. })
  609. // // TODO niko/etl
  610. // // 3 Share idle
  611. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  612. // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  613. // 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)
  614. // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  615. as = generateAllocationSet(start)
  616. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  617. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  618. assertAllocationTotals(t, as, "3a", map[string]float64{
  619. "namespace1": 39.69,
  620. "namespace2": 40.31,
  621. "namespace3": 20.00,
  622. })
  623. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  624. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  625. // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  626. // 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)
  627. // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  628. as = generateAllocationSet(start)
  629. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  630. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  631. assertAllocationTotals(t, as, "3a", map[string]float64{
  632. "namespace1": 35.00,
  633. "namespace2": 45.00,
  634. "namespace3": 20.00,
  635. })
  636. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  637. // 4 Share resources
  638. // 4a Share namespace ShareEven
  639. // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
  640. // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
  641. // idle: 30.0000
  642. as = generateAllocationSet(start)
  643. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  644. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  645. ShareSplit: ShareEven,
  646. })
  647. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  648. assertAllocationTotals(t, as, "4a", map[string]float64{
  649. "namespace1": 32.50,
  650. "namespace2": 37.50,
  651. IdleSuffix: 30.00,
  652. })
  653. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  654. // 4b Share namespace ShareWeighted
  655. // namespace1: 32.5000 =
  656. // namespace2: 37.5000 =
  657. // idle: 30.0000
  658. as = generateAllocationSet(start)
  659. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  660. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  661. ShareSplit: ShareWeighted,
  662. })
  663. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  664. assertAllocationTotals(t, as, "4b", map[string]float64{
  665. "namespace1": 31.82,
  666. "namespace2": 38.18,
  667. IdleSuffix: 30.00,
  668. })
  669. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  670. // 4c Share label ShareEven
  671. // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
  672. // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
  673. // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
  674. // idle: 30.0000
  675. as = generateAllocationSet(start)
  676. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  677. ShareFuncs: []AllocationMatchFunc{isApp1},
  678. ShareSplit: ShareEven,
  679. })
  680. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  681. assertAllocationTotals(t, as, "4c", map[string]float64{
  682. "namespace1": 15.00,
  683. "namespace2": 35.00,
  684. "namespace3": 20.00,
  685. IdleSuffix: 30.00,
  686. })
  687. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  688. // 4d Share overhead ShareWeighted
  689. // namespace1: 37.5000 = 25.00 + (7.0*24.0)*(25.00/70.00)
  690. // namespace2: 45.0000 = 30.00 + (7.0*24.0)*(30.00/70.00)
  691. // namespace3: 22.5000 = 15.00 + (7.0*24.0)*(15.00/70.00)
  692. // idle: 30.0000
  693. as = generateAllocationSet(start)
  694. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  695. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  696. ShareSplit: ShareWeighted,
  697. })
  698. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  699. assertAllocationTotals(t, as, "4d", map[string]float64{
  700. "namespace1": 85.00,
  701. "namespace2": 102.00,
  702. "namespace3": 51.00,
  703. IdleSuffix: 30.00,
  704. })
  705. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  706. // 5 Filters
  707. isCluster := func(matchCluster string) func(*Allocation) bool {
  708. return func(a *Allocation) bool {
  709. cluster, err := a.Properties.GetCluster()
  710. return err == nil && cluster == matchCluster
  711. }
  712. }
  713. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  714. return func(a *Allocation) bool {
  715. namespace, err := a.Properties.GetNamespace()
  716. return err == nil && namespace == matchNamespace
  717. }
  718. }
  719. // 5a Filter by cluster with separate idle
  720. as = generateAllocationSet(start)
  721. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  722. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  723. ShareIdle: ShareNone,
  724. })
  725. assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
  726. assertAllocationTotals(t, as, "5a", map[string]float64{
  727. "cluster1": 40.00,
  728. IdleSuffix: 20.00,
  729. })
  730. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  731. // 5b Filter by cluster with shared idle
  732. as = generateAllocationSet(start)
  733. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  734. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  735. ShareIdle: ShareWeighted,
  736. })
  737. assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
  738. assertAllocationTotals(t, as, "5b", map[string]float64{
  739. "cluster1": 60.00,
  740. })
  741. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  742. // 5c Filter by cluster, agg by namespace, with separate idle
  743. as = generateAllocationSet(start)
  744. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  745. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  746. ShareIdle: ShareNone,
  747. })
  748. assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
  749. assertAllocationTotals(t, as, "5c", map[string]float64{
  750. "namespace1": 25.00,
  751. "namespace2": 15.00,
  752. IdleSuffix: 20.00,
  753. })
  754. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  755. // 5d Filter by namespace, agg by cluster, with separate idle
  756. as = generateAllocationSet(start)
  757. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  758. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  759. ShareIdle: ShareNone,
  760. })
  761. assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
  762. assertAllocationTotals(t, as, "5d", map[string]float64{
  763. "cluster1": 15.00,
  764. "cluster2": 15.00,
  765. IdleSuffix: 10.31,
  766. })
  767. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  768. // 6 Combinations and options
  769. // 6a SplitIdle
  770. as = generateAllocationSet(start)
  771. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  772. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  773. assertAllocationTotals(t, as, "6a", map[string]float64{
  774. "namespace1": 25.00,
  775. "namespace2": 30.00,
  776. "namespace3": 15.00,
  777. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  778. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  779. })
  780. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  781. // 6b Share idle weighted with filters
  782. // Should match values from unfiltered aggregation
  783. // as = generateAllocationSet(start)
  784. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  785. // printAllocationSet("6b unfiltered", as)
  786. as = generateAllocationSet(start)
  787. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  788. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  789. ShareIdle: ShareWeighted,
  790. })
  791. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  792. assertAllocationTotals(t, as, "6b", map[string]float64{
  793. "namespace2": 40.31,
  794. })
  795. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  796. // 6c Share idle even with filters
  797. // Should match values from unfiltered aggregation
  798. // as = generateAllocationSet(start)
  799. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  800. // printAllocationSet("6c unfiltered", as)
  801. as = generateAllocationSet(start)
  802. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  803. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  804. ShareIdle: ShareEven,
  805. })
  806. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  807. assertAllocationTotals(t, as, "6b", map[string]float64{
  808. "namespace2": 45.00,
  809. })
  810. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  811. // 6d Share resources with filters
  812. // 6e Share idle and share resources
  813. // 7 Edge cases and errors
  814. // 7a Empty AggregationProperties
  815. // 7b Filter all
  816. // 7c Share all
  817. // 7d Share and filter the same allocations
  818. }
  819. // TODO niko/etl
  820. //func TestAllocationSet_Clone(t *testing.T) {}
  821. // TODO niko/etl
  822. //func TestAllocationSet_Delete(t *testing.T) {}
  823. // TODO niko/etl
  824. //func TestAllocationSet_End(t *testing.T) {}
  825. // TODO niko/etl
  826. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  827. // TODO niko/etl
  828. //func TestAllocationSet_Insert(t *testing.T) {}
  829. // TODO niko/etl
  830. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  831. // TODO niko/etl
  832. //func TestAllocationSet_Length(t *testing.T) {}
  833. // TODO niko/etl
  834. //func TestAllocationSet_Map(t *testing.T) {}
  835. // TODO niko/etl
  836. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  837. // TODO niko/etl
  838. //func TestAllocationSet_Resolution(t *testing.T) {}
  839. // TODO niko/etl
  840. //func TestAllocationSet_Seconds(t *testing.T) {}
  841. // TODO niko/etl
  842. //func TestAllocationSet_Set(t *testing.T) {}
  843. // TODO niko/etl
  844. //func TestAllocationSet_Start(t *testing.T) {}
  845. // TODO niko/etl
  846. //func TestAllocationSet_TotalCost(t *testing.T) {}
  847. // TODO niko/etl
  848. //func TestNewAllocationSetRange(t *testing.T) {}
  849. func TestAllocationSetRange_Accumulate(t *testing.T) {
  850. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  851. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  852. today := time.Now().UTC().Truncate(day)
  853. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  854. // Accumulating any combination of nil and/or empty set should result in empty set
  855. result, err := NewAllocationSetRange(nil).Accumulate()
  856. if err != nil {
  857. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  858. }
  859. if !result.IsEmpty() {
  860. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  861. }
  862. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  863. if err != nil {
  864. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  865. }
  866. if !result.IsEmpty() {
  867. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  868. }
  869. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  870. if err != nil {
  871. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  872. }
  873. if !result.IsEmpty() {
  874. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  875. }
  876. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  877. if err != nil {
  878. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  879. }
  880. if !result.IsEmpty() {
  881. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  882. }
  883. todayAS := NewAllocationSet(today, tomorrow)
  884. todayAS.Set(NewUnitAllocation("", today, day, nil))
  885. yesterdayAS := NewAllocationSet(yesterday, today)
  886. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  887. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  888. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  889. if err != nil {
  890. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  891. }
  892. if result == nil {
  893. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  894. }
  895. if result.TotalCost() != 5.0 {
  896. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  897. }
  898. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  899. if err != nil {
  900. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  901. }
  902. if result == nil {
  903. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  904. }
  905. if result.TotalCost() != 5.0 {
  906. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  907. }
  908. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  909. if err != nil {
  910. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  911. }
  912. if result == nil {
  913. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  914. }
  915. if result.TotalCost() != 5.0 {
  916. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  917. }
  918. // Accumulate two non-nil should result in sum of both with appropriate start, end
  919. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  920. if err != nil {
  921. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  922. }
  923. if result == nil {
  924. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  925. }
  926. if result.TotalCost() != 10.0 {
  927. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  928. }
  929. allocMap := result.Map()
  930. if len(allocMap) != 1 {
  931. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  932. }
  933. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  934. if alloc == nil {
  935. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  936. }
  937. if alloc.CPUCoreHours != 2.0 {
  938. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  939. }
  940. if alloc.CPUCost != 2.0 {
  941. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  942. }
  943. if alloc.CPUEfficiency != 1.0 {
  944. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency)
  945. }
  946. if alloc.GPUHours != 2.0 {
  947. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  948. }
  949. if alloc.GPUCost != 2.0 {
  950. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  951. }
  952. if alloc.NetworkCost != 2.0 {
  953. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  954. }
  955. if alloc.PVByteHours != 2.0 {
  956. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  957. }
  958. if alloc.PVCost != 2.0 {
  959. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  960. }
  961. if alloc.RAMByteHours != 2.0 {
  962. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  963. }
  964. if alloc.RAMCost != 2.0 {
  965. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  966. }
  967. if alloc.RAMEfficiency != 1.0 {
  968. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency)
  969. }
  970. if alloc.TotalCost != 10.0 {
  971. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost)
  972. }
  973. if alloc.TotalEfficiency != 1.0 {
  974. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency)
  975. }
  976. if !alloc.Start.Equal(yesterday) {
  977. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  978. }
  979. if !alloc.End.Equal(tomorrow) {
  980. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  981. }
  982. if alloc.Minutes != 2880.0 {
  983. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes)
  984. }
  985. }
  986. // TODO niko/etl
  987. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  988. // TODO niko/etl
  989. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  990. // TODO niko/etl
  991. // func TestAllocationSetRange_Append(t *testing.T) {}
  992. // TODO niko/etl
  993. // func TestAllocationSetRange_Length(t *testing.T) {}
  994. // TODO niko/etl
  995. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  996. // TODO niko/etl
  997. // func TestAllocationSetRange_Slice(t *testing.T) {}
  998. // TODO niko/etl
  999. // func TestAllocationSetRange_Window(t *testing.T) {}