allocation_test.go 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  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. //Annotations
  297. a23stu7.Properties.SetAnnotations(map[string]string{"team": "team1"})
  298. a23vwx8.Properties.SetAnnotations(map[string]string{"team": "team2"})
  299. a23vwx9.Properties.SetAnnotations(map[string]string{"team": "team1"})
  300. // Services
  301. a12jkl6.Properties.SetServices([]string{"service1"})
  302. a22pqr6.Properties.SetServices([]string{"service1"})
  303. return NewAllocationSet(start, start.Add(day),
  304. // idle
  305. a1i, a2i,
  306. // cluster 1, namespace1
  307. a1111, a11abc2, a11def3,
  308. // cluster 1, namespace 2
  309. a12ghi4, a12ghi5, a12jkl6,
  310. // cluster 2, namespace 2
  311. a22mno4, a22mno5, a22pqr6,
  312. // cluster 2, namespace 3
  313. a23stu7, a23vwx8, a23vwx9,
  314. )
  315. }
  316. func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
  317. if err != nil {
  318. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
  319. }
  320. if as.Length() != length {
  321. t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
  322. }
  323. if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
  324. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
  325. }
  326. }
  327. func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
  328. as.Each(func(k string, a *Allocation) {
  329. if exp, ok := exps[a.Name]; ok {
  330. if math.Round(a.TotalCost*100) != math.Round(exp*100) {
  331. t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost)
  332. }
  333. } else {
  334. t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
  335. }
  336. })
  337. }
  338. func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
  339. as.Each(func(k string, a *Allocation) {
  340. if !a.Start.Equal(expStart) {
  341. t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
  342. }
  343. if !a.End.Equal(expEnd) {
  344. t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
  345. }
  346. if a.Minutes != expMinutes {
  347. t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes)
  348. }
  349. })
  350. }
  351. func printAllocationSet(msg string, as *AllocationSet) {
  352. fmt.Printf("--- %s ---\n", msg)
  353. as.Each(func(k string, a *Allocation) {
  354. fmt.Printf(" > %s\n", a)
  355. })
  356. }
  357. func TestAllocationSet_AggregateBy(t *testing.T) {
  358. // Test AggregateBy against the following workload topology, which is
  359. // generated by generateAllocationSet:
  360. // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
  361. // +----------------------------------------+------+------+------+------+------+------+
  362. // cluster1:
  363. // idle: 20.00 5.00 15.00 0.00 0.00 0.00
  364. // namespace1:
  365. // pod1:
  366. // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
  367. // pod-abc: (deployment1)
  368. // container2: 5.00 1.00 1.00 1.00 1.00 1.00
  369. // pod-def: (deployment1)
  370. // container3: 5.00 1.00 1.00 1.00 1.00 1.00
  371. // namespace2:
  372. // pod-ghi: (deployment2)
  373. // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  374. // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
  375. // pod-jkl: (daemonset1)
  376. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  377. // +-----------------------------------------+------+------+------+------+------+------+
  378. // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
  379. // +-----------------------------------------+------+------+------+------+------+------+
  380. // cluster2:
  381. // idle: 10.00 5.00 5.00 0.00 0.00 0.00
  382. // namespace2:
  383. // pod-mno: (deployment2)
  384. // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  385. // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
  386. // pod-pqr: (daemonset1)
  387. // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
  388. // namespace3:
  389. // pod-stu: (deployment3)
  390. // container7: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  391. // pod-vwx: (statefulset1)
  392. // container8: an[team=team2] 5.00 1.00 1.00 1.00 1.00 1.00
  393. // container9: an[team=team1] 5.00 1.00 1.00 1.00 1.00 1.00
  394. // +----------------------------------------+------+------+------+------+------+------+
  395. // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
  396. // +----------------------------------------+------+------+------+------+------+------+
  397. // total 100.00 22.00 42.00 12.00 12.00 12.00
  398. // +----------------------------------------+------+------+------+------+------+------+
  399. // Scenarios to test:
  400. // 1 Single-aggregation
  401. // 1a AggregationProperties=(Cluster)
  402. // 1b AggregationProperties=(Namespace)
  403. // 1c AggregationProperties=(Pod)
  404. // 1d AggregationProperties=(Container)
  405. // 1e AggregationProperties=(ControllerKind)
  406. // 1f AggregationProperties=(Controller)
  407. // 1g AggregationProperties=(Service)
  408. // 1h AggregationProperties=(Label:app)
  409. // 2 Multi-aggregation
  410. // 2a AggregationProperties=(Cluster, Namespace)
  411. // 2b AggregationProperties=(Namespace, Label:app)
  412. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  413. // 2d AggregationProperties=(Label:app, Label:environment)
  414. // 3 Share idle
  415. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  416. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
  417. // 4 Share resources
  418. // 4a Share namespace ShareEven
  419. // 4b Share cluster ShareWeighted
  420. // 4c Share label ShareEven
  421. // 4d Share overhead ShareWeighted
  422. // 5 Filters
  423. // 5a Filter by cluster with separate idle
  424. // 5b Filter by cluster with shared idle
  425. // TODO niko/idle more filter tests
  426. // 6 Combinations and options
  427. // 6a SplitIdle
  428. // 6b Share idle with filters
  429. // 6c Share resources with filters
  430. // 6d Share idle and share resources
  431. // 7 Edge cases and errors
  432. // 7a Empty AggregationProperties
  433. // 7b Filter all
  434. // 7c Share all
  435. // 7d Share and filter the same allocations
  436. // Definitions and set-up:
  437. var as *AllocationSet
  438. var err error
  439. endYesterday := time.Now().UTC().Truncate(day)
  440. startYesterday := endYesterday.Add(-day)
  441. numClusters := 2
  442. numNamespaces := 3
  443. numPods := 9
  444. numContainers := 9
  445. numControllerKinds := 3
  446. numControllers := 5
  447. numServices := 1
  448. numLabelApps := 2
  449. // By default, idle is reported as a single, merged allocation
  450. numIdle := 1
  451. // There will only ever be one __unallocated__
  452. numUnallocated := 1
  453. // There are two clusters, so each gets an idle entry when they are split
  454. numSplitIdle := 2
  455. activeTotalCost := 70.0
  456. idleTotalCost := 30.0
  457. sharedOverheadHourlyCost := 7.0
  458. isNamespace3 := func(a *Allocation) bool {
  459. ns, err := a.Properties.GetNamespace()
  460. return err == nil && ns == "namespace3"
  461. }
  462. isApp1 := func(a *Allocation) bool {
  463. ls, _ := a.Properties.GetLabels()
  464. if app, ok := ls["app"]; ok && app == "app1" {
  465. return true
  466. }
  467. return false
  468. }
  469. end := time.Now().UTC().Truncate(day)
  470. start := end.Add(-day)
  471. // Tests:
  472. // 1 Single-aggregation
  473. // 1a AggregationProperties=(Cluster)
  474. as = generateAllocationSet(start)
  475. err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
  476. assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
  477. assertAllocationTotals(t, as, "1a", map[string]float64{
  478. "cluster1": 40.00,
  479. "cluster2": 30.00,
  480. IdleSuffix: 30.00,
  481. })
  482. assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
  483. // 1b AggregationProperties=(Namespace)
  484. as = generateAllocationSet(start)
  485. err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
  486. assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  487. assertAllocationTotals(t, as, "1b", map[string]float64{
  488. "namespace1": 25.00,
  489. "namespace2": 30.00,
  490. "namespace3": 15.00,
  491. IdleSuffix: 30.00,
  492. })
  493. assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
  494. // 1c AggregationProperties=(Pod)
  495. as = generateAllocationSet(start)
  496. err = as.AggregateBy(Properties{PodProp: true}, nil)
  497. assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
  498. assertAllocationTotals(t, as, "1c", map[string]float64{
  499. "pod-jkl": 5.00,
  500. "pod-stu": 5.00,
  501. "pod-abc": 5.00,
  502. "pod-pqr": 5.00,
  503. "pod-def": 5.00,
  504. "pod-vwx": 10.00,
  505. "pod1": 15.00,
  506. "pod-mno": 10.00,
  507. "pod-ghi": 10.00,
  508. IdleSuffix: 30.00,
  509. })
  510. assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
  511. // 1d AggregationProperties=(Container)
  512. as = generateAllocationSet(start)
  513. err = as.AggregateBy(Properties{ContainerProp: true}, nil)
  514. assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
  515. assertAllocationTotals(t, as, "1d", map[string]float64{
  516. "container2": 5.00,
  517. "container9": 5.00,
  518. "container6": 10.00,
  519. "container3": 5.00,
  520. "container4": 10.00,
  521. "container7": 5.00,
  522. "container8": 5.00,
  523. "container5": 10.00,
  524. "container1": 15.00,
  525. IdleSuffix: 30.00,
  526. })
  527. assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
  528. // 1e AggregationProperties=(ControllerKind)
  529. as = generateAllocationSet(start)
  530. err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
  531. assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  532. assertAllocationTotals(t, as, "1e", map[string]float64{
  533. "daemonset": 10.00,
  534. "deployment": 35.00,
  535. "statefulset": 10.00,
  536. IdleSuffix: 30.00,
  537. UnallocatedSuffix: 15.00,
  538. })
  539. assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
  540. // 1f AggregationProperties=(Controller)
  541. as = generateAllocationSet(start)
  542. err = as.AggregateBy(Properties{ControllerProp: true}, nil)
  543. assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  544. assertAllocationTotals(t, as, "1f", map[string]float64{
  545. "deployment/deployment2": 20.00,
  546. "daemonset/daemonset1": 10.00,
  547. "deployment/deployment3": 5.00,
  548. "statefulset/statefulset1": 10.00,
  549. "deployment/deployment1": 10.00,
  550. IdleSuffix: 30.00,
  551. UnallocatedSuffix: 15.00,
  552. })
  553. assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
  554. // 1g AggregationProperties=(Service)
  555. as = generateAllocationSet(start)
  556. err = as.AggregateBy(Properties{ServiceProp: true}, nil)
  557. assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  558. assertAllocationTotals(t, as, "1g", map[string]float64{
  559. "service1": 10.00,
  560. IdleSuffix: 30.00,
  561. UnallocatedSuffix: 60.00,
  562. })
  563. assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
  564. // 1h AggregationProperties=(Label:app)
  565. as = generateAllocationSet(start)
  566. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
  567. assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  568. assertAllocationTotals(t, as, "1h", map[string]float64{
  569. "app=app1": 15.00,
  570. "app=app2": 20.00,
  571. IdleSuffix: 30.00,
  572. UnallocatedSuffix: 35.00,
  573. })
  574. assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
  575. // 1i AggregationProperties=(ControllerKind:deployment)
  576. as = generateAllocationSet(start)
  577. err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
  578. assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  579. assertAllocationTotals(t, as, "1i", map[string]float64{
  580. "deployment": 35.00,
  581. IdleSuffix: 30.00,
  582. UnallocatedSuffix: 35.00,
  583. })
  584. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  585. // 1j AggregationProperties=(Annotation:team)
  586. as = generateAllocationSet(start)
  587. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
  588. assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
  589. assertAllocationTotals(t, as, "1j", map[string]float64{
  590. "team=team1": 10.00,
  591. "team=team2": 5.00,
  592. IdleSuffix: 30.00,
  593. UnallocatedSuffix: 55.00,
  594. })
  595. assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
  596. // 2 Multi-aggregation
  597. // 2a AggregationProperties=(Cluster, Namespace)
  598. // 2b AggregationProperties=(Namespace, Label:app)
  599. // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
  600. // 2d AggregationProperties=(Label:app, Label:environment)
  601. as = generateAllocationSet(start)
  602. err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  603. // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
  604. assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
  605. assertAllocationTotals(t, as, "2d", map[string]float64{
  606. "app=app1/env=env1": 15.00,
  607. "app=app2/env=env2": 10.00,
  608. "app=app2/" + UnallocatedSuffix: 10.00,
  609. IdleSuffix: 30.00,
  610. UnallocatedSuffix: 35.00,
  611. })
  612. // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
  613. as = generateAllocationSet(start)
  614. err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
  615. assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
  616. assertAllocationTotals(t, as, "2e", map[string]float64{
  617. "cluster1/app=app2/env=env2": 10.00,
  618. "__idle__": 30.00,
  619. "cluster1/app=app1/env=env1": 15.00,
  620. "cluster1/" + UnallocatedSuffix: 15.00,
  621. "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
  622. "cluster2/" + UnallocatedSuffix: 20.00,
  623. })
  624. // 2f AggregationProperties=(annotation:team, pod)
  625. as = generateAllocationSet(start)
  626. err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
  627. assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
  628. assertAllocationTotals(t, as, "2e", map[string]float64{
  629. "pod-jkl/" + UnallocatedSuffix: 5.00,
  630. "pod-stu/team=team1": 5.00,
  631. "pod-abc/" + UnallocatedSuffix: 5.00,
  632. "pod-pqr/" + UnallocatedSuffix: 5.00,
  633. "pod-def/" + UnallocatedSuffix: 5.00,
  634. "pod-vwx/team=team1": 5.00,
  635. "pod-vwx/team=team2": 5.00,
  636. "pod1/" + UnallocatedSuffix: 15.00,
  637. "pod-mno/" + UnallocatedSuffix: 10.00,
  638. "pod-ghi/" + UnallocatedSuffix: 10.00,
  639. IdleSuffix: 30.00,
  640. })
  641. // // TODO niko/etl
  642. // // 3 Share idle
  643. // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
  644. // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
  645. // 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)
  646. // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
  647. as = generateAllocationSet(start)
  648. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  649. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  650. assertAllocationTotals(t, as, "3a", map[string]float64{
  651. "namespace1": 39.69,
  652. "namespace2": 40.31,
  653. "namespace3": 20.00,
  654. })
  655. assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
  656. // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
  657. // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
  658. // 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)
  659. // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
  660. as = generateAllocationSet(start)
  661. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  662. assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
  663. assertAllocationTotals(t, as, "3a", map[string]float64{
  664. "namespace1": 35.00,
  665. "namespace2": 45.00,
  666. "namespace3": 20.00,
  667. })
  668. assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
  669. // 4 Share resources
  670. // 4a Share namespace ShareEven
  671. // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
  672. // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
  673. // idle: 30.0000
  674. as = generateAllocationSet(start)
  675. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  676. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  677. ShareSplit: ShareEven,
  678. })
  679. assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
  680. assertAllocationTotals(t, as, "4a", map[string]float64{
  681. "namespace1": 32.50,
  682. "namespace2": 37.50,
  683. IdleSuffix: 30.00,
  684. })
  685. assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
  686. // 4b Share namespace ShareWeighted
  687. // namespace1: 32.5000 =
  688. // namespace2: 37.5000 =
  689. // idle: 30.0000
  690. as = generateAllocationSet(start)
  691. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  692. ShareFuncs: []AllocationMatchFunc{isNamespace3},
  693. ShareSplit: ShareWeighted,
  694. })
  695. assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
  696. assertAllocationTotals(t, as, "4b", map[string]float64{
  697. "namespace1": 31.82,
  698. "namespace2": 38.18,
  699. IdleSuffix: 30.00,
  700. })
  701. assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
  702. // 4c Share label ShareEven
  703. // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
  704. // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
  705. // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
  706. // idle: 30.0000
  707. as = generateAllocationSet(start)
  708. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  709. ShareFuncs: []AllocationMatchFunc{isApp1},
  710. ShareSplit: ShareEven,
  711. })
  712. assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
  713. assertAllocationTotals(t, as, "4c", map[string]float64{
  714. "namespace1": 15.00,
  715. "namespace2": 35.00,
  716. "namespace3": 20.00,
  717. IdleSuffix: 30.00,
  718. })
  719. assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
  720. // 4d Share overhead ShareWeighted
  721. // namespace1: 37.5000 = 25.00 + (7.0*24.0)*(25.00/70.00)
  722. // namespace2: 45.0000 = 30.00 + (7.0*24.0)*(30.00/70.00)
  723. // namespace3: 22.5000 = 15.00 + (7.0*24.0)*(15.00/70.00)
  724. // idle: 30.0000
  725. as = generateAllocationSet(start)
  726. err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
  727. SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
  728. ShareSplit: ShareWeighted,
  729. })
  730. assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
  731. assertAllocationTotals(t, as, "4d", map[string]float64{
  732. "namespace1": 85.00,
  733. "namespace2": 102.00,
  734. "namespace3": 51.00,
  735. IdleSuffix: 30.00,
  736. })
  737. assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
  738. // 5 Filters
  739. isCluster := func(matchCluster string) func(*Allocation) bool {
  740. return func(a *Allocation) bool {
  741. cluster, err := a.Properties.GetCluster()
  742. return err == nil && cluster == matchCluster
  743. }
  744. }
  745. isNamespace := func(matchNamespace string) func(*Allocation) bool {
  746. return func(a *Allocation) bool {
  747. namespace, err := a.Properties.GetNamespace()
  748. return err == nil && namespace == matchNamespace
  749. }
  750. }
  751. // 5a Filter by cluster with separate idle
  752. as = generateAllocationSet(start)
  753. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  754. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  755. ShareIdle: ShareNone,
  756. })
  757. assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
  758. assertAllocationTotals(t, as, "5a", map[string]float64{
  759. "cluster1": 40.00,
  760. IdleSuffix: 20.00,
  761. })
  762. assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
  763. // 5b Filter by cluster with shared idle
  764. as = generateAllocationSet(start)
  765. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  766. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  767. ShareIdle: ShareWeighted,
  768. })
  769. assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
  770. assertAllocationTotals(t, as, "5b", map[string]float64{
  771. "cluster1": 60.00,
  772. })
  773. assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
  774. // 5c Filter by cluster, agg by namespace, with separate idle
  775. as = generateAllocationSet(start)
  776. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  777. FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
  778. ShareIdle: ShareNone,
  779. })
  780. assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
  781. assertAllocationTotals(t, as, "5c", map[string]float64{
  782. "namespace1": 25.00,
  783. "namespace2": 15.00,
  784. IdleSuffix: 20.00,
  785. })
  786. assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
  787. // 5d Filter by namespace, agg by cluster, with separate idle
  788. as = generateAllocationSet(start)
  789. err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
  790. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  791. ShareIdle: ShareNone,
  792. })
  793. assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
  794. assertAllocationTotals(t, as, "5d", map[string]float64{
  795. "cluster1": 15.00,
  796. "cluster2": 15.00,
  797. IdleSuffix: 10.31,
  798. })
  799. assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
  800. // 6 Combinations and options
  801. // 6a SplitIdle
  802. as = generateAllocationSet(start)
  803. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
  804. assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
  805. assertAllocationTotals(t, as, "6a", map[string]float64{
  806. "namespace1": 25.00,
  807. "namespace2": 30.00,
  808. "namespace3": 15.00,
  809. fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
  810. fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
  811. })
  812. assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
  813. // 6b Share idle weighted with filters
  814. // Should match values from unfiltered aggregation
  815. // as = generateAllocationSet(start)
  816. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
  817. // printAllocationSet("6b unfiltered", as)
  818. as = generateAllocationSet(start)
  819. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  820. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  821. ShareIdle: ShareWeighted,
  822. })
  823. assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
  824. assertAllocationTotals(t, as, "6b", map[string]float64{
  825. "namespace2": 40.31,
  826. })
  827. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  828. // 6c Share idle even with filters
  829. // Should match values from unfiltered aggregation
  830. // as = generateAllocationSet(start)
  831. // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
  832. // printAllocationSet("6c unfiltered", as)
  833. as = generateAllocationSet(start)
  834. err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
  835. FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
  836. ShareIdle: ShareEven,
  837. })
  838. assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
  839. assertAllocationTotals(t, as, "6b", map[string]float64{
  840. "namespace2": 45.00,
  841. })
  842. assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
  843. // 6d Share resources with filters
  844. // 6e Share idle and share resources
  845. // 7 Edge cases and errors
  846. // 7a Empty AggregationProperties
  847. // 7b Filter all
  848. // 7c Share all
  849. // 7d Share and filter the same allocations
  850. }
  851. // TODO niko/etl
  852. //func TestAllocationSet_Clone(t *testing.T) {}
  853. // TODO niko/etl
  854. //func TestAllocationSet_Delete(t *testing.T) {}
  855. // TODO niko/etl
  856. //func TestAllocationSet_End(t *testing.T) {}
  857. // TODO niko/etl
  858. //func TestAllocationSet_IdleAllocations(t *testing.T) {}
  859. // TODO niko/etl
  860. //func TestAllocationSet_Insert(t *testing.T) {}
  861. // TODO niko/etl
  862. //func TestAllocationSet_IsEmpty(t *testing.T) {}
  863. // TODO niko/etl
  864. //func TestAllocationSet_Length(t *testing.T) {}
  865. // TODO niko/etl
  866. //func TestAllocationSet_Map(t *testing.T) {}
  867. // TODO niko/etl
  868. //func TestAllocationSet_MarshalJSON(t *testing.T) {}
  869. // TODO niko/etl
  870. //func TestAllocationSet_Resolution(t *testing.T) {}
  871. // TODO niko/etl
  872. //func TestAllocationSet_Seconds(t *testing.T) {}
  873. // TODO niko/etl
  874. //func TestAllocationSet_Set(t *testing.T) {}
  875. // TODO niko/etl
  876. //func TestAllocationSet_Start(t *testing.T) {}
  877. // TODO niko/etl
  878. //func TestAllocationSet_TotalCost(t *testing.T) {}
  879. // TODO niko/etl
  880. //func TestNewAllocationSetRange(t *testing.T) {}
  881. func TestAllocationSetRange_Accumulate(t *testing.T) {
  882. ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
  883. yesterday := time.Now().UTC().Truncate(day).Add(-day)
  884. today := time.Now().UTC().Truncate(day)
  885. tomorrow := time.Now().UTC().Truncate(day).Add(day)
  886. // Accumulating any combination of nil and/or empty set should result in empty set
  887. result, err := NewAllocationSetRange(nil).Accumulate()
  888. if err != nil {
  889. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  890. }
  891. if !result.IsEmpty() {
  892. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  893. }
  894. result, err = NewAllocationSetRange(nil, nil).Accumulate()
  895. if err != nil {
  896. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  897. }
  898. if !result.IsEmpty() {
  899. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  900. }
  901. result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
  902. if err != nil {
  903. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  904. }
  905. if !result.IsEmpty() {
  906. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  907. }
  908. result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
  909. if err != nil {
  910. t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
  911. }
  912. if !result.IsEmpty() {
  913. t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
  914. }
  915. todayAS := NewAllocationSet(today, tomorrow)
  916. todayAS.Set(NewUnitAllocation("", today, day, nil))
  917. yesterdayAS := NewAllocationSet(yesterday, today)
  918. yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
  919. // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
  920. result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
  921. if err != nil {
  922. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  923. }
  924. if result == nil {
  925. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  926. }
  927. if result.TotalCost() != 5.0 {
  928. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  929. }
  930. result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
  931. if err != nil {
  932. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  933. }
  934. if result == nil {
  935. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  936. }
  937. if result.TotalCost() != 5.0 {
  938. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  939. }
  940. result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
  941. if err != nil {
  942. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  943. }
  944. if result == nil {
  945. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  946. }
  947. if result.TotalCost() != 5.0 {
  948. t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
  949. }
  950. // Accumulate two non-nil should result in sum of both with appropriate start, end
  951. result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
  952. if err != nil {
  953. t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
  954. }
  955. if result == nil {
  956. t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
  957. }
  958. if result.TotalCost() != 10.0 {
  959. t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
  960. }
  961. allocMap := result.Map()
  962. if len(allocMap) != 1 {
  963. t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
  964. }
  965. alloc := allocMap["cluster1/namespace1/pod1/container1"]
  966. if alloc == nil {
  967. t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
  968. }
  969. if alloc.CPUCoreHours != 2.0 {
  970. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
  971. }
  972. if alloc.CPUCost != 2.0 {
  973. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
  974. }
  975. if alloc.CPUEfficiency != 1.0 {
  976. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency)
  977. }
  978. if alloc.GPUHours != 2.0 {
  979. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
  980. }
  981. if alloc.GPUCost != 2.0 {
  982. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
  983. }
  984. if alloc.NetworkCost != 2.0 {
  985. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
  986. }
  987. if alloc.PVByteHours != 2.0 {
  988. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
  989. }
  990. if alloc.PVCost != 2.0 {
  991. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
  992. }
  993. if alloc.RAMByteHours != 2.0 {
  994. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
  995. }
  996. if alloc.RAMCost != 2.0 {
  997. t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
  998. }
  999. if alloc.RAMEfficiency != 1.0 {
  1000. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency)
  1001. }
  1002. if alloc.TotalCost != 10.0 {
  1003. t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost)
  1004. }
  1005. if alloc.TotalEfficiency != 1.0 {
  1006. t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency)
  1007. }
  1008. if !alloc.Start.Equal(yesterday) {
  1009. t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
  1010. }
  1011. if !alloc.End.Equal(tomorrow) {
  1012. t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
  1013. }
  1014. if alloc.Minutes != 2880.0 {
  1015. t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes)
  1016. }
  1017. }
  1018. // TODO niko/etl
  1019. // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
  1020. // TODO niko/etl
  1021. // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
  1022. // TODO niko/etl
  1023. // func TestAllocationSetRange_Append(t *testing.T) {}
  1024. // TODO niko/etl
  1025. // func TestAllocationSetRange_Length(t *testing.T) {}
  1026. // TODO niko/etl
  1027. // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
  1028. // TODO niko/etl
  1029. // func TestAllocationSetRange_Slice(t *testing.T) {}
  1030. // TODO niko/etl
  1031. // func TestAllocationSetRange_Window(t *testing.T) {}