From 800f9555daff9a83c0ecb71013b25c99b07b6d16 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 15:09:20 +0200 Subject: [PATCH 1/9] CdrStats in tutorial_local_test --- general_tests/tutorial_local_test.go | 63 ++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index ac4618da3..a06d0402a 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -28,7 +28,7 @@ import ( "testing" "time" - //"github.com/cgrates/cgrates/apier/v1" + "github.com/cgrates/cgrates/apier/v1" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" @@ -326,7 +326,7 @@ func TestTutLocalMaxDebit(t *testing.T) { // Make sure queueids were created func TestTutFsCallsCdrStats(t *testing.T) { - if !*testCalls { + if !*testLocal { return } var queueIds []string @@ -707,15 +707,72 @@ func TestTutLocalLeastCost(t *testing.T) { &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second}, }, } + eStLcr2 := &engine.LCRCost{ + Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_LOWEST, StrategyParams: "", Weight: 10.0}, + SupplierCosts: []*engine.LCRSupplierCost{ + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second}, + &engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second}, + }, + } if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil { t.Error(err) } else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) { t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry) - } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) { + } else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0]) } } +// Make sure all stats queues were updated +func TestTutLocalCdrStatsAfter(t *testing.T) { + if !*testLocal { + return + } + var statMetrics map[string]float64 + eMetrics := map[string]float64{engine.ACC: 0.3452380952, engine.ACD: 111.4761904762, engine.ASR: 100} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: utils.META_DEFAULT}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACD: 90.2, engine.ASR: 100, engine.TCC: 1.675, engine.TCD: 451, engine.ACC: 0.335} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST1"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.ACC: 0.35, engine.ACD: 120, engine.ASR: 100, engine.TCC: 1.675, engine.TCD: 451} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1001"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.TCD: 451, engine.ACC: 0.325, engine.ACD: 90, engine.ASR: 100, engine.TCC: 1.675} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1002"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.TCC: 1.675, engine.TCD: 451, engine.ACC: 0.325, engine.ACD: 90, engine.ASR: 100} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1003"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.TCC: 0.7, engine.TCD: 240, engine.ACC: 0.35, engine.ACD: 120, engine.ASR: 100} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL1"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } + eMetrics = map[string]float64{engine.TCD: 331, engine.ACC: 0.33125, engine.ACD: 82.75, engine.ASR: 100, engine.TCC: 1.325} + if err := tutLocalRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL2"}, &statMetrics); err != nil { + t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error()) + } else if !reflect.DeepEqual(eMetrics, statMetrics) { + t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics) + } +} + func TestTutLocalStopCgrEngine(t *testing.T) { if !*testLocal { return From 23c5313d6c476e0be967663dcf024b512b297db4 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 15:35:38 +0200 Subject: [PATCH 2/9] Adding tutlocal sample config --- data/conf/samples/tutlocal/cgrates.json | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 data/conf/samples/tutlocal/cgrates.json diff --git a/data/conf/samples/tutlocal/cgrates.json b/data/conf/samples/tutlocal/cgrates.json new file mode 100644 index 000000000..a2d71d5e5 --- /dev/null +++ b/data/conf/samples/tutlocal/cgrates.json @@ -0,0 +1,32 @@ +{ +// CGRateS Configuration file +// +// Used for cgradmin +// Starts rater, scheduler + +"listen": { + "rpc_json": ":2012", // RPC JSON listening address + "rpc_gob": ":2013", // RPC GOB listening address + "http": ":2080", // HTTP listening address +}, + +"rater": { + "enabled": true, // enable Rater service: + "cdrstats": "internal", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234> +}, + +"scheduler": { + "enabled": true, // start Scheduler service: +}, + +"cdrs": { + "enabled": true, // start the CDR Server service: + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> + "cdrstats": "internal", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234> +}, + +"cdr_stats": { + "enabled": true, // starts the cdrstats service: +}, + +} From 77a9709b276b68300d3c5f189a92a9634d7e458d Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 16:23:11 +0200 Subject: [PATCH 3/9] Fix loader to accept *disconnect as strategy in MaxCallCost, adding MaxCallCost in tutorial TP --- apier/v1/apier_local_test.go | 2 +- data/tariffplans/tutorial/DestinationRates.csv | 2 ++ data/tariffplans/tutorial/Destinations.csv | 1 + data/tariffplans/tutorial/RatingPlans.csv | 2 ++ engine/loader_helpers.go | 2 +- engine/loader_helpers_test.go | 4 +++- general_tests/tutorial_local_test.go | 2 +- 7 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 1520e9a1b..6361ade72 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -1745,7 +1745,7 @@ func TestApierGetCacheStats3(t *testing.T) { return } var rcvStats *utils.CacheStats - expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1} + expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1} var args utils.AttrCacheStats if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil { t.Error("Got error on ApierV1.GetCacheStats: ", err.Error()) diff --git a/data/tariffplans/tutorial/DestinationRates.csv b/data/tariffplans/tutorial/DestinationRates.csv index 8a4e10c34..62bd4b736 100644 --- a/data/tariffplans/tutorial/DestinationRates.csv +++ b/data/tariffplans/tutorial/DestinationRates.csv @@ -6,3 +6,5 @@ DR_1003_10CNT,DST_1003,RT_10CNT,*up,4,0, DR_FS_40CNT,DST_FS,RT_40CNT,*up,4,0, DR_FS_10CNT,DST_FS,RT_10CNT,*up,4,0, DR_SPECIAL_1002,DST_1002,RT_1CNT,*up,4,0, +DR_1007_MAXCOST_DISC,DST_1007,RT_1CNT,*up,4,0.62,*disconnect +DR_1007_MAXCOST_FREE,DST_1007,RT_1CNT,*up,4,0.62,*free diff --git a/data/tariffplans/tutorial/Destinations.csv b/data/tariffplans/tutorial/Destinations.csv index 1c19d97ea..5e01a2632 100644 --- a/data/tariffplans/tutorial/Destinations.csv +++ b/data/tariffplans/tutorial/Destinations.csv @@ -1,4 +1,5 @@ #Tag,Prefix DST_1002,1002 DST_1003,1003 +DST_1007,1007 DST_FS,10 diff --git a/data/tariffplans/tutorial/RatingPlans.csv b/data/tariffplans/tutorial/RatingPlans.csv index 7eb4dc71d..8b58cb47f 100644 --- a/data/tariffplans/tutorial/RatingPlans.csv +++ b/data/tariffplans/tutorial/RatingPlans.csv @@ -3,6 +3,7 @@ RP_RETAIL1,DR_FS_40CNT,PEAK,10 RP_RETAIL1,DR_FS_10CNT,OFFPEAK_MORNING,10 RP_RETAIL1,DR_FS_10CNT,OFFPEAK_EVENING,10 RP_RETAIL1,DR_FS_10CNT,OFFPEAK_WEEKEND,10 +RP_RETAIL1,DR_1007_MAXCOST_DISC,ALWAYS,10 RP_RETAIL2,DR_1002_20CNT,PEAK,10 RP_RETAIL2,DR_1003_20CNT,PEAK,10 RP_RETAIL2,DR_FS_40CNT,PEAK,10 @@ -15,4 +16,5 @@ RP_RETAIL2,DR_1003_10CNT,OFFPEAK_WEEKEND,10 RP_RETAIL2,DR_FS_10CNT,OFFPEAK_MORNING,10 RP_RETAIL2,DR_FS_10CNT,OFFPEAK_EVENING,10 RP_RETAIL2,DR_FS_10CNT,OFFPEAK_WEEKEND,10 +RP_RETAIL2,DR_1007_MAXCOST_FREE,ALWAYS,10 RP_SPECIAL_1002,DR_SPECIAL_1002,ALWAYS,10 diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index f31d5ef41..ce50dc19e 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -763,7 +763,7 @@ var FileValidators = map[string]*FileLineRegexValidator{ regexp.MustCompile(`(?:\w+\s*),(?:\d+\.*\d*s*),(?:\d+\.*\d*s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*)$`), "Tag([0-9A-Za-z_]),ConnectFee([0-9.]),Rate([0-9.]),RateUnit([0-9.]ns|us|µs|ms|s|m|h),RateIncrementStart([0-9.]ns|us|µs|ms|s|m|h),GroupIntervalStart([0-9.]ns|us|µs|ms|s|m|h)"}, utils.DESTINATION_RATES_CSV: &FileLineRegexValidator{utils.DESTINATION_RATES_NRCOLS, - regexp.MustCompile(`^(?:\w+\s*),(?:\w+\s*|\*any),(?:\w+\s*),(?:\*up|\*down|\*middle),(?:\d+),(?:\d+\.*\d*s*)?,(?:\*free|\*diconnect)?$`), + regexp.MustCompile(`^(?:\w+\s*),(?:\w+\s*|\*any),(?:\w+\s*),(?:\*up|\*down|\*middle),(?:\d+),(?:\d+\.*\d*s*)?,(?:\*free|\*disconnect)?$`), "Tag([0-9A-Za-z_]),DestinationsTag([0-9A-Za-z_]|*any),RatesTag([0-9A-Za-z_]),RoundingMethod(*up|*middle|*down),RoundingDecimals([0-9.])"}, utils.RATING_PLANS_CSV: &FileLineRegexValidator{utils.DESTRATE_TIMINGS_NRCOLS, regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+.?\d*){1}$`), diff --git a/engine/loader_helpers_test.go b/engine/loader_helpers_test.go index 9d2ceefa5..e84db4f2e 100644 --- a/engine/loader_helpers_test.go +++ b/engine/loader_helpers_test.go @@ -50,6 +50,8 @@ DR_RETAIL,GERMANY,RT_1CENT,*up,0,0, DUMMY,INVALID;DATA DR_DATA_1,*any,RT_DATA_2c,*up,2,0, _TNT_1211_01_V_ANY,CST_1246534_BRB02,C_TNT_1211_01_V_1246534_BRB02_ANY,*up,2,, +DR_1007_MAXCOST_DISC,DST_1007,RT_1CNT,*up,4,0.62,*disconnect +DR_1007_MAXCOST_FREE,DST_1007,RT_1CNT,*up,4,0.62,*free ` var ratingPlansSample = `#Tag,DestinationRatesTag,TimingTag,Weight RP_RETAIL,DR_RETAIL,ALWAYS,10 @@ -199,7 +201,7 @@ func TestDestRatesValidator(t *testing.T) { if valid { t.Error("Validation passed for invalid line", string(ln)) } - case 2, 4, 5: + case 2, 4, 5, 6, 7: if !valid { t.Error("Validation did not pass for valid line", string(ln)) } diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index a06d0402a..9e02464ce 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -116,7 +116,7 @@ func TestTutLocalCacheStats(t *testing.T) { return } var rcvStats *utils.CacheStats - expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, + expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1, LcrProfiles: 4} var args utils.AttrCacheStats if err := tutLocalRpc.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil { From cf9539f2bdf2ed3a108937d34f13cf527235a316 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 May 2015 17:07:05 +0200 Subject: [PATCH 4/9] Adding ToJSON method in callcost, tests for MaxCallCost --- .../tariffplans/tutorial/DestinationRates.csv | 4 +- data/tariffplans/tutorial/Rates.csv | 1 + engine/callcost.go | 6 ++ general_tests/tutorial_local_test.go | 61 +++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/data/tariffplans/tutorial/DestinationRates.csv b/data/tariffplans/tutorial/DestinationRates.csv index 62bd4b736..ef225e687 100644 --- a/data/tariffplans/tutorial/DestinationRates.csv +++ b/data/tariffplans/tutorial/DestinationRates.csv @@ -6,5 +6,5 @@ DR_1003_10CNT,DST_1003,RT_10CNT,*up,4,0, DR_FS_40CNT,DST_FS,RT_40CNT,*up,4,0, DR_FS_10CNT,DST_FS,RT_10CNT,*up,4,0, DR_SPECIAL_1002,DST_1002,RT_1CNT,*up,4,0, -DR_1007_MAXCOST_DISC,DST_1007,RT_1CNT,*up,4,0.62,*disconnect -DR_1007_MAXCOST_FREE,DST_1007,RT_1CNT,*up,4,0.62,*free +DR_1007_MAXCOST_DISC,DST_1007,RT_1CNT_PER_SEC,*up,4,0.62,*disconnect +DR_1007_MAXCOST_FREE,DST_1007,RT_1CNT_PER_SEC,*up,4,0.62,*free diff --git a/data/tariffplans/tutorial/Rates.csv b/data/tariffplans/tutorial/Rates.csv index 5f571f24b..0164076c9 100644 --- a/data/tariffplans/tutorial/Rates.csv +++ b/data/tariffplans/tutorial/Rates.csv @@ -6,3 +6,4 @@ RT_20CNT,0,0.1,60s,1s,60s RT_40CNT,0.8,0.4,60s,30s,0s RT_40CNT,0,0.2,60s,10s,60s RT_1CNT,0,0.01,60s,60s,0s +RT_1CNT_PER_SEC,0,0.01,1s,1s,0s diff --git a/engine/callcost.go b/engine/callcost.go index 333aa1e3c..19a02bd3b 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -18,6 +18,7 @@ along with this program. If not, see package engine import ( + "encoding/json" "errors" "reflect" "time" @@ -161,3 +162,8 @@ func (cc *CallCost) GetLongestRounding() (roundingDecimals int, roundingMethod s } return } + +func (cc *CallCost) AsJSON() string { + ccJson, _ := json.Marshal(cc) + return string(ccJson) +} diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 9e02464ce..88ed5f2d5 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -296,6 +296,67 @@ func TestTutLocalGetCosts(t *testing.T) { } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } + tStart = time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1001", + Account: "1001", + Destination: "1007", + TimeStart: tStart, + TimeEnd: tStart.Add(time.Duration(50) * time.Second), + } + if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if cc.Cost != 0.5 { + t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1001", + Account: "1001", + Destination: "1007", + TimeStart: tStart, + TimeEnd: tStart.Add(time.Duration(70) * time.Second), + } + if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if cc.Cost != 0.62 { + t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1002", + Account: "1002", + Destination: "1007", + TimeStart: tStart, + TimeEnd: tStart.Add(time.Duration(50) * time.Second), + } + if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if cc.Cost != 0.5 { + t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1002", + Account: "1002", + Destination: "1007", + TimeStart: tStart, + TimeEnd: tStart.Add(time.Duration(70) * time.Second), + } + if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if cc.Cost != 0.62 { + t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) + } } // Check call costs From eb8df9892a2f1303829f5a39ba1914d9afd1f539 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 25 May 2015 20:16:36 +0300 Subject: [PATCH 5/9] getcost uses maxcost strategy --- engine/account.go | 2 +- engine/balances.go | 4 ++-- engine/callcost_test.go | 20 ++++++++++---------- engine/calldesc.go | 34 +++++++++++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/engine/account.go b/engine/account.go index fb2e2e246..494d71f66 100644 --- a/engine/account.go +++ b/engine/account.go @@ -316,7 +316,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo //log.Print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") } //log.Printf("After balances CD: %+v", cd) - leftCC, err = cd.GetCost() + leftCC, err = cd.getCost() if err != nil { Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) } diff --git a/engine/balances.go b/engine/balances.go index 2c3cc202c..f5c7e8efb 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -242,7 +242,7 @@ func (b *Balance) GetCost(cd *CallDescriptor, getStandardIfEmpty bool) (*CallCos origAccount := cd.Account cd.Account = cd.Subject cd.RatingInfos = nil - cc, err := cd.GetCost() + cc, err := cd.getCost() // restor orig values cd.Subject = origSubject cd.Account = origAccount @@ -250,7 +250,7 @@ func (b *Balance) GetCost(cd *CallDescriptor, getStandardIfEmpty bool) (*CallCos } if getStandardIfEmpty { cd.RatingInfos = nil - return cd.GetCost() + return cd.getCost() } else { cc := cd.CreateCallCost() cc.Cost = 0 diff --git a/engine/callcost_test.go b/engine/callcost_test.go index 5d862b228..5349b104b 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -29,7 +29,7 @@ func TestSingleResultMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 0, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 1, 0, 0, time.UTC) cd := &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc1, _ := cd.GetCost() + cc1, _ := cd.getCost() if cc1.Cost != 61 { t.Errorf("expected 61 was %v", cc1.Cost) } @@ -53,7 +53,7 @@ func TestMultipleResultMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 0, 0, 0, time.UTC) cd := &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc1, _ := cd.GetCost() + cc1, _ := cd.getCost() if cc1.Cost != 61 { t.Errorf("expected 61 was %v", cc1.Cost) for _, ts := range cc1.Timespans { @@ -63,7 +63,7 @@ func TestMultipleResultMerge(t *testing.T) { t1 = time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) cd = &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc2, _ := cd.GetCost() + cc2, _ := cd.getCost() if cc2.Cost != 30 { t.Errorf("expected 30 was %v", cc2.Cost) for _, ts := range cc1.Timespans { @@ -83,7 +83,7 @@ func TestMultipleInputLeftMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) cd := &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc1, _ := cd.GetCost() + cc1, _ := cd.getCost() //log.Printf("Timing: %+v", cc1.Timespans[1].RateInterval.Timing) //log.Printf("Rating: %+v", cc1.Timespans[1].RateInterval.Rating) if cc1.Cost != 91 { @@ -92,7 +92,7 @@ func TestMultipleInputLeftMerge(t *testing.T) { /*t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 02, 0, 0, time.UTC) cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc2, _ := cd.GetCost() + cc2, _ := cd.getCost() if cc2.Cost != 30 { t.Errorf("expected 30 was %v", cc2.Cost) } @@ -109,14 +109,14 @@ func TestMultipleInputRightMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 58, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) cd := &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc1, _ := cd.GetCost() + cc1, _ := cd.getCost() if cc1.Cost != 61 { t.Errorf("expected 61 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) cd = &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc2, _ := cd.GetCost() + cc2, _ := cd.getCost() if cc2.Cost != 91 { t.Errorf("expected 91 was %v", cc2.Cost) } @@ -133,7 +133,7 @@ func TestCallCostMergeEmpty(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 58, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) cd := &CallDescriptor{Direction: OUTBOUND, Category: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cc1, _ := cd.GetCost() + cc1, _ := cd.getCost() cc2 := &CallCost{} cc1.Merge(cc2) if len(cc1.Timespans) != 1 { @@ -177,7 +177,7 @@ func TestCallCostToDataCostError(t *testing.T) { TimeEnd: time.Date(2014, 3, 4, 6, 1, 5, 0, time.UTC), TOR: utils.VOICE, } - cc, _ := cd.GetCost() + cc, _ := cd.getCost() _, err := cc.ToDataCost() if err == nil { t.Error("Failed to throw error on call to datacost!") @@ -195,7 +195,7 @@ func TestCallCostToDataCostError(t *testing.T) { TimeEnd: time.Date(2014, 3, 4, 6, 1, 5, 0, time.UTC), TOR: DATA, } - cc, _ := cd.GetCost() + cc, _ := cd.getCost() dc, err := cc.ToDataCost() if err != nil { t.Error("Error convertiong to data cost: ", err) diff --git a/engine/calldesc.go b/engine/calldesc.go index 3e1b26fff..626d754ba 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -405,6 +405,38 @@ func (cd *CallDescriptor) GetDuration() time.Duration { Creates a CallCost structure with the cost information calculated for the received CallDescriptor. */ func (cd *CallDescriptor) GetCost() (*CallCost, error) { + cc, err := cd.getCost() + if err != nil { + return nil, err + } + + cost := 0.0 + for i, ts := range cc.Timespans { + // only add connect fee if this is the first/only call cost request + //log.Printf("Interval: %+v", ts.RateInterval.Timing) + if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { + cost += ts.RateInterval.Rating.ConnectFee + } + // handle max cost + maxCost, strategy := ts.RateInterval.GetMaxCost() + if strategy != "" && maxCost > 0 { + if strategy == utils.MAX_COST_FREE && cd.MaxCostSoFar >= maxCost { + cost = maxCost + cd.MaxCostSoFar = maxCost + } else { + cost += ts.getCost() + cd.MaxCostSoFar += cost + } + + } else { + cost += ts.getCost() + } + } + cc.Cost = cost + return cc, nil +} + +func (cd *CallDescriptor) getCost() (*CallCost, error) { // check for 0 duration if cd.TimeEnd.Sub(cd.TimeStart) == 0 { return cd.CreateCallCost(), nil @@ -420,7 +452,6 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error())) return &CallCost{Cost: -1}, err } - timespans := cd.splitInTimeSpans() cost := 0.0 @@ -432,6 +463,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } cost += ts.getCost() } + //startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.Category)) cc := cd.CreateCallCost() cc.Cost = cost From eeadc8a55e2a1e9e8c6c1b135dd4db68ada7239e Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 26 May 2015 10:21:53 +0300 Subject: [PATCH 6/9] founding for the new call cost --- engine/calldesc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/calldesc.go b/engine/calldesc.go index 626d754ba..047c86dd1 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -433,6 +433,10 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } } cc.Cost = cost + // global rounding + roundingDecimals, roundingMethod := cc.GetLongestRounding() + cc.Cost = utils.Round(cc.Cost, roundingDecimals, roundingMethod) + return cc, nil } From ec60709daa0ec70d8d4296709fc0520a03ef8380 Mon Sep 17 00:00:00 2001 From: DanB Date: Tue, 26 May 2015 11:21:19 +0200 Subject: [PATCH 7/9] Adding MaxCallCost related tests in local --- general_tests/tutorial_kam_calls_test.go | 38 +++++++++--- general_tests/tutorial_local_test.go | 79 ++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 12 deletions(-) diff --git a/general_tests/tutorial_kam_calls_test.go b/general_tests/tutorial_kam_calls_test.go index 0e544f230..3be9123e8 100644 --- a/general_tests/tutorial_kam_calls_test.go +++ b/general_tests/tutorial_kam_calls_test.go @@ -303,6 +303,30 @@ func TestTutKamCallsCall1007To1002(t *testing.T) { } } +/* +// Should hangup at 62 seconds +func TestTutKamCallsCall1006To1007(t *testing.T) { + if !*testCalls { + return + } + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1006@127.0.0.1", Username: "1006", Password: "CGRateS.org", Realm: "*"}, "sip:1007@127.0.0.1", + "sip:127.0.0.1:5060", time.Duration(90)*time.Second, 5077); err != nil { + t.Fatal(err) + } +} + +// Call from 1001 (prepaid) to 1007, should not cost more than 62 which is MaxCallCost +func TestTutKamCallsCall1001To1007(t *testing.T) { + if !*testCalls { + return + } + if err := engine.PjsuaCallUri(&engine.PjsuaAccount{Id: "sip:1001@127.0.0.1", Username: "1001", Password: "CGRateS.org", Realm: "*"}, "sip:1007@127.0.0.1", + "sip:127.0.0.1:5060", time.Duration(100)*time.Second, 5079); err != nil { + t.Fatal(err) + } +} +*/ + // Make sure account was debited properly func TestTutKamCallsAccount1001(t *testing.T) { if !*testCalls { @@ -342,7 +366,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl2" { // Usage as seconds - t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } req = utils.RpcCdrsFilter{Accounts: []string{"1001"}, RunIds: []string{"derived_run1"}, FilterOnDerived: true} @@ -358,7 +382,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl2" { - t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } req = utils.RpcCdrsFilter{Accounts: []string{"1002"}, RunIds: []string{utils.META_DEFAULT}} @@ -380,7 +404,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl1" { - t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } req = utils.RpcCdrsFilter{Accounts: []string{"1003"}, RunIds: []string{utils.META_DEFAULT}} @@ -402,7 +426,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl1" { - t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } req = utils.RpcCdrsFilter{Accounts: []string{"1004"}, RunIds: []string{utils.META_DEFAULT}} @@ -424,7 +448,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl1" { - t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } req = utils.RpcCdrsFilter{Accounts: []string{"1006"}, RunIds: []string{utils.META_DEFAULT}} @@ -446,7 +470,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl3" { - t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } req = utils.RpcCdrsFilter{Accounts: []string{"1007"}, RunIds: []string{utils.META_DEFAULT}} @@ -468,7 +492,7 @@ func TestTutKamCallsCdrs(t *testing.T) { t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } if reply[0].Supplier != "suppl3" { - t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) + t.Errorf("Unexpected Supplier for CDR: %+v", reply[0]) } } } diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 88ed5f2d5..c15b30e22 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -325,7 +325,7 @@ func TestTutLocalGetCosts(t *testing.T) { if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.62 { - t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) + t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } cd = engine.CallDescriptor{ Direction: "*out", @@ -354,7 +354,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 0.62 { + } else if cc.Cost != 0.7 { // In case of *disconnect strategy, it will not be applied so we can go on negative costs t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } } @@ -364,8 +364,7 @@ func TestTutLocalMaxDebit(t *testing.T) { if !*testLocal { return } - tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") - tEnd, _ := utils.ParseDate("2014-08-04T13:00:20Z") + tStart := time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", @@ -375,7 +374,7 @@ func TestTutLocalMaxDebit(t *testing.T) { Destination: "1002", DurationIndex: 0, TimeStart: tStart, - TimeEnd: tEnd, + TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { @@ -383,6 +382,76 @@ func TestTutLocalMaxDebit(t *testing.T) { } else if cc.GetDuration() == 20 { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1001", + Account: "1001", + Destination: "1007", + DurationIndex: 0, + TimeStart: tStart, + TimeEnd: tStart.Add(time.Duration(120) * time.Second), + } + if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if cc.GetDuration() == 120 { + t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) + } + cd = engine.CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "1004", + Account: "1004", + Destination: "1007", + DurationIndex: 0, + TimeStart: tStart, + TimeEnd: tStart.Add(time.Duration(120) * time.Second), + } + if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if cc.GetDuration() != time.Duration(62)*time.Second { // We have as strategy *dsconnect + t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) + } + var maxTime float64 + if err := tutLocalRpc.Call("Responder.GetMaxSessionTime", cd, &maxTime); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if maxTime != 62000000000 { // We have as strategy *dsconnect + t.Errorf("Calling Responder.GetMaxSessionTime got maxTime: %f", maxTime) + } +} + +// Check call costs +func TestTutLocalDerivedMaxSessionTime(t *testing.T) { + if !*testLocal { + return + } + tStart := time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) + ev := engine.StoredCdr{ + CgrId: utils.Sha1("testevent1", tStart.String()), + TOR: utils.VOICE, + AccId: "testevent1", + CdrHost: "127.0.0.1", + ReqType: utils.META_PREPAID, + Direction: utils.OUT, + Tenant: "cgrates.org", + Category: "call", + Account: "1004", + Subject: "1004", + Destination: "1007", + SetupTime: tStart, + AnswerTime: tStart, + Usage: time.Duration(120) * time.Second, + Supplier: "suppl1", + Cost: -1, + } + var maxTime float64 + if err := tutLocalRpc.Call("Responder.GetDerivedMaxSessionTime", ev, &maxTime); err != nil { + t.Error("Got error on Responder.GetCost: ", err.Error()) + } else if maxTime != 62000000000 { // We have as strategy *dsconnect + t.Errorf("Calling Responder.GetMaxSessionTime got maxTime: %f", maxTime) + } } // Make sure queueids were created From 97025381b32cc392ef2e0b07df69edd076f97a0b Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 26 May 2015 12:29:53 +0300 Subject: [PATCH 8/9] test for GetCost with maxcost enabled --- engine/calldesc.go | 14 ++++++------ engine/calldesc_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ local_test.sh | 3 +-- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 047c86dd1..2a69a8830 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -417,20 +417,22 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.Rating.ConnectFee } + //log.Printf("TS: %+v", ts) // handle max cost maxCost, strategy := ts.RateInterval.GetMaxCost() + + cost += ts.getCost() + cd.MaxCostSoFar += cost + //log.Print("Before: ", cost) if strategy != "" && maxCost > 0 { + //log.Print("HERE: ", strategy, maxCost) if strategy == utils.MAX_COST_FREE && cd.MaxCostSoFar >= maxCost { cost = maxCost cd.MaxCostSoFar = maxCost - } else { - cost += ts.getCost() - cd.MaxCostSoFar += cost } - } else { - cost += ts.getCost() } + //log.Print("Cost: ", cost) } cc.Cost = cost // global rounding @@ -615,7 +617,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { // Interface method used to add/substract an amount of cents or bonus seconds (as returned by GetCost method) // from user's money balance. // This methods combines the Debit and GetMaxSessionDuration and will debit the max available time as returned -// by the GetMaxSessionTime method. The amount filed has to be filled in call descriptor. +// by the GetMaxSessionDuration method. The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { if account, err := cd.getAccount(); err != nil || account == nil { Logger.Err(fmt.Sprintf("Could not get user balance for <%s>: %s.", cd.GetAccountKey(), err.Error())) diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index cd0642a3b..56d4cb2a3 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -512,6 +512,29 @@ func TestMaxSessionTimeWithMaxCost(t *testing.T) { } } +func TestGetCostWithMaxCost(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "max", + Account: "max", + Destination: "0723123113", + TimeStart: time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 3, 23, 6, 30, 0, 0, time.UTC), + MaxCostSoFar: 0, + } + cc, err := cd.GetCost() + expected := 1800.0 + if cc.Cost != expected || err != nil { + t.Errorf("Expected %v was %v", expected, cc.Cost) + } +} + func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") for _, at := range ap { @@ -535,6 +558,30 @@ func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { } } +func TestGetCostWithMaxCostFree(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "max", + Account: "max", + Destination: "0723123113", + TimeStart: time.Date(2015, 3, 23, 19, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 3, 23, 19, 30, 0, 0, time.UTC), + MaxCostSoFar: 0, + } + + cc, err := cd.GetCost() + expected := 10.0 + if cc.Cost != expected || err != nil { + t.Errorf("Expected %v was %v", expected, cc.Cost) + } +} + func TestMaxSessionTimeWithAccountAlias(t *testing.T) { cd := &CallDescriptor{ TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), diff --git a/local_test.sh b/local_test.sh index b102597e5..f111919d6 100755 --- a/local_test.sh +++ b/local_test.sh @@ -1,5 +1,5 @@ #! /usr/bin/env sh - +./build.sh ./test.sh gen=$? echo 'go test github.com/cgrates/cgrates/apier/v1 -local' @@ -28,4 +28,3 @@ gnr=$? exit $gen && $ap1 && $ap2 && $en && $cdrc && $cfg && $gnr - From 844840e184c17f67a9e9b404469ba431c0addd7c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 26 May 2015 13:50:54 +0300 Subject: [PATCH 9/9] fix for maxcost callcost information --- engine/balances.go | 4 +++- engine/calldesc.go | 2 +- engine/calldesc_test.go | 23 +++++++++++++++++++++++ engine/timespans.go | 11 ++++++++++- engine/timespans_test.go | 2 +- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index f5c7e8efb..52b8d386d 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -337,7 +337,7 @@ func (b *Balance) DebitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala if ts.Increments == nil { ts.createIncrementsSlice() } - //log.Printf("TS: %+v", ts) + if ts.RateInterval == nil { Logger.Err(fmt.Sprintf("Nil RateInterval ERROR on TS: %+v, CC: %+v, from CD: %+v", ts, cc, cd)) return nil, errors.New("timespan with no rate interval assigned") @@ -472,6 +472,8 @@ func (b *Balance) DebitMoney(cd *CallDescriptor, ub *Account, count bool, dryRun if count { ub.countUnits(&Action{BalanceType: utils.MONETARY, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationIds: cc.Destination}}) } + + //log.Printf("TS: %+v", cc.Cost) // go to nextincrement continue } diff --git a/engine/calldesc.go b/engine/calldesc.go index 2a69a8830..7aa77fcd8 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -576,7 +576,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) } //log.Printf("Debit CD: %+v", cd) cc, err = account.debitCreditBalance(cd, !dryRun, dryRun, goNegative) - //log.Print("HERE: ", cc, err) + //log.Printf("HERE: %+v %v", cc, err) if err != nil { Logger.Err(fmt.Sprintf(" Error getting cost for account key <%s>: %s", cd.GetAccountKey(), err.Error())) return nil, err diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 56d4cb2a3..df68c9a0a 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -558,6 +558,29 @@ func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { } } +func TestMaxDebitWithMaxCostFree(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "max", + Account: "max", + Destination: "0723123113", + TimeStart: time.Date(2015, 3, 23, 19, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 3, 23, 19, 30, 0, 0, time.UTC), + MaxCostSoFar: 0, + } + cc, err := cd.MaxDebit() + expected := 10.0 + if cc.Cost != expected || err != nil { + t.Errorf("Expected %v was %v", expected, cc.Cost) + } +} + func TestGetCostWithMaxCostFree(t *testing.T) { ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") for _, at := range ap { diff --git a/engine/timespans.go b/engine/timespans.go index 0da7e3040..30d76b292 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -274,7 +274,16 @@ func (ts *TimeSpan) getCost() float64 { ts.Cost = utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) return ts.Cost } else { - return ts.Increments[0].Cost * float64(ts.Increments.Length()) + cost := 0.0 + // some increments may have 0 cost because of the max cost strategy + for _, inc := range ts.Increments { + cost += inc.Cost + } + if ts.RateInterval != nil && ts.RateInterval.Rating != nil { + return utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) + } else { + return utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) + } } } diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 57539eb7d..468e2a51e 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -235,7 +235,7 @@ func TestTimespanGetCostIntervals(t *testing.T) { ts.Increments[i] = &Increment{Cost: 0.02} } if ts.getCost() != 0.22 { - t.Error("Error caclulating timspan cost: ", ts.getCost()) + t.Error("Error caclulating timespan cost: ", ts.getCost()) } }