Skip to content

Commit b2a63b0

Browse files
committed
Computing total for usages and baskets
1 parent 082eb34 commit b2a63b0

File tree

6 files changed

+227
-26
lines changed

6 files changed

+227
-26
lines changed

pkg/pricing/basket.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package pricing
22

3+
import "math/big"
4+
35
type Basket []Usage
46

57
func NewBasket() *Basket {
@@ -14,3 +16,11 @@ func (b *Basket) Add(usage Usage) error {
1416
func (b *Basket) Length() int {
1517
return len(*b)
1618
}
19+
20+
func (b *Basket) Total() *big.Rat {
21+
total := new(big.Rat)
22+
for _, usage := range *b {
23+
total = total.Add(total, usage.Total())
24+
}
25+
return total
26+
}

pkg/pricing/basket_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pricing
22

33
import (
4+
"math/big"
45
"testing"
56

67
. "github.com/smartystreets/goconvey/convey"
@@ -15,7 +16,7 @@ func TestNewBasket(t *testing.T) {
1516
}
1617

1718
func TestBasket_Add(t *testing.T) {
18-
Convey("Testing Basket.Add", t, func() {
19+
Convey("Testing Basket.Add", t, FailureContinues, func() {
1920
basket := NewBasket()
2021
So(basket, ShouldNotBeNil)
2122
So(basket.Length(), ShouldEqual, 0)
@@ -27,5 +28,37 @@ func TestBasket_Add(t *testing.T) {
2728
err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42))
2829
So(err, ShouldBeNil)
2930
So(basket.Length(), ShouldEqual, 2)
31+
32+
err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600))
33+
So(err, ShouldBeNil)
34+
So(basket.Length(), ShouldEqual, 3)
35+
})
36+
}
37+
38+
func TestBasket_Total(t *testing.T) {
39+
Convey("Testing Basket.Total", t, FailureContinues, func() {
40+
basket := NewBasket()
41+
So(basket, ShouldNotBeNil)
42+
current := ratZero
43+
difference := ratZero
44+
So(basket.Total(), ShouldEqualBigRat, current)
45+
46+
err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1))
47+
So(err, ShouldBeNil)
48+
difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012))
49+
current = new(big.Rat).Add(current, difference)
50+
So(basket.Total(), ShouldEqualBigRat, current)
51+
52+
err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42))
53+
So(err, ShouldBeNil)
54+
difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012))
55+
current = new(big.Rat).Add(current, difference)
56+
So(basket.Total(), ShouldEqualBigRat, current)
57+
58+
err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600))
59+
So(err, ShouldBeNil)
60+
difference = big.NewRat(6, 1)
61+
current = new(big.Rat).Add(current, difference)
62+
So(basket.Total(), ShouldEqualBigRat, current)
3063
})
3164
}

pkg/pricing/pricing.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package pricing
22

