From 77ea2754cb6429d75df594f148f291c69a2d8317 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 7 Feb 2014 19:54:09 +0200 Subject: [PATCH 1/2] GetMaxSessionDuration fix should return no more than initial call descriptor duration --- engine/calldesc.go | 17 +++++++++----- engine/calldesc_test.go | 51 +++++++++++++++++++++++++++++++++++++---- utils/coreutils.go | 8 +++++++ utils/utils_test.go | 10 ++++++++ 4 files changed, 75 insertions(+), 11 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 755a0a61f..13f5dce1c 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -21,6 +21,7 @@ package engine import ( "errors" "fmt" + "log" "log/syslog" "time" //"encoding/json" @@ -420,7 +421,6 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { /* Returns the approximate max allowed session for user balance. It will try the max amount received in the call descriptor -and will decrease it by 10% for nine times. So if the user has little credit it will still allow 10% of the initial amount. If the user has no credit then it will return 0. If the user has postpayed plan it returns -1. */ @@ -449,15 +449,15 @@ func (origCd *CallDescriptor) GetMaxSessionDuration() (time.Duration, error) { return 0, err } //Logger.Debug(fmt.Sprintf("availableDuration: %v, availableCredit: %v", availableDuration, availableCredit)) - // check for zero balance - if availableCredit == 0 { - return availableDuration, nil - } initialDuration := cd.TimeEnd.Sub(cd.TimeStart) if initialDuration <= availableDuration { // there are enough minutes for requested interval return initialDuration, nil } + // check for zero balance + if availableCredit == 0 { + return utils.MinDuration(initialDuration, availableDuration), nil + } //Logger.Debug(fmt.Sprintf("initial Duration: %v", initialDuration)) // we must move the timestart for the interval with the available duration because // that was already checked @@ -482,7 +482,11 @@ func (origCd *CallDescriptor) GetMaxSessionDuration() (time.Duration, error) { } } } - return availableDuration, nil + log.Print(initialDuration, availableDuration, initialDuration < availableDuration) + if initialDuration < availableDuration { + return initialDuration, nil + } + return utils.MinDuration(initialDuration, availableDuration), nil } // Interface method used to add/substract an amount of cents or bonus seconds (as returned by GetCost method) @@ -527,6 +531,7 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { if err != nil || remainingDuration == 0 { return new(CallCost), errors.New("no more credit") } + log.Print("REM_DUR: ", remainingDuration) if remainingDuration > 0 { // for postpaying client returns -1 cd.TimeEnd = cd.TimeStart.Add(remainingDuration) } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 1e6ceb7db..b285b26dd 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -296,7 +296,7 @@ func TestMaxSessionTimeWithUserBalance(t *testing.T) { Destination: "0723", Amount: 1000} result, err := cd.GetMaxSessionDuration() - expected := 300 * time.Second + expected := time.Minute if result != expected || err != nil { t.Errorf("Expected %v was %v", expected, result) } @@ -314,7 +314,7 @@ func TestMaxSessionTimeWithUserBalanceAccount(t *testing.T) { Destination: "0723", Amount: 1000} result, err := cd.GetMaxSessionDuration() - expected := 300 * time.Second + expected := time.Minute if result != expected || err != nil { t.Errorf("Expected %v was %v", expected, result) } @@ -331,8 +331,8 @@ func TestMaxSessionTimeNoCredit(t *testing.T) { Destination: "0723", Amount: 5400} result, err := cd.GetMaxSessionDuration() - if result != 100*time.Second || err != nil { - t.Errorf("Expected %v was %v", 100, result) + if result != time.Minute || err != nil { + t.Errorf("Expected %v was %v", time.Minute, result) } } @@ -343,7 +343,8 @@ func TestMaxSessionModifiesCallDesc(t *testing.T) { Direction: "*out", TOR: "0", Tenant: "vdf", - Subject: "broker", + Subject: "minu_from_tm", + Account: "minu", Destination: "0723", Amount: 5400} initial := cd.Clone() @@ -353,6 +354,46 @@ func TestMaxSessionModifiesCallDesc(t *testing.T) { } } +func TestMaxDebitDurationNoGreatherThanInitialDuration(t *testing.T) { + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "minu_from_tm", + Account: "minu", + Destination: "0723", + Amount: 1000} + initialDuration := cd.TimeEnd.Sub(cd.TimeStart) + result, _ := cd.GetMaxSessionDuration() + if result > initialDuration { + t.Error("max session duration greather than initial duration", initialDuration, result) + } +} + +func TestDebitAndMaxDebit(t *testing.T) { + cd1 := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "minu_from_tm", + Account: "minu", + Destination: "0723", + Amount: 5400} + cd2 := cd1.Clone() + cc1, err1 := cd1.Debit() + cc2, err2 := cd2.MaxDebit() + if err1 != nil || err2 != nil { + t.Error("Error debiting and/or maxdebiting: ", err1, err2) + } + if !reflect.DeepEqual(cc1, cc2) { + t.Errorf("Debit and MaxDebit differ: %+v != %+v", cc1, cc2) + } +} + /*********************************** BENCHMARKS ***************************************/ func BenchmarkStorageGetting(b *testing.B) { b.StopTimer() diff --git a/utils/coreutils.go b/utils/coreutils.go index cf1fce232..08506ac1c 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -187,3 +187,11 @@ func ParseDurationWithSecs(durStr string) (time.Duration, error) { func BalanceKey(tenant, account, direction string) string { return fmt.Sprintf("%s:%s:%s", direction, tenant, account) } + +// returns the minimum duration between the two +func MinDuration(d1, d2 time.Duration) time.Duration { + if d1 < d2 { + return d1 + } + return d2 +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 676824f70..36f53d1a7 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -336,3 +336,13 @@ func TestParseDurationWithSecs(t *testing.T) { t.Error("Parsed different than expected") } } + +func TestMinDuration(t *testing.T) { + d1, _ := time.ParseDuration("1m") + d2, _ := time.ParseDuration("59s") + minD1 := MinDuration(d1, d2) + minD2 := MinDuration(d2, d1) + if minD1 != d2 || minD2 != d2 { + t.Error("Error getting min duration: ", minD1, minD2) + } +} From 914fe77117d12a3aecc36a85b1e16c1de59bb58c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 7 Feb 2014 22:26:39 +0200 Subject: [PATCH 2/2] rating subject can have multiple predefined values Valid values can start with *zero + any duration 1m, 15m, 1h10m --- engine/balances.go | 80 +++++++++--------- engine/calldesc.go | 1 - engine/userbalance.go | 3 - engine/userbalance_test.go | 22 ++--- utils/consts.go | 163 +++++++++++++++++++------------------ utils/coreutils.go | 11 +++ utils/utils_test.go | 10 +++ 7 files changed, 151 insertions(+), 139 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index c8e9217ba..f1f067849 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -136,56 +136,50 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *UserBalance, moneyB if increment.paid { continue } - if b.RateSubject == ZEROSECOND || b.RateSubject == "" { - amount := increment.Duration.Seconds() - if b.Value >= amount { - b.Value -= amount - increment.SetMinuteBalance(b.Uuid) - increment.MinuteInfo = &MinuteInfo{cc.Destination, amount} - increment.Cost = 0 - increment.paid = true - if count { - ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) - } + if duration, err := utils.ParseZeroRatingSubject(b.RateSubject); err == nil { + seconds := duration.Seconds() + amount := seconds + if seconds == 1 { + amount = increment.Duration.Seconds() } - continue - } - if b.RateSubject == ZEROMINUTE { - amount := time.Minute.Seconds() if b.Value >= amount { // balance has at least 60 seconds newTs := ts - if incrementIndex != 0 { - // if increment it's not at the begining we must split the timespan - newTs = ts.SplitByIncrement(incrementIndex) - } - newTs.RoundToDuration(time.Minute) - newTs.RateInterval = &RateInterval{ - Rating: &RIRate{ - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0, - RateIncrement: time.Minute, - RateUnit: time.Minute, + inc := increment + if seconds > 1 { // we need to recreate increments + if incrementIndex != 0 { + // if increment it's not at the begining we must split the timespan + newTs = ts.SplitByIncrement(incrementIndex) + } + newTs.RoundToDuration(time.Minute) + newTs.RateInterval = &RateInterval{ + Rating: &RIRate{ + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0, + RateIncrement: time.Minute, + RateUnit: time.Minute, + }, }, }, - }, + } + newTs.createIncrementsSlice() + // insert the new timespan + if newTs != ts { + tsIndex++ + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) + cc.Timespans[tsIndex] = newTs + tsWasSplit = true + } + cc.Timespans.RemoveOverlapedFromIndex(tsIndex) + inc = newTs.Increments[0] } - newTs.createIncrementsSlice() - // insert the new timespan - if newTs != ts { - tsIndex++ - cc.Timespans = append(cc.Timespans, nil) - copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) - cc.Timespans[tsIndex] = newTs - tsWasSplit = true - } - cc.Timespans.RemoveOverlapedFromIndex(tsIndex) b.Value -= amount - newTs.Increments[0].SetMinuteBalance(b.Uuid) - newTs.Increments[0].MinuteInfo = &MinuteInfo{cc.Destination, amount} - newTs.Increments[0].Cost = 0 - newTs.Increments[0].paid = true + inc.SetMinuteBalance(b.Uuid) + inc.MinuteInfo = &MinuteInfo{cc.Destination, amount} + inc.Cost = 0 + inc.paid = true if count { ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) } diff --git a/engine/calldesc.go b/engine/calldesc.go index 13f5dce1c..8be71b04d 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -531,7 +531,6 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { if err != nil || remainingDuration == 0 { return new(CallCost), errors.New("no more credit") } - log.Print("REM_DUR: ", remainingDuration) if remainingDuration > 0 { // for postpaying client returns -1 cd.TimeEnd = cd.TimeStart.Add(remainingDuration) } diff --git a/engine/userbalance.go b/engine/userbalance.go index e61ebfd4c..ce1c05a2d 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -48,9 +48,6 @@ const ( TRIGGER_MAX_COUNTER = "*max_counter" TRIGGER_MIN_BALANCE = "*min_balance" TRIGGER_MAX_BALANCE = "*max_balance" - // minute subjects - ZEROSECOND = "*zerosecond" - ZEROMINUTE = "*zerominute" ) var ( diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 4f91fd63f..b176d841b 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -214,7 +214,7 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { } func TestDebitCreditZeroSecond(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -243,7 +243,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { } func TestDebitCreditZeroMinute(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -276,8 +276,8 @@ func TestDebitCreditZeroMinute(t *testing.T) { } } func TestDebitCreditZeroMixedMinute(t *testing.T) { - b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: "*zero1m"} + b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -311,7 +311,7 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { } func TestDebitCreditNoCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -351,7 +351,7 @@ func TestDebitCreditNoCredit(t *testing.T) { } func TestDebitCreditHasCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -393,7 +393,7 @@ func TestDebitCreditHasCredit(t *testing.T) { } func TestDebitCreditSplitMinutesMoney(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -430,7 +430,7 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } func TestDebitCreditMoreTimespans(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -467,8 +467,8 @@ func TestDebitCreditMoreTimespans(t *testing.T) { } func TestDebitCreditMoreTimespansMixed(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -506,7 +506,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { } func TestDebitCreditNoConectFeeCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", diff --git a/utils/consts.go b/utils/consts.go index 8cadcd6ef..33982b065 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1,87 +1,88 @@ package utils const ( - VERSION = "0.9.1c4" - POSTGRES = "postgres" - MYSQL = "mysql" - MONGO = "mongo" - REDIS = "redis" - LOCALHOST = "127.0.0.1" - FSCDR_FILE_CSV = "freeswitch_file_csv" - FSCDR_HTTP_JSON = "freeswitch_http_json" - NOT_IMPLEMENTED = "not implemented" - PREPAID = "prepaid" - POSTPAID = "postpaid" - PSEUDOPREPAID = "pseudoprepaid" - RATED = "rated" - ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" - ERR_SERVER_ERROR = "SERVER_ERROR" - ERR_NOT_FOUND = "NOT_FOUND" - ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING" - ERR_EXISTS = "EXISTS" - ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE" - TBL_TP_TIMINGS = "tp_timings" - TBL_TP_DESTINATIONS = "tp_destinations" - TBL_TP_RATES = "tp_rates" - TBL_TP_DESTINATION_RATES = "tp_destination_rates" - TBL_TP_RATING_PLANS = "tp_rating_plans" - TBL_TP_RATE_PROFILES = "tp_rating_profiles" - TBL_TP_ACTIONS = "tp_actions" - TBL_TP_ACTION_PLANS = "tp_action_plans" - TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" - TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions" - TBL_CDRS_PRIMARY = "cdrs_primary" - TBL_CDRS_EXTRA = "cdrs_extra" - TBL_COST_DETAILS = "cost_details" - TBL_RATED_CDRS = "rated_cdrs" - TIMINGS_CSV = "Timings.csv" - DESTINATIONS_CSV = "Destinations.csv" - RATES_CSV = "Rates.csv" - DESTINATION_RATES_CSV = "DestinationRates.csv" - RATING_PLANS_CSV = "RatingPlans.csv" - RATING_PROFILES_CSV = "RatingProfiles.csv" - ACTIONS_CSV = "Actions.csv" - ACTION_PLANS_CSV = "ActionPlans.csv" - ACTION_TRIGGERS_CSV = "ActionTriggers.csv" - ACCOUNT_ACTIONS_CSV = "AccountActions.csv" - TIMINGS_NRCOLS = 6 - DESTINATIONS_NRCOLS = 2 - RATES_NRCOLS = 8 - DESTINATION_RATES_NRCOLS = 3 - DESTRATE_TIMINGS_NRCOLS = 4 - RATE_PROFILES_NRCOLS = 7 - ACTIONS_NRCOLS = 11 - ACTION_PLANS_NRCOLS = 4 - ACTION_TRIGGERS_NRCOLS = 8 - ACCOUNT_ACTIONS_NRCOLS = 5 - ROUNDING_UP = "*up" - ROUNDING_MIDDLE = "*middle" - ROUNDING_DOWN = "*down" - ANY = "*any" - COMMENT_CHAR = '#' - CSV_SEP = ',' - FALLBACK_SEP = ';' - JSON = "json" - MSGPACK = "msgpack" - CSV_LOAD = "CSVLOAD" - CGRID = "cgrid" - ACCID = "accid" - CDRHOST = "cdrhost" - CDRSOURCE = "cdrsource" - REQTYPE = "reqtype" - DIRECTION = "direction" - TENANT = "tenant" - TOR = "tor" - ACCOUNT = "account" - SUBJECT = "subject" - DESTINATION = "destination" - ANSWER_TIME = "answer_time" - DURATION = "duration" - DEFAULT_RUNID = "default" - STATIC_VALUE_PREFIX = "^" - CDRE_CSV = "csv" - CDRE_DRYRUN = "dry_run" - INTERNAL = "internal" + VERSION = "0.9.1c4" + POSTGRES = "postgres" + MYSQL = "mysql" + MONGO = "mongo" + REDIS = "redis" + LOCALHOST = "127.0.0.1" + FSCDR_FILE_CSV = "freeswitch_file_csv" + FSCDR_HTTP_JSON = "freeswitch_http_json" + NOT_IMPLEMENTED = "not implemented" + PREPAID = "prepaid" + POSTPAID = "postpaid" + PSEUDOPREPAID = "pseudoprepaid" + RATED = "rated" + ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" + ERR_SERVER_ERROR = "SERVER_ERROR" + ERR_NOT_FOUND = "NOT_FOUND" + ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING" + ERR_EXISTS = "EXISTS" + ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE" + TBL_TP_TIMINGS = "tp_timings" + TBL_TP_DESTINATIONS = "tp_destinations" + TBL_TP_RATES = "tp_rates" + TBL_TP_DESTINATION_RATES = "tp_destination_rates" + TBL_TP_RATING_PLANS = "tp_rating_plans" + TBL_TP_RATE_PROFILES = "tp_rating_profiles" + TBL_TP_ACTIONS = "tp_actions" + TBL_TP_ACTION_PLANS = "tp_action_plans" + TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" + TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions" + TBL_CDRS_PRIMARY = "cdrs_primary" + TBL_CDRS_EXTRA = "cdrs_extra" + TBL_COST_DETAILS = "cost_details" + TBL_RATED_CDRS = "rated_cdrs" + TIMINGS_CSV = "Timings.csv" + DESTINATIONS_CSV = "Destinations.csv" + RATES_CSV = "Rates.csv" + DESTINATION_RATES_CSV = "DestinationRates.csv" + RATING_PLANS_CSV = "RatingPlans.csv" + RATING_PROFILES_CSV = "RatingProfiles.csv" + ACTIONS_CSV = "Actions.csv" + ACTION_PLANS_CSV = "ActionPlans.csv" + ACTION_TRIGGERS_CSV = "ActionTriggers.csv" + ACCOUNT_ACTIONS_CSV = "AccountActions.csv" + TIMINGS_NRCOLS = 6 + DESTINATIONS_NRCOLS = 2 + RATES_NRCOLS = 8 + DESTINATION_RATES_NRCOLS = 3 + DESTRATE_TIMINGS_NRCOLS = 4 + RATE_PROFILES_NRCOLS = 7 + ACTIONS_NRCOLS = 11 + ACTION_PLANS_NRCOLS = 4 + ACTION_TRIGGERS_NRCOLS = 8 + ACCOUNT_ACTIONS_NRCOLS = 5 + ROUNDING_UP = "*up" + ROUNDING_MIDDLE = "*middle" + ROUNDING_DOWN = "*down" + ANY = "*any" + COMMENT_CHAR = '#' + CSV_SEP = ',' + FALLBACK_SEP = ';' + JSON = "json" + MSGPACK = "msgpack" + CSV_LOAD = "CSVLOAD" + CGRID = "cgrid" + ACCID = "accid" + CDRHOST = "cdrhost" + CDRSOURCE = "cdrsource" + REQTYPE = "reqtype" + DIRECTION = "direction" + TENANT = "tenant" + TOR = "tor" + ACCOUNT = "account" + SUBJECT = "subject" + DESTINATION = "destination" + ANSWER_TIME = "answer_time" + DURATION = "duration" + DEFAULT_RUNID = "default" + STATIC_VALUE_PREFIX = "^" + CDRE_CSV = "csv" + CDRE_DRYRUN = "dry_run" + INTERNAL = "internal" + ZERO_RATING_SUBJECT_PREFIX = "*zero" ) var ( diff --git a/utils/coreutils.go b/utils/coreutils.go index 08506ac1c..80ebb4d5b 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -195,3 +195,14 @@ func MinDuration(d1, d2 time.Duration) time.Duration { } return d2 } + +func ParseZeroRatingSubject(rateSubj string) (time.Duration, error) { + if rateSubj == "" { + rateSubj = ZERO_RATING_SUBJECT_PREFIX + "1s" + } + if !strings.HasPrefix(rateSubj, ZERO_RATING_SUBJECT_PREFIX) { + return 0, errors.New("malformed rating subject: " + rateSubj) + } + durStr := rateSubj[len(ZERO_RATING_SUBJECT_PREFIX):] + return time.ParseDuration(durStr) +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 36f53d1a7..b99df7647 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -346,3 +346,13 @@ func TestMinDuration(t *testing.T) { t.Error("Error getting min duration: ", minD1, minD2) } } + +func TestParseZeroRatingSubject(t *testing.T) { + subj := []string{"", "*zero1s", "*zero5m", "*zero10h"} + dur := []time.Duration{time.Second, time.Second, 5 * time.Minute, 10 * time.Hour} + for i, s := range subj { + if d, err := ParseZeroRatingSubject(s); err != nil || d != dur[i] { + t.Error("Error parsing rating subject: ", s, d, err) + } + } +}