diff --git a/apier/v1/apier.go b/apier/v1/apier.go index cf3f86389..21d64d422 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -523,14 +523,14 @@ func (self *ApierV1) SetActions(attrs utils.AttrSetActions, reply *string) error } a := &engine.Action{ - Id: utils.GenUUID(), + Id: attrs.ActionsId, ActionType: apiAct.Identifier, Weight: apiAct.Weight, ExpirationString: apiAct.ExpiryTime, ExtraParameters: apiAct.ExtraParameters, Filter: apiAct.Filter, Balance: &engine.BalanceFilter{ // TODO: update this part - Uuid: utils.StringPointer(utils.GenUUID()), + Uuid: utils.StringPointer(apiAct.BalanceUuid), ID: utils.StringPointer(apiAct.BalanceId), Type: utils.StringPointer(apiAct.BalanceType), Value: vf, @@ -562,7 +562,8 @@ func (self *ApierV1) GetActions(actsId string, reply *[]*utils.TPAction) error { return utils.NewErrServerError(err) } for _, engAct := range engActs { - act := &utils.TPAction{Identifier: engAct.ActionType, + act := &utils.TPAction{ + Identifier: engAct.ActionType, ExpiryTime: engAct.ExpirationString, ExtraParameters: engAct.ExtraParameters, Filter: engAct.Filter, @@ -1063,3 +1064,63 @@ func (self *ApierV1) GetLoadHistory(attrs utils.Paginator, reply *[]*engine.Load *reply = loadHist[offset:nrItems] return nil } + +type AttrRemActions struct { + ActionIDs []string +} + +func (self *ApierV1) RemActions(attr AttrRemActions, reply *string) error { + if attr.ActionIDs == nil { + err := utils.ErrNotFound + *reply = err.Error() + return err + } + stringMap := utils.NewStringMap(attr.ActionIDs...) + keys, err := self.RatingDb.GetKeysForPrefix(utils.ACTION_TRIGGER_PREFIX, true) + if err != nil { + *reply = err.Error() + return err + } + for _, key := range keys { + getAttrs, err := self.RatingDb.GetActionTriggers(key[len(utils.ACTION_TRIGGER_PREFIX):]) + if err != nil { + *reply = err.Error() + return err + } + for _, atr := range getAttrs { + if _, found := stringMap[atr.ActionsID]; found { + // found action trigger referencing action; abort + err := fmt.Errorf("action %s refenced by action trigger %s", atr.ActionsID, atr.ID) + *reply = err.Error() + return err + } + } + } + allAplsMap, err := self.RatingDb.GetAllActionPlans() + if err != nil && err != utils.ErrNotFound { + *reply = err.Error() + return err + } + for _, apl := range allAplsMap { + for _, atm := range apl.ActionTimings { + if _, found := stringMap[atm.ActionsID]; found { + err := fmt.Errorf("action %s refenced by action plan %s", atm.ActionsID, apl.Id) + *reply = err.Error() + return err + } + } + + } + for _, aID := range attr.ActionIDs { + if err := self.RatingDb.RemoveActions(aID); err != nil { + *reply = err.Error() + return err + } + } + if err := self.RatingDb.CacheRatingPrefixes(utils.ACTION_PREFIX); err != nil { + *reply = err.Error() + return err + } + *reply = utils.OK + return nil +} diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 124b6a984..0e1e7f09e 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -1297,7 +1297,7 @@ func TestApierResetDataAfterLoadFromFolder(t *testing.T) { if rcvStats.Destinations != 5 || rcvStats.RatingPlans != 5 || rcvStats.RatingProfiles != 5 || - rcvStats.Actions != 10 || + rcvStats.Actions != 11 || rcvStats.DerivedChargers != 3 { t.Errorf("Calling ApierV1.GetCacheStats received: %+v", rcvStats) } diff --git a/apier/v2/accounts.go b/apier/v2/accounts.go index 08f6832dd..83d3d56f2 100644 --- a/apier/v2/accounts.go +++ b/apier/v2/accounts.go @@ -33,7 +33,7 @@ func (self *ApierV2) GetAccounts(attr utils.AttrGetAccounts, reply *[]*engine.Ac var accountKeys []string var err error if len(attr.AccountIds) == 0 { - if accountKeys, err = self.AccountDb.GetKeysForPrefix(utils.ACCOUNT_PREFIX+utils.ConcatenatedKey(attr.Tenant), true); err != nil { + if accountKeys, err = self.AccountDb.GetKeysForPrefix(utils.ACCOUNT_PREFIX+attr.Tenant, true); err != nil { return err } } else { diff --git a/apier/v2/apier.go b/apier/v2/apier.go index 9f5f4cc2a..e337400b0 100644 --- a/apier/v2/apier.go +++ b/apier/v2/apier.go @@ -21,6 +21,7 @@ package v2 import ( "errors" "fmt" + "math" "os" "path" "strings" @@ -263,3 +264,58 @@ func (self *ApierV2) LoadTariffPlanFromFolder(attrs utils.AttrLoadTpFromFolder, *reply = *li return nil } + +type AttrGetActions struct { + ActionIDs []string + Offset int // Set the item offset + Limit int // Limit number of items retrieved +} + +// Retrieves actions attached to specific ActionsId within cache +func (self *ApierV2) GetActions(attr AttrGetActions, reply *map[string]engine.Actions) error { + var actionKeys []string + var err error + if len(attr.ActionIDs) == 0 { + if actionKeys, err = self.AccountDb.GetKeysForPrefix(utils.ACTION_PREFIX, false); err != nil { + return err + } + } else { + for _, accID := range attr.ActionIDs { + if len(accID) == 0 { // Source of error returned from redis (key not found) + continue + } + actionKeys = append(actionKeys, utils.ACCOUNT_PREFIX+accID) + } + } + if len(actionKeys) == 0 { + return nil + } + if attr.Offset > len(actionKeys) { + attr.Offset = len(actionKeys) + } + if attr.Offset < 0 { + attr.Offset = 0 + } + var limitedActions []string + if attr.Limit != 0 { + max := math.Min(float64(attr.Offset+attr.Limit), float64(len(actionKeys))) + limitedActions = actionKeys[attr.Offset:int(max)] + } else { + limitedActions = actionKeys[attr.Offset:] + } + retActions := make(map[string]engine.Actions) + for _, accKey := range limitedActions { + key := accKey[len(utils.ACTION_PREFIX):] + acts, err := self.RatingDb.GetActions(key, false) + if err != nil { + return utils.NewErrServerError(err) + } + if len(acts) > 0 { + retActions[key] = acts + + } + } + + *reply = retActions + return nil +} diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index c342e56ba..ddf5b3b14 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -22,7 +22,7 @@ import ( "flag" "fmt" "log" - _ "net/http/pprof" + // _ "net/http/pprof" "os" "runtime" "runtime/pprof" diff --git a/console/actions.go b/console/actions.go index 25e9af0e5..1d2504029 100644 --- a/console/actions.go +++ b/console/actions.go @@ -18,7 +18,10 @@ along with this program. If not, see package console -import "github.com/cgrates/cgrates/utils" +import ( + "github.com/cgrates/cgrates/apier/v2" + "github.com/cgrates/cgrates/engine" +) func init() { c := &CmdGetActions{ @@ -33,7 +36,7 @@ func init() { type CmdGetActions struct { name string rpcMethod string - rpcParams *StringWrapper + rpcParams *v2.AttrGetActions *CommandExecuter } @@ -47,7 +50,7 @@ func (self *CmdGetActions) RpcMethod() string { func (self *CmdGetActions) RpcParams(reset bool) interface{} { if reset || self.rpcParams == nil { - self.rpcParams = &StringWrapper{} + self.rpcParams = &v2.AttrGetActions{} } return self.rpcParams } @@ -57,6 +60,6 @@ func (self *CmdGetActions) PostprocessRpcParams() error { } func (self *CmdGetActions) RpcResult() interface{} { - a := make([]*utils.TPAction, 0) + a := make(map[string]engine.Actions, 0) return &a } diff --git a/console/actions_remove.go b/console/actions_remove.go new file mode 100644 index 000000000..c7d5f54db --- /dev/null +++ b/console/actions_remove.go @@ -0,0 +1,62 @@ +/* +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 "github.com/cgrates/cgrates/apier/v1" + +func init() { + c := &CmdRemoveActions{ + name: "actions_remove", + rpcMethod: "ApierV1.RemActions", + } + commands[c.Name()] = c + c.CommandExecuter = &CommandExecuter{c} +} + +// Commander implementation +type CmdRemoveActions struct { + name string + rpcMethod string + rpcParams *v1.AttrRemActions + *CommandExecuter +} + +func (self *CmdRemoveActions) Name() string { + return self.name +} + +func (self *CmdRemoveActions) RpcMethod() string { + return self.rpcMethod +} + +func (self *CmdRemoveActions) RpcParams(reset bool) interface{} { + if reset || self.rpcParams == nil { + self.rpcParams = &v1.AttrRemActions{} + } + return self.rpcParams +} + +func (self *CmdRemoveActions) PostprocessRpcParams() error { + return nil +} + +func (self *CmdRemoveActions) RpcResult() interface{} { + var s string + return &s +} diff --git a/data/tariffplans/testtp/AccountActions.csv b/data/tariffplans/testtp/AccountActions.csv index 850d03f77..9fa3f8977 100644 --- a/data/tariffplans/testtp/AccountActions.csv +++ b/data/tariffplans/testtp/AccountActions.csv @@ -9,4 +9,5 @@ cgrates.org,1010,TEST_DATA_r,,true, cgrates.org,1011,TEST_VOICE,,, cgrates.org,1012,PREPAID_10,,, cgrates.org,1013,TEST_NEG,,, -cgrates.org,1014,TEST_RPC,,, \ No newline at end of file +cgrates.org,1014,TEST_RPC,,, +cgrates.org,1015,,,, diff --git a/data/tariffplans/testtp/Actions.csv b/data/tariffplans/testtp/Actions.csv index 680866202..20d61a27d 100644 --- a/data/tariffplans/testtp/Actions.csv +++ b/data/tariffplans/testtp/Actions.csv @@ -10,3 +10,5 @@ TOPUP_DATA_r,*topup,,,,*data,*out,,DATA_DEST,datar,,*unlimited,,50000000000,10,f TOPUP_VOICE,*topup,,,,*voice,*out,,GERMANY_MOBILE,,,*unlimited,,50000,10,false,false,10 TOPUP_NEG,*topup,,,,*voice,*out,,GERMANY;!GERMANY_MOBILE,*zero1m,,*unlimited,,100,10,false,false,10 RPC,*cgr_rpc,"{""Address"": ""localhost:2013"",""Transport"":""*gob"",""Method"":""ApierV2.SetAccount"",""Attempts"":1,""Async"" :false,""Params"":{""Account"":""rpc"",""Tenant"":""cgrates.org""}}",,,,,,,,,,,,,,, +DID,*debit,,,,*monetary,*out,,*any,,,*unlimited,*any,"{""Method"":""*incremental"",""Params"":{""Units"":1, ""Interval"":""month"",""Increment"":""day""}}",10.0,,,10.0 +DID,*cdrlog,"{""action"":""^DID"",""prev_balance"":""BalanceValue""}",,,*monetary,*out,,*any,,,*unlimited,,,10.0,,,10.0 diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index f2fb1f898..fbcf9e8f2 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -833,7 +833,7 @@ func TestLoadActions(t *testing.T) { as1 := csvr.actions["MINI"] expected := []*Action{ &Action{ - Id: "MINI0", + Id: "MINI", ActionType: TOPUP_RESET, ExpirationString: UNLIMITED, ExtraParameters: "", @@ -853,7 +853,7 @@ func TestLoadActions(t *testing.T) { }, }, &Action{ - Id: "MINI1", + Id: "MINI", ActionType: TOPUP, ExpirationString: UNLIMITED, ExtraParameters: "", @@ -880,7 +880,7 @@ func TestLoadActions(t *testing.T) { as2 := csvr.actions["SHARED"] expected = []*Action{ &Action{ - Id: "SHARED0", + Id: "SHARED", ActionType: TOPUP, ExpirationString: UNLIMITED, Weight: 10, @@ -905,7 +905,7 @@ func TestLoadActions(t *testing.T) { as3 := csvr.actions["DEFEE"] expected = []*Action{ &Action{ - Id: "DEFEE0", + Id: "DEFEE", ActionType: CDRLOG, ExtraParameters: `{"Category":"^ddi","MediationRunId":"^did_run"}`, Weight: 10, diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 96a4b32c6..327be197f 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -59,6 +59,7 @@ type RatingStorage interface { SetDerivedChargers(string, *utils.DerivedChargers) error GetActions(string, bool) (Actions, error) SetActions(string, Actions) error + RemoveActions(string) error GetSharedGroup(string, bool) (*SharedGroup, error) SetSharedGroup(*SharedGroup) error GetActionTriggers(string) (ActionTriggers, error) @@ -204,6 +205,7 @@ func NewCodecMsgpackMarshaler() *CodecMsgpackMarshaler { cmm := &CodecMsgpackMarshaler{new(codec.MsgpackHandle)} mh := cmm.mh mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) + mh.RawToString = true return cmm } diff --git a/engine/storage_map.go b/engine/storage_map.go index afab1a06d..f9a747a5b 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -469,6 +469,13 @@ func (ms *MapStorage) SetActions(key string, as Actions) (err error) { return } +func (ms *MapStorage) RemoveActions(key string) (err error) { + ms.mu.Lock() + defer ms.mu.Unlock() + delete(ms.dict, utils.ACTION_PREFIX+key) + return +} + func (ms *MapStorage) GetSharedGroup(key string, skipCache bool) (sg *SharedGroup, err error) { ms.mu.RLock() defer ms.mu.RUnlock() diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go index 0e2e59113..70fc91dc4 100644 --- a/engine/storage_mongo_datadb.go +++ b/engine/storage_mongo_datadb.go @@ -916,6 +916,10 @@ func (ms *MongoStorage) SetActions(key string, as Actions) error { return err } +func (ms *MongoStorage) RemoveActions(key string) error { + return ms.db.C(colAct).Remove(bson.M{"key": key}) +} + func (ms *MongoStorage) GetSharedGroup(key string, skipCache bool) (sg *SharedGroup, err error) { if !skipCache { if x, err := cache2go.Get(utils.SHARED_GROUP_PREFIX + key); err == nil { diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 896181922..be591ff48 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -590,6 +590,11 @@ func (rs *RedisStorage) SetActions(key string, as Actions) (err error) { return } +func (rs *RedisStorage) RemoveActions(key string) (err error) { + err = rs.db.Cmd("DEL", utils.ACTION_PREFIX+key).Err + return +} + func (rs *RedisStorage) GetSharedGroup(key string, skipCache bool) (sg *SharedGroup, err error) { key = utils.SHARED_GROUP_PREFIX + key if !skipCache { diff --git a/engine/tp_reader.go b/engine/tp_reader.go index a2a71470b..4eaf2e81f 100644 --- a/engine/tp_reader.go +++ b/engine/tp_reader.go @@ -514,7 +514,7 @@ func (tpr *TpReader) LoadActions() (err error) { } } acts[idx] = &Action{ - Id: tag + strconv.Itoa(idx), + Id: tag, ActionType: tpact.Identifier, //BalanceType: tpact.BalanceType, Weight: tpact.Weight, @@ -990,7 +990,7 @@ func (tpr *TpReader) LoadAccountActionsFiltered(qriedAA *TpAccountAction) error } } acts[idx] = &Action{ - Id: tag + strconv.Itoa(idx), + Id: tag, ActionType: tpact.Identifier, //BalanceType: tpact.BalanceType, Weight: tpact.Weight, @@ -1338,7 +1338,7 @@ func (tpr *TpReader) LoadCdrStatsFiltered(tag string, save bool) (err error) { } } acts[idx] = &Action{ - Id: tag + strconv.Itoa(idx), + Id: tag, ActionType: tpact.Identifier, //BalanceType: tpact.BalanceType, Weight: tpact.Weight, diff --git a/general_tests/tp_it_test.go b/general_tests/tp_it_test.go index 8165ed19c..a917c9961 100644 --- a/general_tests/tp_it_test.go +++ b/general_tests/tp_it_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/cgrates/cgrates/apier/v1" + "github.com/cgrates/cgrates/apier/v2" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" @@ -239,3 +240,128 @@ func TestTpExecuteActionCgrRpc(t *testing.T) { t.Error("Got error on ApierV2.GetAccount: ", err.Error()) } } + +func TestTpCreateExecuteActionMatch(t *testing.T) { + if !*testIntegration { + return + } + var reply string + if err := tpRPC.Call("ApierV2.SetActions", utils.AttrSetActions{ + ActionsId: "PAYMENT_2056bd2fe137082970f97102b64e42fd", + Actions: []*utils.TPAction{ + &utils.TPAction{ + BalanceType: "*monetary", + Directions: "*out", + Identifier: "*topup", + RatingSubject: "", + Units: "10.500000", + Weight: 10, + }, + }, + }, &reply); err != nil { + t.Error("Got error on ApierV2.SetActions: ", err.Error()) + } else if reply != utils.OK { + t.Errorf("Calling ApierV2.SetActions got reply: %s", reply) + } + if err := tpRPC.Call("ApierV2.ExecuteAction", utils.AttrExecuteAction{ + Tenant: "cgrates.org", + Account: "1015", + ActionsId: "PAYMENT_2056bd2fe137082970f97102b64e42fd", + }, &reply); err != nil { + t.Error("Got error on ApierV2.ExecuteAction: ", err.Error()) + } else if reply != utils.OK { + t.Errorf("Calling ExecuteAction got reply: %s", reply) + } + if err := tpRPC.Call("ApierV2.ExecuteAction", utils.AttrExecuteAction{ + Tenant: "cgrates.org", + Account: "1015", + ActionsId: "PAYMENT_2056bd2fe137082970f97102b64e42fd", + }, &reply); err != nil { + t.Error("Got error on ApierV2.ExecuteAction: ", err.Error()) + } else if reply != utils.OK { + t.Errorf("Calling ExecuteAction got reply: %s", reply) + } + var acnt engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1015"} + if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error("Got error on ApierV2.GetAccount: ", err.Error()) + } + if len(acnt.BalanceMap) != 1 || + len(acnt.BalanceMap[utils.MONETARY]) != 1 || + acnt.BalanceMap[utils.MONETARY].GetTotalValue() != 21 { + t.Error("error matching previous created balance: ", utils.ToIJSON(acnt.BalanceMap)) + } +} + +func TestTpSetRemActions(t *testing.T) { + if !*testIntegration { + return + } + var reply string + if err := tpRPC.Call("ApierV2.SetActions", utils.AttrSetActions{ + ActionsId: "TO_BE_DELETED", + Actions: []*utils.TPAction{ + &utils.TPAction{ + BalanceType: "*monetary", + Directions: "*out", + Identifier: "*topup", + RatingSubject: "", + Units: "10.500000", + Weight: 10, + }, + }, + }, &reply); err != nil { + t.Error("Got error on ApierV2.SetActions: ", err.Error()) + } else if reply != utils.OK { + t.Errorf("Calling ApierV2.SetActions got reply: %s", reply) + } + actionsMap := make(map[string]engine.Actions) + if err := tpRPC.Call("ApierV2.GetActions", v2.AttrGetActions{ + ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, + }, &actionsMap); err != nil { + t.Error("Got error on ApierV2.GetActions: ", err.Error()) + } else if len(actionsMap) != 1 { + t.Errorf("Calling ApierV2.GetActions got reply: %s", utils.ToIJSON(actionsMap)) + } + if err := tpRPC.Call("ApierV2.RemActions", v1.AttrRemActions{ + ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, + }, &reply); err != nil { + t.Error("Got error on ApierV2.RemActions: ", err.Error()) + } else if reply != utils.OK { + t.Errorf("Calling ApierV2.RemActions got reply: %s", reply) + } + if err := tpRPC.Call("ApierV2.GetActions", v2.AttrGetActions{ + ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, + }, &actionsMap); err == nil { + t.Error("no error on ApierV2.GetActions: ", err) + } +} + +func TestTpRemActionsRefenced(t *testing.T) { + if !*testIntegration { + return + } + actionsMap := make(map[string]engine.Actions) + if err := tpRPC.Call("ApierV2.GetActions", v2.AttrGetActions{ + ActionIDs: []string{"TOPUP_VOICE"}, + }, &actionsMap); err != nil { + t.Error("Got error on ApierV2.GetActions: ", err.Error()) + } else if len(actionsMap) != 1 { + t.Errorf("Calling ApierV2.GetActions got reply: %s", utils.ToIJSON(actionsMap)) + } + var reply string + if err := tpRPC.Call("ApierV2.RemActions", v1.AttrRemActions{ + ActionIDs: []string{"TOPUP_VOICE"}, + }, &reply); err == nil { + t.Error("No error on ApierV2.RemActions: ", err.Error()) + } else if reply == utils.OK { + t.Errorf("Calling ApierV2.RemActions got reply: %s", reply) + } + if err := tpRPC.Call("ApierV2.GetActions", v2.AttrGetActions{ + ActionIDs: []string{"TOPUP_VOICE"}, + }, &actionsMap); err != nil { + t.Error("Got error on ApierV2.GetActions: ", err.Error()) + } else if len(actionsMap) != 1 { + t.Errorf("Calling ApierV2.GetActions got reply: %s", utils.ToIJSON(actionsMap)) + } +} diff --git a/sessionmanager/data_it_test.go b/sessionmanager/data_it_test.go index dd21c6c8c..726e6d5d4 100644 --- a/sessionmanager/data_it_test.go +++ b/sessionmanager/data_it_test.go @@ -212,6 +212,12 @@ func TestSMGDataLastUsedMultipleData(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } + aSessions := make([]*ActiveSession, 0) + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } smgEv = SMGenericEvent{ utils.EVENT_NAME: "TEST_EVENT", utils.TOR: utils.DATA, @@ -238,6 +244,11 @@ func TestSMGDataLastUsedMultipleData(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1068576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } smgEv = SMGenericEvent{ utils.EVENT_NAME: "TEST_EVENT", utils.TOR: utils.DATA, @@ -264,7 +275,11 @@ func TestSMGDataLastUsedMultipleData(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } - + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1088576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } smgEv = SMGenericEvent{ utils.EVENT_NAME: "TEST_EVENT", utils.TOR: utils.DATA, @@ -291,6 +306,11 @@ func TestSMGDataLastUsedMultipleData(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1108576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } smgEv = SMGenericEvent{ utils.EVENT_NAME: "TEST_EVENT", utils.TOR: utils.DATA, @@ -317,7 +337,11 @@ func TestSMGDataLastUsedMultipleData(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } - + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1128576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } smgEv = SMGenericEvent{ utils.EVENT_NAME: "TEST_EVENT", utils.TOR: utils.DATA, @@ -341,6 +365,11 @@ func TestSMGDataLastUsedMultipleData(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 0 { + t.Errorf("wrong active sessions: %+v", aSessions) + } } func TestSMGDataDerivedChargingNoCredit(t *testing.T) { @@ -474,6 +503,12 @@ func TestSMGDataTTLExpiredMultiUpdates(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } + aSessions := make([]*ActiveSession, 0) + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } smgEv = SMGenericEvent{ utils.EVENT_NAME: "TEST_EVENT", @@ -509,4 +544,410 @@ func TestSMGDataTTLExpiredMultiUpdates(t *testing.T) { } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 0 { + t.Errorf("wrong active sessions: %+v", aSessions) + } +} + +func TestSMGDataMultipleDataNoUsage(t *testing.T) { + if !*testIntegration { + return + } + var acnt *engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1010"} + eAcntVal := 49997767680.000000 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + smgEv := SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.SETUP_TIME: "2016-01-05 18:30:49", + utils.ANSWER_TIME: "2016-01-05 18:31:05", + utils.USAGE: "1048576", + } + var maxUsage float64 + if err := smgRPC.Call("SMGenericV1.SessionStart", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 1054720 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + aSessions := make([]*ActiveSession, 0) + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "0", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "0", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "0", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "0", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.LastUsed: "0", + } + var rpl string + if err = smgRPC.Call("SMGenericV1.SessionEnd", smgEv, &rpl); err != nil || rpl != utils.OK { + t.Error(err) + } + eAcntVal = 49997767680.000000 // refunded + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 0 { + t.Errorf("wrong active sessions: %+v", aSessions) + } +} + +func TestSMGDataMultipleDataConstantUsage(t *testing.T) { + if !*testIntegration { + return + } + var acnt *engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1010"} + eAcntVal := 49997767680.000000 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + smgEv := SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.SETUP_TIME: "2016-01-05 18:30:49", + utils.ANSWER_TIME: "2016-01-05 18:31:05", + utils.USAGE: "1048576", + } + var maxUsage float64 + if err := smgRPC.Call("SMGenericV1.SessionStart", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 1054720 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + aSessions := make([]*ActiveSession, 0) + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1048576 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "600", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1049176 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "600", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1049776 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "600", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1050376 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.USAGE: "1048576", + utils.LastUsed: "600", + } + if err := smgRPC.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage); err != nil { + t.Error(err) + } + if maxUsage != 1.048576e+06 { + t.Error("Bad max usage: ", maxUsage) + } + eAcntVal = 49996712960.000000 // 0 + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 1 || aSessions[0].Usage.Seconds() != 1050976 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } + smgEv = SMGenericEvent{ + utils.EVENT_NAME: "TEST_EVENT", + utils.TOR: utils.DATA, + utils.ACCID: "12349", + utils.DIRECTION: utils.OUT, + utils.ACCOUNT: "1010", + utils.SUBJECT: "1010", + utils.DESTINATION: "222", + utils.CATEGORY: "data", + utils.TENANT: "cgrates.org", + utils.REQTYPE: utils.META_PREPAID, + utils.LastUsed: "0", + } + var rpl string + if err = smgRPC.Call("SMGenericV1.SessionEnd", smgEv, &rpl); err != nil || rpl != utils.OK { + t.Error(err) + } + eAcntVal = 49997757440.000000 // 10240 (from the start) + if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.DATA].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.DATA].GetTotalValue()) + } + if err := smgRPC.Call("SMGenericV1.ActiveSessions", utils.AttrSMGGetActiveSessions{}, &aSessions); err != nil { + t.Error(err) + } else if len(aSessions) != 0 { + t.Errorf("wrong active sessions: %f", aSessions[0].Usage.Seconds()) + } } diff --git a/sessionmanager/smg_session.go b/sessionmanager/smg_session.go index d1701a905..df622953b 100644 --- a/sessionmanager/smg_session.go +++ b/sessionmanager/smg_session.go @@ -90,13 +90,15 @@ func (self *SMGSession) debit(dur time.Duration, lastUsed *time.Duration) (time. // total usage correction self.totalUsage -= self.lastUsage self.totalUsage += *lastUsed - //utils.Logger.Debug(fmt.Sprintf("Correction: %f", self.totalUsage.Seconds())) + //utils.Logger.Debug(fmt.Sprintf("TotalUsage Correction: %f", self.totalUsage.Seconds())) } } // apply correction from previous run if self.extraDuration < dur { dur -= self.extraDuration } else { + self.lastUsage = requestedDuration + self.totalUsage += self.lastUsage ccDuration := self.extraDuration // fake ccDuration self.extraDuration -= dur return ccDuration, nil @@ -111,6 +113,7 @@ func (self *SMGSession) debit(dur time.Duration, lastUsed *time.Duration) (time. self.cd.DurationIndex += dur cc := &engine.CallCost{} if err := self.rater.Call("Responder.MaxDebit", self.cd, cc); err != nil { + self.lastUsage = 0 self.lastDebit = 0 return 0, err } diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 62f38434b..12bfc9173 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -275,6 +275,7 @@ type TPActions struct { type TPAction struct { Identifier string // Identifier mapped in the code BalanceId string // Balance identification string (account scope) + BalanceUuid string // Balance identification string (global scope) BalanceType string // Type of balance the action will operate on Directions string // Balance direction Units string // Number of units to add/deduct diff --git a/utils/value_formula.go b/utils/value_formula.go index 1ac113aa1..1e6221087 100644 --- a/utils/value_formula.go +++ b/utils/value_formula.go @@ -37,6 +37,10 @@ var ValueFormulas = map[string]valueFormula{ INCREMENTAL: incrementalFormula, } +func (vf *ValueFormula) String() string { + return ToJSON(vf) +} + func incrementalFormula(params map[string]interface{}) float64 { // check parameters unitsInterface, unitsFound := params["Units"]