3+
import "time"
4+
35
type PricingObject struct {
46
Path string
57
Identifier string
@@ -8,7 +10,7 @@ type PricingObject struct {
810
UnitQuantity float64
911
UnitPriceCap float64
1012
UsageUnit string
11-
UsageGranularity string
13+
UsageGranularity time.Duration
1214
}
1315

1416
type PricingList []PricingObject
@@ -27,7 +29,7 @@ func init() {
2729
UnitPrice: 0.012,
2830
UnitQuantity: 60,
2931
UnitPriceCap: 6,
30-
UsageGranularity: "minute",
32+
UsageGranularity: time.Minute,
3133
},
3234
{
3335
Path: "/ip/dynamic",
@@ -36,7 +38,7 @@ func init() {
3638
UnitPrice: 0.004,
3739
UnitQuantity: 60,
3840
UnitPriceCap: 1.99,
39-
UsageGranularity: "minute",
41+
UsageGranularity: time.Minute,
4042
},
4143
{
4244
Path: "/ip/reserved",
@@ -45,7 +47,7 @@ func init() {
4547
UnitPrice: 0.004,
4648
UnitQuantity: 60,
4749
UnitPriceCap: 1.99,
48-
UsageGranularity: "minute",
50+
UsageGranularity: time.Minute,
4951
},
5052
{
5153
Path: "/storage/local/ssd/storage",
@@ -54,7 +56,7 @@ func init() {
5456
UnitPrice: 0.004,
5557
UnitQuantity: 50,
5658
UnitPriceCap: 2,
57-
UsageGranularity: "hour",
59+
UsageGranularity: time.Hour,
5860
},
5961
}
6062
}

pkg/pricing/usage.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package pricing
22

3-
import "math/big"
3+
import (
4+
"math/big"
5+
"time"
6+
)
47

58
type Usage struct {
69
PricingObject *PricingObject
@@ -26,8 +29,29 @@ func NewUsage(object *PricingObject) Usage {
2629
return NewUsageWithQuantity(object, 0)
2730
}
2831

29-
func (u *Usage) SetQuantity(quantity *big.Rat) {
30-
u.Quantity = quantity
32+
func (u *Usage) SetQuantity(quantity *big.Rat) error {
33+
u.Quantity = ratMax(quantity, ratZero)
34+
return nil
35+
}
36+
37+
func (u *Usage) SetDuration(duration time.Duration) error {
38+
minutes := new(big.Rat).SetFloat64(duration.Minutes())
39+
factor := new(big.Rat).SetInt64((u.PricingObject.UsageGranularity / time.Minute).Nanoseconds())
40+
quantity := new(big.Rat).Quo(minutes, factor)
41+
ceil := new(big.Rat).SetInt(ratCeil(quantity))
42+
return u.SetQuantity(ceil)
43+
}
44+
45+
func (u *Usage) SetStartEnd(start, end time.Time) error {
46+
roundedStart := start.Round(u.PricingObject.UsageGranularity)
47+
if roundedStart.After(start) {
48+
roundedStart = roundedStart.Add(-u.PricingObject.UsageGranularity)
49+
}
50+
roundedEnd := end.Round(u.PricingObject.UsageGranularity)
51+
if roundedEnd.Before(end) {
52+
roundedEnd = roundedEnd.Add(u.PricingObject.UsageGranularity)
53+
}
54+
return u.SetDuration(roundedEnd.Sub(roundedStart))
3155
}
3256

3357
func (u *Usage) BillableQuantity() *big.Rat {

pkg/pricing/usage_test.go

Lines changed: 127 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
11
package pricing
22

33
import (
4-
"fmt"
54
"math/big"
65
"testing"
6+
"time"
77

88
. "github.com/smartystreets/goconvey/convey"
99
)
1010

11-
func ShouldEqualBigRat(actual interface{}, expected ...interface{}) string {
12-
actualRat := actual.(*big.Rat)
13-
expectRat := expected[0].(*big.Rat)
14-
cmp := actualRat.Cmp(expectRat)
15-
if cmp == 0 {
16-
return ""
17-
}
18-
19-
output := fmt.Sprintf("big.Rat are not matching: %q != %q\n", actualRat, expectRat)
20-
21-
actualFloat64, _ := actualRat.Float64()
22-
expectFloat64, _ := expectRat.Float64()
23-
output += fmt.Sprintf(" %f != %f", actualFloat64, expectFloat64)
24-
return output
25-
}
26-
2711
func TestNewUsageByPathWithQuantity(t *testing.T) {
2812
Convey("Testing NewUsageByPathWithQuantity()", t, func() {
2913
usage := NewUsageByPathWithQuantity("/compute/c1/run", 1)
@@ -32,6 +16,14 @@ func TestNewUsageByPathWithQuantity(t *testing.T) {
3216
})
3317
}
3418

19+
func TestNewUsageByPath(t *testing.T) {
20+
Convey("Testing NewUsageByPath()", t, func() {
21+
usage := NewUsageByPath("/compute/c1/run")
22+
So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run")
23+
So(usage.Quantity, ShouldEqualBigRat, ratZero)
24+
})
25+
}
26+
3527
func TestNewUsageWithQuantity(t *testing.T) {
3628
Convey("Testing NewUsageWithQuantity()", t, func() {
3729
object := CurrentPricing.GetByPath("/compute/c1/run")
@@ -41,6 +33,124 @@ func TestNewUsageWithQuantity(t *testing.T) {
4133
})
4234
}
4335

36+
func TestUsage_SetStartEnd(t *testing.T) {
37+
Convey("Testing Usage.SetStartEnd()", t, func() {
38+
object := PricingObject{
39+
UsageGranularity: time.Minute,
40+
}
41+
usage := NewUsage(&object)
42+
layout := "2006-Jan-02 15:04:05"
43+
start, err := time.Parse(layout, "2015-Jan-25 13:15:42")
44+
So(err, ShouldBeNil)
45+
end, err := time.Parse(layout, "2015-Jan-25 13:16:10")
46+
So(err, ShouldBeNil)
47+
err = usage.SetStartEnd(start, end)
48+
So(err, ShouldBeNil)
49+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(2, 1))
50+
})
51+
}
52+
53+
func TestUsage_SetDuration(t *testing.T) {
54+
Convey("Testing Usage.SetDuration()", t, FailureContinues, func() {
55+
Convey("UsageGranularity=time.Minute", func() {
56+
object := PricingObject{
57+
UsageGranularity: time.Minute,
58+
}
59+
usage := NewUsage(&object)
60+
61+
err := usage.SetDuration(time.Minute * 10)
62+
So(err, ShouldBeNil)
63+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(10, 1))
64+
65+
err = usage.SetDuration(time.Minute + time.Second)
66+
So(err, ShouldBeNil)
67+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(2, 1))
68+
69+
err = usage.SetDuration(0 * time.Minute)
70+
So(err, ShouldBeNil)
71+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1))
72+
73+
err = usage.SetDuration(-1 * time.Minute)
74+
So(err, ShouldBeNil)
75+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1))
76+
77+
err = usage.SetDuration(10*time.Hour + 5*time.Minute + 10*time.Second)
78+
So(err, ShouldBeNil)
79+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(60*10+5+1, 1))
80+
81+
err = usage.SetDuration(10 * time.Nanosecond)
82+
So(err, ShouldBeNil)
83+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
84+
})
85+
86+
Convey("UsageGranularity=time.Hour", func() {
87+
object := PricingObject{
88+
UsageGranularity: time.Hour,
89+
}
90+
usage := NewUsage(&object)
91+
92+
err := usage.SetDuration(time.Minute * 10)
93+
So(err, ShouldBeNil)
94+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
95+
96+
err = usage.SetDuration(time.Minute + time.Second)
97+
So(err, ShouldBeNil)
98+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
99+
100+
err = usage.SetDuration(0 * time.Minute)
101+
So(err, ShouldBeNil)
102+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1))
103+
104+
err = usage.SetDuration(-1 * time.Minute)
105+
So(err, ShouldBeNil)
106+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1))
107+
108+
err = usage.SetDuration(10*time.Hour + 5*time.Minute + 10*time.Second)
109+
So(err, ShouldBeNil)
110+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(11, 1))
111+
112+
err = usage.SetDuration(10 * time.Nanosecond)
113+
So(err, ShouldBeNil)
114+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
115+
})
116+
117+
Convey("UsageGranularity=time.Hour*24", func() {
118+
object := PricingObject{
119+
UsageGranularity: time.Hour * 24,
120+
}
121+
usage := NewUsage(&object)
122+
123+
err := usage.SetDuration(time.Minute * 10)
124+
So(err, ShouldBeNil)
125+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
126+
127+
err = usage.SetDuration(time.Minute + time.Second)
128+
So(err, ShouldBeNil)
129+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
130+
131+
err = usage.SetDuration(0 * time.Minute)
132+
So(err, ShouldBeNil)
133+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1))
134+
135+
err = usage.SetDuration(-1 * time.Minute)
136+
So(err, ShouldBeNil)
137+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1))
138+
139+
err = usage.SetDuration(10*time.Hour + 5*time.Minute + 10*time.Second)
140+
So(err, ShouldBeNil)
141+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
142+
143+
err = usage.SetDuration(3*24*time.Hour + 1*time.Minute)
144+
So(err, ShouldBeNil)
145+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(4, 1))
146+
147+
err = usage.SetDuration(10 * time.Nanosecond)
148+
So(err, ShouldBeNil)
149+
So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1))
150+
})
151+
})
152+
}
153+
44154
func TestUsage_BillableQuantity(t *testing.T) {
45155
Convey("Testing Usage.BillableQuantity()", t, FailureContinues, func() {
46156
object := &PricingObject{

pkg/pricing/utils_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pricing
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
)
7+
8+
func ShouldEqualBigRat(actual interface{}, expected ...interface{}) string {
9+
actualRat := actual.(*big.Rat)
10+
expectRat := expected[0].(*big.Rat)
11+
cmp := actualRat.Cmp(expectRat)
12+
if cmp == 0 {
13+
return ""
14+
}
15+
16+
output := fmt.Sprintf("big.Rat are not matching: %q != %q\n", actualRat, expectRat)
17+
18+
actualFloat64, _ := actualRat.Float64()
19+
expectFloat64, _ := expectRat.Float64()
20+
output += fmt.Sprintf(" %f != %f", actualFloat64, expectFloat64)
21+
return output
22+
}

0 commit comments

Comments
 (0)