diff --git a/timespans/actions.go b/timespans/actions.go index e446af2cc..4dd1a54df 100644 --- a/timespans/actions.go +++ b/timespans/actions.go @@ -76,6 +76,7 @@ var ( "TOPUP_RESET": topupResetAction, "TOPUP": topupAction, "DEBIT": debitAction, + "RESET_COUNTERS": resetCountersAction, } ) @@ -130,6 +131,11 @@ func debitAction(ub *UserBalance, a *Action) (err error) { return } +func resetCountersAction(ub *UserBalance, a *Action) (err error) { + //ub.UnitsCounters + return +} + type ActionTrigger struct { BalanceId string ThresholdValue float64 diff --git a/timespans/calldesc_test.go b/timespans/calldesc_test.go index 854f39f4d..d6d72f9e5 100644 --- a/timespans/calldesc_test.go +++ b/timespans/calldesc_test.go @@ -24,13 +24,25 @@ import ( //"log" ) -var ( - getter StorageGetter -) - func init() { - getter, _ = NewRedisStorage("tcp:127.0.0.1:6379", 10) - SetStorageGetter(getter) + storageGetter, _ = NewRedisStorage("tcp:127.0.0.1:6379", 10) + SetStorageGetter(storageGetter) + populateDB() +} + +func populateDB() { + ub := &UserBalance{ + Id: "OUT:vdf:minu", + Type: UB_TYPE_PREPAID, + /*BalanceMap: map[string]float64{ + CREDIT: 21, + },*/ + MinuteBuckets: []*MinuteBucket{ + &MinuteBucket{Seconds: 200, DestinationId: "NAT", Weight: 10}, + &MinuteBucket{Seconds: 100, DestinationId: "RET", Weight: 20}, + }, + } + storageGetter.SetUserBalance(ub) } func TestSplitSpans(t *testing.T) { @@ -162,9 +174,9 @@ func TestMaxSessionTimeNoUserBalance(t *testing.T) { } func TestMaxSessionTimeWithUserBalance(t *testing.T) { - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "minitsboy", Destination: "0723", Amount: 5400} + cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "minu", Destination: "0723", Amount: 5400} result, err := cd.GetMaxSessionTime() - expected := 200.0 + expected := 300.0 if result != expected || err != nil { t.Errorf("Expected %v was %v", expected, result) } @@ -179,8 +191,8 @@ func TestMaxSessionTimeNoCredit(t *testing.T) { } /*func TestGetCostWithVolumeDiscount(t *testing.T) { - getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) - defer getter.Close() + storageGetter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer storageGetter.Close() vd1 := &VolumeDiscount{100, 10} vd2 := &VolumeDiscount{500, 20} seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd1, vd2}} @@ -219,7 +231,7 @@ func BenchmarkRedisGetting(b *testing.B) { cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { - getter.GetActivationPeriodsOrFallback(cd.GetKey()) + storageGetter.GetActivationPeriodsOrFallback(cd.GetKey()) } } diff --git a/timespans/destinations.go b/timespans/destinations.go index 8e9b54369..81d35434e 100644 --- a/timespans/destinations.go +++ b/timespans/destinations.go @@ -32,6 +32,17 @@ var ( DestinationCacheMap = make(destinationCacheMap) ) +func GetDestination(dId string) (d *Destination, err error) { + d, exists := DestinationCacheMap[dId] + if !exists { + d, err = storageGetter.GetDestination(dId) + if err == nil && d != nil { + DestinationCacheMap[dId] = d + } + } + return +} + /* De-serializes the destination for the storage. Used for key-value storages. */ diff --git a/timespans/destinations_test.go b/timespans/destinations_test.go index 7913d1bc0..345fe8a1d 100644 --- a/timespans/destinations_test.go +++ b/timespans/destinations_test.go @@ -58,6 +58,34 @@ func TestDestinationContainsPrefix(t *testing.T) { } +func TestDestinationGetExists(t *testing.T) { + d, err := GetDestination("NAT") + if err != nil || d == nil { + t.Error("Could not get destination: ", d) + } +} + +func TestDestinationGetExistsCache(t *testing.T) { + GetDestination("NAT") + if _, exists := DestinationCacheMap["NAT"]; !exists { + t.Error("Destination not cached!") + } +} + +func TestDestinationGetNotExists(t *testing.T) { + d, err := GetDestination("not existing") + if d != nil { + t.Error("Got false destination: ", err) + } +} + +func TestDestinationGetNotExistsCache(t *testing.T) { + GetDestination("not existing") + if _, exists := DestinationCacheMap["not existing"]; exists { + t.Error("Bad destination cached") + } +} + /********************************* Benchmarks **********************************/ func BenchmarkDestinationRedisStoreRestore(b *testing.B) { diff --git a/timespans/minute_buckets.go b/timespans/minute_buckets.go index 04138188e..6944ec360 100644 --- a/timespans/minute_buckets.go +++ b/timespans/minute_buckets.go @@ -20,6 +20,7 @@ package timespans import ( // "log" + "sort" "math" ) @@ -39,3 +40,32 @@ func (mb *MinuteBucket) GetSecondsForCredit(credit float64) (seconds float64) { } return } + +/* +Structure to store minute buckets according to weight, precision or price. +*/ +type bucketsorter []*MinuteBucket + +func (bs bucketsorter) Len() int { + return len(bs) +} + +func (bs bucketsorter) Swap(i, j int) { + bs[i], bs[j] = bs[j], bs[i] +} + +func (bs bucketsorter) Less(j, i int) bool { + return bs[i].Weight < bs[j].Weight || + bs[i].precision < bs[j].precision || + bs[i].Price > bs[j].Price +} + +func (bs bucketsorter) Sort() { + sort.Sort(bs) +} + +func (ub *UserBalance) ResetActionTriggers() { + for _, at := range ub.ActionTriggers { + at.executed = false + } +} diff --git a/timespans/minute_buckets_test.go b/timespans/minute_buckets_test.go new file mode 100644 index 000000000..bc8b38850 --- /dev/null +++ b/timespans/minute_buckets_test.go @@ -0,0 +1,56 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2012 Radu Ioan Fericean + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package timespans + +import ( + "testing" +) + +func TestMinutBucketSortWeight(t *testing.T) { + mb1 := &MinuteBucket{Weight: 1, precision: 2, Price: 2} + mb2 := &MinuteBucket{Weight: 2, precision: 1, Price: 1} + var bs bucketsorter + bs = append(bs, mb2, mb1) + bs.Sort() + if bs[0] != mb1 || bs[1] != mb2 { + t.Error("Buckets not sorted by weight!") + } +} + +func TestMinutBucketSortPrecision(t *testing.T) { + mb1 := &MinuteBucket{Weight: 1, precision: 2, Price: 2} + mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1} + var bs bucketsorter + bs = append(bs, mb2, mb1) + bs.Sort() + if bs[0] != mb1 || bs[1] != mb2 { + t.Error("Buckets not sorted by precision!") + } +} + +func TestMinutBucketSortPrice(t *testing.T) { + mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1} + mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 2} + var bs bucketsorter + bs = append(bs, mb2, mb1) + bs.Sort() + if bs[0] != mb1 || bs[1] != mb2 { + t.Error("Buckets not sorted by price!") + } +} diff --git a/timespans/userbalance.go b/timespans/userbalance.go index b49595dc0..cac57fd0a 100644 --- a/timespans/userbalance.go +++ b/timespans/userbalance.go @@ -65,31 +65,6 @@ func (a AmountTooBig) Error() string { return "Amount excedes balance!" } -/* -Structure to store minute buckets according to weight, precision or price. -*/ -type bucketsorter []*MinuteBucket - -func (bs bucketsorter) Len() int { - return len(bs) -} - -func (bs bucketsorter) Swap(i, j int) { - bs[i], bs[j] = bs[j], bs[i] -} - -func (bs bucketsorter) Less(j, i int) bool { - return bs[i].Weight < bs[j].Weight || - bs[i].precision < bs[j].precision || - bs[i].Price > bs[j].Price -} - -func (ub *UserBalance) ResetActionTriggers() { - for _, at := range ub.ActionTriggers { - at.executed = false - } -} - /* Returns user's available minutes for the specified destination */ @@ -99,8 +74,8 @@ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds float64, buck return } for _, mb := range ub.MinuteBuckets { - d, exists := DestinationCacheMap[mb.DestinationId] - if !exists { + d, err := GetDestination(mb.DestinationId) + if err != nil { continue } contains, precision := d.containsPrefix(prefix) @@ -207,7 +182,7 @@ func (ub *UserBalance) addMinuteBucket(newMb *MinuteBucket) { } } -func (ub *UserBalance) ExecuteActionTriggers() { +func (ub *UserBalance) executeActionTriggers() { ub.ActionTriggers.Sort() for _, at := range ub.ActionTriggers { if at.executed { diff --git a/timespans/userbalance_test.go b/timespans/userbalance_test.go index ded3954fc..68c6c3529 100644 --- a/timespans/userbalance_test.go +++ b/timespans/userbalance_test.go @@ -30,16 +30,16 @@ var ( ) func init() { - getter, _ = NewRedisStorage("tcp:127.0.0.1:6379", 10) - SetStorageGetter(getter) + storageGetter, _ = NewRedisStorage("tcp:127.0.0.1:6379", 10) + SetStorageGetter(storageGetter) } func TestUserBalanceStoreRestore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT: 21}} - getter.SetUserBalance(rifsBalance) - ub1, err := getter.GetUserBalance("other") + storageGetter.SetUserBalance(rifsBalance) + ub1, err := storageGetter.GetUserBalance("other") if err != nil || ub1.BalanceMap[CREDIT] != rifsBalance.BalanceMap[CREDIT] { t.Errorf("Expected %v was %v", rifsBalance.BalanceMap[CREDIT], ub1.BalanceMap[CREDIT]) } @@ -72,8 +72,8 @@ func TestUserBalanceRedisStore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT: 21}} - getter.SetUserBalance(rifsBalance) - result, _ := getter.GetUserBalance(rifsBalance.Id) + storageGetter.SetUserBalance(rifsBalance) + result, _ := storageGetter.GetUserBalance(rifsBalance.Id) if !reflect.DeepEqual(rifsBalance, result) { t.Errorf("Expected %v was %v", rifsBalance, result) } @@ -261,7 +261,7 @@ func TestDebitNegativeSMSBalance(t *testing.T) { b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT: 21}} rifsBalance.MinuteBuckets[0].Seconds, rifsBalance.MinuteBuckets[1].Seconds = 0.0, 0.0 - err := rifsBalance.resetUserBalance(getter) + err := rifsBalance.resetUserBalance(storageGetter) if err != nil || rifsBalance.MinuteBuckets[0] == b1 || rifsBalance.BalanceMap[SMS] != seara.SmsCredit { @@ -307,7 +307,7 @@ func TestGetVolumeDiscountSteps(t *testing.T) { func TestRecivedCallsBonus(t *testing.T) { _ := NewKyotoStorage("../data/test.kch") - defer getter.Close() + defer storageGetter.Close() rcb := &RecivedCallBonus{Credit: 100} seara := &TariffPlan{Id: "seara_voo", SmsCredit: 100, ReceivedCallSecondsLimit: 10, RecivedCallBonus: rcb} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]float64{CREDIT: 21}, tariffPlan: seara, ReceivedCallSeconds: 1} @@ -355,8 +355,8 @@ func BenchmarkUserBalanceRedisStoreRestore(b *testing.B) { b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT: 21}} for i := 0; i < b.N; i++ { - getter.SetUserBalance(rifsBalance) - getter.GetUserBalance(rifsBalance.Id) + storageGetter.SetUserBalance(rifsBalance) + storageGetter.GetUserBalance(rifsBalance.Id) } }