diff --git a/cmd/cgr-loader/cgr-loader.go b/cmd/cgr-loader/cgr-loader.go index 54afcb49e..faf3be155 100644 --- a/cmd/cgr-loader/cgr-loader.go +++ b/cmd/cgr-loader/cgr-loader.go @@ -36,7 +36,7 @@ import ( var ( //separator = flag.String("separator", ",", "Default field separator") cgrConfig, _ = config.NewDefaultCGRConfig() - migrateRC8 = flag.String("migrate_rc8", "", "Migrate Accounts, Actions, ActionTriggers and DerivedChargers to RC8 structures, possible values: *all,acc,atr,act,dcs,apl") + migrateRC8 = flag.String("migrate_rc8", "", "Migrate Accounts, Actions, ActionTriggers, DerivedChargers, ActionPlans and SharedGroups to RC8 structures, possible values: *all,acc,atr,act,dcs,apl,shg") tpdb_type = flag.String("tpdb_type", cgrConfig.TpDbType, "The type of the TariffPlan database ") tpdb_host = flag.String("tpdb_host", cgrConfig.TpDbHost, "The TariffPlan host to connect to.") tpdb_port = flag.String("tpdb_port", cgrConfig.TpDbPort, "The TariffPlan port to bind to.") @@ -147,6 +147,11 @@ func main() { log.Print(err.Error()) } } + if strings.Contains(*migrateRC8, "shg") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8rat.migrateSharedGroups(); err != nil { + log.Print(err.Error()) + } + } log.Print("Done!") return } diff --git a/cmd/cgr-loader/migrator_rc8.go b/cmd/cgr-loader/migrator_rc8.go index 421da2e40..08eca3650 100644 --- a/cmd/cgr-loader/migrator_rc8.go +++ b/cmd/cgr-loader/migrator_rc8.go @@ -145,6 +145,12 @@ func (at *ActionPlan) IsASAP() bool { return at.Timing.Timing.StartTime == utils.ASAP } +type SharedGroup struct { + Id string + AccountParameters map[string]*engine.SharingParameters + MemberIds []string +} + type ActionPlans []*ActionPlan func (mig MigratorRC8) migrateAccounts() error { @@ -519,3 +525,41 @@ func (mig MigratorRC8) migrateActionPlans() error { } return nil } + +func (mig MigratorRC8) migrateSharedGroups() error { + keys, err := mig.db.Cmd("KEYS", utils.SHARED_GROUP_PREFIX+"*").List() + if err != nil { + return err + } + newShgMap := make(map[string]*engine.SharedGroup, len(keys)) + for _, key := range keys { + log.Printf("Migrating shared groups: %s...", key) + oldShg := SharedGroup{} + var values []byte + if values, err = mig.db.Cmd("GET", key).Bytes(); err == nil { + if err := mig.ms.Unmarshal(values, &oldShg); err != nil { + return err + } + } + newShg := &engine.SharedGroup{ + Id: oldShg.Id, + AccountParameters: oldShg.AccountParameters, + MemberIds: make(utils.StringMap), + } + for _, accID := range oldShg.MemberIds { + newShg.MemberIds[accID] = true + } + newShgMap[key] = newShg + } + // write data back + for key, shg := range newShgMap { + result, err := mig.ms.Marshal(&shg) + if err != nil { + return err + } + if err = mig.db.Cmd("SET", key, result).Err; err != nil { + return err + } + } + return nil +} diff --git a/console/smg_event.go b/console/smg_event.go new file mode 100644 index 000000000..0ce6cfacd --- /dev/null +++ b/console/smg_event.go @@ -0,0 +1,87 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) 2012-2015 ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package console + +import ( + "strings" + + "github.com/cgrates/cgrates/sessionmanager" +) + +type AttrSmgEvent struct { + Method string // shoul be ignored after RPC call + sessionmanager.SMGenericEvent +} + +func init() { + c := &CmdSmgEvent{ + name: "smg_event", + } + commands[c.Name()] = c + c.CommandExecuter = &CommandExecuter{c} +} + +// Commander implementation +type CmdSmgEvent struct { + name string + rpcMethod string + rpcParams interface{} + *CommandExecuter +} + +func (self *CmdSmgEvent) Name() string { + return self.name +} + +func (self *CmdSmgEvent) RpcMethod() string { + return self.rpcMethod +} + +func (self *CmdSmgEvent) RpcParams(reset bool) interface{} { + if reset || self.rpcParams == nil { + self.rpcParams = &AttrSmgEvent{} + } + return self.rpcParams +} + +func (self *CmdSmgEvent) PostprocessRpcParams() error { + param := self.rpcParams.(*AttrSmgEvent) + self.rpcMethod = "SMGenericV1." + param.Method + self.rpcParams = param.SMGenericEvent + return nil +} + +func (self *CmdSmgEvent) RpcResult() interface{} { + methodElems := strings.Split(self.rpcMethod, ".") + if len(methodElems) != 2 { + return nil + } + switch methodElems[1] { + case "SessionEnd", "ChargeEvent", "ProcessCdr": + var s string + return &s + case "SessionStart", "SessionUpdate", "GetMaxUsage": + var f float64 + return &f + case "GetLcrSuppliers": + ss := make([]string, 0) + return ss + } + return nil +} diff --git a/engine/account.go b/engine/account.go index 747722756..b3dcb777b 100644 --- a/engine/account.go +++ b/engine/account.go @@ -149,12 +149,15 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error { // add shared group member sg, err := ratingStorage.GetSharedGroup(sgId, false) if err != nil || sg == nil { - //than problem + //than is problem utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgId)) } else { - if !utils.IsSliceMember(sg.MemberIds, ub.Id) { + if _, found := sg.MemberIds[ub.Id]; !found { // add member and save - sg.MemberIds = append(sg.MemberIds, ub.Id) + if sg.MemberIds == nil { + sg.MemberIds = make(utils.StringMap) + } + sg.MemberIds[ub.Id] = true ratingStorage.SetSharedGroup(sg) } } @@ -610,7 +613,7 @@ func (acc *Account) GetSharedGroups() (groups []string) { return } -func (account *Account) GetUniqueSharedGroupMembers(cd *CallDescriptor) ([]string, error) { +func (account *Account) GetUniqueSharedGroupMembers(cd *CallDescriptor) (utils.StringMap, error) { var balances []*Balance balances = append(balances, account.getBalancesForPrefix(cd.Destination, cd.Category, cd.Direction, utils.MONETARY, "")...) balances = append(balances, account.getBalancesForPrefix(cd.Destination, cd.Category, cd.Direction, cd.TOR, "")...) @@ -621,17 +624,15 @@ func (account *Account) GetUniqueSharedGroupMembers(cd *CallDescriptor) ([]strin sharedGroupIds = append(sharedGroupIds, sg) } } - var memberIds []string + memberIds := make(utils.StringMap) for _, sgID := range sharedGroupIds { sharedGroup, err := ratingStorage.GetSharedGroup(sgID, false) if err != nil { utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgID)) return nil, err } - for _, memberId := range sharedGroup.MemberIds { - if !utils.IsSliceMember(memberIds, memberId) { - memberIds = append(memberIds, memberId) - } + for memberID := range sharedGroup.MemberIds { + memberIds[memberID] = true } } return memberIds, nil diff --git a/engine/account_test.go b/engine/account_test.go index cc3be8e1d..334836999 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -1111,7 +1111,7 @@ func TestDebitShared(t *testing.T) { utils.MONETARY: BalanceChain{&Balance{Uuid: "moneyc", Value: 130, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} - sg := &SharedGroup{Id: "SG_TEST", MemberIds: []string{rif.Id, groupie.Id}, AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_MINE_RANDOM}}} + sg := &SharedGroup{Id: "SG_TEST", MemberIds: utils.NewStringMap(rif.Id, groupie.Id), AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_MINE_RANDOM}}} accountingStorage.SetAccount(groupie) ratingStorage.SetSharedGroup(sg) @@ -1181,7 +1181,7 @@ func TestMaxDurationShared(t *testing.T) { utils.MONETARY: BalanceChain{&Balance{Uuid: "moneyc", Value: 130, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} - sg := &SharedGroup{Id: "SG_TEST", MemberIds: []string{rif.Id, groupie.Id}, AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_MINE_RANDOM}}} + sg := &SharedGroup{Id: "SG_TEST", MemberIds: utils.NewStringMap(rif.Id, groupie.Id), AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_MINE_RANDOM}}} accountingStorage.SetAccount(groupie) ratingStorage.SetSharedGroup(sg) diff --git a/engine/action.go b/engine/action.go index abd90f72c..734b86c64 100644 --- a/engine/action.go +++ b/engine/action.go @@ -48,29 +48,30 @@ type Action struct { } const ( - LOG = "*log" - RESET_TRIGGERS = "*reset_triggers" - SET_RECURRENT = "*set_recurrent" - UNSET_RECURRENT = "*unset_recurrent" - ALLOW_NEGATIVE = "*allow_negative" - DENY_NEGATIVE = "*deny_negative" - RESET_ACCOUNT = "*reset_account" - REMOVE_ACCOUNT = "*remove_account" - REMOVE_BALANCE = "*remove_balance" - TOPUP_RESET = "*topup_reset" - TOPUP = "*topup" - DEBIT_RESET = "*debit_reset" - DEBIT = "*debit" - RESET_COUNTERS = "*reset_counters" - ENABLE_ACCOUNT = "*enable_account" - DISABLE_ACCOUNT = "*disable_account" - ENABLE_DISABLE_BALANCE = "*enable_disable_balance" - CALL_URL = "*call_url" - CALL_URL_ASYNC = "*call_url_async" - MAIL_ASYNC = "*mail_async" - UNLIMITED = "*unlimited" - CDRLOG = "*cdrlog" - SET_DDESTINATIONS = "*set_ddestinations" + LOG = "*log" + RESET_TRIGGERS = "*reset_triggers" + SET_RECURRENT = "*set_recurrent" + UNSET_RECURRENT = "*unset_recurrent" + ALLOW_NEGATIVE = "*allow_negative" + DENY_NEGATIVE = "*deny_negative" + RESET_ACCOUNT = "*reset_account" + REMOVE_ACCOUNT = "*remove_account" + REMOVE_BALANCE = "*remove_balance" + TOPUP_RESET = "*topup_reset" + TOPUP = "*topup" + DEBIT_RESET = "*debit_reset" + DEBIT = "*debit" + RESET_COUNTERS = "*reset_counters" + ENABLE_ACCOUNT = "*enable_account" + DISABLE_ACCOUNT = "*disable_account" + ENABLE_DISABLE_BALANCE = "*enable_disable_balance" + CALL_URL = "*call_url" + CALL_URL_ASYNC = "*call_url_async" + MAIL_ASYNC = "*mail_async" + UNLIMITED = "*unlimited" + CDRLOG = "*cdrlog" + SET_DDESTINATIONS = "*set_ddestinations" + TRANSFER_MONETARY_DEFAULT = "*transfer_monetary_default" ) func (a *Action) Clone() *Action { @@ -133,6 +134,8 @@ func getActionFunc(typ string) (actionTypeFunc, bool) { return removeAccount, true case REMOVE_BALANCE: return removeBalance, true + case TRANSFER_MONETARY_DEFAULT: + return transferMonetaryDefault, true } return nil, false } @@ -588,6 +591,25 @@ func removeBalance(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) return accountingStorage.SetAccount(ub) } +func transferMonetaryDefault(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error { + if _, exists := acc.BalanceMap[utils.MONETARY]; !exists { + return utils.ErrNotFound + } + defaultBalance := acc.GetDefaultMoneyBalance() + bChain := acc.BalanceMap[utils.MONETARY] + for _, balance := range bChain { + if balance.Uuid != defaultBalance.Uuid && + balance.Id != defaultBalance.Id { // extra caution + if balance.Value > 0 { + defaultBalance.Value += balance.Value + balance.Value = 0 + } + } + } + // update account in storage + return accountingStorage.SetAccount(acc) +} + // Structure to store actions according to weight type Actions []*Action diff --git a/engine/actions_test.go b/engine/actions_test.go index a3904b150..bccf168c6 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -1451,6 +1451,62 @@ func TestActionRemoveBalance(t *testing.T) { } } +func TestActionTransferMonetaryDefault(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:trans", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Id: utils.META_DEFAULT, + Value: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 3, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: -2, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: TRANSFER_MONETARY_DEFAULT, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:trans": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:trans") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 17 || + afterUb.BalanceMap[utils.MONETARY][0].Value != 19 || + afterUb.BalanceMap[utils.MONETARY][1].Value != 0 || + afterUb.BalanceMap[utils.MONETARY][2].Value != 0 || + afterUb.BalanceMap[utils.MONETARY][3].Value != -2 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + /**************** Benchmarks ********************************/ func BenchmarkUUID(b *testing.B) { diff --git a/engine/calldesc.go b/engine/calldesc.go index 4d5ed20f6..00d7c801e 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -624,7 +624,7 @@ func (cd *CallDescriptor) GetMaxSessionDuration() (duration time.Duration, err e if _, err := Guardian.Guard(func() (interface{}, error) { duration, err = cd.getMaxSessionDuration(account) return 0, err - }, 0, memberIds...); err != nil { + }, 0, memberIds.Slice()...); err != nil { return 0, err } } else { @@ -681,7 +681,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { _, err = Guardian.Guard(func() (interface{}, error) { cc, err = cd.debit(account, false, true) return 0, err - }, 0, memberIds...) + }, 0, memberIds.Slice()...) } else { return nil, sgerr } @@ -700,7 +700,7 @@ func (cd *CallDescriptor) FakeDebit() (cc *CallCost, err error) { _, err = Guardian.Guard(func() (interface{}, error) { cc, err = cd.debit(account, true, true) return 0, err - }, 0, memberIds...) + }, 0, memberIds.Slice()...) } else { return nil, sgerr } @@ -749,7 +749,7 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { cc, err = cd.debit(account, false, true) //log.Print(balanceMap[0].Value, balanceMap[1].Value) return 0, err - }, 0, memberIds...) + }, 0, memberIds.Slice()...) if err != nil { return cc, err } @@ -766,6 +766,7 @@ func (cd *CallDescriptor) RefundIncrements() (left float64, err error) { // all must be locked in order to use cache accMap := make(map[string]struct{}) var accountIDs []string + cd.Increments.Decompress() for _, increment := range cd.Increments { accMap[increment.BalanceInfo.AccountId] = struct{}{} } diff --git a/engine/rateinterval.go b/engine/rateinterval.go index 4c2f546df..268c150bd 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -301,6 +301,12 @@ func (i *RateInterval) String_DISABLED() string { } func (i *RateInterval) Equal(o *RateInterval) bool { + if i == nil && o == nil { + return true + } + if i.Weight != o.Weight { + return false + } if i.Timing == nil && o.Timing == nil { return true } diff --git a/engine/rateinterval_test.go b/engine/rateinterval_test.go index 702123323..8938dd5a6 100644 --- a/engine/rateinterval_test.go +++ b/engine/rateinterval_test.go @@ -191,6 +191,14 @@ func TestRateIntervalEqual(t *testing.T) { } } +func TestRateIntervalEqualWeight(t *testing.T) { + i1 := &RateInterval{Weight: 1} + i2 := &RateInterval{Weight: 2} + if i1.Equal(i2) { + t.Errorf("%v and %v should not be equal", i1, i2) + } +} + func TestRateIntervalNotEqual(t *testing.T) { i1 := &RateInterval{ Timing: &RITiming{ diff --git a/engine/responder.go b/engine/responder.go index bd91fdf99..d9586df26 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -450,6 +450,7 @@ func (rs *Responder) GetSessionRuns(ev *CDR, sRuns *[]*SessionRun) error { } cd := &CallDescriptor{ CgrId: ev.GetCgrId(rs.Timezone), + TOR: ev.ToR, Direction: ev.GetDirection(dc.DirectionField), Tenant: ev.GetTenant(dc.TenantField), Category: ev.GetCategory(dc.CategoryField), diff --git a/engine/responder_test.go b/engine/responder_test.go index 94235c95a..680a45529 100644 --- a/engine/responder_test.go +++ b/engine/responder_test.go @@ -153,15 +153,15 @@ func TestResponderGetSessionRuns(t *testing.T) { &SessionRun{DerivedCharger: extra1DC, CallDescriptor: &CallDescriptor{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), Direction: "*out", Category: "0", Tenant: "vdf", Subject: "rif", Account: "minitsboy", Destination: "0256", TimeStart: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), - ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}}, + TOR: utils.VOICE, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}}, &SessionRun{DerivedCharger: extra2DC, CallDescriptor: &CallDescriptor{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), Direction: "*out", Category: "call", Tenant: "vdf", Subject: "ivo", Account: "ivo", Destination: "1002", TimeStart: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), - ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}}, + TOR: utils.VOICE, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}}, &SessionRun{DerivedCharger: dfDC, CallDescriptor: &CallDescriptor{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), Direction: "*out", Category: "call", Tenant: "vdf", Subject: "dan2", Account: "dan2", Destination: "1002", TimeStart: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), - ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}}} + TOR: utils.VOICE, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}}} if err := rsponder.GetSessionRuns(cdr, &sesRuns); err != nil { t.Error(err) } else if !reflect.DeepEqual(eSRuns, sesRuns) { diff --git a/engine/sharedgroup.go b/engine/sharedgroup.go index 4ec1bde23..ab8e4750b 100644 --- a/engine/sharedgroup.go +++ b/engine/sharedgroup.go @@ -40,7 +40,7 @@ const ( type SharedGroup struct { Id string AccountParameters map[string]*SharingParameters - MemberIds []string + MemberIds utils.StringMap //members []*Account // accounts caching } @@ -92,7 +92,7 @@ func (sg *SharedGroup) SortBalancesByStrategy(myBalance *Balance, bc BalanceChai // Returns all shared group's balances collected from user accounts' func (sg *SharedGroup) GetBalances(destination, category, direction, balanceType string, ub *Account) (bc BalanceChain) { // if len(sg.members) == 0 { - for _, ubId := range sg.MemberIds { + for ubId := range sg.MemberIds { var nUb *Account if ubId == ub.Id { // skip the initiating user nUb = ub diff --git a/engine/sharedgroup_test.go b/engine/sharedgroup_test.go index 52d3c3a06..b74df0846 100644 --- a/engine/sharedgroup_test.go +++ b/engine/sharedgroup_test.go @@ -21,6 +21,8 @@ package engine import ( "reflect" "testing" + + "github.com/cgrates/cgrates/utils" ) func TestSharedSetGet(t *testing.T) { @@ -30,7 +32,7 @@ func TestSharedSetGet(t *testing.T) { AccountParameters: map[string]*SharingParameters{ "test": &SharingParameters{Strategy: STRATEGY_HIGHEST}, }, - MemberIds: []string{"1", "2", "3"}, + MemberIds: utils.NewStringMap("1", "2", "3"), } err := ratingStorage.SetSharedGroup(sg) if err != nil { diff --git a/engine/timespans.go b/engine/timespans.go index d3aba2964..b89739552 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -38,6 +38,7 @@ type TimeSpan struct { DurationIndex time.Duration // the call duration so far till TimeEnd Increments Increments MatchedSubject, MatchedPrefix, MatchedDestId, RatingPlanId string + CompressFactor int } type Increment struct { @@ -60,7 +61,8 @@ type UnitInfo struct { func (mi *UnitInfo) Equal(other *UnitInfo) bool { return mi.DestinationId == other.DestinationId && - mi.Quantity == other.Quantity + mi.Quantity == other.Quantity && + mi.TOR == other.TOR } // Holds information about the balance that made a specific payment @@ -162,31 +164,51 @@ func (timespans *TimeSpans) OverlapWithTimeSpans(paidTs TimeSpans, newTs *TimeSp return false } -func (tss TimeSpans) Compress() { - for _, ts := range tss { - var cIncrs Increments - for _, incr := range ts.Increments { - if len(cIncrs) == 0 || !cIncrs[len(cIncrs)-1].Equal(incr) { - incr.GetCompressFactor() // sideefect - cIncrs = append(cIncrs, incr) - } else { - cIncrs[len(cIncrs)-1].CompressFactor++ - } - } - ts.Increments = cIncrs +func (tss *TimeSpans) Compress() { // must be pointer receiver + for _, ts := range *tss { + ts.Increments.Compress() } + var cTss TimeSpans + for _, ts := range *tss { + if len(cTss) == 0 || !cTss[len(cTss)-1].Equal(ts) { + ts.GetCompressFactor() // sideefect + cTss = append(cTss, ts) + } else { + cTs := cTss[len(cTss)-1] + cTs.CompressFactor++ + cTs.Cost += ts.Cost + cTs.TimeEnd = ts.TimeEnd + cTs.DurationIndex = ts.DurationIndex + } + } + *tss = cTss } -func (tss TimeSpans) Decompress() { - for _, ts := range tss { - var incrs Increments - for _, cIncr := range ts.Increments { - for i := 0; i < cIncr.GetCompressFactor(); i++ { - incrs = append(incrs, cIncr.Clone()) - } - } - ts.Increments = incrs +func (tss *TimeSpans) Decompress() { // must be pointer receiver + for _, ts := range *tss { + ts.Increments.Decompress() } + var cTss TimeSpans + for _, cTs := range *tss { + var duration time.Duration + if cTs.GetCompressFactor() > 1 { + duration = cTs.GetUnitDuration() + } + for i := cTs.GetCompressFactor(); i > 1; i-- { + uTs := &TimeSpan{} + *uTs = *cTs // cloned by copy + uTs.TimeEnd = cTs.TimeStart.Add(duration) + uTs.DurationIndex = cTs.DurationIndex - time.Duration((i-1)*int(duration)) + uTs.CompressFactor = 1 + uTs.Cost = cTs.Cost / float64(cTs.GetCompressFactor()) + cTs.TimeStart = uTs.TimeEnd + cTss = append(cTss, uTs) + } + cTs.Cost = cTs.GetUnitCost() + cTs.CompressFactor = 1 + cTss = append(cTss, cTs) + } + *tss = cTss } func (incr *Increment) Clone() *Increment { @@ -215,12 +237,48 @@ func (incr *Increment) GetCompressFactor() int { return incr.CompressFactor } +func (incr *Increment) GetCost() float64 { + return float64(incr.GetCompressFactor()) * incr.Cost +} + type Increments []*Increment +func (incs Increments) Equal(other Increments) bool { + for index, i := range incs { + if !i.Equal(other[index]) || i.GetCompressFactor() != other[index].GetCompressFactor() { + return false + } + } + return true +} + +func (incs *Increments) Compress() { // must be pointer receiver + var cIncrs Increments + for _, incr := range *incs { + if len(cIncrs) == 0 || !cIncrs[len(cIncrs)-1].Equal(incr) { + incr.GetCompressFactor() // sideefect + cIncrs = append(cIncrs, incr) + } else { + cIncrs[len(cIncrs)-1].CompressFactor++ + } + } + *incs = cIncrs +} + +func (incs *Increments) Decompress() { // must be pointer receiver + var cIncrs Increments + for _, cIncr := range *incs { + for i := 0; i < cIncr.GetCompressFactor(); i++ { + cIncrs = append(cIncrs, cIncr.Clone()) + } + } + *incs = cIncrs +} + func (incs Increments) GetTotalCost() float64 { cost := 0.0 for _, increment := range incs { - cost += (float64(increment.GetCompressFactor()) * increment.Cost) + cost += increment.GetCost() } return cost } @@ -237,6 +295,15 @@ func (ts *TimeSpan) GetDuration() time.Duration { return ts.TimeEnd.Sub(ts.TimeStart) } +//Returns the duration of a unitary timespan in a compressed set +func (ts *TimeSpan) GetUnitDuration() time.Duration { + return time.Duration(int(ts.TimeEnd.Sub(ts.TimeStart)) / ts.GetCompressFactor()) +} + +func (ts *TimeSpan) GetUnitCost() float64 { + return ts.Cost / float64(ts.GetCompressFactor()) +} + // Returns true if the given time is inside timespan range. func (ts *TimeSpan) Contains(t time.Time) bool { return t.After(ts.TimeStart) && t.Before(ts.TimeEnd) @@ -573,3 +640,21 @@ func (ts *TimeSpan) hasBetterRateIntervalThan(interval *RateInterval) bool { } return true } + +func (ts *TimeSpan) Equal(other *TimeSpan) bool { + return ts.Increments.Equal(other.Increments) && + ts.RateInterval.Equal(other.RateInterval) && + ts.GetUnitCost() == other.GetUnitCost() && + ts.GetUnitDuration() == other.GetUnitDuration() && + ts.MatchedSubject == other.MatchedSubject && + ts.MatchedPrefix == other.MatchedPrefix && + ts.MatchedDestId == other.MatchedDestId && + ts.RatingPlanId == other.RatingPlanId +} + +func (ts *TimeSpan) GetCompressFactor() int { + if ts.CompressFactor == 0 { + ts.CompressFactor = 1 + } + return ts.CompressFactor +} diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 2cc0f9deb..47b64315d 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -25,7 +25,7 @@ import ( "github.com/cgrates/cgrates/utils" ) -func TestRightMargin(t *testing.T) { +func TestTSRightMargin(t *testing.T) { i := &RateInterval{ Timing: &RITiming{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}}} t1 := time.Date(2012, time.February, 3, 23, 45, 0, 0, time.UTC) @@ -52,7 +52,7 @@ func TestRightMargin(t *testing.T) { } } -func TestSplitMiddle(t *testing.T) { +func TestTSSplitMiddle(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ WeekDays: utils.WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, @@ -75,7 +75,7 @@ func TestSplitMiddle(t *testing.T) { } } -func TestRightHourMargin(t *testing.T) { +func TestTSRightHourMargin(t *testing.T) { i := &RateInterval{Timing: &RITiming{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, EndTime: "17:59:00"}} t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC) @@ -100,7 +100,7 @@ func TestRightHourMargin(t *testing.T) { } } -func TestLeftMargin(t *testing.T) { +func TestTSLeftMargin(t *testing.T) { i := &RateInterval{Timing: &RITiming{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}}} t1 := time.Date(2012, time.February, 5, 23, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 6, 0, 10, 0, 0, time.UTC) @@ -124,7 +124,7 @@ func TestLeftMargin(t *testing.T) { } } -func TestLeftHourMargin(t *testing.T) { +func TestTSLeftHourMargin(t *testing.T) { i := &RateInterval{Timing: &RITiming{Months: utils.Months{time.December}, MonthDays: utils.MonthDays{1}, StartTime: "09:00:00"}} t1 := time.Date(2012, time.December, 1, 8, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.December, 1, 9, 20, 0, 0, time.UTC) @@ -148,7 +148,7 @@ func TestLeftHourMargin(t *testing.T) { } } -func TestEnclosingMargin(t *testing.T) { +func TestTSEnclosingMargin(t *testing.T) { i := &RateInterval{Timing: &RITiming{WeekDays: []time.Weekday{time.Sunday}}} t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 18, 10, 0, 0, time.UTC) @@ -162,7 +162,7 @@ func TestEnclosingMargin(t *testing.T) { } } -func TestOutsideMargin(t *testing.T) { +func TestTSOutsideMargin(t *testing.T) { i := &RateInterval{Timing: &RITiming{WeekDays: []time.Weekday{time.Monday}}} t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 18, 10, 0, 0, time.UTC) @@ -173,7 +173,7 @@ func TestOutsideMargin(t *testing.T) { } } -func TestContains(t *testing.T) { +func TestTSContains(t *testing.T) { t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) t3 := time.Date(2012, time.February, 5, 17, 50, 0, 0, time.UTC) @@ -189,7 +189,7 @@ func TestContains(t *testing.T) { } } -func TestSplitByRatingPlan(t *testing.T) { +func TestTSSplitByRatingPlan(t *testing.T) { t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) t3 := time.Date(2012, time.February, 5, 17, 50, 0, 0, time.UTC) @@ -210,7 +210,7 @@ func TestSplitByRatingPlan(t *testing.T) { } } -func TestTimespanGetCost(t *testing.T) { +func TestTSTimespanGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) ts1 := TimeSpan{TimeStart: t1, TimeEnd: t2} @@ -233,7 +233,7 @@ func TestTimespanGetCost(t *testing.T) { } } -func TestTimespanGetCostIntervals(t *testing.T) { +func TestTSTimespanGetCostIntervals(t *testing.T) { ts := &TimeSpan{} ts.Increments = make(Increments, 11) for i := 0; i < 11; i++ { @@ -244,7 +244,7 @@ func TestTimespanGetCostIntervals(t *testing.T) { } } -func TestSetRateInterval(t *testing.T) { +func TestTSSetRateInterval(t *testing.T) { i1 := &RateInterval{ Timing: &RITiming{}, Rating: &RIRate{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}}, @@ -267,7 +267,7 @@ func TestSetRateInterval(t *testing.T) { } } -func TestTimespanSplitGroupedRates(t *testing.T) { +func TestTSTimespanSplitGroupedRates(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ EndTime: "17:59:00", @@ -305,7 +305,7 @@ func TestTimespanSplitGroupedRates(t *testing.T) { } } -func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { +func TestTSTimespanSplitGroupedRatesIncrements(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ EndTime: "17:59:00", @@ -361,7 +361,7 @@ func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { } } -func TestTimespanSplitRightHourMarginBeforeGroup(t *testing.T) { +func TestTSTimespanSplitRightHourMarginBeforeGroup(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ EndTime: "17:00:30", @@ -398,7 +398,7 @@ func TestTimespanSplitRightHourMarginBeforeGroup(t *testing.T) { } } -func TestTimespanSplitGroupSecondSplit(t *testing.T) { +func TestTSTimespanSplitGroupSecondSplit(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ EndTime: "17:03:30", @@ -445,7 +445,7 @@ func TestTimespanSplitGroupSecondSplit(t *testing.T) { } } -func TestTimespanSplitLong(t *testing.T) { +func TestTSTimespanSplitLong(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ StartTime: "18:00:00", @@ -475,7 +475,7 @@ func TestTimespanSplitLong(t *testing.T) { } } -func TestTimespanSplitMultipleGroup(t *testing.T) { +func TestTSTimespanSplitMultipleGroup(t *testing.T) { i := &RateInterval{ Timing: &RITiming{ EndTime: "17:05:00", @@ -522,7 +522,7 @@ func TestTimespanSplitMultipleGroup(t *testing.T) { } } -func TestTimespanExpandingPastEnd(t *testing.T) { +func TestTSTimespanExpandingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -546,7 +546,7 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } -func TestTimespanExpandingDurationIndex(t *testing.T) { +func TestTSTimespanExpandingDurationIndex(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -568,7 +568,7 @@ func TestTimespanExpandingDurationIndex(t *testing.T) { } } -func TestTimespanExpandingRoundingPastEnd(t *testing.T) { +func TestTSTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -592,7 +592,7 @@ func TestTimespanExpandingRoundingPastEnd(t *testing.T) { } } -func TestTimespanExpandingPastEndMultiple(t *testing.T) { +func TestTSTimespanExpandingPastEndMultiple(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -620,7 +620,7 @@ func TestTimespanExpandingPastEndMultiple(t *testing.T) { } } -func TestTimespanExpandingPastEndMultipleEqual(t *testing.T) { +func TestTSTimespanExpandingPastEndMultipleEqual(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -648,7 +648,7 @@ func TestTimespanExpandingPastEndMultipleEqual(t *testing.T) { } } -func TestTimespanExpandingBeforeEnd(t *testing.T) { +func TestTSTimespanExpandingBeforeEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -674,7 +674,7 @@ func TestTimespanExpandingBeforeEnd(t *testing.T) { } } -func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { +func TestTSTimespanExpandingBeforeEndMultiple(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -704,7 +704,7 @@ func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { } } -func TestTimespanCreateSecondsSlice(t *testing.T) { +func TestTSTimespanCreateSecondsSlice(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), @@ -721,7 +721,7 @@ func TestTimespanCreateSecondsSlice(t *testing.T) { } } -func TestTimespanCreateIncrements(t *testing.T) { +func TestTSTimespanCreateIncrements(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 100000000, time.UTC), @@ -747,7 +747,7 @@ func TestTimespanCreateIncrements(t *testing.T) { } } -func TestTimespanSplitByIncrement(t *testing.T) { +func TestTSTimespanSplitByIncrement(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), @@ -782,7 +782,7 @@ func TestTimespanSplitByIncrement(t *testing.T) { } } -func TestTimespanSplitByIncrementStart(t *testing.T) { +func TestTSTimespanSplitByIncrementStart(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), @@ -816,7 +816,7 @@ func TestTimespanSplitByIncrementStart(t *testing.T) { } } -func TestTimespanSplitByIncrementEnd(t *testing.T) { +func TestTSTimespanSplitByIncrementEnd(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), @@ -850,7 +850,7 @@ func TestTimespanSplitByIncrementEnd(t *testing.T) { } } -func TestTimespanSplitByDuration(t *testing.T) { +func TestTSTimespanSplitByDuration(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), @@ -888,7 +888,7 @@ func TestTimespanSplitByDuration(t *testing.T) { } } -func TestRemoveOverlapedFromIndexMiddle(t *testing.T) { +func TestTSRemoveOverlapedFromIndexMiddle(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -919,7 +919,7 @@ func TestRemoveOverlapedFromIndexMiddle(t *testing.T) { } } -func TestRemoveOverlapedFromIndexMiddleNonBounds(t *testing.T) { +func TestTSRemoveOverlapedFromIndexMiddleNonBounds(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -952,7 +952,7 @@ func TestRemoveOverlapedFromIndexMiddleNonBounds(t *testing.T) { } } -func TestRemoveOverlapedFromIndexMiddleNonBoundsOver(t *testing.T) { +func TestTSRemoveOverlapedFromIndexMiddleNonBoundsOver(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -984,7 +984,7 @@ func TestRemoveOverlapedFromIndexMiddleNonBoundsOver(t *testing.T) { } } -func TestRemoveOverlapedFromIndexEnd(t *testing.T) { +func TestTSRemoveOverlapedFromIndexEnd(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1014,7 +1014,7 @@ func TestRemoveOverlapedFromIndexEnd(t *testing.T) { } } -func TestRemoveOverlapedFromIndexEndPast(t *testing.T) { +func TestTSRemoveOverlapedFromIndexEndPast(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1044,7 +1044,7 @@ func TestRemoveOverlapedFromIndexEndPast(t *testing.T) { } } -func TestRemoveOverlapedFromIndexAll(t *testing.T) { +func TestTSRemoveOverlapedFromIndexAll(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1073,7 +1073,7 @@ func TestRemoveOverlapedFromIndexAll(t *testing.T) { } } -func TestRemoveOverlapedFromIndexNone(t *testing.T) { +func TestTSRemoveOverlapedFromIndexNone(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1105,7 +1105,7 @@ func TestRemoveOverlapedFromIndexNone(t *testing.T) { } } -func TestRemoveOverlapedFromIndexOne(t *testing.T) { +func TestTSRemoveOverlapedFromIndexOne(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1122,7 +1122,7 @@ func TestRemoveOverlapedFromIndexOne(t *testing.T) { } } -func TestRemoveOverlapedFromIndexTwo(t *testing.T) { +func TestTSRemoveOverlapedFromIndexTwo(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1143,7 +1143,7 @@ func TestRemoveOverlapedFromIndexTwo(t *testing.T) { } } -func TestOverlapWithTimeSpansMiddleLong(t *testing.T) { +func TestTSOverlapWithTimeSpansMiddleLong(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1180,7 +1180,7 @@ func TestOverlapWithTimeSpansMiddleLong(t *testing.T) { } } -func TestOverlapWithTimeSpansMiddleMedium(t *testing.T) { +func TestTSOverlapWithTimeSpansMiddleMedium(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1218,7 +1218,7 @@ func TestOverlapWithTimeSpansMiddleMedium(t *testing.T) { } } -func TestOverlapWithTimeSpansMiddleShort(t *testing.T) { +func TestTSOverlapWithTimeSpansMiddleShort(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1257,7 +1257,7 @@ func TestOverlapWithTimeSpansMiddleShort(t *testing.T) { } } -func TestOverlapWithTimeSpansStart(t *testing.T) { +func TestTSOverlapWithTimeSpansStart(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1294,7 +1294,7 @@ func TestOverlapWithTimeSpansStart(t *testing.T) { } } -func TestOverlapWithTimeSpansAlmostEnd(t *testing.T) { +func TestTSOverlapWithTimeSpansAlmostEnd(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1333,7 +1333,7 @@ func TestOverlapWithTimeSpansAlmostEnd(t *testing.T) { } } -func TestOverlapWithTimeSpansEnd(t *testing.T) { +func TestTSOverlapWithTimeSpansEnd(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1371,7 +1371,7 @@ func TestOverlapWithTimeSpansEnd(t *testing.T) { } } -func TestOverlapWithTimeSpansPastEnd(t *testing.T) { +func TestTSOverlapWithTimeSpansPastEnd(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1409,7 +1409,7 @@ func TestOverlapWithTimeSpansPastEnd(t *testing.T) { } } -func TestOverlapWithTimeSpansAll(t *testing.T) { +func TestTSOverlapWithTimeSpansAll(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1445,7 +1445,7 @@ func TestOverlapWithTimeSpansAll(t *testing.T) { } } -func TestOverlapWithTimeSpansAllPast(t *testing.T) { +func TestTSOverlapWithTimeSpansAllPast(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1481,7 +1481,7 @@ func TestOverlapWithTimeSpansAllPast(t *testing.T) { } } -func TestOverlapWithTimeSpansOne(t *testing.T) { +func TestTSOverlapWithTimeSpansOne(t *testing.T) { tss := TimeSpans{ &TimeSpan{ TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC), @@ -1505,7 +1505,7 @@ func TestOverlapWithTimeSpansOne(t *testing.T) { } } -func TestTSCompressDecompress(t *testing.T) { +func TestTSIncrementsCompressDecompress(t *testing.T) { tss := TimeSpans{ &TimeSpan{ Increments: Increments{ @@ -1557,7 +1557,7 @@ func TestTSCompressDecompress(t *testing.T) { } } -func TestTSMultipleCompressDecompress(t *testing.T) { +func TestTSMultipleIncrementsCompressDecompress(t *testing.T) { tss := TimeSpans{ &TimeSpan{ Increments: Increments{ @@ -1694,3 +1694,150 @@ func TestTSBetterIntervalAgainAfter(t *testing.T) { t.Error("Wrong better rate interval!") } } + +func TestTSCompressDecompress(t *testing.T) { + tss := TimeSpans{ + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 18, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC), + Cost: 1.2, + DurationIndex: 1 * time.Minute, + }, + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC), + Cost: 1.2, + DurationIndex: 2 * time.Minute, + }, + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC), + Cost: 1.2, + DurationIndex: 3 * time.Minute, + }, + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 22, 0, 0, time.UTC), + Cost: 1.2, + DurationIndex: 4 * time.Minute, + }, + } + tss.Compress() + if len(tss) != 1 || + !tss[0].TimeStart.Equal(time.Date(2015, 1, 9, 16, 18, 0, 0, time.UTC)) || + !tss[0].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 22, 0, 0, time.UTC)) || + tss[0].DurationIndex != 4*time.Minute || + tss[0].Cost != 4.8 || + tss[0].CompressFactor != 4 { + for _, ts := range tss { + t.Logf("TS: %+v", ts) + } + t.Error("Error compressing timespans: ", tss) + } + tss.Decompress() + if len(tss) != 4 || + !tss[0].TimeStart.Equal(time.Date(2015, 1, 9, 16, 18, 0, 0, time.UTC)) || + !tss[0].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC)) || + tss[0].DurationIndex != 1*time.Minute || + tss[0].CompressFactor != 1 || + tss[0].Cost != 1.2 || + !tss[1].TimeStart.Equal(time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC)) || + !tss[1].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC)) || + tss[1].DurationIndex != 2*time.Minute || + tss[1].CompressFactor != 1 || + tss[1].Cost != 1.2 || + !tss[2].TimeStart.Equal(time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC)) || + !tss[2].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC)) || + tss[2].DurationIndex != 3*time.Minute || + tss[2].CompressFactor != 1 || + tss[2].Cost != 1.2 || + !tss[3].TimeStart.Equal(time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC)) || + !tss[3].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 22, 0, 0, time.UTC)) || + tss[3].DurationIndex != 4*time.Minute || + tss[3].CompressFactor != 1 || + tss[3].Cost != 1.2 { + for i, ts := range tss { + t.Logf("TS(%d): %+v", i, ts) + } + t.Error("Error decompressing timespans: ", tss) + } +} + +func TestTSDifferentCompressDecompress(t *testing.T) { + tss := TimeSpans{ + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 18, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC), + RateInterval: &RateInterval{Weight: 1}, + Cost: 1.2, + DurationIndex: 1 * time.Minute, + }, + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC), + RateInterval: &RateInterval{Weight: 2}, + Cost: 1.2, + DurationIndex: 2 * time.Minute, + }, + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC), + RateInterval: &RateInterval{Weight: 1}, + Cost: 1.2, + DurationIndex: 3 * time.Minute, + }, + &TimeSpan{ + TimeStart: time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC), + TimeEnd: time.Date(2015, 1, 9, 16, 22, 0, 0, time.UTC), + RateInterval: &RateInterval{Weight: 1}, + Cost: 1.2, + DurationIndex: 4 * time.Minute, + }, + } + tss.Compress() + if len(tss) != 3 || + !tss[0].TimeStart.Equal(time.Date(2015, 1, 9, 16, 18, 0, 0, time.UTC)) || + !tss[0].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC)) || + tss[0].DurationIndex != 1*time.Minute || + tss[0].Cost != 1.2 || + !tss[1].TimeStart.Equal(time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC)) || + !tss[1].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC)) || + tss[1].DurationIndex != 2*time.Minute || + tss[1].Cost != 1.2 || + !tss[2].TimeStart.Equal(time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC)) || + !tss[2].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 22, 0, 0, time.UTC)) || + tss[2].DurationIndex != 4*time.Minute || + tss[2].Cost != 2.4 { + for _, ts := range tss { + t.Logf("TS: %+v", ts) + } + t.Error("Error compressing timespans: ", tss) + } + tss.Decompress() + if len(tss) != 4 || + !tss[0].TimeStart.Equal(time.Date(2015, 1, 9, 16, 18, 0, 0, time.UTC)) || + !tss[0].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC)) || + tss[0].DurationIndex != 1*time.Minute || + tss[0].CompressFactor != 1 || + tss[0].Cost != 1.2 || + !tss[1].TimeStart.Equal(time.Date(2015, 1, 9, 16, 19, 0, 0, time.UTC)) || + !tss[1].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC)) || + tss[1].DurationIndex != 2*time.Minute || + tss[1].CompressFactor != 1 || + tss[1].Cost != 1.2 || + !tss[2].TimeStart.Equal(time.Date(2015, 1, 9, 16, 20, 0, 0, time.UTC)) || + !tss[2].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC)) || + tss[2].DurationIndex != 3*time.Minute || + tss[2].CompressFactor != 1 || + tss[2].Cost != 1.2 || + !tss[3].TimeStart.Equal(time.Date(2015, 1, 9, 16, 21, 0, 0, time.UTC)) || + !tss[3].TimeEnd.Equal(time.Date(2015, 1, 9, 16, 22, 0, 0, time.UTC)) || + tss[3].DurationIndex != 4*time.Minute || + tss[3].CompressFactor != 1 || + tss[3].Cost != 1.2 { + for i, ts := range tss { + t.Logf("TS(%d): %+v", i, ts) + } + t.Error("Error decompressing timespans: ", tss) + } +} diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 16523bc3e..e6e91b01f 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -205,6 +205,8 @@ func (s *Session) Refund(lastCC *engine.CallCost, hangupTime time.Time) error { TOR: lastCC.TOR, Increments: refundIncrements, } + cd.Increments.Compress() + utils.Logger.Info(fmt.Sprintf("Refunding duration %v with cd: %+v", refundDuration, cd)) var response float64 err := s.sessionManager.Rater().Call("Responder.RefundIncrements", cd, &response) if err != nil { @@ -233,6 +235,7 @@ func (s *Session) SaveOperations() { for _, cc := range sr.CallCosts[1:] { firstCC.Merge(cc) } + firstCC.Timespans.Compress() var reply string err := s.sessionManager.CdrSrv().Call("CdrServer.LogCallCost", &engine.CallCostLog{ diff --git a/sessionmanager/session_test.go b/sessionmanager/session_test.go index e01d8c74d..f53c566d4 100644 --- a/sessionmanager/session_test.go +++ b/sessionmanager/session_test.go @@ -105,7 +105,7 @@ func TestSessionRefund(t *testing.T) { cc := &engine.CallCost{Timespans: engine.TimeSpans{ts}} hangupTime := time.Date(2015, 6, 10, 14, 7, 20, 0, time.UTC) s.Refund(cc, hangupTime) - if len(mc.refundCd.Increments) != 10 || len(cc.Timespans) != 1 || cc.Timespans[0].TimeEnd != hangupTime { + if len(mc.refundCd.Increments) != 1 || mc.refundCd.Increments[0].GetCompressFactor() != 10 || len(cc.Timespans) != 1 || cc.Timespans[0].TimeEnd != hangupTime { t.Errorf("Error refunding: %+v, %+v", mc.refundCd.Increments, cc.Timespans[0]) } } @@ -125,7 +125,7 @@ func TestSessionRefundAll(t *testing.T) { cc := &engine.CallCost{Timespans: engine.TimeSpans{ts}} hangupTime := time.Date(2015, 6, 10, 14, 7, 0, 0, time.UTC) s.Refund(cc, hangupTime) - if len(mc.refundCd.Increments) != 30 || len(cc.Timespans) != 0 { + if len(mc.refundCd.Increments) != 1 || mc.refundCd.Increments[0].GetCompressFactor() != 30 || len(cc.Timespans) != 0 { t.Errorf("Error refunding: %+v, %+v", len(mc.refundCd.Increments), cc.Timespans) } } @@ -154,7 +154,7 @@ func TestSessionRefundManyAll(t *testing.T) { cc := &engine.CallCost{Timespans: engine.TimeSpans{ts1, ts2}} hangupTime := time.Date(2015, 6, 10, 14, 07, 0, 0, time.UTC) s.Refund(cc, hangupTime) - if len(mc.refundCd.Increments) != 60 || len(cc.Timespans) != 0 { + if len(mc.refundCd.Increments) != 1 || mc.refundCd.Increments[0].GetCompressFactor() != 60 || len(cc.Timespans) != 0 { t.Errorf("Error refunding: %+v, %+v", len(mc.refundCd.Increments), cc.Timespans) } } diff --git a/sessionmanager/smg_event_test.go b/sessionmanager/smg_event_test.go index a52503560..f6c395b7b 100644 --- a/sessionmanager/smg_event_test.go +++ b/sessionmanager/smg_event_test.go @@ -129,7 +129,7 @@ func TestSMGenericEventParseFields(t *testing.T) { func TestSMGenericEventAsStoredCdr(t *testing.T) { smGev := SMGenericEvent{} smGev[utils.EVENT_NAME] = "TEST_EVENT" - smGev[utils.TOR] = utils.VOICE + smGev[utils.TOR] = utils.SMS smGev[utils.ACCID] = "12345" smGev[utils.DIRECTION] = utils.OUT smGev[utils.ACCOUNT] = "account1" @@ -148,7 +148,7 @@ func TestSMGenericEventAsStoredCdr(t *testing.T) { smGev["Extra1"] = "Value1" smGev["Extra2"] = 5 eStoredCdr := &engine.CDR{CGRID: "0711eaa78e53937f1593dabc08c83ea04a915f2e", - ToR: utils.VOICE, OriginID: "12345", OriginHost: "10.0.3.15", Source: "SMG_TEST_EVENT", RequestType: utils.META_PREPAID, + ToR: utils.SMS, OriginID: "12345", OriginHost: "10.0.3.15", Source: "SMG_TEST_EVENT", RequestType: utils.META_PREPAID, Direction: utils.OUT, Tenant: "cgrates.org", Category: "call", Account: "account1", Subject: "subject1", Destination: "+4986517174963", SetupTime: time.Date(2015, 11, 9, 14, 21, 24, 0, time.UTC), AnswerTime: time.Date(2015, 11, 9, 14, 22, 2, 0, time.UTC), Usage: time.Duration(83) * time.Second, PDD: time.Duration(300) * time.Millisecond, Supplier: "supplier1", DisconnectCause: "NORMAL_DISCONNECT", diff --git a/sessionmanager/smg_session.go b/sessionmanager/smg_session.go index 2fa99c2db..e2b5289e9 100644 --- a/sessionmanager/smg_session.go +++ b/sessionmanager/smg_session.go @@ -108,6 +108,7 @@ func (self *SMGSession) debit(dur time.Duration) (time.Duration, error) { // Attempts to refund a duration, error on failure func (self *SMGSession) refund(refundDuration time.Duration) error { + initialRefundDuration := refundDuration lastCC := self.callCosts[len(self.callCosts)-1] lastCC.Timespans.Decompress() var refundIncrements engine.Increments @@ -156,6 +157,8 @@ func (self *SMGSession) refund(refundDuration time.Duration) error { TOR: lastCC.TOR, Increments: refundIncrements, } + cd.Increments.Compress() + utils.Logger.Info(fmt.Sprintf("Refunding duration %v with cd: %+v", initialRefundDuration, utils.ToJSON(cd))) var response float64 err := self.rater.Call("Responder.RefundIncrements", cd, &response) if err != nil { @@ -170,10 +173,12 @@ func (self *SMGSession) refund(refundDuration time.Duration) error { // Session has ended, check debits and refund the extra charged duration func (self *SMGSession) close(endTime time.Time) error { - lastCC := self.callCosts[len(self.callCosts)-1] - end := lastCC.GetEndTime() - refundDuration := end.Sub(endTime) - self.refund(refundDuration) + if len(self.callCosts) != 0 { // We have had at least one cost calculation + lastCC := self.callCosts[len(self.callCosts)-1] + end := lastCC.GetEndTime() + refundDuration := end.Sub(endTime) + self.refund(refundDuration) + } return nil } @@ -205,6 +210,7 @@ func (self *SMGSession) saveOperations() error { for _, cc := range self.callCosts[1:] { firstCC.Merge(cc) } + firstCC.Timespans.Compress() var reply string err := self.cdrsrv.Call("CdrServer.LogCallCost", &engine.CallCostLog{ CgrId: self.eventStart.GetCgrId(self.timezone),