| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153 |
- package kubecost
- import (
- "fmt"
- "math"
- "testing"
- "time"
- )
- const day = 24 * time.Hour
- func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *Properties) *Allocation {
- if name == "" {
- name = "cluster1/namespace1/pod1/container1"
- }
- properties := &Properties{}
- if props == nil {
- properties.SetCluster("cluster1")
- properties.SetNode("node1")
- properties.SetNamespace("namespace1")
- properties.SetControllerKind("deployment")
- properties.SetController("deployment1")
- properties.SetPod("pod1")
- properties.SetContainer("container1")
- } else {
- properties = props
- }
- end := start.Add(resolution)
- alloc := &Allocation{
- Name: name,
- Properties: *properties,
- Start: start,
- End: end,
- Minutes: 1440,
- CPUCoreHours: 1,
- CPUCost: 1,
- CPUEfficiency: 1,
- GPUHours: 1,
- GPUCost: 1,
- NetworkCost: 1,
- PVByteHours: 1,
- PVCost: 1,
- RAMByteHours: 1,
- RAMCost: 1,
- RAMEfficiency: 1,
- TotalCost: 5,
- TotalEfficiency: 1,
- }
- // If idle allocation, remove non-idle costs, but maintain total cost
- if alloc.IsIdle() {
- alloc.PVByteHours = 0.0
- alloc.PVCost = 0.0
- alloc.NetworkCost = 0.0
- alloc.CPUCoreHours += 1.0
- alloc.CPUCost += 1.0
- alloc.RAMByteHours += 1.0
- alloc.RAMCost += 1.0
- }
- return alloc
- }
- func TestAllocation_Add(t *testing.T) {
- var nilAlloc *Allocation
- zeroAlloc := &Allocation{}
- // nil + nil == nil
- nilNilSum, err := nilAlloc.Add(nilAlloc)
- if err != nil {
- t.Fatalf("Allocation.Add unexpected error: %s", err)
- }
- if nilNilSum != nil {
- t.Fatalf("Allocation.Add failed; exp: nil; act: %s", nilNilSum)
- }
- // nil + zero == zero
- nilZeroSum, err := nilAlloc.Add(zeroAlloc)
- if err != nil {
- t.Fatalf("Allocation.Add unexpected error: %s", err)
- }
- if nilZeroSum == nil || nilZeroSum.TotalCost != 0.0 {
- t.Fatalf("Allocation.Add failed; exp: 0.0; act: %s", nilZeroSum)
- }
- // TODO niko/etl more
- }
- // TODO niko/etl
- // func TestAllocation_Clone(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocation_IsIdle(t *testing.T) {}
- func TestAllocation_MatchesAll(t *testing.T) {
- var alloc *Allocation
- // nil Allocations never match
- if alloc.MatchesAll() {
- t.Fatalf("Allocation.MatchesAll: expected no match on nil allocation")
- }
- today := time.Now().UTC().Truncate(day)
- alloc = NewUnitAllocation("", today, day, nil)
- // Matches when no Properties are given
- if !alloc.MatchesAll() {
- t.Fatalf("Allocation.MatchesAll: expected match on no conditions")
- }
- // Matches when all Properties match
- if !alloc.MatchesAll(Properties{
- NamespaceProp: "namespace1",
- }, Properties{
- ClusterProp: "cluster1",
- ControllerKindProp: "deployment",
- }, Properties{
- NodeProp: "node1",
- }) {
- t.Fatalf("Allocation.MatchesAll: expected match when all Properties are met")
- }
- // Doesn't match when one Property doesn't match
- if alloc.MatchesAll(Properties{
- NamespaceProp: "namespace1",
- ServiceProp: []string{"missing"},
- }, Properties{
- ClusterProp: "cluster1",
- ControllerKindProp: "deployment",
- }) {
- t.Fatalf("Allocation.MatchesAll: expected no match when one Properties is not met")
- }
- // Doesn't match when no Properties are met
- if alloc.MatchesAll(Properties{
- NamespaceProp: "namespace1",
- ServiceProp: []string{"missing"},
- }, Properties{
- ClusterProp: "cluster2",
- ControllerKindProp: "deployment",
- }) {
- t.Fatalf("Allocation.MatchesAll: expected no match when no Properties are met")
- }
- }
- func TestAllocation_MatchesOne(t *testing.T) {
- var alloc *Allocation
- // nil Allocations never match
- if alloc.MatchesOne() {
- t.Fatalf("Allocation.MatchesOne: expected no match on nil allocation")
- }
- today := time.Now().UTC().Truncate(day)
- alloc = NewUnitAllocation("", today, day, nil)
- // Doesn't match when no Properties are given
- if alloc.MatchesOne() {
- t.Fatalf("Allocation.MatchesOne: expected no match on no conditions")
- }
- // Matches when all Properties match
- if !alloc.MatchesOne(Properties{
- NamespaceProp: "namespace1",
- }, Properties{
- ClusterProp: "cluster1",
- ControllerKindProp: "deployment",
- }) {
- t.Fatalf("Allocation.MatchesOne: expected match when all Properties are met")
- }
- // Matches when one Property doesn't match
- if !alloc.MatchesOne(Properties{
- NamespaceProp: "namespace1",
- ServiceProp: []string{"missing"},
- }, Properties{
- ClusterProp: "cluster1",
- ControllerKindProp: "deployment",
- }) {
- t.Fatalf("Allocation.MatchesOne: expected match when one Properties is met")
- }
- // Doesn't match when no Properties are met
- if alloc.MatchesOne(Properties{
- NamespaceProp: "namespace1",
- ServiceProp: []string{"missing"},
- }, Properties{
- ClusterProp: "cluster2",
- ControllerKindProp: "deployment",
- }) {
- t.Fatalf("Allocation.MatchesOne: expected no match when no Properties are met")
- }
- }
- func TestAllocation_String(t *testing.T) {
- // TODO niko/etl
- }
- func TestNewAllocationSet(t *testing.T) {
- // TODO niko/etl
- }
- func generateAllocationSet(start time.Time) *AllocationSet {
- // Idle allocations
- a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &Properties{
- ClusterProp: "cluster1",
- NodeProp: "node1",
- })
- a1i.CPUCost = 5.0
- a1i.RAMCost = 15.0
- a1i.GPUCost = 0.0
- a1i.TotalCost = 20.0
- a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &Properties{
- ClusterProp: "cluster2",
- })
- a2i.CPUCost = 5.0
- a2i.RAMCost = 5.0
- a2i.GPUCost = 0.0
- a2i.TotalCost = 10.0
- // Active allocations
- a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &Properties{
- ClusterProp: "cluster1",
- NamespaceProp: "namespace1",
- PodProp: "pod1",
- ContainerProp: "container1",
- })
- a1111.RAMCost = 11.00
- a1111.TotalCost = 15.00
- a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &Properties{
- ClusterProp: "cluster1",
- NamespaceProp: "namespace1",
- PodProp: "pod-abc",
- ContainerProp: "container2",
- })
- a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &Properties{
- ClusterProp: "cluster1",
- NamespaceProp: "namespace1",
- PodProp: "pod-def",
- ContainerProp: "container3",
- })
- a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &Properties{
- ClusterProp: "cluster1",
- NamespaceProp: "namespace2",
- PodProp: "pod-ghi",
- ContainerProp: "container4",
- })
- a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &Properties{
- ClusterProp: "cluster1",
- NamespaceProp: "namespace2",
- PodProp: "pod-ghi",
- ContainerProp: "container5",
- })
- a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &Properties{
- ClusterProp: "cluster1",
- NamespaceProp: "namespace2",
- PodProp: "pod-jkl",
- ContainerProp: "container6",
- })
- a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &Properties{
- ClusterProp: "cluster2",
- NamespaceProp: "namespace2",
- PodProp: "pod-mno",
- ContainerProp: "container4",
- })
- a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &Properties{
- ClusterProp: "cluster2",
- NamespaceProp: "namespace2",
- PodProp: "pod-mno",
- ContainerProp: "container5",
- })
- a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &Properties{
- ClusterProp: "cluster2",
- NamespaceProp: "namespace2",
- PodProp: "pod-pqr",
- ContainerProp: "container6",
- })
- a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &Properties{
- ClusterProp: "cluster2",
- NamespaceProp: "namespace3",
- PodProp: "pod-stu",
- ContainerProp: "container7",
- })
- a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &Properties{
- ClusterProp: "cluster2",
- NamespaceProp: "namespace3",
- PodProp: "pod-vwx",
- ContainerProp: "container8",
- })
- a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &Properties{
- ClusterProp: "cluster2",
- NamespaceProp: "namespace3",
- PodProp: "pod-vwx",
- ContainerProp: "container9",
- })
- // Controllers
- a11abc2.Properties.SetControllerKind("deployment")
- a11abc2.Properties.SetController("deployment1")
- a11def3.Properties.SetControllerKind("deployment")
- a11def3.Properties.SetController("deployment1")
- a12ghi4.Properties.SetControllerKind("deployment")
- a12ghi4.Properties.SetController("deployment2")
- a12ghi5.Properties.SetControllerKind("deployment")
- a12ghi5.Properties.SetController("deployment2")
- a22mno4.Properties.SetControllerKind("deployment")
- a22mno4.Properties.SetController("deployment2")
- a22mno5.Properties.SetControllerKind("deployment")
- a22mno5.Properties.SetController("deployment2")
- a23stu7.Properties.SetControllerKind("deployment")
- a23stu7.Properties.SetController("deployment3")
- a12jkl6.Properties.SetControllerKind("daemonset")
- a12jkl6.Properties.SetController("daemonset1")
- a22pqr6.Properties.SetControllerKind("daemonset")
- a22pqr6.Properties.SetController("daemonset1")
- a23vwx8.Properties.SetControllerKind("statefulset")
- a23vwx8.Properties.SetController("statefulset1")
- a23vwx9.Properties.SetControllerKind("statefulset")
- a23vwx9.Properties.SetController("statefulset1")
- // Labels
- a1111.Properties.SetLabels(map[string]string{"app": "app1", "env": "env1"})
- a12ghi4.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
- a12ghi5.Properties.SetLabels(map[string]string{"app": "app2", "env": "env2"})
- a22mno4.Properties.SetLabels(map[string]string{"app": "app2"})
- a22mno5.Properties.SetLabels(map[string]string{"app": "app2"})
- // Services
- a12jkl6.Properties.SetServices([]string{"service1"})
- a22pqr6.Properties.SetServices([]string{"service1"})
- return NewAllocationSet(start, start.Add(day),
- // idle
- a1i, a2i,
- // cluster 1, namespace1
- a1111, a11abc2, a11def3,
- // cluster 1, namespace 2
- a12ghi4, a12ghi5, a12jkl6,
- // cluster 2, namespace 2
- a22mno4, a22mno5, a22pqr6,
- // cluster 2, namespace 3
- a23stu7, a23vwx8, a23vwx9,
- )
- }
- func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
- if err != nil {
- t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
- }
- if as.Length() != length {
- t.Fatalf("AllocationSet.AggregateBy[%s]: expected set of length %d, actual %d", msg, length, as.Length())
- }
- if math.Round(as.TotalCost()*100) != math.Round(totalCost*100) {
- t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, totalCost, as.TotalCost())
- }
- }
- func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps map[string]float64) {
- as.Each(func(k string, a *Allocation) {
- if exp, ok := exps[a.Name]; ok {
- if math.Round(a.TotalCost*100) != math.Round(exp*100) {
- t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost)
- }
- } else {
- t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
- }
- })
- }
- func assertAllocationWindow(t *testing.T, as *AllocationSet, msg string, expStart, expEnd time.Time, expMinutes float64) {
- as.Each(func(k string, a *Allocation) {
- if !a.Start.Equal(expStart) {
- t.Fatalf("AllocationSet.AggregateBy[%s]: expected start %s, actual %s", msg, expStart, a.Start)
- }
- if !a.End.Equal(expEnd) {
- t.Fatalf("AllocationSet.AggregateBy[%s]: expected end %s, actual %s", msg, expEnd, a.End)
- }
- if a.Minutes != expMinutes {
- t.Fatalf("AllocationSet.AggregateBy[%s]: expected minutes %f, actual %f", msg, expMinutes, a.Minutes)
- }
- })
- }
- func printAllocationSet(msg string, as *AllocationSet) {
- fmt.Printf("--- %s ---\n", msg)
- as.Each(func(k string, a *Allocation) {
- fmt.Printf(" > %s\n", a)
- })
- }
- func TestAllocationSet_AggregateBy(t *testing.T) {
- // Test AggregateBy against the following workload topology, which is
- // generated by generateAllocationSet:
- // | Hierarchy | Cost | CPU | RAM | GPU | PV | Net |
- // +----------------------------------------+------+------+------+------+------+------+
- // cluster1:
- // idle: 20.00 5.00 15.00 0.00 0.00 0.00
- // namespace1:
- // pod1:
- // container1: [app=app1, env=env1] 15.00 1.00 11.00 1.00 1.00 1.00
- // pod-abc: (deployment1)
- // container2: 5.00 1.00 1.00 1.00 1.00 1.00
- // pod-def: (deployment1)
- // container3: 5.00 1.00 1.00 1.00 1.00 1.00
- // namespace2:
- // pod-ghi: (deployment2)
- // container4: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
- // container5: [app=app2, env=env2] 5.00 1.00 1.00 1.00 1.00 1.00
- // pod-jkl: (daemonset1)
- // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
- // +-----------------------------------------+------+------+------+------+------+------+
- // cluster1 subtotal 60.00 11.00 31.00 6.00 6.00 6.00
- // +-----------------------------------------+------+------+------+------+------+------+
- // cluster2:
- // idle: 10.00 5.00 5.00 0.00 0.00 0.00
- // namespace2:
- // pod-mno: (deployment2)
- // container4: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
- // container5: [app=app2] 5.00 1.00 1.00 1.00 1.00 1.00
- // pod-pqr: (daemonset1)
- // container6: {service1} 5.00 1.00 1.00 1.00 1.00 1.00
- // namespace3:
- // pod-stu: (deployment3)
- // container7: 5.00 1.00 1.00 1.00 1.00 1.00
- // pod-vwx: (statefulset1)
- // container8: 5.00 1.00 1.00 1.00 1.00 1.00
- // container9: 5.00 1.00 1.00 1.00 1.00 1.00
- // +----------------------------------------+------+------+------+------+------+------+
- // cluster2 subtotal 40.00 11.00 11.00 6.00 6.00 6.00
- // +----------------------------------------+------+------+------+------+------+------+
- // total 100.00 22.00 42.00 12.00 12.00 12.00
- // +----------------------------------------+------+------+------+------+------+------+
- // Scenarios to test:
- // 1 Single-aggregation
- // 1a AggregationProperties=(Cluster)
- // 1b AggregationProperties=(Namespace)
- // 1c AggregationProperties=(Pod)
- // 1d AggregationProperties=(Container)
- // 1e AggregationProperties=(ControllerKind)
- // 1f AggregationProperties=(Controller)
- // 1g AggregationProperties=(Service)
- // 1h AggregationProperties=(Label:app)
- // 2 Multi-aggregation
- // 2a AggregationProperties=(Cluster, Namespace)
- // 2b AggregationProperties=(Namespace, Label:app)
- // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
- // 2d AggregationProperties=(Label:app, Label:environment)
- // 3 Share idle
- // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
- // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven (TODO niko/etl)
- // 4 Share resources
- // 4a Share namespace ShareEven
- // 4b Share cluster ShareWeighted
- // 4c Share label ShareEven
- // 4d Share overhead ShareWeighted
- // 5 Filters
- // 5a Filter by cluster with separate idle
- // 5b Filter by cluster with shared idle
- // TODO niko/idle more filter tests
- // 6 Combinations and options
- // 6a SplitIdle
- // 6b Share idle with filters
- // 6c Share resources with filters
- // 6d Share idle and share resources
- // 7 Edge cases and errors
- // 7a Empty AggregationProperties
- // 7b Filter all
- // 7c Share all
- // 7d Share and filter the same allocations
- // Definitions and set-up:
- var as *AllocationSet
- var err error
- endYesterday := time.Now().UTC().Truncate(day)
- startYesterday := endYesterday.Add(-day)
- numClusters := 2
- numNamespaces := 3
- numPods := 9
- numContainers := 9
- numControllerKinds := 3
- numControllers := 5
- numServices := 1
- numLabelApps := 2
- // By default, idle is reported as a single, merged allocation
- numIdle := 1
- // There will only ever be one __unallocated__
- numUnallocated := 1
- // There are two clusters, so each gets an idle entry when they are split
- numSplitIdle := 2
- activeTotalCost := 70.0
- idleTotalCost := 30.0
- sharedOverheadHourlyCost := 7.0
- isNamespace3 := func(a *Allocation) bool {
- ns, err := a.Properties.GetNamespace()
- return err == nil && ns == "namespace3"
- }
- isApp1 := func(a *Allocation) bool {
- ls, _ := a.Properties.GetLabels()
- if app, ok := ls["app"]; ok && app == "app1" {
- return true
- }
- return false
- }
- end := time.Now().UTC().Truncate(day)
- start := end.Add(-day)
- // Tests:
- // 1 Single-aggregation
- // 1a AggregationProperties=(Cluster)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
- assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1a", map[string]float64{
- "cluster1": 40.00,
- "cluster2": 30.00,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
- // 1b AggregationProperties=(Namespace)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
- assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1b", map[string]float64{
- "namespace1": 25.00,
- "namespace2": 30.00,
- "namespace3": 15.00,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
- // 1c AggregationProperties=(Pod)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{PodProp: true}, nil)
- assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1c", map[string]float64{
- "pod-jkl": 5.00,
- "pod-stu": 5.00,
- "pod-abc": 5.00,
- "pod-pqr": 5.00,
- "pod-def": 5.00,
- "pod-vwx": 10.00,
- "pod1": 15.00,
- "pod-mno": 10.00,
- "pod-ghi": 10.00,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
- // 1d AggregationProperties=(Container)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ContainerProp: true}, nil)
- assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1d", map[string]float64{
- "container2": 5.00,
- "container9": 5.00,
- "container6": 10.00,
- "container3": 5.00,
- "container4": 10.00,
- "container7": 5.00,
- "container8": 5.00,
- "container5": 10.00,
- "container1": 15.00,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
- // 1e AggregationProperties=(ControllerKind)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
- assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1e", map[string]float64{
- "daemonset": 10.00,
- "deployment": 35.00,
- "statefulset": 10.00,
- IdleSuffix: 30.00,
- UnallocatedSuffix: 15.00,
- })
- assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
- // 1f AggregationProperties=(Controller)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ControllerProp: true}, nil)
- assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1f", map[string]float64{
- "deployment/deployment2": 20.00,
- "daemonset/daemonset1": 10.00,
- "deployment/deployment3": 5.00,
- "statefulset/statefulset1": 10.00,
- "deployment/deployment1": 10.00,
- IdleSuffix: 30.00,
- UnallocatedSuffix: 15.00,
- })
- assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
- // 1g AggregationProperties=(Service)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ServiceProp: true}, nil)
- assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1g", map[string]float64{
- "service1": 10.00,
- IdleSuffix: 30.00,
- UnallocatedSuffix: 60.00,
- })
- assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
- // 1h AggregationProperties=(Label:app)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
- assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1h", map[string]float64{
- "app=app1": 15.00,
- "app=app2": 20.00,
- IdleSuffix: 30.00,
- UnallocatedSuffix: 35.00,
- })
- assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
- // 1i AggregationProperties=(ControllerKind:deployment)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
- assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "1i", map[string]float64{
- "deployment": 35.00,
- IdleSuffix: 30.00,
- UnallocatedSuffix: 35.00,
- })
- assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
- // 2 Multi-aggregation
- // 2a AggregationProperties=(Cluster, Namespace)
- // 2b AggregationProperties=(Namespace, Label:app)
- // 2c AggregationProperties=(Cluster, Namespace, Pod, Container)
- // 2d AggregationProperties=(Label:app, Label:environment)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": "", "env": ""}}, nil)
- // sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
- assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "2d", map[string]float64{
- "app=app1/env=env1": 15.00,
- "app=app2/env=env2": 10.00,
- "app=app2/" + UnallocatedSuffix: 10.00,
- IdleSuffix: 30.00,
- UnallocatedSuffix: 35.00,
- })
- // 2e AggregationProperties=(Cluster, Label:app, Label:environment)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
- assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "2e", map[string]float64{
- "cluster1/app=app2/env=env2": 10.00,
- "__idle__": 30.00,
- "cluster1/app=app1/env=env1": 15.00,
- "cluster1/" + UnallocatedSuffix: 15.00,
- "cluster2/app=app2/" + UnallocatedSuffix: 10.00,
- "cluster2/" + UnallocatedSuffix: 20.00,
- })
- // // TODO niko/etl
- // // 3 Share idle
- // 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
- // namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
- // 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)
- // namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
- assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "3a", map[string]float64{
- "namespace1": 39.69,
- "namespace2": 40.31,
- "namespace3": 20.00,
- })
- assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
- // 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
- // namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
- // 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)
- // namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
- assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "3a", map[string]float64{
- "namespace1": 35.00,
- "namespace2": 45.00,
- "namespace3": 20.00,
- })
- assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
- // 4 Share resources
- // 4a Share namespace ShareEven
- // namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
- // namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
- // idle: 30.0000
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
- ShareFuncs: []AllocationMatchFunc{isNamespace3},
- ShareSplit: ShareEven,
- })
- assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "4a", map[string]float64{
- "namespace1": 32.50,
- "namespace2": 37.50,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
- // 4b Share namespace ShareWeighted
- // namespace1: 32.5000 =
- // namespace2: 37.5000 =
- // idle: 30.0000
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
- ShareFuncs: []AllocationMatchFunc{isNamespace3},
- ShareSplit: ShareWeighted,
- })
- assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "4b", map[string]float64{
- "namespace1": 31.82,
- "namespace2": 38.18,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
- // 4c Share label ShareEven
- // namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
- // namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
- // namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
- // idle: 30.0000
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
- ShareFuncs: []AllocationMatchFunc{isApp1},
- ShareSplit: ShareEven,
- })
- assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "4c", map[string]float64{
- "namespace1": 15.00,
- "namespace2": 35.00,
- "namespace3": 20.00,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
- // 4d Share overhead ShareWeighted
- // namespace1: 37.5000 = 25.00 + (7.0*24.0)*(25.00/70.00)
- // namespace2: 45.0000 = 30.00 + (7.0*24.0)*(30.00/70.00)
- // namespace3: 22.5000 = 15.00 + (7.0*24.0)*(15.00/70.00)
- // idle: 30.0000
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
- SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
- ShareSplit: ShareWeighted,
- })
- assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
- assertAllocationTotals(t, as, "4d", map[string]float64{
- "namespace1": 85.00,
- "namespace2": 102.00,
- "namespace3": 51.00,
- IdleSuffix: 30.00,
- })
- assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
- // 5 Filters
- isCluster := func(matchCluster string) func(*Allocation) bool {
- return func(a *Allocation) bool {
- cluster, err := a.Properties.GetCluster()
- return err == nil && cluster == matchCluster
- }
- }
- isNamespace := func(matchNamespace string) func(*Allocation) bool {
- return func(a *Allocation) bool {
- namespace, err := a.Properties.GetNamespace()
- return err == nil && namespace == matchNamespace
- }
- }
- // 5a Filter by cluster with separate idle
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
- FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
- ShareIdle: ShareNone,
- })
- assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
- assertAllocationTotals(t, as, "5a", map[string]float64{
- "cluster1": 40.00,
- IdleSuffix: 20.00,
- })
- assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
- // 5b Filter by cluster with shared idle
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
- FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
- ShareIdle: ShareWeighted,
- })
- assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
- assertAllocationTotals(t, as, "5b", map[string]float64{
- "cluster1": 60.00,
- })
- assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
- // 5c Filter by cluster, agg by namespace, with separate idle
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
- FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
- ShareIdle: ShareNone,
- })
- assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
- assertAllocationTotals(t, as, "5c", map[string]float64{
- "namespace1": 25.00,
- "namespace2": 15.00,
- IdleSuffix: 20.00,
- })
- assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
- // 5d Filter by namespace, agg by cluster, with separate idle
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{ClusterProp: ""}, &AllocationAggregationOptions{
- FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
- ShareIdle: ShareNone,
- })
- assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
- assertAllocationTotals(t, as, "5d", map[string]float64{
- "cluster1": 15.00,
- "cluster2": 15.00,
- IdleSuffix: 10.31,
- })
- assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
- // 6 Combinations and options
- // 6a SplitIdle
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
- assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
- assertAllocationTotals(t, as, "6a", map[string]float64{
- "namespace1": 25.00,
- "namespace2": 30.00,
- "namespace3": 15.00,
- fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
- fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
- })
- assertAllocationWindow(t, as, "6a", startYesterday, endYesterday, 1440.0)
- // 6b Share idle weighted with filters
- // Should match values from unfiltered aggregation
- // as = generateAllocationSet(start)
- // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
- // printAllocationSet("6b unfiltered", as)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
- FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
- ShareIdle: ShareWeighted,
- })
- assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
- assertAllocationTotals(t, as, "6b", map[string]float64{
- "namespace2": 40.31,
- })
- assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
- // 6c Share idle even with filters
- // Should match values from unfiltered aggregation
- // as = generateAllocationSet(start)
- // err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
- // printAllocationSet("6c unfiltered", as)
- as = generateAllocationSet(start)
- err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
- FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
- ShareIdle: ShareEven,
- })
- assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
- assertAllocationTotals(t, as, "6b", map[string]float64{
- "namespace2": 45.00,
- })
- assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
- // 6d Share resources with filters
- // 6e Share idle and share resources
- // 7 Edge cases and errors
- // 7a Empty AggregationProperties
- // 7b Filter all
- // 7c Share all
- // 7d Share and filter the same allocations
- }
- // TODO niko/etl
- //func TestAllocationSet_Clone(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Delete(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_End(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_IdleAllocations(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Insert(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_IsEmpty(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Length(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Map(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_MarshalJSON(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Resolution(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Seconds(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Set(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_Start(t *testing.T) {}
- // TODO niko/etl
- //func TestAllocationSet_TotalCost(t *testing.T) {}
- // TODO niko/etl
- //func TestNewAllocationSetRange(t *testing.T) {}
- func TestAllocationSetRange_Accumulate(t *testing.T) {
- ago2d := time.Now().UTC().Truncate(day).Add(-2 * day)
- yesterday := time.Now().UTC().Truncate(day).Add(-day)
- today := time.Now().UTC().Truncate(day)
- tomorrow := time.Now().UTC().Truncate(day).Add(day)
- // Accumulating any combination of nil and/or empty set should result in empty set
- result, err := NewAllocationSetRange(nil).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
- }
- if !result.IsEmpty() {
- t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
- }
- result, err = NewAllocationSetRange(nil, nil).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
- }
- if !result.IsEmpty() {
- t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
- }
- result, err = NewAllocationSetRange(NewAllocationSet(yesterday, today)).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
- }
- if !result.IsEmpty() {
- t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
- }
- result, err = NewAllocationSetRange(nil, NewAllocationSet(ago2d, yesterday), nil, NewAllocationSet(today, tomorrow), nil).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating nil AllocationSetRange: %s", err)
- }
- if !result.IsEmpty() {
- t.Fatalf("accumulating nil AllocationSetRange: expected empty; actual %s", result)
- }
- todayAS := NewAllocationSet(today, tomorrow)
- todayAS.Set(NewUnitAllocation("", today, day, nil))
- yesterdayAS := NewAllocationSet(yesterday, today)
- yesterdayAS.Set(NewUnitAllocation("", yesterday, day, nil))
- // Accumulate non-nil with nil should result in copy of non-nil, regardless of order
- result, err = NewAllocationSetRange(nil, todayAS).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
- }
- if result == nil {
- t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
- }
- if result.TotalCost() != 5.0 {
- t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
- }
- result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
- }
- if result == nil {
- t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
- }
- if result.TotalCost() != 5.0 {
- t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
- }
- result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
- }
- if result == nil {
- t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
- }
- if result.TotalCost() != 5.0 {
- t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
- }
- // Accumulate two non-nil should result in sum of both with appropriate start, end
- result, err = NewAllocationSetRange(yesterdayAS, todayAS).Accumulate()
- if err != nil {
- t.Fatalf("unexpected error accumulating AllocationSetRange of length 1: %s", err)
- }
- if result == nil {
- t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
- }
- if result.TotalCost() != 10.0 {
- t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
- }
- allocMap := result.Map()
- if len(allocMap) != 1 {
- t.Fatalf("accumulating AllocationSetRange: expected length 1; actual length %d", len(allocMap))
- }
- alloc := allocMap["cluster1/namespace1/pod1/container1"]
- if alloc == nil {
- t.Fatalf("accumulating AllocationSetRange: expected allocation 'cluster1/namespace1/pod1/container1'")
- }
- if alloc.CPUCoreHours != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", result.TotalCost())
- }
- if alloc.CPUCost != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.CPUCost)
- }
- if alloc.CPUEfficiency != 1.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.CPUEfficiency)
- }
- if alloc.GPUHours != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUHours)
- }
- if alloc.GPUCost != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.GPUCost)
- }
- if alloc.NetworkCost != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
- }
- if alloc.PVByteHours != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
- }
- if alloc.PVCost != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVCost)
- }
- if alloc.RAMByteHours != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMByteHours)
- }
- if alloc.RAMCost != 2.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.RAMCost)
- }
- if alloc.RAMEfficiency != 1.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency)
- }
- if alloc.TotalCost != 10.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost)
- }
- if alloc.TotalEfficiency != 1.0 {
- t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency)
- }
- if !alloc.Start.Equal(yesterday) {
- t.Fatalf("accumulating AllocationSetRange: expected to start %s; actual %s", yesterday, alloc.Start)
- }
- if !alloc.End.Equal(tomorrow) {
- t.Fatalf("accumulating AllocationSetRange: expected to end %s; actual %s", tomorrow, alloc.End)
- }
- if alloc.Minutes != 2880.0 {
- t.Fatalf("accumulating AllocationSetRange: expected %f minutes; actual %f", 2880.0, alloc.Minutes)
- }
- }
- // TODO niko/etl
- // func TestAllocationSetRange_AccumulateBy(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocationSetRange_AggregateBy(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocationSetRange_Append(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocationSetRange_Length(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocationSetRange_MarshalJSON(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocationSetRange_Slice(t *testing.T) {}
- // TODO niko/etl
- // func TestAllocationSetRange_Window(t *testing.T) {}
|