From bea5803f76b01a3eabb316359c27fb93570559fc Mon Sep 17 00:00:00 2001 From: TeoV Date: Wed, 14 Feb 2018 11:59:51 +0200 Subject: [PATCH] Add 2 new actions (*topup_zero_negative and *set_expiry) and test for them --- engine/account.go | 26 ++++++---- engine/account_test.go | 12 ++--- engine/action.go | 41 +++++++++++---- engine/action_trigger.go | 2 +- engine/actions_it_test.go | 5 -- engine/actions_test.go | 102 +++++++++++++++++++++++++++++++++++++- engine/balances.go | 10 ++-- engine/balances_test.go | 8 +-- engine/units_counter.go | 2 +- 9 files changed, 169 insertions(+), 39 deletions(-) diff --git a/engine/account.go b/engine/account.go index 8662b2bf0..0c433de7b 100644 --- a/engine/account.go +++ b/engine/account.go @@ -140,7 +140,6 @@ func (acc *Account) setBalanceAction(a *Action) error { acc.BalanceMap[*a.Balance.Type] = append(acc.BalanceMap[*a.Balance.Type], balance) } } - if a.Balance.ID != nil && *a.Balance.ID == utils.META_DEFAULT { // treat it separately since modifyBalance sets expiry and others parameters, not specific for *default if a.Balance.Value != nil { balance.ID = *a.Balance.ID @@ -185,7 +184,7 @@ func (acc *Account) setBalanceAction(a *Action) error { // Debits some amount of user's specified balance adding the balance if it does not exists. // Returns the remaining credit in user's balance. -func (ub *Account) debitBalanceAction(a *Action, reset bool) error { +func (ub *Account) debitBalanceAction(a *Action, reset, resetIfNegative, resetExpiry bool) error { if a == nil { return errors.New("nil action") } @@ -204,14 +203,23 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error { continue // just to be safe (cleaned expired balances above) } b.account = ub - if b.MatchFilter(a.Balance, false) { - if reset { - b.SetValue(0) + //if resetExpiry if false we do normal match otherwise modify + if resetExpiry { + if b.MatchFilter(a.Balance, false, resetExpiry) { + b.ExpirationDate = a.Balance.GetExpirationDate() + b.dirty = true + found = true + } + } else { + if b.MatchFilter(a.Balance, false, resetExpiry) { + if reset || (resetIfNegative && b.Value < 0) { + b.SetValue(0) + } + b.SubstractValue(bClone.GetValue()) + b.dirty = true + found = true + a.balanceValue = b.GetValue() } - b.SubstractValue(bClone.GetValue()) - b.dirty = true - found = true - a.balanceValue = b.GetValue() } } // if it is not found then we add it to the list diff --git a/engine/account_test.go b/engine/account_test.go index a97e04c98..d2acbce6c 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -969,7 +969,7 @@ func TestAccountdebitBalance(t *testing.T) { Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), } a := &Action{Balance: newMb} - ub.debitBalanceAction(a, false) + ub.debitBalanceAction(a, false, false, false) if len(ub.BalanceMap[utils.VOICE]) != 3 || !ub.BalanceMap[utils.VOICE][2].DestinationIDs.Equal(*newMb.DestinationIDs) { t.Errorf("Error adding minute bucket! %d %+v %+v", len(ub.BalanceMap[utils.VOICE]), ub.BalanceMap[utils.VOICE][2], newMb) } @@ -1012,7 +1012,7 @@ func TestAccountdebitBalanceExists(t *testing.T) { Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), } a := &Action{Balance: newMb} - ub.debitBalanceAction(a, false) + ub.debitBalanceAction(a, false, false, false) if len(ub.BalanceMap[utils.VOICE]) != 2 || ub.BalanceMap[utils.VOICE][0].GetValue() != 25 { t.Error("Error adding minute bucket!") } @@ -1024,7 +1024,7 @@ func TestAccountAddMinuteNil(t *testing.T) { AllowNegative: true, BalanceMap: map[string]Balances{utils.SMS: Balances{&Balance{Value: 14}}, utils.DATA: Balances{&Balance{Value: 1024}}, utils.VOICE: Balances{&Balance{Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } - ub.debitBalanceAction(nil, false) + ub.debitBalanceAction(nil, false, false, false) if len(ub.BalanceMap[utils.VOICE]) != 2 { t.Error("Error adding minute bucket!") } @@ -1051,17 +1051,17 @@ func TestAccountAddMinutBucketEmpty(t *testing.T) { } ub := &Account{} a := &Action{Balance: mb1} - ub.debitBalanceAction(a, false) + ub.debitBalanceAction(a, false, false, false) if len(ub.BalanceMap[utils.VOICE]) != 1 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE]) } a = &Action{Balance: mb2} - ub.debitBalanceAction(a, false) + ub.debitBalanceAction(a, false, false, false) if len(ub.BalanceMap[utils.VOICE]) != 1 || ub.BalanceMap[utils.VOICE][0].GetValue() != 20 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE]) } a = &Action{Balance: mb3} - ub.debitBalanceAction(a, false) + ub.debitBalanceAction(a, false, false, false) if len(ub.BalanceMap[utils.VOICE]) != 2 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE]) } diff --git a/engine/action.go b/engine/action.go index b554184fa..1778f1e36 100644 --- a/engine/action.go +++ b/engine/action.go @@ -79,6 +79,8 @@ const ( SET_DDESTINATIONS = "*set_ddestinations" TRANSFER_MONETARY_DEFAULT = "*transfer_monetary_default" CGR_RPC = "*cgr_rpc" + TopUpZeroNegative = "*topup_zero_negative" + SetExpiry = "*set_expiry" ) func (a *Action) Clone() *Action { @@ -115,6 +117,8 @@ func getActionFunc(typ string) (actionTypeFunc, bool) { SET_BALANCE: setBalanceAction, TRANSFER_MONETARY_DEFAULT: transferMonetaryDefaultAction, CGR_RPC: cgrRPCAction, + TopUpZeroNegative: topupZeroNegativeAction, + SetExpiry: setExpiryAction, } f, exists := actionFuncMap[typ] return f, exists @@ -310,7 +314,7 @@ func topupResetAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Ac } c := a.Clone() genericMakeNegative(c) - err = genericDebit(ub, c, true) + err = genericDebit(ub, c, true, false) a.balanceValue = c.balanceValue return } @@ -321,7 +325,7 @@ func topupAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions } c := a.Clone() genericMakeNegative(c) - err = genericDebit(ub, c, false) + err = genericDebit(ub, c, false, false) a.balanceValue = c.balanceValue return } @@ -333,14 +337,14 @@ func debitResetAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Ac if ub.BalanceMap == nil { // Init the map since otherwise will get error if nil ub.BalanceMap = make(map[string]Balances, 0) } - return genericDebit(ub, a, true) + return genericDebit(ub, a, true, false) } func debitAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) (err error) { if ub == nil { return errors.New("nil account") } - err = genericDebit(ub, a, false) + err = genericDebit(ub, a, false, false) return } @@ -360,14 +364,14 @@ func genericMakeNegative(a *Action) { } } -func genericDebit(ub *Account, a *Action, reset bool) (err error) { +func genericDebit(ub *Account, a *Action, reset, resetExpiry bool) (err error) { if ub == nil { return errors.New("nil account") } if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]Balances) } - return ub.debitBalanceAction(a, reset) + return ub.debitBalanceAction(a, reset, false, resetExpiry) } func enableAccountAction(acc *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) (err error) { @@ -544,7 +548,6 @@ func setddestinations(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Ac } func removeAccountAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) error { - var accID string if ub != nil { accID = ub.ID @@ -615,7 +618,7 @@ func removeBalanceAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs bChain := ub.BalanceMap[a.Balance.GetType()] found := false for i := 0; i < len(bChain); i++ { - if bChain[i].MatchFilter(a.Balance, false) { + if bChain[i].MatchFilter(a.Balance, false, false) { // delete without preserving order bChain[i] = bChain[len(bChain)-1] bChain = bChain[:len(bChain)-1] @@ -650,7 +653,7 @@ func transferMonetaryDefaultAction(acc *Account, sq *CDRStatsQueueTriggered, a * for _, balance := range bChain { if balance.Uuid != defaultBalance.Uuid && balance.ID != defaultBalance.ID && // extra caution - balance.MatchFilter(a.Balance, false) { + balance.MatchFilter(a.Balance, false, false) { if balance.Value > 0 { defaultBalance.Value += balance.Value balance.Value = 0 @@ -747,6 +750,26 @@ func cgrRPCAction(account *Account, sq *CDRStatsQueueTriggered, a *Action, acs A return nil } +func topupZeroNegativeAction(account *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) error { + if account == nil { + return errors.New("nil account") + } + if account.BalanceMap == nil { + account.BalanceMap = make(map[string]Balances) + } + return account.debitBalanceAction(a, false, true, false) +} + +func setExpiryAction(account *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) error { + if account == nil { + return errors.New("nil account") + } + if account.BalanceMap == nil { + account.BalanceMap = make(map[string]Balances) + } + return account.debitBalanceAction(a, false, false, true) +} + // Structure to store actions according to weight type Actions []*Action diff --git a/engine/action_trigger.go b/engine/action_trigger.go index d51d5c1b9..43e2984c3 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -145,7 +145,7 @@ func (at *ActionTrigger) Match(a *Action) bool { thresholdType = t.ThresholdType == "" || at.ThresholdType == t.ThresholdType } - return thresholdType && at.Balance.CreateBalance().MatchFilter(a.Balance, false) + return thresholdType && at.Balance.CreateBalance().MatchFilter(a.Balance, false, false) } func (at *ActionTrigger) CreateBalance() *Balance { diff --git a/engine/actions_it_test.go b/engine/actions_it_test.go index 0a44997ae..fbac11db3 100644 --- a/engine/actions_it_test.go +++ b/engine/actions_it_test.go @@ -39,7 +39,6 @@ var actsLclCfgPath = path.Join(*dataDir, "conf", "samples", "actions") var waitRater = flag.Int("wait_rater", 100, "Number of miliseconds to wait for rater to start and cache") func TestActionsitInitCfg(t *testing.T) { - // Init config first var err error actsLclCfg, err = config.NewCGRConfigFromFolder(actsLclCfgPath) @@ -51,7 +50,6 @@ func TestActionsitInitCfg(t *testing.T) { } func TestActionsitInitCdrDb(t *testing.T) { - if err := InitStorDb(actsLclCfg); err != nil { t.Fatal(err) } @@ -59,7 +57,6 @@ func TestActionsitInitCdrDb(t *testing.T) { // Finds cgr-engine executable and starts it with default configuration func TestActionsitStartEngine(t *testing.T) { - if _, err := StartEngine(actsLclCfgPath, *waitRater); err != nil { t.Fatal(err) } @@ -67,7 +64,6 @@ func TestActionsitStartEngine(t *testing.T) { // Connect rpc client to rater func TestActionsitRpcConn(t *testing.T) { - var err error time.Sleep(500 * time.Millisecond) actsLclRpc, err = jsonrpc.Dial("tcp", actsLclCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed @@ -121,7 +117,6 @@ func TestActionsitSetCdrlogDebit(t *testing.T) { } func TestActionsitSetCdrlogTopup(t *testing.T) { - var reply string attrsSetAccount := &utils.AttrSetAccount{Tenant: "cgrates.org", Account: "dan2905"} if err := actsLclRpc.Call("ApierV1.SetAccount", attrsSetAccount, &reply); err != nil { diff --git a/engine/actions_test.go b/engine/actions_test.go index 28767e61b..e176b0635 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -2449,7 +2449,107 @@ func TestActionCdrlogBalanceValue(t *testing.T) { if len(cdrs) != 2 || cdrs[0].ExtraFields["BalanceValue"] != "11.1" || cdrs[1].ExtraFields["BalanceValue"] != "9" { - t.Errorf("Wrong cdrlogs: %", utils.ToIJSON(cdrs)) + t.Errorf("Wrong cdrlogs: %s", utils.ToIJSON(cdrs)) + } +} + +func TestActionTopUpZeroNegative(t *testing.T) { + account := &Account{ + ID: "cgrates.org:zeroNegative", + BalanceMap: map[string]Balances{ + utils.MONETARY: Balances{ + &Balance{ + ID: "Bal1", + Value: -10, + }, + &Balance{ + ID: "Bal2", + Value: 5, + }, + }, + }, + } + err := dm.DataDB().SetAccount(account) + if err != nil { + t.Error("Error setting account: ", err) + } + at := &ActionTiming{ + accountIDs: utils.StringMap{"cgrates.org:zeroNegative": true}, + Timing: &RateInterval{}, + actions: []*Action{ + &Action{ + Id: "ZeroMonetary", + ActionType: TopUpZeroNegative, + Balance: &BalanceFilter{ + Type: utils.StringPointer(utils.MONETARY), + }, + }, + }, + } + err = at.Execute(nil, nil) + acc, err := dm.DataDB().GetAccount("cgrates.org:zeroNegative") + if err != nil || acc == nil { + t.Error("Error getting account: ", acc, err) + } + //Verify value for first balance(Bal1) should be 0 after execute action TopUpZeroNegative + if acc.BalanceMap[utils.MONETARY][0].Value != 0 { + t.Errorf("Expecting 0, received: %+v", acc.BalanceMap[utils.MONETARY][0].Value) + } + //Verify value for secound balance(Bal2) should be the same + if acc.BalanceMap[utils.MONETARY][1].Value != 5 { + t.Errorf("Expecting 5, received: %+v", acc.BalanceMap[utils.MONETARY][1].Value) + } +} + +func TestActionSetExpiry(t *testing.T) { + var cloneTimeNowPlus24h time.Time + timeNowPlus24h := time.Now().Add(time.Duration(24 * time.Hour)) + //Need clone because time.Now adds extra information that DeepEqual doesn't like + if err := utils.Clone(timeNowPlus24h, &cloneTimeNowPlus24h); err != nil { + t.Error(err) + } + account := &Account{ + ID: "cgrates.org:zeroNegative", + BalanceMap: map[string]Balances{ + utils.MONETARY: Balances{ + &Balance{ + ID: "Bal1", + Value: -10, + }, + &Balance{ + ID: "Bal2", + Value: 5, + }, + }, + }, + } + err := dm.DataDB().SetAccount(account) + if err != nil { + t.Error("Error setting account: ", err) + } + at := &ActionTiming{ + accountIDs: utils.StringMap{"cgrates.org:zeroNegative": true}, + Timing: &RateInterval{}, + actions: []*Action{ + &Action{ + Id: "SetExpiry", + ActionType: SetExpiry, + Balance: &BalanceFilter{ + ID: utils.StringPointer("Bal1"), + Type: utils.StringPointer(utils.MONETARY), + ExpirationDate: utils.TimePointer(cloneTimeNowPlus24h), + }, + }, + }, + } + err = at.Execute(nil, nil) + acc, err := dm.DataDB().GetAccount("cgrates.org:zeroNegative") + if err != nil || acc == nil { + t.Error("Error getting account: ", acc, err) + } + //Verify ExpirationDate for first balance(Bal1) + if acc.BalanceMap[utils.MONETARY][0].ExpirationDate != cloneTimeNowPlus24h { + t.Errorf("Expecting: %+v, received: %+v", cloneTimeNowPlus24h, acc.BalanceMap[utils.MONETARY][0].ExpirationDate) } } diff --git a/engine/balances.go b/engine/balances.go index bc55da63f..863c55bf7 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -71,7 +71,7 @@ func (b *Balance) Equal(o *Balance) bool { b.Blocker == o.Blocker } -func (b *Balance) MatchFilter(o *BalanceFilter, skipIds bool) bool { +func (b *Balance) MatchFilter(o *BalanceFilter, skipIds, skipExpiry bool) bool { if o == nil { return true } @@ -81,8 +81,12 @@ func (b *Balance) MatchFilter(o *BalanceFilter, skipIds bool) bool { if !skipIds && o.ID != nil && *o.ID != "" { return b.ID == *o.ID } - return (o.ExpirationDate == nil || b.ExpirationDate.Equal(*o.ExpirationDate)) && - (o.Weight == nil || b.Weight == *o.Weight) && + if !skipExpiry { + if o.ExpirationDate != nil && !b.ExpirationDate.Equal(*o.ExpirationDate) { + return false + } + } + return (o.Weight == nil || b.Weight == *o.Weight) && (o.Blocker == nil || b.Blocker == *o.Blocker) && (o.Disabled == nil || b.Disabled == *o.Disabled) && (o.DestinationIDs == nil || b.DestinationIDs.Includes(*o.DestinationIDs)) && diff --git a/engine/balances_test.go b/engine/balances_test.go index e53379409..a657217c3 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -90,7 +90,7 @@ func TestBalanceEqual(t *testing.T) { func TestBalanceMatchFilter(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}} mb2 := &BalanceFilter{Weight: utils.Float64Pointer(1), RatingSubject: nil, DestinationIDs: nil} - if !mb1.MatchFilter(mb2, false) { + if !mb1.MatchFilter(mb2, false, false) { t.Errorf("Match filter failure: %+v == %+v", mb1, mb2) } } @@ -98,7 +98,7 @@ func TestBalanceMatchFilter(t *testing.T) { func TestBalanceMatchFilterEmpty(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}} mb2 := &BalanceFilter{} - if !mb1.MatchFilter(mb2, false) { + if !mb1.MatchFilter(mb2, false, false) { t.Errorf("Match filter failure: %+v == %+v", mb1, mb2) } } @@ -106,7 +106,7 @@ func TestBalanceMatchFilterEmpty(t *testing.T) { func TestBalanceMatchFilterId(t *testing.T) { mb1 := &Balance{ID: "T1", Weight: 2, precision: 2, RatingSubject: "2", DestinationIDs: utils.NewStringMap("NAT")} mb2 := &BalanceFilter{ID: utils.StringPointer("T1"), Weight: utils.Float64Pointer(1), RatingSubject: utils.StringPointer("1"), DestinationIDs: nil} - if !mb1.MatchFilter(mb2, false) { + if !mb1.MatchFilter(mb2, false, false) { t.Errorf("Match filter failure: %+v == %+v", mb1, mb2) } } @@ -114,7 +114,7 @@ func TestBalanceMatchFilterId(t *testing.T) { func TestBalanceMatchFilterDiffId(t *testing.T) { mb1 := &Balance{ID: "T1", Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}} mb2 := &BalanceFilter{ID: utils.StringPointer("T2"), Weight: utils.Float64Pointer(1), RatingSubject: utils.StringPointer("1"), DestinationIDs: nil} - if mb1.MatchFilter(mb2, false) { + if mb1.MatchFilter(mb2, false, false) { t.Errorf("Match filter failure: %+v != %+v", mb1, mb2) } } diff --git a/engine/units_counter.go b/engine/units_counter.go index fe960fb00..07558577d 100644 --- a/engine/units_counter.go +++ b/engine/units_counter.go @@ -79,7 +79,7 @@ func (ucs UnitCounters) addUnits(amount float64, kind string, cc *CallCost, b *B continue } - if uc.CounterType == utils.COUNTER_BALANCE && b != nil && b.MatchFilter(c.Filter, true) { + if uc.CounterType == utils.COUNTER_BALANCE && b != nil && b.MatchFilter(c.Filter, true, false) { c.Value += amount continue }