From 8967c2fa3a35eb290a2ead03b63967fc10a93b85 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 3 Mar 2014 20:37:13 +0200 Subject: [PATCH 1/6] fix rounding up problem --- utils/coreutils.go | 10 +++++++++- utils/utils_test.go | 23 ++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/utils/coreutils.go b/utils/coreutils.go index ac033245a..71e713cd8 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -77,13 +77,21 @@ func GenUUID() string { // Round(NaN) = NaN func Round(x float64, prec int, method string) float64 { var rounder float64 + maxPrec := 7 // define a max precison to cut float errors + if maxPrec < prec { + maxPrec = prec + } pow := math.Pow(10, float64(prec)) intermed := x * pow _, frac := math.Modf(intermed) switch method { case ROUNDING_UP: - rounder = math.Ceil(intermed) + if frac >= math.Pow10(-maxPrec) { // Max precision we go, rest is float chaos + rounder = math.Ceil(intermed) + } else { + rounder = math.Floor(intermed) + } case ROUNDING_DOWN: rounder = math.Floor(intermed) case ROUNDING_MIDDLE: diff --git a/utils/utils_test.go b/utils/utils_test.go index b99df7647..394170f92 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -105,6 +105,14 @@ func TestRoundByMethodUp2(t *testing.T) { } } +func TestRoundByMethodUp3(t *testing.T) { + result := Round(0.0701, 2, ROUNDING_UP) + expected := 0.08 + if result != expected { + t.Errorf("Error rounding up: sould be %v was %v", expected, result) + } +} + func TestRoundByMethodDown1(t *testing.T) { result := Round(12.49, 1, ROUNDING_DOWN) expected := 12.4 @@ -254,7 +262,7 @@ func TestMissingStructFieldsIncorrect(t *testing.T) { } } -func TestRound(t *testing.T) { +func TestRoundTo(t *testing.T) { minute := time.Minute result := RoundTo(minute, 0*time.Second) expected := 0 * time.Second @@ -293,6 +301,19 @@ func TestRound(t *testing.T) { } } +func TestRoundAlredyHavingPrecision(t *testing.T) { + x := 0.07 + if y := Round(x, 2, ROUNDING_UP); y != x { + t.Error("Error rounding when already has desired precision: ", y) + } + if y := Round(x, 2, ROUNDING_MIDDLE); y != x { + t.Error("Error rounding when already has desired precision: ", y) + } + if y := Round(x, 2, ROUNDING_DOWN); y != x { + t.Error("Error rounding when already has desired precision: ", y) + } +} + func TestSplitPrefix(t *testing.T) { a := SplitPrefix("0123456789", 1) if len(a) != 10 { From 0b0474fa232735bf59f3f09214e7231caa5e55a0 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 4 Mar 2014 22:02:20 +0200 Subject: [PATCH 2/6] fix for MaxDebit bug max duration was calculated badly --- engine/balances.go | 16 +++++++++---- engine/callcost.go | 5 ++-- engine/calldesc.go | 6 ++--- engine/calldesc_test.go | 34 ++++++++++++++++++++++++++- engine/history_test.go | 1 + engine/loader_csv_test.go | 48 +++++++++++++++++++++++++++------------ 6 files changed, 84 insertions(+), 26 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 0130e15ae..36ff17046 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -83,7 +83,8 @@ func (b *Balance) Clone() *Balance { } // Returns the available number of seconds for a specified credit -func (b *Balance) GetMinutesForCredit(cd *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) { +func (b *Balance) GetMinutesForCredit(origCD *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) { + cd := origCD.Clone() duration = time.Duration(b.Value) * time.Second credit = initialCredit cc, err := b.GetCost(cd) @@ -112,6 +113,7 @@ func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) { if b.RateSubject != "" { cd.Subject = b.RateSubject cd.Account = cd.Subject + cd.RatingInfos = nil return cd.GetCost() } cc := cd.CreateCallCost() @@ -221,15 +223,21 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan if moneyBal != nil && b.Value >= seconds { b.Value -= seconds b.Value = utils.Round(b.Value, roundingDecimals, utils.ROUNDING_MIDDLE) - moneyBal.Value -= cost + nInc.BalanceInfo.MinuteBalanceUuid = b.Uuid - nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid nInc.BalanceInfo.AccountId = ub.Id nInc.MinuteInfo = &MinuteInfo{newCC.Destination, seconds} + if cost != 0 { + nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid + moneyBal.Value -= cost + moneyBal.Value = utils.Round(moneyBal.Value, roundingDecimals, utils.ROUNDING_MIDDLE) + } nInc.paid = true if count { ub.countUnits(&Action{BalanceType: MINUTES, Direction: newCC.Direction, Balance: &Balance{Value: seconds, DestinationId: newCC.Destination}}) - ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}}) + if cost != 0 { + ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}}) + } } } else { increment.paid = false diff --git a/engine/callcost.go b/engine/callcost.go index 41cdd5ba6..dff4be5c5 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -18,7 +18,6 @@ along with this program. If not, see package engine import ( - "fmt" "reflect" "time" ) @@ -32,7 +31,7 @@ type CallCost struct { } // Pretty printing for call cost -func (cc *CallCost) String() (r string) { +/*func (cc *CallCost) String() (r string) { connectFee := 0.0 if cc.deductConnectFee { connectFee = cc.GetConnectFee() @@ -43,7 +42,7 @@ func (cc *CallCost) String() (r string) { } r += " )" return -} +}*/ // Merges the received timespan if they are similar (same activation period, same interval, same minute info. func (cc *CallCost) Merge(other *CallCost) { diff --git a/engine/calldesc.go b/engine/calldesc.go index cf56d7aec..b1d467f67 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -397,7 +397,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } err := cd.LoadRatingPlans() if err != nil { - Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetAccountKey(), err)) + Logger.Err(fmt.Sprintf("error getting cost for key %s: %v", cd.GetKey(cd.Subject), err)) return &CallCost{Cost: -1}, err } timespans := cd.splitInTimeSpans(nil) @@ -659,7 +659,7 @@ func (cd *CallDescriptor) Clone() *CallDescriptor { CallDuration: cd.CallDuration, Amount: cd.Amount, FallbackSubject: cd.FallbackSubject, - RatingInfos: cd.RatingInfos, - Increments: cd.Increments, + //RatingInfos: cd.RatingInfos, + //Increments: cd.Increments, } } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index bfd330919..43c02ebb2 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -422,7 +422,7 @@ func TestDebitAndMaxDebit(t *testing.T) { Subject: "minu_from_tm", Account: "minu", Destination: "0723", - Amount: 5400} + } cd2 := cd1.Clone() cc1, err1 := cd1.Debit() cc2, err2 := cd2.MaxDebit() @@ -434,6 +434,38 @@ func TestDebitAndMaxDebit(t *testing.T) { } } +func TestMaxDebitZeroDefinedRate(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP1000UKMOB_AT") + for _, at := range ap { + at.Execute() + } + ap, _ = accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd1 := &CallDescriptor{ + Direction: "*out", + TOR: "call", + Tenant: "cgrates.directvoip.co.uk", + Subject: "12345", + Account: "12345", + Destination: "447956", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 1, 0, 0, time.UTC), + LoopIndex: 0, + CallDuration: 0} + cc, err := cd1.MaxDebit() + if err != nil { + t.Error("Error maxdebiting: ", err) + } + if cc.GetDuration() != 49*time.Second { + t.Error("Error obtaining max debit duration: ", cc.GetDuration()) + } + if cc.Cost != 0.9 { + t.Error("Error in max debit cost: ", cc.Cost) + } +} + /*********************************** BENCHMARKS ***************************************/ func BenchmarkStorageGetting(b *testing.B) { b.StopTimer() diff --git a/engine/history_test.go b/engine/history_test.go index aa599ae6e..e77acca36 100644 --- a/engine/history_test.go +++ b/engine/history_test.go @@ -37,6 +37,7 @@ func TestHistoryDestinations(t *testing.T) { scribe := historyScribe.(*history.MockScribe) buf := scribe.BufMap[history.DESTINATIONS_FN] expected := `[{"Id":"ALL","Prefixes":["49","41","43"]}, +{"Id":"DST_UK_Mobile_BIG5","Prefixes":["447956"]}, {"Id":"GERMANY","Prefixes":["49"]}, {"Id":"GERMANY_O2","Prefixes":["41"]}, {"Id":"GERMANY_PREMIUM","Prefixes":["43"]}, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 8cee2496e..b07d14cea 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -43,12 +43,15 @@ RET,0724 PSTN_71,+4971 PSTN_72,+4972 PSTN_70,+4970 +DST_UK_Mobile_BIG5,447956 ` timings = ` WORKDAYS_00,*any,*any,*any,1;2;3;4;5,00:00:00 WORKDAYS_18,*any,*any,*any,1;2;3;4;5,18:00:00 WEEKENDS,*any,*any,*any,6;7,00:00:00 ONE_TIME_RUN,2012,,,,*asap +ALWAYS,*any,*any,*any,*any,00:00:00 +ASAP,*any,*any,*any,*any,*asap ` rates = ` R1,0,0.2,60s,1s,0,*middle,2 @@ -61,6 +64,8 @@ LANDLINE_OFFPEAK,0,1,1s,1s,60s,*up,4 GBP_71,0.000000,5.55555,1s,1s,0s,*up,4 GBP_72,0.000000,7.77777,1s,1s,0s,*up,4 GBP_70,0.000000,1,1s,1s,0s,*up,4 +RT_UK_Mobile_BIG5_PKG,0,0,20s,20s,0s,*up,8 +RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s,*up,8 ` destinationRates = ` RT_STANDARD,GERMANY,R1 @@ -75,8 +80,10 @@ T1,NAT,LANDLINE_OFFPEAK T2,GERMANY,GBP_72 T2,GERMANY_O2,GBP_70 T2,GERMANY_PREMIUM,GBP_71 +DR_UK_Mobile_BIG5_PKG,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5_PKG +DR_UK_Mobile_BIG5,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5 ` - destinationRateTimings = ` + ratingPlans = ` STANDARD,RT_STANDARD,WORKDAYS_00,10 STANDARD,RT_STD_WEEKEND,WORKDAYS_18,10 STANDARD,RT_STD_WEEKEND,WEEKENDS,10 @@ -91,6 +98,8 @@ TDRT,T1,WORKDAYS_00,10 TDRT,T2,WORKDAYS_00,10 G,RT_STANDARD,WORKDAYS_00,10 R,P1,WORKDAYS_00,10 +RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10 +RP_UK,DR_UK_Mobile_BIG5,ALWAYS,10 ` ratingProfiles = ` CUSTOMER_1,0,*out,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb @@ -109,6 +118,8 @@ vdf,0,*out,fallback1,2013-11-18T13:45:00Z,G,fallback2 vdf,0,*out,fallback1,2013-11-18T13:46:00Z,G,fallback2 vdf,0,*out,fallback1,2013-11-18T13:47:00Z,G,fallback2 vdf,0,*out,fallback2,2013-11-18T13:45:00Z,R,rif +cgrates.directvoip.co.uk,call,*out,*any,2013-01-06T00:00:00Z,RP_UK, +cgrates.directvoip.co.uk,call,*out,discounted_minutes,2013-01-06T00:00:00Z,RP_UK_Mobile_BIG5_PKG, ` sharedGroups = ` SG1,*any,*lowest_first,, @@ -119,24 +130,33 @@ SG2,*any,*lowest_first,EVENING, MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,,10 MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,,10 SHARED,*topup,*monetary,*out,100,*unlimited,,,10,SG1,,10 +TOPUP10_AC,*topup_reset,*monetary,*out,1,*unlimited,*any,,10,,,10 +TOPUP1000UKMOB_AC,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 MORE_MINUTES,SHARED,ONE_TIME_RUN,10 +TOPUP10_AT,TOPUP10_AC,ASAP,10 +TOPUP1000UKMOB_AT,TOPUP1000UKMOB_AC,ASAP,10 ` actionTriggers = ` STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10 STANDARD_TRIGGER,*minutes,*out,*max_balance,200,GERMANY,SOME_2,10 +STANDARD_TRIGGERS,*monetary,*out,*min_balance,2,,LOG_WARNING,10 +STANDARD_TRIGGERS,*monetary,*out,*max_balance,20,,LOG_WARNING,10 +STANDARD_TRIGGERS,*monetary,*out,*max_counter,5,FS_USERS,LOG_WARNING,10 ` accountActions = ` vdf,minitsboy;a1;a2,*out,MORE_MINUTES,STANDARD_TRIGGER +cgrates.directvoip.co.uk,12345,*out,TOPUP10_AT,STANDARD_TRIGGERS +cgrates.directvoip.co.uk,12345,*out,TOPUP1000UKMOB_AT,STANDARD_TRIGGERS ` ) var csvr *CSVReader func init() { - csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, destinationRateTimings, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions) + csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, ratingPlans, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions) csvr.LoadDestinations() csvr.LoadTimings() csvr.LoadRates() @@ -154,7 +174,7 @@ func init() { } func TestLoadDestinations(t *testing.T) { - if len(csvr.destinations) != 9 { + if len(csvr.destinations) != 10 { t.Error("Failed to load destinations: ", len(csvr.destinations)) } for _, d := range csvr.destinations { @@ -195,14 +215,12 @@ func TestLoadDestinations(t *testing.T) { if !reflect.DeepEqual(d.Prefixes, []string{`+4970`}) { t.Error("Faild to load destinations", d) } - default: - t.Error("Unknown destination tag!") } } } func TestLoadTimimgs(t *testing.T) { - if len(csvr.timings) != 4 { + if len(csvr.timings) != 6 { t.Error("Failed to load timings: ", csvr.timings) } timing := csvr.timings["WORKDAYS_00"] @@ -252,7 +270,7 @@ func TestLoadTimimgs(t *testing.T) { } func TestLoadRates(t *testing.T) { - if len(csvr.rates) != 9 { + if len(csvr.rates) != 11 { t.Error("Failed to load rates: ", csvr.rates) } rate := csvr.rates["R1"].RateSlots[0] @@ -322,7 +340,7 @@ func TestLoadRates(t *testing.T) { } func TestLoadDestinationRates(t *testing.T) { - if len(csvr.destinationRates) != 7 { + if len(csvr.destinationRates) != 9 { t.Error("Failed to load destinationrates: ", csvr.destinationRates) } drs := csvr.destinationRates["RT_STANDARD"] @@ -434,7 +452,7 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadDestinationRateTimings(t *testing.T) { - if len(csvr.ratingPlans) != 7 { + if len(csvr.ratingPlans) != 9 { t.Error("Failed to load rate timings: ", csvr.ratingPlans) } rplan := csvr.ratingPlans["STANDARD"] @@ -554,7 +572,7 @@ func TestLoadDestinationRateTimings(t *testing.T) { } func TestLoadRatingProfiles(t *testing.T) { - if len(csvr.ratingProfiles) != 12 { + if len(csvr.ratingProfiles) != 14 { t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles) } rp := csvr.ratingProfiles["*out:test:0:trp"] @@ -572,7 +590,7 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 2 { + if len(csvr.actions) != 4 { t.Error("Failed to load actions: ", csvr.actions) } as1 := csvr.actions["MINI"] @@ -681,7 +699,7 @@ func TestLoadSharedGroups(t *testing.T) { } func TestLoadActionTimings(t *testing.T) { - if len(csvr.actionsTimings) != 1 { + if len(csvr.actionsTimings) != 3 { t.Error("Failed to load action timings: ", csvr.actionsTimings) } atm := csvr.actionsTimings["MORE_MINUTES"][0] @@ -707,7 +725,7 @@ func TestLoadActionTimings(t *testing.T) { } func TestLoadActionTriggers(t *testing.T) { - if len(csvr.actionsTriggers) != 1 { + if len(csvr.actionsTriggers) != 2 { t.Error("Failed to load action triggers: ", csvr.actionsTriggers) } atr := csvr.actionsTriggers["STANDARD_TRIGGER"][0] @@ -743,8 +761,8 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 1 { - t.Error("Failed to load account actions: ", csvr.accountActions) + if len(csvr.accountActions) != 3 { + t.Error("Failed to load account actions: ", csvr.accountActions[2]) } aa := csvr.accountActions[0] expected := &Account{ From a753a5511940a019f317fccfd873a875c772af23 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 5 Mar 2014 14:17:47 +0200 Subject: [PATCH 3/6] fixed connectFee in max debit issue --- engine/account_test.go | 2 +- engine/balances.go | 16 ++++++++++++++-- engine/calldesc.go | 3 ++- engine/calldesc_test.go | 34 +++++++++++++++++++++++++++++++++- engine/loader_csv_test.go | 2 +- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/engine/account_test.go b/engine/account_test.go index fe98fb1b8..3bce742b7 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -135,7 +135,7 @@ func TestGetSpecialPricedSeconds(t *testing.T) { Destination: "0723", } seconds, credit, bucketList := ub1.getCreditForPrefix(cd) - expected := 21 * time.Second + expected := 20 * time.Second if credit != 0 || seconds != expected || len(bucketList) != 2 || bucketList[0].Weight < bucketList[1].Weight { t.Log(seconds, credit, bucketList) t.Errorf("Expected %v was %v", expected, seconds) diff --git a/engine/balances.go b/engine/balances.go index 36ff17046..7ec85c336 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -85,21 +85,33 @@ func (b *Balance) Clone() *Balance { // Returns the available number of seconds for a specified credit func (b *Balance) GetMinutesForCredit(origCD *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) { cd := origCD.Clone() - duration = time.Duration(b.Value) * time.Second + availableDuration := time.Duration(b.Value) * time.Second + duration = availableDuration credit = initialCredit cc, err := b.GetCost(cd) if err != nil { Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) return 0, credit } + if cc.deductConnectFee { + connectFee := cc.GetConnectFee() + if connectFee <= credit { + credit -= connectFee + // remove connect fee from the total cost + cc.Cost -= connectFee + } else { + return 0, credit + } + } if cc.Cost > 0 { duration = 0 for _, ts := range cc.Timespans { ts.createIncrementsSlice() for _, incr := range ts.Increments { - if incr.Cost <= credit { + if incr.Cost <= credit && availableDuration-incr.Duration >= 0 { credit -= incr.Cost duration += incr.Duration + availableDuration -= incr.Duration } else { return } diff --git a/engine/calldesc.go b/engine/calldesc.go index b1d467f67..cc87f0b33 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -471,9 +471,10 @@ func (origCD *CallDescriptor) GetMaxSessionDuration() (time.Duration, error) { // we must move the timestart for the interval with the available duration because // that was already checked cd.TimeStart = cd.TimeStart.Add(availableDuration) + // substract the connect fee cc, err := cd.GetCost() - if cc.deductConnectFee { + if availableDuration == 0 && cc.deductConnectFee { // only if we did not already used minutes availableCredit -= cc.GetConnectFee() } if err != nil { diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 43c02ebb2..4f12c1e99 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -461,7 +461,39 @@ func TestMaxDebitZeroDefinedRate(t *testing.T) { if cc.GetDuration() != 49*time.Second { t.Error("Error obtaining max debit duration: ", cc.GetDuration()) } - if cc.Cost != 0.9 { + if cc.Cost != 0.91 { + t.Error("Error in max debit cost: ", cc.Cost) + } +} + +func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP1000UKMOB_AT") + for _, at := range ap { + at.Execute() + } + ap, _ = accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd1 := &CallDescriptor{ + Direction: "*out", + TOR: "call", + Tenant: "cgrates.directvoip.co.uk", + Subject: "12345", + Account: "12345", + Destination: "447956", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 0, 40, 0, time.UTC), + LoopIndex: 0, + CallDuration: 0} + cc, err := cd1.MaxDebit() + if err != nil { + t.Error("Error maxdebiting: ", err) + } + if cc.GetDuration() != 40*time.Second { + t.Error("Error obtaining max debit duration: ", cc.GetDuration()) + } + if cc.Cost != 0.01 { t.Error("Error in max debit cost: ", cc.Cost) } } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index b07d14cea..4812f413c 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -64,7 +64,7 @@ LANDLINE_OFFPEAK,0,1,1s,1s,60s,*up,4 GBP_71,0.000000,5.55555,1s,1s,0s,*up,4 GBP_72,0.000000,7.77777,1s,1s,0s,*up,4 GBP_70,0.000000,1,1s,1s,0s,*up,4 -RT_UK_Mobile_BIG5_PKG,0,0,20s,20s,0s,*up,8 +RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s,*up,8 RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s,*up,8 ` destinationRates = ` From 8f47264248f98207e7a7d58b55695520fbc7817a Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 5 Mar 2014 14:51:18 +0200 Subject: [PATCH 4/6] check for duplicate account actions --- engine/loader_csv.go | 40 +++++++++++++++++++++------------------ engine/loader_csv_test.go | 14 ++++++-------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/engine/loader_csv.go b/engine/loader_csv.go index e57ec2e8a..3233d2f81 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -39,7 +39,7 @@ type CSVReader struct { actionsTimings map[string][]*ActionTiming actionsTriggers map[string][]*ActionTrigger aliases map[string]string - accountActions []*Account + accountActions map[string]*Account destinations []*Destination timings map[string]*utils.TPTiming rates map[string]*utils.TPRate @@ -60,6 +60,7 @@ func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingSto c.actions = make(map[string][]*Action) c.actionsTimings = make(map[string][]*ActionTiming) c.actionsTriggers = make(map[string][]*ActionTrigger) + c.accountActions = make(map[string]*Account) c.rates = make(map[string]*utils.TPRate) c.destinationRates = make(map[string]*utils.TPDestinationRate) c.timings = make(map[string]*utils.TPTiming) @@ -356,7 +357,7 @@ func (csvr *CSVReader) LoadDestinationRates() (err error) { tag := record[0] r, exists := csvr.rates[record[2]] if !exists { - return errors.New(fmt.Sprintf("Could not get rates for tag %v", record[2])) + return fmt.Errorf("Could not get rates for tag %v", record[2]) } destinationExists := false for _, d := range csvr.destinations { @@ -408,11 +409,11 @@ func (csvr *CSVReader) LoadRatingPlans() (err error) { tag := record[0] t, exists := csvr.timings[record[2]] if !exists { - return errors.New(fmt.Sprintf("Could not get timing for tag %v", record[2])) + return fmt.Errorf("Could not get timing for tag %v", record[2]) } drs, exists := csvr.destinationRates[record[1]] if !exists { - return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", record[1])) + return fmt.Errorf("Could not find destination rate for tag %v", record[1]) } rpl := NewRatingPlan(t, record[3]) plan, exists := csvr.ratingPlans[tag] @@ -441,7 +442,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { tenant, tor, direction, subject, fallbacksubject := record[0], record[1], record[2], record[3], record[6] at, err := utils.ParseDate(record[4]) if err != nil { - return errors.New(fmt.Sprintf("Cannot parse activation time from %v", record[4])) + return fmt.Errorf("Cannot parse activation time from %v", record[4]) } // extract aliases from subject aliases := strings.Split(subject, ";") @@ -464,7 +465,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { } } if !exists { - return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", record[5])) + return fmt.Errorf("Could not load rating plans for tag: %v", record[5]) } rpa := &RatingPlanActivation{ ActivationTime: at, @@ -529,7 +530,7 @@ func (csvr *CSVReader) LoadActions() (err error) { } else { units, err = strconv.ParseFloat(record[4], 64) if err != nil { - return errors.New(fmt.Sprintf("Could not parse action units: %v", err)) + return fmt.Errorf("Could not parse action units: %v", err) } } var balanceWeight float64 @@ -538,12 +539,12 @@ func (csvr *CSVReader) LoadActions() (err error) { } else { balanceWeight, err = strconv.ParseFloat(record[8], 64) if err != nil { - return errors.New(fmt.Sprintf("Could not parse action balance weight: %v", err)) + return fmt.Errorf("Could not parse action balance weight: %v", err) } } weight, err := strconv.ParseFloat(record[11], 64) if err != nil { - return errors.New(fmt.Sprintf("Could not parse action weight: %v", err)) + return fmt.Errorf("Could not parse action weight: %v", err) } a := &Action{ Id: utils.GenUUID(), @@ -563,7 +564,7 @@ func (csvr *CSVReader) LoadActions() (err error) { }, } if _, err := utils.ParseDate(a.ExpirationString); err != nil { - return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err)) + return fmt.Errorf("Could not parse expiration time: %v", err) } csvr.actions[tag] = append(csvr.actions[tag], a) } @@ -584,15 +585,15 @@ func (csvr *CSVReader) LoadActionTimings() (err error) { tag := record[0] _, exists := csvr.actions[record[1]] if !exists { - return errors.New(fmt.Sprintf("ActionPlan: Could not load the action for tag: %v", record[1])) + return fmt.Errorf("ActionPlan: Could not load the action for tag: %v", record[1]) } t, exists := csvr.timings[record[2]] if !exists { - return errors.New(fmt.Sprintf("ActionPlan: Could not load the timing for tag: %v", record[2])) + return fmt.Errorf("ActionPlan: Could not load the timing for tag: %v", record[2]) } weight, err := strconv.ParseFloat(record[3], 64) if err != nil { - return errors.New(fmt.Sprintf("ActionTiming: Could not parse action timing weight: %v", err)) + return fmt.Errorf("ActionTiming: Could not parse action timing weight: %v", err) } at := &ActionTiming{ Id: utils.GenUUID(), @@ -628,11 +629,11 @@ func (csvr *CSVReader) LoadActionTriggers() (err error) { tag := record[0] value, err := strconv.ParseFloat(record[4], 64) if err != nil { - return errors.New(fmt.Sprintf("Could not parse action trigger value: %v", err)) + return fmt.Errorf("Could not parse action trigger value: %v", err) } weight, err := strconv.ParseFloat(record[7], 64) if err != nil { - return errors.New(fmt.Sprintf("Could not parse action trigger weight: %v", err)) + return fmt.Errorf("Could not parse action trigger weight: %v", err) } at := &ActionTrigger{ Id: utils.GenUUID(), @@ -670,19 +671,22 @@ func (csvr *CSVReader) LoadAccountActions() (err error) { } } tag := fmt.Sprintf("%s:%s:%s", direction, tenant, account) + if _, alreadyDefined := csvr.accountActions[tag]; alreadyDefined { + return fmt.Errorf("Duplicate account action found: %s", tag) + } aTriggers, exists := csvr.actionsTriggers[record[4]] if record[4] != "" && !exists { // only return error if there was something ther for the tag - return errors.New(fmt.Sprintf("Could not get action triggers for tag %v", record[4])) + return fmt.Errorf("Could not get action triggers for tag %s", record[4]) } ub := &Account{ Id: tag, ActionTriggers: aTriggers, } - csvr.accountActions = append(csvr.accountActions, ub) + csvr.accountActions[tag] = ub aTimings, exists := csvr.actionsTimings[record[3]] if !exists { - log.Printf("Could not get action timing for tag %v", record[3]) + log.Printf("Could not get action timing for tag %s", record[3]) // must not continue here } for _, at := range aTimings { diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 4812f413c..9cb0227c6 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -131,13 +131,12 @@ MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,,10 MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,,10 SHARED,*topup,*monetary,*out,100,*unlimited,,,10,SG1,,10 TOPUP10_AC,*topup_reset,*monetary,*out,1,*unlimited,*any,,10,,,10 -TOPUP1000UKMOB_AC,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 +TOPUP10_AC,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 MORE_MINUTES,SHARED,ONE_TIME_RUN,10 TOPUP10_AT,TOPUP10_AC,ASAP,10 -TOPUP1000UKMOB_AT,TOPUP1000UKMOB_AC,ASAP,10 ` actionTriggers = ` STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10 @@ -149,7 +148,6 @@ STANDARD_TRIGGERS,*monetary,*out,*max_counter,5,FS_USERS,LOG_WARNING,10 accountActions = ` vdf,minitsboy;a1;a2,*out,MORE_MINUTES,STANDARD_TRIGGER cgrates.directvoip.co.uk,12345,*out,TOPUP10_AT,STANDARD_TRIGGERS -cgrates.directvoip.co.uk,12345,*out,TOPUP1000UKMOB_AT,STANDARD_TRIGGERS ` ) @@ -590,7 +588,7 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 4 { + if len(csvr.actions) != 3 { t.Error("Failed to load actions: ", csvr.actions) } as1 := csvr.actions["MINI"] @@ -699,7 +697,7 @@ func TestLoadSharedGroups(t *testing.T) { } func TestLoadActionTimings(t *testing.T) { - if len(csvr.actionsTimings) != 3 { + if len(csvr.actionsTimings) != 2 { t.Error("Failed to load action timings: ", csvr.actionsTimings) } atm := csvr.actionsTimings["MORE_MINUTES"][0] @@ -761,10 +759,10 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 3 { - t.Error("Failed to load account actions: ", csvr.accountActions[2]) + if len(csvr.accountActions) != 2 { + t.Error("Failed to load account actions: ", csvr.accountActions) } - aa := csvr.accountActions[0] + aa := csvr.accountActions["*out:vdf:minitsboy"] expected := &Account{ Id: "*out:vdf:minitsboy", ActionTriggers: csvr.actionsTriggers["STANDARD_TRIGGER"], From 71a50dc78e1dc30babbef687a287f0cd34625091 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 5 Mar 2014 14:56:54 +0200 Subject: [PATCH 5/6] duplicate check for db loader too --- engine/loader_db.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/engine/loader_db.go b/engine/loader_db.go index 6b05d289b..d378e670f 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -35,7 +35,7 @@ type DbReader struct { actions map[string][]*Action actionsTimings map[string][]*ActionTiming actionsTriggers map[string][]*ActionTrigger - accountActions []*Account + accountActions map[string]*Account destinations []*Destination aliases map[string]string timings map[string]*utils.TPTiming @@ -59,6 +59,7 @@ func NewDbReader(storDB LoadStorage, ratingDb RatingStorage, accountDb Accountin c.ratingProfiles = make(map[string]*RatingProfile) c.sharedGroups = make(map[string]*SharedGroup) c.aliases = make(map[string]string) + c.accountActions = make(map[string]*Account) return c } @@ -230,7 +231,7 @@ func (dbr *DbReader) LoadDestinationRates() (err error) { for _, dr := range drs.DestinationRates { rate, exists := dbr.rates[dr.RateId] if !exists { - return errors.New(fmt.Sprintf("Could not find rate for tag %v", dr.RateId)) + return fmt.Errorf("Could not find rate for tag %v", dr.RateId) } dr.Rate = rate destinationExists := false @@ -244,7 +245,7 @@ func (dbr *DbReader) LoadDestinationRates() (err error) { if dbExists, err := dbr.dataDb.HasData(DESTINATION_PREFIX, dr.DestinationId); err != nil { return err } else if !dbExists { - return errors.New(fmt.Sprintf("Could not get destination for tag %v", dr.DestinationId)) + return fmt.Errorf("Could not get destination for tag %v", dr.DestinationId) } } } @@ -261,12 +262,12 @@ func (dbr *DbReader) LoadRatingPlans() error { for _, rplBnd := range rplBnds { t, exists := dbr.timings[rplBnd.TimingId] if !exists { - return errors.New(fmt.Sprintf("Could not get timing for tag %v", rplBnd.TimingId)) + return fmt.Errorf("Could not get timing for tag %v", rplBnd.TimingId) } rplBnd.SetTiming(t) drs, exists := dbr.destinationRates[rplBnd.DestinationRatesId] if !exists { - return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", rplBnd.DestinationRatesId)) + return fmt.Errorf("Could not find destination rate for tag %v", rplBnd.DestinationRatesId) } plan, exists := dbr.ratingPlans[tag] if !exists { @@ -299,14 +300,14 @@ func (dbr *DbReader) LoadRatingProfiles() error { for _, tpRa := range tpRpf.RatingPlanActivations { at, err := utils.ParseDate(tpRa.ActivationTime) if err != nil { - return errors.New(fmt.Sprintf("Cannot parse activation time from %v", tpRa.ActivationTime)) + return fmt.Errorf("Cannot parse activation time from %v", tpRa.ActivationTime) } _, exists := dbr.ratingPlans[tpRa.RatingPlanId] if !exists { if dbExists, err := dbr.dataDb.HasData(RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil { return err } else if !dbExists { - return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", tpRa.RatingPlanId)) + return fmt.Errorf("Could not load rating plans for tag: %v", tpRa.RatingPlanId) } } rpf.RatingPlanActivations = append(rpf.RatingPlanActivations, @@ -382,7 +383,7 @@ func (dbr *DbReader) LoadRatingProfileFiltered(qriedRpf *utils.TPRatingProfile) var resultRatingProfile *RatingProfile mpTpRpfs, err := dbr.storDb.GetTpRatingProfiles(qriedRpf) //map[string]*utils.TPRatingProfile if err != nil { - return fmt.Errorf("No RateProfile for filter %v, error: %s", qriedRpf, err.Error()) + return fmt.Errorf("No RateProfile for filter %v, error: %v", qriedRpf, err) } for _, tpRpf := range mpTpRpfs { // Logger.Debug(fmt.Sprintf("Rating profile: %v", tpRpf)) @@ -390,14 +391,14 @@ func (dbr *DbReader) LoadRatingProfileFiltered(qriedRpf *utils.TPRatingProfile) for _, tpRa := range tpRpf.RatingPlanActivations { at, err := utils.ParseDate(tpRa.ActivationTime) if err != nil { - return errors.New(fmt.Sprintf("Cannot parse activation time from %v", tpRa.ActivationTime)) + return fmt.Errorf("Cannot parse activation time from %v", tpRa.ActivationTime) } _, exists := dbr.ratingPlans[tpRa.RatingPlanId] if !exists { if dbExists, err := dbr.dataDb.HasData(RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil { return err } else if !dbExists { - return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", tpRa.RatingPlanId)) + return fmt.Errorf("Could not load rating plans for tag: %v", tpRa.RatingPlanId) } } resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations, @@ -457,11 +458,11 @@ func (dbr *DbReader) LoadActionTimings() (err error) { _, exists := dbr.actions[at.ActionsId] if !exists { - return errors.New(fmt.Sprintf("ActionTiming: Could not load the action for tag: %v", at.ActionsId)) + return fmt.Errorf("ActionTiming: Could not load the action for tag: %v", at.ActionsId) } t, exists := dbr.timings[at.TimingId] if !exists { - return errors.New(fmt.Sprintf("ActionTiming: Could not load the timing for tag: %v", at.TimingId)) + return fmt.Errorf("ActionTiming: Could not load the timing for tag: %v", at.TimingId) } actTmg := &ActionTiming{ Id: utils.GenUUID(), @@ -513,6 +514,9 @@ func (dbr *DbReader) LoadAccountActions() (err error) { return err } for _, aa := range acs { + if _, alreadyDefined := dbr.accountActions[aa.KeyId()]; alreadyDefined { + return fmt.Errorf("Duplicate account action found: %s", aa.KeyId()) + } // extract aliases from subject aliases := strings.Split(aa.Account, ";") if len(aliases) > 1 { @@ -523,13 +527,13 @@ func (dbr *DbReader) LoadAccountActions() (err error) { } aTriggers, exists := dbr.actionsTriggers[aa.ActionTriggersId] if !exists { - return errors.New(fmt.Sprintf("Could not get action triggers for tag %v", aa.ActionTriggersId)) + return fmt.Errorf("Could not get action triggers for tag %v", aa.ActionTriggersId) } ub := &Account{ Id: aa.KeyId(), ActionTriggers: aTriggers, } - dbr.accountActions = append(dbr.accountActions, ub) + dbr.accountActions[aa.KeyId()] = ub aTimings, exists := dbr.actionsTimings[aa.ActionPlanId] if !exists { log.Printf("Could not get action timing for tag %v", aa.ActionPlanId) From fdeecafe374fa6af143595c5db487066188acbc0 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 5 Mar 2014 16:54:29 +0200 Subject: [PATCH 6/6] make sure the aliases are up-to-date after load --- engine/calldesc_test.go | 12 ++---------- engine/loader_csv.go | 6 ++++++ engine/loader_csv_test.go | 5 +++-- engine/loader_db.go | 6 ++++++ engine/storage_interface.go | 1 + engine/storage_map.go | 9 +++++++++ engine/storage_redis.go | 20 ++++++++++++++++++++ 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 4f12c1e99..4f7e32eca 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -435,11 +435,7 @@ func TestDebitAndMaxDebit(t *testing.T) { } func TestMaxDebitZeroDefinedRate(t *testing.T) { - ap, _ := accountingStorage.GetActionTimings("TOPUP1000UKMOB_AT") - for _, at := range ap { - at.Execute() - } - ap, _ = accountingStorage.GetActionTimings("TOPUP10_AT") + ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") for _, at := range ap { at.Execute() } @@ -467,11 +463,7 @@ func TestMaxDebitZeroDefinedRate(t *testing.T) { } func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) { - ap, _ := accountingStorage.GetActionTimings("TOPUP1000UKMOB_AT") - for _, at := range ap { - at.Execute() - } - ap, _ = accountingStorage.GetActionTimings("TOPUP10_AT") + ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") for _, at := range ap { at.Execute() } diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 3233d2f81..f0fb9b8de 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -40,6 +40,7 @@ type CSVReader struct { actionsTriggers map[string][]*ActionTrigger aliases map[string]string accountActions map[string]*Account + dirtyAliases []string // used to clean aliases that might have changed destinations []*Destination timings map[string]*utils.TPTiming rates map[string]*utils.TPRate @@ -250,6 +251,9 @@ func (csvr *CSVReader) WriteToDatabase(flush, verbose bool) (err error) { if verbose { log.Print("Aliases") } + if err := dataStorage.RemoveAccountAliases(csvr.dirtyAliases); err != nil { + return err + } for key, alias := range csvr.aliases { err = dataStorage.SetAlias(key, alias) if err != nil { @@ -444,6 +448,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { if err != nil { return fmt.Errorf("Cannot parse activation time from %v", record[4]) } + csvr.dirtyAliases = append(csvr.dirtyAliases, subject) // extract aliases from subject aliases := strings.Split(subject, ";") if len(aliases) > 1 { @@ -662,6 +667,7 @@ func (csvr *CSVReader) LoadAccountActions() (err error) { } for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() { tenant, account, direction := record[0], record[1], record[2] + csvr.dirtyAliases = append(csvr.dirtyAliases, account) // extract aliases from subject aliases := strings.Split(account, ";") if len(aliases) > 1 { diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 9cb0227c6..b59e18451 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -131,12 +131,13 @@ MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,,10 MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,,10 SHARED,*topup,*monetary,*out,100,*unlimited,,,10,SG1,,10 TOPUP10_AC,*topup_reset,*monetary,*out,1,*unlimited,*any,,10,,,10 -TOPUP10_AC,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 +TOPUP10_AC1,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 MORE_MINUTES,SHARED,ONE_TIME_RUN,10 TOPUP10_AT,TOPUP10_AC,ASAP,10 +TOPUP10_AT,TOPUP10_AC1,ASAP,10 ` actionTriggers = ` STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10 @@ -588,7 +589,7 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 3 { + if len(csvr.actions) != 4 { t.Error("Failed to load actions: ", csvr.actions) } as1 := csvr.actions["MINI"] diff --git a/engine/loader_db.go b/engine/loader_db.go index d378e670f..ac44b6ecc 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -36,6 +36,7 @@ type DbReader struct { actionsTimings map[string][]*ActionTiming actionsTriggers map[string][]*ActionTrigger accountActions map[string]*Account + dirtyAliases []string // used to clean aliases that might have changed destinations []*Destination aliases map[string]string timings map[string]*utils.TPTiming @@ -195,6 +196,9 @@ func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) { if verbose { log.Print("Aliases") } + if err := storage.RemoveAccountAliases(dbr.dirtyAliases); err != nil { + return err + } for key, alias := range dbr.aliases { err = storage.SetAlias(key, alias) if err != nil { @@ -288,6 +292,7 @@ func (dbr *DbReader) LoadRatingProfiles() error { return err } for _, tpRpf := range mpTpRpfs { + dbr.dirtyAliases = append(dbr.dirtyAliases, tpRpf.Subject) // extract aliases from subject aliases := strings.Split(tpRpf.Subject, ";") if len(aliases) > 1 { @@ -517,6 +522,7 @@ func (dbr *DbReader) LoadAccountActions() (err error) { if _, alreadyDefined := dbr.accountActions[aa.KeyId()]; alreadyDefined { return fmt.Errorf("Duplicate account action found: %s", aa.KeyId()) } + dbr.dirtyAliases = append(dbr.dirtyAliases, aa.Account) // extract aliases from subject aliases := strings.Split(aa.Account, ";") if len(aliases) > 1 { diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 5af838c2b..a3a3fa734 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -78,6 +78,7 @@ type RatingStorage interface { SetRatingProfile(*RatingProfile) error GetAlias(string, bool) (string, error) SetAlias(string, string) error + RemoveAccountAliases([]string) error GetDestination(string) (*Destination, error) SetDestination(*Destination) error } diff --git a/engine/storage_map.go b/engine/storage_map.go index b9e319aab..b0fb2aa9f 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -221,6 +221,15 @@ func (ms *MapStorage) SetAlias(key, alias string) (err error) { return } +func (ms *MapStorage) RemoveAccountAliases(accounts []string) (err error) { + for key, value := range ms.dict { + if strings.HasPrefix(key, ALIAS_PREFIX) && utils.IsSliceMember(accounts, string(value)) { + delete(ms.dict, key) + } + } + return +} + func (ms *MapStorage) GetDestination(key string) (dest *Destination, err error) { key = DESTINATION_PREFIX + key if values, ok := ms.dict[key]; ok { diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 242a54f6c..cf0aa29f8 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -287,6 +287,26 @@ func (rs *RedisStorage) SetAlias(key, alias string) (err error) { return } +func (rs *RedisStorage) RemoveAccountAliases(accounts []string) (err error) { + if alsKeys, err := rs.db.Keys(ALIAS_PREFIX + "*"); err != nil { + return err + } else { + for _, key := range alsKeys { + alias, err := rs.GetAlias(key, true) + if err != nil { + return err + } + if utils.IsSliceMember(accounts, alias) { + if _, err = rs.db.Del(key); err != nil { + return err + } + } + } + } + + return +} + func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) { key = DESTINATION_PREFIX + key var values []byte