From 3c01feec63a0153036d445aa16af85c34fa0bb20 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Mon, 31 Aug 2015 18:23:04 +0200 Subject: [PATCH 01/16] Initial version of lib pricing --- pkg/pricing/basket.go | 16 +++++ pkg/pricing/basket_test.go | 31 ++++++++ pkg/pricing/pricing.go | 80 +++++++++++++++++++++ pkg/pricing/pricing_test.go | 33 +++++++++ pkg/pricing/usage.go | 32 +++++++++ pkg/pricing/usage_test.go | 136 ++++++++++++++++++++++++++++++++++++ 6 files changed, 328 insertions(+) create mode 100644 pkg/pricing/basket.go create mode 100644 pkg/pricing/basket_test.go create mode 100644 pkg/pricing/pricing.go create mode 100644 pkg/pricing/pricing_test.go create mode 100644 pkg/pricing/usage.go create mode 100644 pkg/pricing/usage_test.go diff --git a/pkg/pricing/basket.go b/pkg/pricing/basket.go new file mode 100644 index 0000000000..90734c6983 --- /dev/null +++ b/pkg/pricing/basket.go @@ -0,0 +1,16 @@ +package pricing + +type Basket []Usage + +func NewBasket() *Basket { + return &Basket{} +} + +func (b *Basket) Add(usage Usage) error { + *b = append(*b, usage) + return nil +} + +func (b *Basket) Length() int { + return len(*b) +} diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go new file mode 100644 index 0000000000..6ebad595bb --- /dev/null +++ b/pkg/pricing/basket_test.go @@ -0,0 +1,31 @@ +package pricing + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestNewBasket(t *testing.T) { + Convey("Testing NewBasket()", t, func() { + basket := NewBasket() + So(basket, ShouldNotBeNil) + So(basket.Length(), ShouldEqual, 0) + }) +} + +func TestBasket_Add(t *testing.T) { + Convey("Testing Basket.Add", t, func() { + basket := NewBasket() + So(basket, ShouldNotBeNil) + So(basket.Length(), ShouldEqual, 0) + + err := basket.Add(NewUsageByPath("/compute/c1/run", 1)) + So(err, ShouldBeNil) + So(basket.Length(), ShouldEqual, 1) + + err = basket.Add(NewUsageByPath("/compute/c1/run", 42)) + So(err, ShouldBeNil) + So(basket.Length(), ShouldEqual, 2) + }) +} diff --git a/pkg/pricing/pricing.go b/pkg/pricing/pricing.go new file mode 100644 index 0000000000..e91373eba5 --- /dev/null +++ b/pkg/pricing/pricing.go @@ -0,0 +1,80 @@ +package pricing + +type PricingObject struct { + Path string + Identifier string + Currency string + UnitPrice float64 + UnitQuantity float64 + UnitPriceCap float64 + UsageUnit string + UsageGranularity string +} + +type PricingList []PricingObject + +// CurrentPricing tries to be up-to-date with the real pricing +// we cannot guarantee of these values since we hardcode values for now +// later, we should be able to call a dedicated pricing API +var CurrentPricing PricingList + +func init() { + CurrentPricing = PricingList{ + { + Path: "/compute/c1/run", + Identifier: "aaaaaaaa-aaaa-4aaa-8aaa-111111111111", + Currency: "EUR", + UnitPrice: 0.012, + UnitQuantity: 60, + UnitPriceCap: 6, + UsageGranularity: "minute", + }, + { + Path: "/ip/dynamic", + Identifier: "467116bf-4631-49fb-905b-e07701c2db11", + Currency: "EUR", + UnitPrice: 0.004, + UnitQuantity: 60, + UnitPriceCap: 1.99, + UsageGranularity: "minute", + }, + { + Path: "/ip/reserved", + Identifier: "467116bf-4631-49fb-905b-e07701c2db22", + Currency: "EUR", + UnitPrice: 0.004, + UnitQuantity: 60, + UnitPriceCap: 1.99, + UsageGranularity: "minute", + }, + { + Path: "/storage/local/ssd/storage", + Identifier: "bbbbbbbb-bbbb-4bbb-8bbb-111111111113", + Currency: "EUR", + UnitPrice: 0.004, + UnitQuantity: 50, + UnitPriceCap: 2, + UsageGranularity: "hour", + }, + } +} + +// GetByPath returns an object matching a path +func (pl *PricingList) GetByPath(path string) *PricingObject { + for _, object := range *pl { + if object.Path == path { + return &object + } + } + return nil +} + +// GetByIdentifier returns an object matching a identifier +func (pl *PricingList) GetByIdentifier(identifier string) *PricingObject { + for _, object := range *pl { + if object.Identifier == identifier { + return &object + } + } + return nil +} diff --git a/pkg/pricing/pricing_test.go b/pkg/pricing/pricing_test.go new file mode 100644 index 0000000000..e4e42cf5ce --- /dev/null +++ b/pkg/pricing/pricing_test.go @@ -0,0 +1,33 @@ +package pricing + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGetByPath(t *testing.T) { + Convey("Testing GetByPath", t, func() { + object := CurrentPricing.GetByPath("/compute/c1/run") + So(object, ShouldNotBeNil) + So(object.Path, ShouldEqual, "/compute/c1/run") + + object = CurrentPricing.GetByPath("/ip/dynamic") + So(object, ShouldNotBeNil) + So(object.Path, ShouldEqual, "/ip/dynamic") + + object = CurrentPricing.GetByPath("/dontexists") + So(object, ShouldBeNil) + }) +} + +func TestGetByIdentifier(t *testing.T) { + Convey("Testing GetByIdentifier", t, func() { + object := CurrentPricing.GetByIdentifier("aaaaaaaa-aaaa-4aaa-8aaa-111111111111") + So(object, ShouldNotBeNil) + So(object.Path, ShouldEqual, "/compute/c1/run") + + object = CurrentPricing.GetByIdentifier("dontexists") + So(object, ShouldBeNil) + }) +} diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go new file mode 100644 index 0000000000..e85d419494 --- /dev/null +++ b/pkg/pricing/usage.go @@ -0,0 +1,32 @@ +package pricing + +import "math" + +type Usage struct { + PricingObject *PricingObject + Quantity float64 +} + +func NewUsageByPath(objectPath string, quantity float64) Usage { + return NewUsage(CurrentPricing.GetByPath(objectPath), quantity) +} + +func NewUsage(object *PricingObject, quantity float64) Usage { + return Usage{ + PricingObject: object, + Quantity: quantity, + } +} + +func (u *Usage) BillableQuantity() float64 { + return math.Ceil(math.Max(u.Quantity, 0)/u.PricingObject.UnitQuantity) * u.PricingObject.UnitQuantity +} + +func (u *Usage) LostQuantity() float64 { + return u.BillableQuantity() - math.Max(u.Quantity, 0) +} + +func (u *Usage) Total() float64 { + total := u.PricingObject.UnitPrice * u.BillableQuantity() + return math.Min(total, u.PricingObject.UnitPriceCap) +} diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go new file mode 100644 index 0000000000..daf1ce3ede --- /dev/null +++ b/pkg/pricing/usage_test.go @@ -0,0 +1,136 @@ +package pricing + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestNewUsageByPath(t *testing.T) { + Convey("Testing NewUsageByPath()", t, func() { + usage := NewUsageByPath("/compute/c1/run", 1) + So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") + So(usage.Quantity, ShouldEqual, 1) + }) +} + +func TestNewUsage(t *testing.T) { + Convey("Testing NewUsage()", t, func() { + object := CurrentPricing.GetByPath("/compute/c1/run") + usage := NewUsage(object, 1) + So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") + So(usage.Quantity, ShouldEqual, 1) + }) +} + +func TestUsage_BillableQuantity(t *testing.T) { + Convey("Testing Usage.BillableQuantity()", t, FailureContinues, func() { + object := &PricingObject{ + UnitQuantity: 60, + } + usage := NewUsage(object, -1) + So(usage.BillableQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, -1000) + So(usage.BillableQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, 0) + So(usage.BillableQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, 1) + So(usage.BillableQuantity(), ShouldEqual, 60) + + usage = NewUsage(object, 59) + So(usage.BillableQuantity(), ShouldEqual, 60) + + usage = NewUsage(object, 59.9999) + So(usage.BillableQuantity(), ShouldEqual, 60) + + usage = NewUsage(object, 60) + So(usage.BillableQuantity(), ShouldEqual, 60) + + usage = NewUsage(object, 60.00001) + So(usage.BillableQuantity(), ShouldEqual, 60*2) + + usage = NewUsage(object, 61) + So(usage.BillableQuantity(), ShouldEqual, 60*2) + + usage = NewUsage(object, 119) + So(usage.BillableQuantity(), ShouldEqual, 60*2) + + usage = NewUsage(object, 121) + So(usage.BillableQuantity(), ShouldEqual, 60*3) + + usage = NewUsage(object, 1000) + So(usage.BillableQuantity(), ShouldEqual, 60*17) + }) +} + +func TestUsage_LostQuantity(t *testing.T) { + Convey("Testing Usage.LostQuantity()", t, FailureContinues, func() { + object := &PricingObject{ + UnitQuantity: 60, + } + usage := NewUsage(object, -1) + So(usage.LostQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, -1000) + So(usage.LostQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, 0) + So(usage.LostQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, 1) + So(usage.LostQuantity(), ShouldEqual, 60-1) + + usage = NewUsage(object, 59) + So(usage.LostQuantity(), ShouldEqual, 60-59) + + // oops error, float64 precision isn't sufficient + // usage = NewUsage(object, 59.9999) + // So(usage.LostQuantity(), ShouldEqual, 60-59.9999) + + usage = NewUsage(object, 60) + So(usage.LostQuantity(), ShouldEqual, 0) + + usage = NewUsage(object, 60.00001) + So(usage.LostQuantity(), ShouldEqual, 60*2-60.00001) + + usage = NewUsage(object, 61) + So(usage.LostQuantity(), ShouldEqual, 60*2-61) + + usage = NewUsage(object, 119) + So(usage.LostQuantity(), ShouldEqual, 60*2-119) + + usage = NewUsage(object, 121) + So(usage.LostQuantity(), ShouldEqual, 60*3-121) + + usage = NewUsage(object, 1000) + So(usage.LostQuantity(), ShouldEqual, 60*17-1000) + }) +} + +func TestUsage_Total(t *testing.T) { + Convey("Testing Usage.Total()", t, func() { + object := PricingObject{ + UnitQuantity: 60, + UnitPrice: 0.012, + UnitPriceCap: 6, + } + + usage := NewUsage(&object, -1) + So(usage.Total(), ShouldEqual, 0) + + usage = NewUsage(&object, 0) + So(usage.Total(), ShouldEqual, 0) + + usage = NewUsage(&object, 1) + So(usage.Total(), ShouldEqual, 0.012*60) + + usage = NewUsage(&object, 61) + So(usage.Total(), ShouldEqual, 0.012*120) + + usage = NewUsage(&object, 1000) + So(usage.Total(), ShouldEqual, 6) + }) +} From 661178a62f62d126fb37417bb991c1eed0c71fc6 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Mon, 31 Aug 2015 19:36:00 +0200 Subject: [PATCH 02/16] Switched from float64 to big.Rat --- pkg/pricing/usage.go | 35 +++++++++++----- pkg/pricing/usage_test.go | 85 +++++++++++++++++++++++---------------- pkg/pricing/utils.go | 37 +++++++++++++++++ 3 files changed, 113 insertions(+), 44 deletions(-) create mode 100644 pkg/pricing/utils.go diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index e85d419494..71aa9cd442 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -1,10 +1,10 @@ package pricing -import "math" +import "math/big" type Usage struct { PricingObject *PricingObject - Quantity float64 + Quantity *big.Rat } func NewUsageByPath(objectPath string, quantity float64) Usage { @@ -14,19 +14,34 @@ func NewUsageByPath(objectPath string, quantity float64) Usage { func NewUsage(object *PricingObject, quantity float64) Usage { return Usage{ PricingObject: object, - Quantity: quantity, + Quantity: new(big.Rat).SetFloat64(quantity), } } -func (u *Usage) BillableQuantity() float64 { - return math.Ceil(math.Max(u.Quantity, 0)/u.PricingObject.UnitQuantity) * u.PricingObject.UnitQuantity +func (u *Usage) BillableQuantity() *big.Rat { + if u.Quantity.Cmp(big.NewRat(0, 1)) < 1 { + return big.NewRat(0, 1) + } + + //return math.Ceil(u.Quantity/u.PricingObject.UnitQuantity) * u.PricingObject.UnitQuantity + unitQuantity := new(big.Rat).SetFloat64(u.PricingObject.UnitQuantity) + quantityQuotient := new(big.Rat).Quo(u.Quantity, unitQuantity) + ceil := new(big.Rat).SetInt(ratCeil(quantityQuotient)) + return new(big.Rat).Mul(ceil, unitQuantity) } -func (u *Usage) LostQuantity() float64 { - return u.BillableQuantity() - math.Max(u.Quantity, 0) +func (u *Usage) LostQuantity() *big.Rat { + //return u.BillableQuantity() - math.Max(u.Quantity, 0) + + return new(big.Rat).Sub(u.BillableQuantity(), ratMax(u.Quantity, ratZero)) } -func (u *Usage) Total() float64 { - total := u.PricingObject.UnitPrice * u.BillableQuantity() - return math.Min(total, u.PricingObject.UnitPriceCap) +func (u *Usage) Total() *big.Rat { + //return math.Min(u.PricingObject.UnitPrice * u.BillableQuantity(), u.PricingObject.UnitPriceCap) + + unitPrice := new(big.Rat).SetFloat64(u.PricingObject.UnitPrice) + total := new(big.Rat).Mul(u.BillableQuantity(), unitPrice) + + unitPriceCap := new(big.Rat).SetFloat64(u.PricingObject.UnitPriceCap) + return ratMin(total, unitPriceCap) } diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go index daf1ce3ede..ddff6aa96c 100644 --- a/pkg/pricing/usage_test.go +++ b/pkg/pricing/usage_test.go @@ -1,16 +1,34 @@ package pricing import ( + "fmt" + "math/big" "testing" . "github.com/smartystreets/goconvey/convey" ) +func ShouldEqualBigRat(actual interface{}, expected ...interface{}) string { + actualRat := actual.(*big.Rat) + expectRat := expected[0].(*big.Rat) + cmp := actualRat.Cmp(expectRat) + if cmp == 0 { + return "" + } + + output := fmt.Sprintf("big.Rat are not matching: %q != %q\n", actualRat, expectRat) + + actualFloat64, _ := actualRat.Float64() + expectFloat64, _ := expectRat.Float64() + output += fmt.Sprintf(" %f != %f", actualFloat64, expectFloat64) + return output +} + func TestNewUsageByPath(t *testing.T) { Convey("Testing NewUsageByPath()", t, func() { usage := NewUsageByPath("/compute/c1/run", 1) So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") - So(usage.Quantity, ShouldEqual, 1) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) }) } @@ -19,7 +37,7 @@ func TestNewUsage(t *testing.T) { object := CurrentPricing.GetByPath("/compute/c1/run") usage := NewUsage(object, 1) So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") - So(usage.Quantity, ShouldEqual, 1) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) }) } @@ -29,40 +47,40 @@ func TestUsage_BillableQuantity(t *testing.T) { UnitQuantity: 60, } usage := NewUsage(object, -1) - So(usage.BillableQuantity(), ShouldEqual, 0) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, -1000) - So(usage.BillableQuantity(), ShouldEqual, 0) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, 0) - So(usage.BillableQuantity(), ShouldEqual, 0) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, 1) - So(usage.BillableQuantity(), ShouldEqual, 60) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) usage = NewUsage(object, 59) - So(usage.BillableQuantity(), ShouldEqual, 60) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) usage = NewUsage(object, 59.9999) - So(usage.BillableQuantity(), ShouldEqual, 60) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) usage = NewUsage(object, 60) - So(usage.BillableQuantity(), ShouldEqual, 60) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) usage = NewUsage(object, 60.00001) - So(usage.BillableQuantity(), ShouldEqual, 60*2) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) usage = NewUsage(object, 61) - So(usage.BillableQuantity(), ShouldEqual, 60*2) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) usage = NewUsage(object, 119) - So(usage.BillableQuantity(), ShouldEqual, 60*2) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) usage = NewUsage(object, 121) - So(usage.BillableQuantity(), ShouldEqual, 60*3) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*3, 1)) usage = NewUsage(object, 1000) - So(usage.BillableQuantity(), ShouldEqual, 60*17) + So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*17, 1)) }) } @@ -72,46 +90,45 @@ func TestUsage_LostQuantity(t *testing.T) { UnitQuantity: 60, } usage := NewUsage(object, -1) - So(usage.LostQuantity(), ShouldEqual, 0) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, -1000) - So(usage.LostQuantity(), ShouldEqual, 0) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, 0) - So(usage.LostQuantity(), ShouldEqual, 0) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, 1) - So(usage.LostQuantity(), ShouldEqual, 60-1) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60-1, 1)) usage = NewUsage(object, 59) - So(usage.LostQuantity(), ShouldEqual, 60-59) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60-59, 1)) - // oops error, float64 precision isn't sufficient - // usage = NewUsage(object, 59.9999) - // So(usage.LostQuantity(), ShouldEqual, 60-59.9999) + usage = NewUsage(object, 59.9999) + So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).Sub(big.NewRat(60, 1), new(big.Rat).SetFloat64(59.9999))) usage = NewUsage(object, 60) - So(usage.LostQuantity(), ShouldEqual, 0) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(object, 60.00001) - So(usage.LostQuantity(), ShouldEqual, 60*2-60.00001) + So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).SetFloat64(60*2-60.00001)) usage = NewUsage(object, 61) - So(usage.LostQuantity(), ShouldEqual, 60*2-61) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*2-61, 1)) usage = NewUsage(object, 119) - So(usage.LostQuantity(), ShouldEqual, 60*2-119) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*2-119, 1)) usage = NewUsage(object, 121) - So(usage.LostQuantity(), ShouldEqual, 60*3-121) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*3-121, 1)) usage = NewUsage(object, 1000) - So(usage.LostQuantity(), ShouldEqual, 60*17-1000) + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*17-1000, 1)) }) } func TestUsage_Total(t *testing.T) { - Convey("Testing Usage.Total()", t, func() { + Convey("Testing Usage.Total()", t, FailureContinues, func() { object := PricingObject{ UnitQuantity: 60, UnitPrice: 0.012, @@ -119,18 +136,18 @@ func TestUsage_Total(t *testing.T) { } usage := NewUsage(&object, -1) - So(usage.Total(), ShouldEqual, 0) + So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(&object, 0) - So(usage.Total(), ShouldEqual, 0) + So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsage(&object, 1) - So(usage.Total(), ShouldEqual, 0.012*60) + So(usage.Total(), ShouldEqualBigRat, new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012))) usage = NewUsage(&object, 61) - So(usage.Total(), ShouldEqual, 0.012*120) + So(usage.Total(), ShouldEqualBigRat, new(big.Rat).Mul(big.NewRat(120, 1), new(big.Rat).SetFloat64(0.012))) usage = NewUsage(&object, 1000) - So(usage.Total(), ShouldEqual, 6) + So(usage.Total(), ShouldEqualBigRat, big.NewRat(6, 1)) }) } diff --git a/pkg/pricing/utils.go b/pkg/pricing/utils.go new file mode 100644 index 0000000000..4a4c6bc84b --- /dev/null +++ b/pkg/pricing/utils.go @@ -0,0 +1,37 @@ +package pricing + +import "math/big" + +var ( + intZero = big.NewInt(0) + intOne = big.NewInt(1) + ratZero = big.NewRat(0, 1) + ratOne = big.NewRat(1, 1) +) + +// Returns a new big.Int set to the ceiling of x. +func ratCeil(x *big.Rat) *big.Int { + z := new(big.Int) + m := new(big.Int) + z.DivMod(x.Num(), x.Denom(), m) + if m.Cmp(intZero) == 1 { + z.Add(z, intOne) + } + return z +} + +// Returns a new big.Rat set to maximum of x and y +func ratMax(x, y *big.Rat) *big.Rat { + if x.Cmp(y) < 1 { + return y + } + return x +} + +// Returns a new big.Rat set to minimum of x and y +func ratMin(x, y *big.Rat) *big.Rat { + if x.Cmp(y) > 0 { + return y + } + return x +} From 082eb3416b2f45fa9c08ebed057797176f57adfa Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Mon, 31 Aug 2015 23:07:10 +0200 Subject: [PATCH 03/16] Added new helper to initialize Usage --- pkg/pricing/basket_test.go | 4 +-- pkg/pricing/usage.go | 18 ++++++++-- pkg/pricing/usage_test.go | 70 +++++++++++++++++++------------------- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 6ebad595bb..1d39c87a64 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -20,11 +20,11 @@ func TestBasket_Add(t *testing.T) { So(basket, ShouldNotBeNil) So(basket.Length(), ShouldEqual, 0) - err := basket.Add(NewUsageByPath("/compute/c1/run", 1)) + err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1)) So(err, ShouldBeNil) So(basket.Length(), ShouldEqual, 1) - err = basket.Add(NewUsageByPath("/compute/c1/run", 42)) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) So(err, ShouldBeNil) So(basket.Length(), ShouldEqual, 2) }) diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index 71aa9cd442..6d3834b0ac 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -7,17 +7,29 @@ type Usage struct { Quantity *big.Rat } -func NewUsageByPath(objectPath string, quantity float64) Usage { - return NewUsage(CurrentPricing.GetByPath(objectPath), quantity) +func NewUsageByPath(objectPath string) Usage { + return NewUsageByPathWithQuantity(objectPath, 0) } -func NewUsage(object *PricingObject, quantity float64) Usage { +func NewUsageByPathWithQuantity(objectPath string, quantity float64) Usage { + return NewUsageWithQuantity(CurrentPricing.GetByPath(objectPath), quantity) +} + +func NewUsageWithQuantity(object *PricingObject, quantity float64) Usage { return Usage{ PricingObject: object, Quantity: new(big.Rat).SetFloat64(quantity), } } +func NewUsage(object *PricingObject) Usage { + return NewUsageWithQuantity(object, 0) +} + +func (u *Usage) SetQuantity(quantity *big.Rat) { + u.Quantity = quantity +} + func (u *Usage) BillableQuantity() *big.Rat { if u.Quantity.Cmp(big.NewRat(0, 1)) < 1 { return big.NewRat(0, 1) diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go index ddff6aa96c..2c6516b3ef 100644 --- a/pkg/pricing/usage_test.go +++ b/pkg/pricing/usage_test.go @@ -24,18 +24,18 @@ func ShouldEqualBigRat(actual interface{}, expected ...interface{}) string { return output } -func TestNewUsageByPath(t *testing.T) { - Convey("Testing NewUsageByPath()", t, func() { - usage := NewUsageByPath("/compute/c1/run", 1) +func TestNewUsageByPathWithQuantity(t *testing.T) { + Convey("Testing NewUsageByPathWithQuantity()", t, func() { + usage := NewUsageByPathWithQuantity("/compute/c1/run", 1) So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) }) } -func TestNewUsage(t *testing.T) { - Convey("Testing NewUsage()", t, func() { +func TestNewUsageWithQuantity(t *testing.T) { + Convey("Testing NewUsageWithQuantity()", t, func() { object := CurrentPricing.GetByPath("/compute/c1/run") - usage := NewUsage(object, 1) + usage := NewUsageWithQuantity(object, 1) So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) }) @@ -46,40 +46,40 @@ func TestUsage_BillableQuantity(t *testing.T) { object := &PricingObject{ UnitQuantity: 60, } - usage := NewUsage(object, -1) + usage := NewUsageWithQuantity(object, -1) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, -1000) + usage = NewUsageWithQuantity(object, -1000) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, 0) + usage = NewUsageWithQuantity(object, 0) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, 1) + usage = NewUsageWithQuantity(object, 1) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsage(object, 59) + usage = NewUsageWithQuantity(object, 59) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsage(object, 59.9999) + usage = NewUsageWithQuantity(object, 59.9999) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsage(object, 60) + usage = NewUsageWithQuantity(object, 60) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsage(object, 60.00001) + usage = NewUsageWithQuantity(object, 60.00001) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) - usage = NewUsage(object, 61) + usage = NewUsageWithQuantity(object, 61) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) - usage = NewUsage(object, 119) + usage = NewUsageWithQuantity(object, 119) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) - usage = NewUsage(object, 121) + usage = NewUsageWithQuantity(object, 121) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*3, 1)) - usage = NewUsage(object, 1000) + usage = NewUsageWithQuantity(object, 1000) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*17, 1)) }) } @@ -89,40 +89,40 @@ func TestUsage_LostQuantity(t *testing.T) { object := &PricingObject{ UnitQuantity: 60, } - usage := NewUsage(object, -1) + usage := NewUsageWithQuantity(object, -1) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, -1000) + usage = NewUsageWithQuantity(object, -1000) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, 0) + usage = NewUsageWithQuantity(object, 0) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, 1) + usage = NewUsageWithQuantity(object, 1) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60-1, 1)) - usage = NewUsage(object, 59) + usage = NewUsageWithQuantity(object, 59) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60-59, 1)) - usage = NewUsage(object, 59.9999) + usage = NewUsageWithQuantity(object, 59.9999) So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).Sub(big.NewRat(60, 1), new(big.Rat).SetFloat64(59.9999))) - usage = NewUsage(object, 60) + usage = NewUsageWithQuantity(object, 60) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(object, 60.00001) + usage = NewUsageWithQuantity(object, 60.00001) So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).SetFloat64(60*2-60.00001)) - usage = NewUsage(object, 61) + usage = NewUsageWithQuantity(object, 61) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*2-61, 1)) - usage = NewUsage(object, 119) + usage = NewUsageWithQuantity(object, 119) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*2-119, 1)) - usage = NewUsage(object, 121) + usage = NewUsageWithQuantity(object, 121) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*3-121, 1)) - usage = NewUsage(object, 1000) + usage = NewUsageWithQuantity(object, 1000) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*17-1000, 1)) }) } @@ -135,19 +135,19 @@ func TestUsage_Total(t *testing.T) { UnitPriceCap: 6, } - usage := NewUsage(&object, -1) + usage := NewUsageWithQuantity(&object, -1) So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(&object, 0) + usage = NewUsageWithQuantity(&object, 0) So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsage(&object, 1) + usage = NewUsageWithQuantity(&object, 1) So(usage.Total(), ShouldEqualBigRat, new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012))) - usage = NewUsage(&object, 61) + usage = NewUsageWithQuantity(&object, 61) So(usage.Total(), ShouldEqualBigRat, new(big.Rat).Mul(big.NewRat(120, 1), new(big.Rat).SetFloat64(0.012))) - usage = NewUsage(&object, 1000) + usage = NewUsageWithQuantity(&object, 1000) So(usage.Total(), ShouldEqualBigRat, big.NewRat(6, 1)) }) } From b2a63b0ca4157b9c9f16fe4daa21ba843fb4836e Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 12:17:26 +0200 Subject: [PATCH 04/16] Computing total for usages and baskets --- pkg/pricing/basket.go | 10 +++ pkg/pricing/basket_test.go | 35 ++++++++- pkg/pricing/pricing.go | 12 ++-- pkg/pricing/usage.go | 30 +++++++- pkg/pricing/usage_test.go | 144 ++++++++++++++++++++++++++++++++----- pkg/pricing/utils_test.go | 22 ++++++ 6 files changed, 227 insertions(+), 26 deletions(-) create mode 100644 pkg/pricing/utils_test.go diff --git a/pkg/pricing/basket.go b/pkg/pricing/basket.go index 90734c6983..e6500486ce 100644 --- a/pkg/pricing/basket.go +++ b/pkg/pricing/basket.go @@ -1,5 +1,7 @@ package pricing +import "math/big" + type Basket []Usage func NewBasket() *Basket { @@ -14,3 +16,11 @@ func (b *Basket) Add(usage Usage) error { func (b *Basket) Length() int { return len(*b) } + +func (b *Basket) Total() *big.Rat { + total := new(big.Rat) + for _, usage := range *b { + total = total.Add(total, usage.Total()) + } + return total +} diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 1d39c87a64..5df698efda 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -1,6 +1,7 @@ package pricing import ( + "math/big" "testing" . "github.com/smartystreets/goconvey/convey" @@ -15,7 +16,7 @@ func TestNewBasket(t *testing.T) { } func TestBasket_Add(t *testing.T) { - Convey("Testing Basket.Add", t, func() { + Convey("Testing Basket.Add", t, FailureContinues, func() { basket := NewBasket() So(basket, ShouldNotBeNil) So(basket.Length(), ShouldEqual, 0) @@ -27,5 +28,37 @@ func TestBasket_Add(t *testing.T) { err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) So(err, ShouldBeNil) So(basket.Length(), ShouldEqual, 2) + + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600)) + So(err, ShouldBeNil) + So(basket.Length(), ShouldEqual, 3) + }) +} + +func TestBasket_Total(t *testing.T) { + Convey("Testing Basket.Total", t, FailureContinues, func() { + basket := NewBasket() + So(basket, ShouldNotBeNil) + current := ratZero + difference := ratZero + So(basket.Total(), ShouldEqualBigRat, current) + + err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1)) + So(err, ShouldBeNil) + difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) + current = new(big.Rat).Add(current, difference) + So(basket.Total(), ShouldEqualBigRat, current) + + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) + So(err, ShouldBeNil) + difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) + current = new(big.Rat).Add(current, difference) + So(basket.Total(), ShouldEqualBigRat, current) + + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600)) + So(err, ShouldBeNil) + difference = big.NewRat(6, 1) + current = new(big.Rat).Add(current, difference) + So(basket.Total(), ShouldEqualBigRat, current) }) } diff --git a/pkg/pricing/pricing.go b/pkg/pricing/pricing.go index e91373eba5..50c9252b04 100644 --- a/pkg/pricing/pricing.go +++ b/pkg/pricing/pricing.go @@ -1,5 +1,7 @@ package pricing +import "time" + type PricingObject struct { Path string Identifier string @@ -8,7 +10,7 @@ type PricingObject struct { UnitQuantity float64 UnitPriceCap float64 UsageUnit string - UsageGranularity string + UsageGranularity time.Duration } type PricingList []PricingObject @@ -27,7 +29,7 @@ func init() { UnitPrice: 0.012, UnitQuantity: 60, UnitPriceCap: 6, - UsageGranularity: "minute", + UsageGranularity: time.Minute, }, { Path: "/ip/dynamic", @@ -36,7 +38,7 @@ func init() { UnitPrice: 0.004, UnitQuantity: 60, UnitPriceCap: 1.99, - UsageGranularity: "minute", + UsageGranularity: time.Minute, }, { Path: "/ip/reserved", @@ -45,7 +47,7 @@ func init() { UnitPrice: 0.004, UnitQuantity: 60, UnitPriceCap: 1.99, - UsageGranularity: "minute", + UsageGranularity: time.Minute, }, { Path: "/storage/local/ssd/storage", @@ -54,7 +56,7 @@ func init() { UnitPrice: 0.004, UnitQuantity: 50, UnitPriceCap: 2, - UsageGranularity: "hour", + UsageGranularity: time.Hour, }, } } diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index 6d3834b0ac..8304beed2b 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -1,6 +1,9 @@ package pricing -import "math/big" +import ( + "math/big" + "time" +) type Usage struct { PricingObject *PricingObject @@ -26,8 +29,29 @@ func NewUsage(object *PricingObject) Usage { return NewUsageWithQuantity(object, 0) } -func (u *Usage) SetQuantity(quantity *big.Rat) { - u.Quantity = quantity +func (u *Usage) SetQuantity(quantity *big.Rat) error { + u.Quantity = ratMax(quantity, ratZero) + return nil +} + +func (u *Usage) SetDuration(duration time.Duration) error { + minutes := new(big.Rat).SetFloat64(duration.Minutes()) + factor := new(big.Rat).SetInt64((u.PricingObject.UsageGranularity / time.Minute).Nanoseconds()) + quantity := new(big.Rat).Quo(minutes, factor) + ceil := new(big.Rat).SetInt(ratCeil(quantity)) + return u.SetQuantity(ceil) +} + +func (u *Usage) SetStartEnd(start, end time.Time) error { + roundedStart := start.Round(u.PricingObject.UsageGranularity) + if roundedStart.After(start) { + roundedStart = roundedStart.Add(-u.PricingObject.UsageGranularity) + } + roundedEnd := end.Round(u.PricingObject.UsageGranularity) + if roundedEnd.Before(end) { + roundedEnd = roundedEnd.Add(u.PricingObject.UsageGranularity) + } + return u.SetDuration(roundedEnd.Sub(roundedStart)) } func (u *Usage) BillableQuantity() *big.Rat { diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go index 2c6516b3ef..8d8b96d304 100644 --- a/pkg/pricing/usage_test.go +++ b/pkg/pricing/usage_test.go @@ -1,29 +1,13 @@ package pricing import ( - "fmt" "math/big" "testing" + "time" . "github.com/smartystreets/goconvey/convey" ) -func ShouldEqualBigRat(actual interface{}, expected ...interface{}) string { - actualRat := actual.(*big.Rat) - expectRat := expected[0].(*big.Rat) - cmp := actualRat.Cmp(expectRat) - if cmp == 0 { - return "" - } - - output := fmt.Sprintf("big.Rat are not matching: %q != %q\n", actualRat, expectRat) - - actualFloat64, _ := actualRat.Float64() - expectFloat64, _ := expectRat.Float64() - output += fmt.Sprintf(" %f != %f", actualFloat64, expectFloat64) - return output -} - func TestNewUsageByPathWithQuantity(t *testing.T) { Convey("Testing NewUsageByPathWithQuantity()", t, func() { usage := NewUsageByPathWithQuantity("/compute/c1/run", 1) @@ -32,6 +16,14 @@ func TestNewUsageByPathWithQuantity(t *testing.T) { }) } +func TestNewUsageByPath(t *testing.T) { + Convey("Testing NewUsageByPath()", t, func() { + usage := NewUsageByPath("/compute/c1/run") + So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") + So(usage.Quantity, ShouldEqualBigRat, ratZero) + }) +} + func TestNewUsageWithQuantity(t *testing.T) { Convey("Testing NewUsageWithQuantity()", t, func() { object := CurrentPricing.GetByPath("/compute/c1/run") @@ -41,6 +33,124 @@ func TestNewUsageWithQuantity(t *testing.T) { }) } +func TestUsage_SetStartEnd(t *testing.T) { + Convey("Testing Usage.SetStartEnd()", t, func() { + object := PricingObject{ + UsageGranularity: time.Minute, + } + usage := NewUsage(&object) + layout := "2006-Jan-02 15:04:05" + start, err := time.Parse(layout, "2015-Jan-25 13:15:42") + So(err, ShouldBeNil) + end, err := time.Parse(layout, "2015-Jan-25 13:16:10") + So(err, ShouldBeNil) + err = usage.SetStartEnd(start, end) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(2, 1)) + }) +} + +func TestUsage_SetDuration(t *testing.T) { + Convey("Testing Usage.SetDuration()", t, FailureContinues, func() { + Convey("UsageGranularity=time.Minute", func() { + object := PricingObject{ + UsageGranularity: time.Minute, + } + usage := NewUsage(&object) + + err := usage.SetDuration(time.Minute * 10) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(10, 1)) + + err = usage.SetDuration(time.Minute + time.Second) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(2, 1)) + + err = usage.SetDuration(0 * time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1)) + + err = usage.SetDuration(-1 * time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1)) + + err = usage.SetDuration(10*time.Hour + 5*time.Minute + 10*time.Second) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(60*10+5+1, 1)) + + err = usage.SetDuration(10 * time.Nanosecond) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + }) + + Convey("UsageGranularity=time.Hour", func() { + object := PricingObject{ + UsageGranularity: time.Hour, + } + usage := NewUsage(&object) + + err := usage.SetDuration(time.Minute * 10) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + + err = usage.SetDuration(time.Minute + time.Second) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + + err = usage.SetDuration(0 * time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1)) + + err = usage.SetDuration(-1 * time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1)) + + err = usage.SetDuration(10*time.Hour + 5*time.Minute + 10*time.Second) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(11, 1)) + + err = usage.SetDuration(10 * time.Nanosecond) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + }) + + Convey("UsageGranularity=time.Hour*24", func() { + object := PricingObject{ + UsageGranularity: time.Hour * 24, + } + usage := NewUsage(&object) + + err := usage.SetDuration(time.Minute * 10) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + + err = usage.SetDuration(time.Minute + time.Second) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + + err = usage.SetDuration(0 * time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1)) + + err = usage.SetDuration(-1 * time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(0, 1)) + + err = usage.SetDuration(10*time.Hour + 5*time.Minute + 10*time.Second) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + + err = usage.SetDuration(3*24*time.Hour + 1*time.Minute) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(4, 1)) + + err = usage.SetDuration(10 * time.Nanosecond) + So(err, ShouldBeNil) + So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) + }) + }) +} + func TestUsage_BillableQuantity(t *testing.T) { Convey("Testing Usage.BillableQuantity()", t, FailureContinues, func() { object := &PricingObject{ diff --git a/pkg/pricing/utils_test.go b/pkg/pricing/utils_test.go new file mode 100644 index 0000000000..72d3de9561 --- /dev/null +++ b/pkg/pricing/utils_test.go @@ -0,0 +1,22 @@ +package pricing + +import ( + "fmt" + "math/big" +) + +func ShouldEqualBigRat(actual interface{}, expected ...interface{}) string { + actualRat := actual.(*big.Rat) + expectRat := expected[0].(*big.Rat) + cmp := actualRat.Cmp(expectRat) + if cmp == 0 { + return "" + } + + output := fmt.Sprintf("big.Rat are not matching: %q != %q\n", actualRat, expectRat) + + actualFloat64, _ := actualRat.Float64() + expectFloat64, _ := expectRat.Float64() + output += fmt.Sprintf(" %f != %f", actualFloat64, expectFloat64) + return output +} From 0cc7fe35ddda91cf63e9978e0979534aab74769e Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 13:37:54 +0200 Subject: [PATCH 05/16] Added Basket.SetDuration, testing Basket.Total --- pkg/pricing/basket.go | 17 +++++++++- pkg/pricing/basket_test.go | 68 +++++++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/pkg/pricing/basket.go b/pkg/pricing/basket.go index e6500486ce..73580fd0a1 100644 --- a/pkg/pricing/basket.go +++ b/pkg/pricing/basket.go @@ -1,6 +1,9 @@ package pricing -import "math/big" +import ( + "math/big" + "time" +) type Basket []Usage @@ -17,6 +20,18 @@ func (b *Basket) Length() int { return len(*b) } +func (b *Basket) SetDuration(duration time.Duration) error { + var err error + for i, usage := range *b { + err = usage.SetDuration(duration) + if err != nil { + return err + } + (*b)[i] = usage + } + return nil +} + func (b *Basket) Total() *big.Rat { total := new(big.Rat) for _, usage := range *b { diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 5df698efda..1edf82a5ee 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -3,6 +3,7 @@ package pricing import ( "math/big" "testing" + "time" . "github.com/smartystreets/goconvey/convey" ) @@ -37,28 +38,55 @@ func TestBasket_Add(t *testing.T) { func TestBasket_Total(t *testing.T) { Convey("Testing Basket.Total", t, FailureContinues, func() { - basket := NewBasket() - So(basket, ShouldNotBeNil) - current := ratZero - difference := ratZero - So(basket.Total(), ShouldEqualBigRat, current) + Convey("3 compute instances", func() { + basket := NewBasket() + So(basket, ShouldNotBeNil) + current := ratZero + difference := ratZero + So(basket.Total(), ShouldEqualBigRat, current) - err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1)) - So(err, ShouldBeNil) - difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) - current = new(big.Rat).Add(current, difference) - So(basket.Total(), ShouldEqualBigRat, current) + err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1)) + So(err, ShouldBeNil) + difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) + current = new(big.Rat).Add(current, difference) + So(basket.Total(), ShouldEqualBigRat, current) - err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) - So(err, ShouldBeNil) - difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) - current = new(big.Rat).Add(current, difference) - So(basket.Total(), ShouldEqualBigRat, current) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) + So(err, ShouldBeNil) + difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) + //difference = big.NewRat(72, 100) + current = new(big.Rat).Add(current, difference) + So(basket.Total(), ShouldEqualBigRat, current) - err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600)) - So(err, ShouldBeNil) - difference = big.NewRat(6, 1) - current = new(big.Rat).Add(current, difference) - So(basket.Total(), ShouldEqualBigRat, current) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600)) + So(err, ShouldBeNil) + difference = big.NewRat(6, 1) + current = new(big.Rat).Add(current, difference) + So(basket.Total(), ShouldEqualBigRat, current) + }) + Convey("1 compute instance with 2 volumes and 1 ip", func() { + basket := NewBasket() + + basket.Add(NewUsageByPath("/compute/c1/run")) + basket.Add(NewUsageByPath("/ip/dynamic")) + basket.Add(NewUsageByPath("/storage/local/ssd/storage")) + basket.Add(NewUsageByPath("/storage/local/ssd/storage")) + So(basket.Length(), ShouldEqual, 4) + + basket.SetDuration(1 * time.Minute) + So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(1.36)) + + basket.SetDuration(1 * time.Hour) + So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(1.36)) + + basket.SetDuration(2 * time.Hour) + So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(2.32)) + + basket.SetDuration(2 * 24 * time.Hour) + So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(8.39)) + + basket.SetDuration(30 * 24 * time.Hour) + So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(1.99)) + }) }) } From 41523b5c0c982117ed23c117ffaf8f8fd1b2df95 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 14:07:13 +0200 Subject: [PATCH 06/16] Switched every float64s to big.Rat --- pkg/pricing/basket_test.go | 39 +++++++------------ pkg/pricing/pricing.go | 35 +++++++++-------- pkg/pricing/usage.go | 24 +++++------- pkg/pricing/usage_test.go | 80 +++++++++++++++++++------------------- 4 files changed, 84 insertions(+), 94 deletions(-) diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 1edf82a5ee..75b7006fec 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -22,15 +22,15 @@ func TestBasket_Add(t *testing.T) { So(basket, ShouldNotBeNil) So(basket.Length(), ShouldEqual, 0) - err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1)) + err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(1, 1))) So(err, ShouldBeNil) So(basket.Length(), ShouldEqual, 1) - err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(42, 1))) So(err, ShouldBeNil) So(basket.Length(), ShouldEqual, 2) - err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600)) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(600, 1))) So(err, ShouldBeNil) So(basket.Length(), ShouldEqual, 3) }) @@ -41,28 +41,19 @@ func TestBasket_Total(t *testing.T) { Convey("3 compute instances", func() { basket := NewBasket() So(basket, ShouldNotBeNil) - current := ratZero - difference := ratZero - So(basket.Total(), ShouldEqualBigRat, current) + So(basket.Total(), ShouldEqualBigRat, ratZero) - err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 1)) + err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(1, 1))) So(err, ShouldBeNil) - difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) - current = new(big.Rat).Add(current, difference) - So(basket.Total(), ShouldEqualBigRat, current) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(72, 100)) // 0.72 - err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 42)) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(42, 1))) So(err, ShouldBeNil) - difference = new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012)) - //difference = big.NewRat(72, 100) - current = new(big.Rat).Add(current, difference) - So(basket.Total(), ShouldEqualBigRat, current) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(144, 100)) // 1.44 - err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", 600)) + err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(600, 1))) So(err, ShouldBeNil) - difference = big.NewRat(6, 1) - current = new(big.Rat).Add(current, difference) - So(basket.Total(), ShouldEqualBigRat, current) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(744, 100)) // 7.44 }) Convey("1 compute instance with 2 volumes and 1 ip", func() { basket := NewBasket() @@ -74,19 +65,19 @@ func TestBasket_Total(t *testing.T) { So(basket.Length(), ShouldEqual, 4) basket.SetDuration(1 * time.Minute) - So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(1.36)) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(136, 100)) // 1.36 basket.SetDuration(1 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(1.36)) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(136, 100)) // 1.36 basket.SetDuration(2 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(2.32)) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(232, 100)) // 2.32 basket.SetDuration(2 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(8.39)) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(8399, 1000)) // 8.399 basket.SetDuration(30 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, new(big.Rat).SetFloat64(1.99)) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(11999, 1000)) // 11.999 }) }) } diff --git a/pkg/pricing/pricing.go b/pkg/pricing/pricing.go index 50c9252b04..eb8abf9846 100644 --- a/pkg/pricing/pricing.go +++ b/pkg/pricing/pricing.go @@ -1,15 +1,18 @@ package pricing -import "time" +import ( + "math/big" + "time" +) type PricingObject struct { Path string Identifier string Currency string - UnitPrice float64 - UnitQuantity float64 - UnitPriceCap float64 UsageUnit string + UnitPrice *big.Rat + UnitQuantity *big.Rat + UnitPriceCap *big.Rat UsageGranularity time.Duration } @@ -26,36 +29,36 @@ func init() { Path: "/compute/c1/run", Identifier: "aaaaaaaa-aaaa-4aaa-8aaa-111111111111", Currency: "EUR", - UnitPrice: 0.012, - UnitQuantity: 60, - UnitPriceCap: 6, + UnitPrice: big.NewRat(12, 1000), // 0.012 + UnitQuantity: big.NewRat(60000, 1000), // 60 + UnitPriceCap: big.NewRat(6000, 1000), // 6 UsageGranularity: time.Minute, }, { Path: "/ip/dynamic", Identifier: "467116bf-4631-49fb-905b-e07701c2db11", Currency: "EUR", - UnitPrice: 0.004, - UnitQuantity: 60, - UnitPriceCap: 1.99, + UnitPrice: big.NewRat(4, 1000), // 0.004 + UnitQuantity: big.NewRat(60000, 1000), // 60 + UnitPriceCap: big.NewRat(1999, 1000), // 1.99 UsageGranularity: time.Minute, }, { Path: "/ip/reserved", Identifier: "467116bf-4631-49fb-905b-e07701c2db22", Currency: "EUR", - UnitPrice: 0.004, - UnitQuantity: 60, - UnitPriceCap: 1.99, + UnitPrice: big.NewRat(4, 1000), // 0.004 + UnitQuantity: big.NewRat(60000, 1000), // 60 + UnitPriceCap: big.NewRat(1990, 1000), // 1.99 UsageGranularity: time.Minute, }, { Path: "/storage/local/ssd/storage", Identifier: "bbbbbbbb-bbbb-4bbb-8bbb-111111111113", Currency: "EUR", - UnitPrice: 0.004, - UnitQuantity: 50, - UnitPriceCap: 2, + UnitPrice: big.NewRat(4, 1000), // 0.004 + UnitQuantity: big.NewRat(50000, 1000), // 50 + UnitPriceCap: big.NewRat(2000, 1000), // 2 UsageGranularity: time.Hour, }, } diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index 8304beed2b..96df50be1d 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -11,22 +11,22 @@ type Usage struct { } func NewUsageByPath(objectPath string) Usage { - return NewUsageByPathWithQuantity(objectPath, 0) + return NewUsageByPathWithQuantity(objectPath, ratZero) } -func NewUsageByPathWithQuantity(objectPath string, quantity float64) Usage { +func NewUsageByPathWithQuantity(objectPath string, quantity *big.Rat) Usage { return NewUsageWithQuantity(CurrentPricing.GetByPath(objectPath), quantity) } -func NewUsageWithQuantity(object *PricingObject, quantity float64) Usage { +func NewUsageWithQuantity(object *PricingObject, quantity *big.Rat) Usage { return Usage{ PricingObject: object, - Quantity: new(big.Rat).SetFloat64(quantity), + Quantity: quantity, } } func NewUsage(object *PricingObject) Usage { - return NewUsageWithQuantity(object, 0) + return NewUsageWithQuantity(object, ratZero) } func (u *Usage) SetQuantity(quantity *big.Rat) error { @@ -55,15 +55,14 @@ func (u *Usage) SetStartEnd(start, end time.Time) error { } func (u *Usage) BillableQuantity() *big.Rat { - if u.Quantity.Cmp(big.NewRat(0, 1)) < 1 { + if u.Quantity.Cmp(ratZero) < 1 { return big.NewRat(0, 1) } //return math.Ceil(u.Quantity/u.PricingObject.UnitQuantity) * u.PricingObject.UnitQuantity - unitQuantity := new(big.Rat).SetFloat64(u.PricingObject.UnitQuantity) - quantityQuotient := new(big.Rat).Quo(u.Quantity, unitQuantity) + quantityQuotient := new(big.Rat).Quo(u.Quantity, u.PricingObject.UnitQuantity) ceil := new(big.Rat).SetInt(ratCeil(quantityQuotient)) - return new(big.Rat).Mul(ceil, unitQuantity) + return new(big.Rat).Mul(ceil, u.PricingObject.UnitQuantity) } func (u *Usage) LostQuantity() *big.Rat { @@ -75,9 +74,6 @@ func (u *Usage) LostQuantity() *big.Rat { func (u *Usage) Total() *big.Rat { //return math.Min(u.PricingObject.UnitPrice * u.BillableQuantity(), u.PricingObject.UnitPriceCap) - unitPrice := new(big.Rat).SetFloat64(u.PricingObject.UnitPrice) - total := new(big.Rat).Mul(u.BillableQuantity(), unitPrice) - - unitPriceCap := new(big.Rat).SetFloat64(u.PricingObject.UnitPriceCap) - return ratMin(total, unitPriceCap) + total := new(big.Rat).Mul(u.BillableQuantity(), u.PricingObject.UnitPrice) + return ratMin(total, u.PricingObject.UnitPriceCap) } diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go index 8d8b96d304..0df16eec36 100644 --- a/pkg/pricing/usage_test.go +++ b/pkg/pricing/usage_test.go @@ -10,7 +10,7 @@ import ( func TestNewUsageByPathWithQuantity(t *testing.T) { Convey("Testing NewUsageByPathWithQuantity()", t, func() { - usage := NewUsageByPathWithQuantity("/compute/c1/run", 1) + usage := NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(1, 1)) So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) }) @@ -27,7 +27,7 @@ func TestNewUsageByPath(t *testing.T) { func TestNewUsageWithQuantity(t *testing.T) { Convey("Testing NewUsageWithQuantity()", t, func() { object := CurrentPricing.GetByPath("/compute/c1/run") - usage := NewUsageWithQuantity(object, 1) + usage := NewUsageWithQuantity(object, big.NewRat(1, 1)) So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run") So(usage.Quantity, ShouldEqualBigRat, big.NewRat(1, 1)) }) @@ -154,42 +154,42 @@ func TestUsage_SetDuration(t *testing.T) { func TestUsage_BillableQuantity(t *testing.T) { Convey("Testing Usage.BillableQuantity()", t, FailureContinues, func() { object := &PricingObject{ - UnitQuantity: 60, + UnitQuantity: big.NewRat(60, 1), } - usage := NewUsageWithQuantity(object, -1) + usage := NewUsageWithQuantity(object, big.NewRat(-1, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, -1000) + usage = NewUsageWithQuantity(object, big.NewRat(-1000, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, 0) + usage = NewUsageWithQuantity(object, big.NewRat(0, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, 1) + usage = NewUsageWithQuantity(object, big.NewRat(1, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsageWithQuantity(object, 59) + usage = NewUsageWithQuantity(object, big.NewRat(59, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsageWithQuantity(object, 59.9999) + usage = NewUsageWithQuantity(object, big.NewRat(599999, 10000)) // 59.9999 So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsageWithQuantity(object, 60) + usage = NewUsageWithQuantity(object, big.NewRat(60, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60, 1)) - usage = NewUsageWithQuantity(object, 60.00001) + usage = NewUsageWithQuantity(object, big.NewRat(6000001, 100000)) // 60.00001 So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) - usage = NewUsageWithQuantity(object, 61) + usage = NewUsageWithQuantity(object, big.NewRat(61, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) - usage = NewUsageWithQuantity(object, 119) + usage = NewUsageWithQuantity(object, big.NewRat(119, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*2, 1)) - usage = NewUsageWithQuantity(object, 121) + usage = NewUsageWithQuantity(object, big.NewRat(121, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*3, 1)) - usage = NewUsageWithQuantity(object, 1000) + usage = NewUsageWithQuantity(object, big.NewRat(1000, 1)) So(usage.BillableQuantity(), ShouldEqualBigRat, big.NewRat(60*17, 1)) }) } @@ -197,42 +197,42 @@ func TestUsage_BillableQuantity(t *testing.T) { func TestUsage_LostQuantity(t *testing.T) { Convey("Testing Usage.LostQuantity()", t, FailureContinues, func() { object := &PricingObject{ - UnitQuantity: 60, + UnitQuantity: big.NewRat(60, 1), } - usage := NewUsageWithQuantity(object, -1) + usage := NewUsageWithQuantity(object, big.NewRat(-1, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, -1000) + usage = NewUsageWithQuantity(object, big.NewRat(-1000, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, 0) + usage = NewUsageWithQuantity(object, big.NewRat(0, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, 1) + usage = NewUsageWithQuantity(object, big.NewRat(1, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60-1, 1)) - usage = NewUsageWithQuantity(object, 59) + usage = NewUsageWithQuantity(object, big.NewRat(59, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60-59, 1)) - usage = NewUsageWithQuantity(object, 59.9999) - So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).Sub(big.NewRat(60, 1), new(big.Rat).SetFloat64(59.9999))) + usage = NewUsageWithQuantity(object, big.NewRat(599999, 10000)) // 59.9999 + So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).Sub(big.NewRat(60, 1), big.NewRat(599999, 10000))) - usage = NewUsageWithQuantity(object, 60) + usage = NewUsageWithQuantity(object, big.NewRat(60, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(object, 60.00001) - So(usage.LostQuantity(), ShouldEqualBigRat, new(big.Rat).SetFloat64(60*2-60.00001)) + usage = NewUsageWithQuantity(object, big.NewRat(6000001, 100000)) // 60.00001 + So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(6000000*2-6000001, 100000)) - usage = NewUsageWithQuantity(object, 61) + usage = NewUsageWithQuantity(object, big.NewRat(61, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*2-61, 1)) - usage = NewUsageWithQuantity(object, 119) + usage = NewUsageWithQuantity(object, big.NewRat(119, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*2-119, 1)) - usage = NewUsageWithQuantity(object, 121) + usage = NewUsageWithQuantity(object, big.NewRat(121, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*3-121, 1)) - usage = NewUsageWithQuantity(object, 1000) + usage = NewUsageWithQuantity(object, big.NewRat(1000, 1)) So(usage.LostQuantity(), ShouldEqualBigRat, big.NewRat(60*17-1000, 1)) }) } @@ -240,24 +240,24 @@ func TestUsage_LostQuantity(t *testing.T) { func TestUsage_Total(t *testing.T) { Convey("Testing Usage.Total()", t, FailureContinues, func() { object := PricingObject{ - UnitQuantity: 60, - UnitPrice: 0.012, - UnitPriceCap: 6, + UnitQuantity: big.NewRat(60, 1), + UnitPrice: big.NewRat(12, 1000), // 0.012 + UnitPriceCap: big.NewRat(6, 1), } - usage := NewUsageWithQuantity(&object, -1) + usage := NewUsageWithQuantity(&object, big.NewRat(-1, 1)) So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(&object, 0) + usage = NewUsageWithQuantity(&object, big.NewRat(0, 1)) So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) - usage = NewUsageWithQuantity(&object, 1) - So(usage.Total(), ShouldEqualBigRat, new(big.Rat).Mul(big.NewRat(60, 1), new(big.Rat).SetFloat64(0.012))) + usage = NewUsageWithQuantity(&object, big.NewRat(1, 1)) + So(usage.Total(), ShouldEqualBigRat, big.NewRat(60*12, 1000)) // 60 * 0.012 - usage = NewUsageWithQuantity(&object, 61) - So(usage.Total(), ShouldEqualBigRat, new(big.Rat).Mul(big.NewRat(120, 1), new(big.Rat).SetFloat64(0.012))) + usage = NewUsageWithQuantity(&object, big.NewRat(61, 1)) + So(usage.Total(), ShouldEqualBigRat, big.NewRat(120*12, 1000)) // 120 * 0.012 - usage = NewUsageWithQuantity(&object, 1000) + usage = NewUsageWithQuantity(&object, big.NewRat(1000, 1)) So(usage.Total(), ShouldEqualBigRat, big.NewRat(6, 1)) }) } From 0a520460b3950a76e4ff8d9a71a02bb6d58157dc Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 14:09:19 +0200 Subject: [PATCH 07/16] Added a false-positive test for future development --- pkg/pricing/basket_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 75b7006fec..27bdb903bd 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -78,6 +78,10 @@ func TestBasket_Total(t *testing.T) { basket.SetDuration(30 * 24 * time.Hour) So(basket.Total(), ShouldEqualBigRat, big.NewRat(11999, 1000)) // 11.999 + + // FIXME: this test is false, the capacity is per month + basket.SetDuration(365 * 24 * time.Hour) + So(basket.Total(), ShouldEqualBigRat, big.NewRat(11999, 1000)) // 11.999 }) }) } From 11d3133cdab922ab20fcb9e1f0f5be67b39d6cdc Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 14:13:16 +0200 Subject: [PATCH 08/16] Testing pkg/pricing --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d38469209e..b06e81b947 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ FPM_ARGS ?= \ NAME = scw SRC = cmd/scw -PACKAGES = pkg/api pkg/commands pkg/utils pkg/cli pkg/sshcommand pkg/config pkg/scwversion +PACKAGES = pkg/api pkg/commands pkg/utils pkg/cli pkg/sshcommand pkg/config pkg/scwversion pkg/pricing REV = $(shell git rev-parse HEAD || echo "nogit") TAG = $(shell git describe --tags --always || echo "nogit") BUILDER = scaleway-cli-builder @@ -72,7 +72,7 @@ $(INSTALL_LIST): %_install: $(IREF_LIST): %_iref: pkg/scwversion/version.go $(GOTEST) -i ./$* $(TEST_LIST): %_test: - $(GOTEST) ./$* + $(GOTEST) -v ./$* $(FMT_LIST): %_fmt: $(GOFMT) ./$* From e10ccec28a979ddf5fed352399bc62a8bca72491 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 14:22:04 +0200 Subject: [PATCH 09/16] party -c -t -d=vendor --- pkg/pricing/basket_test.go | 2 +- pkg/pricing/pricing_test.go | 2 +- pkg/pricing/usage_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 27bdb903bd..2e1936220d 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - . "github.com/smartystreets/goconvey/convey" + . "github.com/scaleway/scaleway-cli/vendor/github.com/smartystreets/goconvey/convey" ) func TestNewBasket(t *testing.T) { diff --git a/pkg/pricing/pricing_test.go b/pkg/pricing/pricing_test.go index e4e42cf5ce..7055d7b5b7 100644 --- a/pkg/pricing/pricing_test.go +++ b/pkg/pricing/pricing_test.go @@ -3,7 +3,7 @@ package pricing import ( "testing" - . "github.com/smartystreets/goconvey/convey" + . "github.com/scaleway/scaleway-cli/vendor/github.com/smartystreets/goconvey/convey" ) func TestGetByPath(t *testing.T) { diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go index 0df16eec36..4ad4bdc1b0 100644 --- a/pkg/pricing/usage_test.go +++ b/pkg/pricing/usage_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - . "github.com/smartystreets/goconvey/convey" + . "github.com/scaleway/scaleway-cli/vendor/github.com/smartystreets/goconvey/convey" ) func TestNewUsageByPathWithQuantity(t *testing.T) { From d1e200a670c30f41758ff145d9743af09e0f2acd Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 15:47:31 +0200 Subject: [PATCH 10/16] Added 'scw _billing' placeholder --- pkg/cli/commands.go | 8 +++++--- pkg/cli/test.go | 2 +- pkg/cli/x_billing.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 pkg/cli/x_billing.go diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 44eff4d41c..e291084f46 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -13,12 +13,10 @@ var Commands = []*Command{ cmdAttach, cmdCommit, - cmdCompletion, cmdCp, cmdCreate, cmdEvents, cmdExec, - cmdFlushCache, cmdHistory, cmdImages, cmdInfo, @@ -27,7 +25,6 @@ var Commands = []*Command{ cmdLogin, cmdLogout, cmdLogs, - cmdPatch, cmdPort, cmdPs, cmdRename, @@ -43,4 +40,9 @@ var Commands = []*Command{ cmdUserdata, cmdVersion, cmdWait, + + cmdBilling, + cmdCompletion, + cmdFlushCache, + cmdPatch, } diff --git a/pkg/cli/test.go b/pkg/cli/test.go index 23ad437b0a..771cda71f3 100644 --- a/pkg/cli/test.go +++ b/pkg/cli/test.go @@ -17,7 +17,7 @@ var ( "version", "wait", } secretCommands []string = []string{ - "_patch", "_completion", "_flush-cache", "_userdata", + "_patch", "_completion", "_flush-cache", "_userdata", "_billing", } publicOptions []string = []string{ "-h, --help=false", diff --git a/pkg/cli/x_billing.go b/pkg/cli/x_billing.go new file mode 100644 index 0000000000..252f64201c --- /dev/null +++ b/pkg/cli/x_billing.go @@ -0,0 +1,35 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import "fmt" + +var cmdBilling = &Command{ + Exec: runBilling, + UsageLine: "_billing [OPTIONS]", + Description: "", + Hidden: true, + Help: "Get resources billing estimation", +} + +func init() { + cmdBilling.Flag.BoolVar(&billingHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var billingHelp bool // -h, --help flag + +func runBilling(cmd *Command, args []string) error { + if billingHelp { + return cmd.PrintUsage() + } + if len(args) > 0 { + return cmd.PrintShortUsage() + } + + fmt.Println("BILLING") + + return nil +} From d55c0b6caf85716f8872a7601ff8018b0cad5e1f Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 20:19:39 +0200 Subject: [PATCH 11/16] Printing billing usage for servers --- pkg/cli/x_billing.go | 55 ++++++++++++++++++++++++++++++++++++++++---- pkg/pricing/usage.go | 9 ++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/pkg/cli/x_billing.go b/pkg/cli/x_billing.go index 252f64201c..467b56148a 100644 --- a/pkg/cli/x_billing.go +++ b/pkg/cli/x_billing.go @@ -4,7 +4,16 @@ package cli -import "fmt" +import ( + "fmt" + "text/tabwriter" + "time" + + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/pkg/pricing" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" +) var cmdBilling = &Command{ Exec: runBilling, @@ -16,20 +25,56 @@ var cmdBilling = &Command{ func init() { cmdBilling.Flag.BoolVar(&billingHelp, []string{"h", "-help"}, false, "Print usage") + cmdBilling.Flag.BoolVar(&billingNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") +} + +// BillingArgs are flags for the `RunBilling` function +type BillingArgs struct { + NoTrunc bool } // Flags -var billingHelp bool // -h, --help flag +var billingHelp bool // -h, --help flag +var billingNoTrunc bool // --no-trunc flag -func runBilling(cmd *Command, args []string) error { +func runBilling(cmd *Command, rawArgs []string) error { if billingHelp { return cmd.PrintUsage() } - if len(args) > 0 { + if len(rawArgs) > 0 { return cmd.PrintShortUsage() } - fmt.Println("BILLING") + // cli parsing + args := commands.PsArgs{ + NoTrunc: billingNoTrunc, + } + ctx := cmd.GetContext(rawArgs) + + // table + w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "ID\tNAME\tSTARTED\tMONTH PRICE\n") + + // servers + servers, err := cmd.API.GetServers(true, 0) + if err != nil { + return err + } + for _, server := range *servers { + if server.State != "running" { + continue + } + shortID := utils.TruncIf(server.Identifier, 8, !args.NoTrunc) + shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !args.NoTrunc) + modificationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.ModificationDate) + modificationAgo := time.Now().UTC().Sub(modificationTime) + shortModificationDate := units.HumanDuration(modificationAgo) + usage := pricing.NewUsageByPath("/compute/c1/run") + usage.SetStartEnd(modificationTime, time.Now().UTC()) + + fmt.Fprintf(w, "server/%s\t%s\t%s\t%s\n", shortID, shortName, shortModificationDate, usage.TotalString()) + } return nil } diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index 96df50be1d..1be5e73ac2 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -1,8 +1,11 @@ package pricing import ( + "fmt" "math/big" "time" + + "github.com/dustin/go-humanize" ) type Usage struct { @@ -77,3 +80,9 @@ func (u *Usage) Total() *big.Rat { total := new(big.Rat).Mul(u.BillableQuantity(), u.PricingObject.UnitPrice) return ratMin(total, u.PricingObject.UnitPriceCap) } + +func (u *Usage) TotalString() string { + total := u.Total() + floatVal, _ := total.Float64() + return fmt.Sprintf("%s %s", humanize.Ftoa(floatVal), u.PricingObject.Currency) +} From ea7a572b8065a0c6bebf3d27a273f0296eb55da8 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 20:51:29 +0200 Subject: [PATCH 12/16] Computing total price --- pkg/cli/x_billing.go | 8 ++++++++ pkg/pricing/usage.go | 7 +------ pkg/pricing/utils.go | 12 +++++++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pkg/cli/x_billing.go b/pkg/cli/x_billing.go index 467b56148a..3493afd868 100644 --- a/pkg/cli/x_billing.go +++ b/pkg/cli/x_billing.go @@ -6,6 +6,7 @@ package cli import ( "fmt" + "math/big" "text/tabwriter" "time" @@ -61,6 +62,9 @@ func runBilling(cmd *Command, rawArgs []string) error { if err != nil { return err } + + totalMonthPrice := new(big.Rat) + for _, server := range *servers { if server.State != "running" { continue @@ -73,8 +77,12 @@ func runBilling(cmd *Command, rawArgs []string) error { usage := pricing.NewUsageByPath("/compute/c1/run") usage.SetStartEnd(modificationTime, time.Now().UTC()) + totalMonthPrice = totalMonthPrice.Add(totalMonthPrice, usage.Total()) + fmt.Fprintf(w, "server/%s\t%s\t%s\t%s\n", shortID, shortName, shortModificationDate, usage.TotalString()) } + fmt.Fprintf(w, "TOTAL\t\t\t%s\n", pricing.PriceString(totalMonthPrice, "EUR")) + return nil } diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index 1be5e73ac2..286f98f1cb 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -1,11 +1,8 @@ package pricing import ( - "fmt" "math/big" "time" - - "github.com/dustin/go-humanize" ) type Usage struct { @@ -82,7 +79,5 @@ func (u *Usage) Total() *big.Rat { } func (u *Usage) TotalString() string { - total := u.Total() - floatVal, _ := total.Float64() - return fmt.Sprintf("%s %s", humanize.Ftoa(floatVal), u.PricingObject.Currency) + return PriceString(u.Total(), u.PricingObject.Currency) } diff --git a/pkg/pricing/utils.go b/pkg/pricing/utils.go index 4a4c6bc84b..4eb5b00c61 100644 --- a/pkg/pricing/utils.go +++ b/pkg/pricing/utils.go @@ -1,6 +1,11 @@ package pricing -import "math/big" +import ( + "fmt" + "math/big" + + "github.com/scaleway/scaleway-cli/vendor/github.com/dustin/go-humanize" +) var ( intZero = big.NewInt(0) @@ -35,3 +40,8 @@ func ratMin(x, y *big.Rat) *big.Rat { } return x } + +func PriceString(price *big.Rat, currency string) string { + floatVal, _ := price.Float64() + return fmt.Sprintf("%s %s", humanize.Ftoa(floatVal), currency) +} From b22e531ec99076525f9d0301269ff539fa56f2d8 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 20:41:33 +0200 Subject: [PATCH 13/16] Updated pricing --- pkg/pricing/basket_test.go | 18 +++++++++--------- pkg/pricing/pricing.go | 24 ++++++++++++------------ pkg/pricing/pricing_test.go | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index 2e1936220d..bc59cd1a94 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -45,15 +45,15 @@ func TestBasket_Total(t *testing.T) { err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(1, 1))) So(err, ShouldBeNil) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(72, 100)) // 0.72 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(12, 100)) // 0.12 err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(42, 1))) So(err, ShouldBeNil) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(144, 100)) // 1.44 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(24, 100)) // 0.24 err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(600, 1))) So(err, ShouldBeNil) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(744, 100)) // 7.44 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(124, 100)) // 1.24 }) Convey("1 compute instance with 2 volumes and 1 ip", func() { basket := NewBasket() @@ -65,23 +65,23 @@ func TestBasket_Total(t *testing.T) { So(basket.Length(), ShouldEqual, 4) basket.SetDuration(1 * time.Minute) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(136, 100)) // 1.36 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(44, 100)) // 0.44 basket.SetDuration(1 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(136, 100)) // 1.36 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(44, 100)) // 0.44 basket.SetDuration(2 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(232, 100)) // 2.32 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(68, 100)) // 0.68 basket.SetDuration(2 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(8399, 1000)) // 8.399 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(219, 100)) // 2.19 basket.SetDuration(30 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(11999, 1000)) // 11.999 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(399, 100)) // 3.99 // FIXME: this test is false, the capacity is per month basket.SetDuration(365 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(11999, 1000)) // 11.999 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(399, 100)) // 3.99 }) }) } diff --git a/pkg/pricing/pricing.go b/pkg/pricing/pricing.go index eb8abf9846..f8b55a9c8a 100644 --- a/pkg/pricing/pricing.go +++ b/pkg/pricing/pricing.go @@ -27,38 +27,38 @@ func init() { CurrentPricing = PricingList{ { Path: "/compute/c1/run", - Identifier: "aaaaaaaa-aaaa-4aaa-8aaa-111111111111", + Identifier: "aaaaaaaa-aaaa-4aaa-8aaa-111111111112", Currency: "EUR", - UnitPrice: big.NewRat(12, 1000), // 0.012 + UnitPrice: big.NewRat(2, 1000), // 0.002 UnitQuantity: big.NewRat(60000, 1000), // 60 - UnitPriceCap: big.NewRat(6000, 1000), // 6 + UnitPriceCap: big.NewRat(1000, 1000), // 1 UsageGranularity: time.Minute, }, { Path: "/ip/dynamic", - Identifier: "467116bf-4631-49fb-905b-e07701c2db11", + Identifier: "467116bf-4631-49fb-905b-e07701c21111", Currency: "EUR", - UnitPrice: big.NewRat(4, 1000), // 0.004 + UnitPrice: big.NewRat(2, 1000), // 0.002 UnitQuantity: big.NewRat(60000, 1000), // 60 - UnitPriceCap: big.NewRat(1999, 1000), // 1.99 + UnitPriceCap: big.NewRat(990, 1000), // 0.99 UsageGranularity: time.Minute, }, { Path: "/ip/reserved", - Identifier: "467116bf-4631-49fb-905b-e07701c2db22", + Identifier: "467116bf-4631-49fb-905b-e07701c22222", Currency: "EUR", - UnitPrice: big.NewRat(4, 1000), // 0.004 + UnitPrice: big.NewRat(2, 1000), // 0.002 UnitQuantity: big.NewRat(60000, 1000), // 60 - UnitPriceCap: big.NewRat(1990, 1000), // 1.99 + UnitPriceCap: big.NewRat(990, 1000), // 0.99 UsageGranularity: time.Minute, }, { Path: "/storage/local/ssd/storage", - Identifier: "bbbbbbbb-bbbb-4bbb-8bbb-111111111113", + Identifier: "bbbbbbbb-bbbb-4bbb-8bbb-111111111144", Currency: "EUR", - UnitPrice: big.NewRat(4, 1000), // 0.004 + UnitPrice: big.NewRat(2, 1000), // 0.002 UnitQuantity: big.NewRat(50000, 1000), // 50 - UnitPriceCap: big.NewRat(2000, 1000), // 2 + UnitPriceCap: big.NewRat(1000, 1000), // 1 UsageGranularity: time.Hour, }, } diff --git a/pkg/pricing/pricing_test.go b/pkg/pricing/pricing_test.go index 7055d7b5b7..6d46a87541 100644 --- a/pkg/pricing/pricing_test.go +++ b/pkg/pricing/pricing_test.go @@ -23,7 +23,7 @@ func TestGetByPath(t *testing.T) { func TestGetByIdentifier(t *testing.T) { Convey("Testing GetByIdentifier", t, func() { - object := CurrentPricing.GetByIdentifier("aaaaaaaa-aaaa-4aaa-8aaa-111111111111") + object := CurrentPricing.GetByIdentifier("aaaaaaaa-aaaa-4aaa-8aaa-111111111112") So(object, ShouldNotBeNil) So(object.Path, ShouldEqual, "/compute/c1/run") From a224b80e8929e4afb9906ce30a5d6df22b7dea3b Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Tue, 1 Sep 2015 23:27:06 +0200 Subject: [PATCH 14/16] Dividing total by PricingObject.UnitQuantity --- pkg/pricing/basket_test.go | 18 +++++++++--------- pkg/pricing/usage.go | 1 + pkg/pricing/usage_test.go | 6 +++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pkg/pricing/basket_test.go b/pkg/pricing/basket_test.go index bc59cd1a94..ea592aa15e 100644 --- a/pkg/pricing/basket_test.go +++ b/pkg/pricing/basket_test.go @@ -45,15 +45,15 @@ func TestBasket_Total(t *testing.T) { err := basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(1, 1))) So(err, ShouldBeNil) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(12, 100)) // 0.12 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(2, 1000)) // 0.002 err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(42, 1))) So(err, ShouldBeNil) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(24, 100)) // 0.24 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(4, 1000)) // 0.004 err = basket.Add(NewUsageByPathWithQuantity("/compute/c1/run", big.NewRat(600, 1))) So(err, ShouldBeNil) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(124, 100)) // 1.24 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(24, 1000)) // 0.024 }) Convey("1 compute instance with 2 volumes and 1 ip", func() { basket := NewBasket() @@ -65,23 +65,23 @@ func TestBasket_Total(t *testing.T) { So(basket.Length(), ShouldEqual, 4) basket.SetDuration(1 * time.Minute) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(44, 100)) // 0.44 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(8, 1000)) // 0.008 basket.SetDuration(1 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(44, 100)) // 0.44 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(8, 1000)) // 0.008 basket.SetDuration(2 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(68, 100)) // 0.68 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(12, 1000)) // 0.012 basket.SetDuration(2 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(219, 100)) // 2.19 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(196, 1000)) // 0.196 basket.SetDuration(30 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(399, 100)) // 3.99 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(2050, 1000)) // 2.05 // FIXME: this test is false, the capacity is per month basket.SetDuration(365 * 24 * time.Hour) - So(basket.Total(), ShouldEqualBigRat, big.NewRat(399, 100)) // 3.99 + So(basket.Total(), ShouldEqualBigRat, big.NewRat(2694, 1000)) // 2.694 }) }) } diff --git a/pkg/pricing/usage.go b/pkg/pricing/usage.go index 286f98f1cb..fe82ae0259 100644 --- a/pkg/pricing/usage.go +++ b/pkg/pricing/usage.go @@ -75,6 +75,7 @@ func (u *Usage) Total() *big.Rat { //return math.Min(u.PricingObject.UnitPrice * u.BillableQuantity(), u.PricingObject.UnitPriceCap) total := new(big.Rat).Mul(u.BillableQuantity(), u.PricingObject.UnitPrice) + total = total.Quo(total, u.PricingObject.UnitQuantity) return ratMin(total, u.PricingObject.UnitPriceCap) } diff --git a/pkg/pricing/usage_test.go b/pkg/pricing/usage_test.go index 4ad4bdc1b0..d3e3320643 100644 --- a/pkg/pricing/usage_test.go +++ b/pkg/pricing/usage_test.go @@ -252,12 +252,12 @@ func TestUsage_Total(t *testing.T) { So(usage.Total(), ShouldEqualBigRat, big.NewRat(0, 1)) usage = NewUsageWithQuantity(&object, big.NewRat(1, 1)) - So(usage.Total(), ShouldEqualBigRat, big.NewRat(60*12, 1000)) // 60 * 0.012 + So(usage.Total(), ShouldEqualBigRat, big.NewRat(12, 1000)) // 0.012 usage = NewUsageWithQuantity(&object, big.NewRat(61, 1)) - So(usage.Total(), ShouldEqualBigRat, big.NewRat(120*12, 1000)) // 120 * 0.012 + So(usage.Total(), ShouldEqualBigRat, big.NewRat(24, 1000)) // 0.024 usage = NewUsageWithQuantity(&object, big.NewRat(1000, 1)) - So(usage.Total(), ShouldEqualBigRat, big.NewRat(6, 1)) + So(usage.Total(), ShouldEqualBigRat, big.NewRat(204, 1000)) // 0.204 }) } From 45b7b9920425ee5cc4a73b96c55affcdd727b9cf Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Sat, 5 Sep 2015 19:54:49 +0200 Subject: [PATCH 15/16] Updated README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 23155c2ead..c1bd8a2ec8 100644 --- a/README.md +++ b/README.md @@ -1130,10 +1130,11 @@ $ scw inspect myserver | jq '.[0].public_ip.address' #### Features -* `scw info` now prints user/organization info from the API ([#142](https://github.com/scaleway/scaleway-cli/issues/130) +* `scw info` now prints user/organization info from the API ([#130](https://github.com/scaleway/scaleway-cli/issues/130) * Added helpers to manipulate new `user_data` API ([#150](https://github.com/scaleway/scaleway-cli/issues/150)) * Support of `scw rm -f/--force` option ([#158](https://github.com/scaleway/scaleway-cli/issues/158)) * Added `scw _userdata local ...` option which interacts with the Metadata API without authentication ([#166](https://github.com/scaleway/scaleway-cli/issues/166)) +* Initial version of `scw _billing` (price estimation tool) ([#118](https://github.com/scaleway/scaleway-cli/issues/118) #### Fixes From c1651df899410bdf583746b5350a551eb1f6dea3 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Sat, 5 Sep 2015 19:55:13 +0200 Subject: [PATCH 16/16] Added a warning message when running 'scw _billing' --- pkg/cli/x_billing.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/cli/x_billing.go b/pkg/cli/x_billing.go index 3493afd868..9769f6fa9c 100644 --- a/pkg/cli/x_billing.go +++ b/pkg/cli/x_billing.go @@ -13,6 +13,7 @@ import ( "github.com/scaleway/scaleway-cli/pkg/commands" "github.com/scaleway/scaleway-cli/pkg/pricing" "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" ) @@ -52,6 +53,11 @@ func runBilling(cmd *Command, rawArgs []string) error { } ctx := cmd.GetContext(rawArgs) + logrus.Warn("") + logrus.Warn("Warning: 'scw _billing' is a work-in-progress price estimation tool") + logrus.Warn("For real usage, visit https://cloud.scaleway.com/#/billing") + logrus.Warn("") + // table w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush()