mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
shared group strategy fully customizable
This commit is contained in:
@@ -173,14 +173,13 @@ func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) {
|
||||
defaultMoneyBalance := ub.GetDefaultMoneyBalance(cc.Direction)
|
||||
// debit minutes
|
||||
for _, balance := range usefulMinuteBalances {
|
||||
balance.DebitMinutes(cc, count, ub, usefulMoneyBalances)
|
||||
if balance.SharedGroup != "" {
|
||||
ub.debitMinutesFromSharedBalances(balance, cc, usefulMoneyBalances, count)
|
||||
} else {
|
||||
balance.DebitMinutes(cc, count, ub, usefulMoneyBalances)
|
||||
}
|
||||
if cc.IsPaid() {
|
||||
goto CONNECT_FEE
|
||||
} else {
|
||||
// chack if it's shared
|
||||
if balance.SharedGroup != "" {
|
||||
ub.debitMinutesFromSharedBalances(balance.SharedGroup, cc, usefulMoneyBalances, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ {
|
||||
@@ -198,14 +197,13 @@ func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) {
|
||||
|
||||
// debit money
|
||||
for _, balance := range usefulMoneyBalances {
|
||||
balance.DebitMoney(cc, count, ub)
|
||||
if balance.SharedGroup != "" {
|
||||
ub.debitMoneyFromSharedBalances(balance, cc, count)
|
||||
} else {
|
||||
balance.DebitMoney(cc, count, ub)
|
||||
}
|
||||
if cc.IsPaid() {
|
||||
goto CONNECT_FEE
|
||||
} else {
|
||||
// chack if it's shared
|
||||
if balance.SharedGroup != "" {
|
||||
ub.debitMoneyFromSharedBalances(balance.SharedGroup, cc, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
// get the highest priority money balanance
|
||||
@@ -263,33 +261,34 @@ CONNECT_FEE:
|
||||
return
|
||||
}
|
||||
|
||||
func (ub *Account) debitMinutesFromSharedBalances(sharedGroupName string, cc *CallCost, moneyBalances BalanceChain, count bool) {
|
||||
func (ub *Account) debitMinutesFromSharedBalances(myBalance *Balance, cc *CallCost, moneyBalances BalanceChain, count bool) {
|
||||
sharedGroupName := myBalance.SharedGroup
|
||||
sharedGroup, err := accountingStorage.GetSharedGroup(sharedGroupName, false)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sharedGroupName))
|
||||
return
|
||||
}
|
||||
sharingMembers := sharedGroup.GetMembersExceptUser(ub.Id)
|
||||
AccLock.GuardMany(sharingMembers, func() (float64, error) {
|
||||
sharingMembers := sharedGroup.Members
|
||||
AccLock.GuardMany(sharedGroup.GetMembersExceptUser(ub.Id), func() (float64, error) {
|
||||
var allMinuteSharedBalances BalanceChain
|
||||
for _, ubId := range sharingMembers {
|
||||
var nUb *Account
|
||||
if ubId == ub.Id { // skip the initiating user
|
||||
continue
|
||||
}
|
||||
|
||||
nUb, err := accountingStorage.GetAccount(ubId)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Could not get user balance: %s", ubId))
|
||||
}
|
||||
if nUb.Disabled {
|
||||
Logger.Warning(fmt.Sprintf("Disabled user in shared group: %s (%s)", ubId, sharedGroupName))
|
||||
continue
|
||||
nUb = ub
|
||||
} else {
|
||||
nUb, err = accountingStorage.GetAccount(ubId)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Could not get user balance: %s", ubId))
|
||||
}
|
||||
if nUb.Disabled {
|
||||
Logger.Warning(fmt.Sprintf("Disabled user in shared group: %s (%s)", ubId, sharedGroupName))
|
||||
continue
|
||||
}
|
||||
}
|
||||
sharedMinuteBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[MINUTES+cc.Direction], sharedGroupName)
|
||||
allMinuteSharedBalances = append(allMinuteSharedBalances, sharedMinuteBalances...)
|
||||
}
|
||||
for sharedBalance := sharedGroup.PopBalanceByStrategy(ub.Id, &allMinuteSharedBalances); sharedBalance != nil; sharedBalance = sharedGroup.PopBalanceByStrategy(ub.Id,
|
||||
&allMinuteSharedBalances) {
|
||||
for _, sharedBalance := range sharedGroup.GetBalancesByStrategy(myBalance, allMinuteSharedBalances) {
|
||||
initialValue := sharedBalance.Value
|
||||
sharedBalance.DebitMinutes(cc, count, sharedBalance.account, moneyBalances)
|
||||
if sharedBalance.Value != initialValue {
|
||||
@@ -303,32 +302,34 @@ func (ub *Account) debitMinutesFromSharedBalances(sharedGroupName string, cc *Ca
|
||||
})
|
||||
}
|
||||
|
||||
func (ub *Account) debitMoneyFromSharedBalances(sharedGroupName string, cc *CallCost, count bool) {
|
||||
func (ub *Account) debitMoneyFromSharedBalances(myBalance *Balance, cc *CallCost, count bool) {
|
||||
sharedGroupName := myBalance.SharedGroup
|
||||
sharedGroup, err := accountingStorage.GetSharedGroup(sharedGroupName, false)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sharedGroup))
|
||||
return
|
||||
}
|
||||
sharingMembers := sharedGroup.GetMembersExceptUser(ub.Id)
|
||||
AccLock.GuardMany(sharingMembers, func() (float64, error) {
|
||||
sharingMembers := sharedGroup.Members
|
||||
AccLock.GuardMany(sharedGroup.GetMembersExceptUser(ub.Id), func() (float64, error) {
|
||||
var allMoneySharedBalances BalanceChain
|
||||
for _, ubId := range sharingMembers {
|
||||
var nUb *Account
|
||||
if ubId == ub.Id { // skip the initiating user
|
||||
continue
|
||||
}
|
||||
|
||||
nUb, err := accountingStorage.GetAccount(ubId)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Could not get user balance: %s", ubId))
|
||||
}
|
||||
if nUb.Disabled {
|
||||
Logger.Warning(fmt.Sprintf("Disabled user in shared group: %s (%s)", ubId, sharedGroupName))
|
||||
continue
|
||||
nUb = ub
|
||||
} else {
|
||||
nUb, err = accountingStorage.GetAccount(ubId)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("Could not get user balance: %s", ubId))
|
||||
}
|
||||
if nUb.Disabled {
|
||||
Logger.Warning(fmt.Sprintf("Disabled user in shared group: %s (%s)", ubId, sharedGroupName))
|
||||
continue
|
||||
}
|
||||
}
|
||||
sharedMoneyBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[CREDIT+cc.Direction], sharedGroupName)
|
||||
allMoneySharedBalances = append(allMoneySharedBalances, sharedMoneyBalances...)
|
||||
}
|
||||
for sharedBalance := sharedGroup.PopBalanceByStrategy(ub.Id, &allMoneySharedBalances); sharedBalance != nil; sharedBalance = sharedGroup.PopBalanceByStrategy(ub.Id, &allMoneySharedBalances) {
|
||||
for _, sharedBalance := range sharedGroup.GetBalancesByStrategy(myBalance, allMoneySharedBalances) {
|
||||
initialValue := sharedBalance.Value
|
||||
sharedBalance.DebitMoney(cc, count, sharedBalance.account)
|
||||
if sharedBalance.Value != initialValue {
|
||||
|
||||
@@ -1058,7 +1058,7 @@ func TestDebitShared(t *testing.T) {
|
||||
CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneyc", Value: 70, SharedGroup: "SG_TEST"}},
|
||||
}}
|
||||
|
||||
sg := &SharedGroup{Id: "SG_TEST", Members: []string{rif.Id, groupie.Id}, AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_RANDOM}}}
|
||||
sg := &SharedGroup{Id: "SG_TEST", Members: []string{rif.Id, groupie.Id}, AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_MINE_RANDOM}}}
|
||||
|
||||
accountingStorage.SetAccount(groupie)
|
||||
accountingStorage.SetSharedGroup("SG_TEST", sg)
|
||||
|
||||
@@ -19,17 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package engine
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
STRATEGY_LOWEST_FIRST = "*lowest_first"
|
||||
STRATEGY_HIGHEST_FIRST = "*highest_first"
|
||||
STRATEGY_RANDOM = "*random"
|
||||
MINE_PREFIX = "*mine_"
|
||||
STRATEGY_MINE_LOWEST = "*mine_lowest"
|
||||
STRATEGY_MINE_HIGHEST = "*mine_highest"
|
||||
STRATEGY_MINE_RANDOM = "*mine_random"
|
||||
STRATEGY_LOWEST = "*lowest"
|
||||
STRATEGY_HIGHEST = "*highest"
|
||||
STRATEGY_RANDOM = "*random"
|
||||
)
|
||||
|
||||
type SharedGroup struct {
|
||||
@@ -55,43 +60,79 @@ func (sg *SharedGroup) GetMembersExceptUser(ubId string) []string {
|
||||
return sg.Members
|
||||
}
|
||||
|
||||
func (sg *SharedGroup) PopBalanceByStrategy(account string, balanceChain *BalanceChain) (bal *Balance) {
|
||||
bc := *balanceChain
|
||||
if len(bc) == 0 {
|
||||
return
|
||||
}
|
||||
index := 0
|
||||
func (sg *SharedGroup) GetBalancesByStrategy(myBalance *Balance, bc BalanceChain) BalanceChain {
|
||||
sharingParameters := sg.AccountParameters[utils.ANY]
|
||||
if sp, hasParamsForAccount := sg.AccountParameters[account]; hasParamsForAccount {
|
||||
if sp, hasParamsForAccount := sg.AccountParameters[myBalance.account.Id]; hasParamsForAccount {
|
||||
sharingParameters = sp
|
||||
}
|
||||
strategy := STRATEGY_RANDOM
|
||||
|
||||
strategy := STRATEGY_MINE_RANDOM
|
||||
if sharingParameters != nil {
|
||||
strategy = sharingParameters.Strategy
|
||||
}
|
||||
switch strategy {
|
||||
case STRATEGY_RANDOM:
|
||||
rand.Seed(time.Now().Unix())
|
||||
index = rand.Intn(len(bc))
|
||||
case STRATEGY_LOWEST_FIRST:
|
||||
minVal := math.MaxFloat64
|
||||
for i, b := range bc {
|
||||
b.RateSubject = sharingParameters.RateSubject
|
||||
if b.Value < minVal {
|
||||
minVal = b.Value
|
||||
index = i
|
||||
}
|
||||
}
|
||||
case STRATEGY_HIGHEST_FIRST:
|
||||
maxVal := math.SmallestNonzeroFloat64
|
||||
for i, b := range bc {
|
||||
b.RateSubject = sharingParameters.RateSubject
|
||||
if b.Value > maxVal {
|
||||
maxVal = b.Value
|
||||
index = i
|
||||
}
|
||||
}
|
||||
case STRATEGY_LOWEST, STRATEGY_MINE_LOWEST:
|
||||
sort.Sort(LowestBalanceChainSorter(bc))
|
||||
case STRATEGY_HIGHEST, STRATEGY_MINE_HIGHEST:
|
||||
sort.Sort(HighestBalanceChainSorter(bc))
|
||||
case STRATEGY_RANDOM, STRATEGY_MINE_RANDOM:
|
||||
rbc := RandomBalanceChainSorter(bc)
|
||||
(&rbc).Sort()
|
||||
bc = BalanceChain(rbc)
|
||||
}
|
||||
bal, bc[index], *balanceChain = bc[index], bc[len(bc)-1], bc[:len(bc)-1]
|
||||
return
|
||||
if strings.HasPrefix(strategy, MINE_PREFIX) {
|
||||
// find index of my balance
|
||||
index := 0
|
||||
for i, b := range bc {
|
||||
if b.Uuid == myBalance.Uuid {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// move my balance first
|
||||
bc[0], bc[index] = bc[index], bc[0]
|
||||
}
|
||||
return bc
|
||||
}
|
||||
|
||||
type LowestBalanceChainSorter []*Balance
|
||||
|
||||
func (lbcs LowestBalanceChainSorter) Len() int {
|
||||
return len(lbcs)
|
||||
}
|
||||
|
||||
func (lbcs LowestBalanceChainSorter) Swap(i, j int) {
|
||||
lbcs[i], lbcs[j] = lbcs[j], lbcs[i]
|
||||
}
|
||||
|
||||
func (lbcs LowestBalanceChainSorter) Less(i, j int) bool {
|
||||
return lbcs[i].Value < lbcs[j].Value
|
||||
}
|
||||
|
||||
type HighestBalanceChainSorter []*Balance
|
||||
|
||||
func (hbcs HighestBalanceChainSorter) Len() int {
|
||||
return len(hbcs)
|
||||
}
|
||||
|
||||
func (hbcs HighestBalanceChainSorter) Swap(i, j int) {
|
||||
hbcs[i], hbcs[j] = hbcs[j], hbcs[i]
|
||||
}
|
||||
|
||||
func (hbcs HighestBalanceChainSorter) Less(i, j int) bool {
|
||||
return hbcs[i].Value > hbcs[j].Value
|
||||
}
|
||||
|
||||
type RandomBalanceChainSorter []*Balance
|
||||
|
||||
func (rbcs *RandomBalanceChainSorter) Sort() {
|
||||
src := *rbcs
|
||||
// randomize balance chain
|
||||
dest := make([]*Balance, len(src))
|
||||
rand.Seed(time.Now().Unix())
|
||||
perm := rand.Perm(len(src))
|
||||
for i, v := range perm {
|
||||
dest[v] = src[i]
|
||||
}
|
||||
*rbcs = dest
|
||||
}
|
||||
|
||||
@@ -35,45 +35,74 @@ func TestSharedGroupGetMembersExcept(t *testing.T) {
|
||||
!reflect.DeepEqual(a3, []string{"1", "2"}) {
|
||||
t.Error("Error getting shared group members: ", a1, a2, a3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSharedPopBalanceByStrategyLow(t *testing.T) {
|
||||
bc := BalanceChain{
|
||||
&Balance{Value: 2.0},
|
||||
&Balance{Value: 1.0},
|
||||
&Balance{Uuid: "uuuu", Value: 1.0, account: &Account{Id: "test"}},
|
||||
&Balance{Value: 3.0},
|
||||
}
|
||||
sg := &SharedGroup{AccountParameters: map[string]*SharingParameters{
|
||||
"test": &SharingParameters{Strategy: STRATEGY_LOWEST_FIRST}},
|
||||
"test": &SharingParameters{Strategy: STRATEGY_LOWEST}},
|
||||
}
|
||||
b := sg.PopBalanceByStrategy("test", &bc)
|
||||
if b.Value != 1.0 {
|
||||
t.Error("Error popping the right balance according to strategy: ", b, bc)
|
||||
}
|
||||
if len(bc) != 2 ||
|
||||
bc[0].Value != 2.0 ||
|
||||
bc[1].Value != 3.0 {
|
||||
t.Error("Error removing balance from chain: ", bc)
|
||||
sbc := sg.GetBalancesByStrategy(bc[1], bc)
|
||||
if len(sbc) != 3 ||
|
||||
sbc[0].Value != 1.0 ||
|
||||
sbc[1].Value != 2.0 {
|
||||
t.Error("Error sorting balance chain: ", sbc[0].Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedPopBalanceByStrategyHigh(t *testing.T) {
|
||||
bc := BalanceChain{
|
||||
&Balance{Value: 2.0},
|
||||
&Balance{Uuid: "uuuu", Value: 2.0, account: &Account{Id: "test"}},
|
||||
&Balance{Value: 1.0},
|
||||
&Balance{Value: 3.0},
|
||||
}
|
||||
sg := &SharedGroup{AccountParameters: map[string]*SharingParameters{
|
||||
"test": &SharingParameters{Strategy: STRATEGY_HIGHEST_FIRST}},
|
||||
"test": &SharingParameters{Strategy: STRATEGY_HIGHEST}},
|
||||
}
|
||||
b := sg.PopBalanceByStrategy("test", &bc)
|
||||
if b.Value != 3.0 {
|
||||
t.Error("Error popping the right balance according to strategy: ", b, bc)
|
||||
}
|
||||
if len(bc) != 2 ||
|
||||
bc[0].Value != 2.0 ||
|
||||
bc[1].Value != 1.0 {
|
||||
t.Error("Error removing balance from chain: ", bc)
|
||||
sbc := sg.GetBalancesByStrategy(bc[0], bc)
|
||||
if len(sbc) != 3 ||
|
||||
sbc[0].Value != 3.0 ||
|
||||
sbc[1].Value != 2.0 {
|
||||
t.Error("Error sorting balance chain: ", sbc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedPopBalanceByStrategyMineHigh(t *testing.T) {
|
||||
bc := BalanceChain{
|
||||
&Balance{Uuid: "uuuu", Value: 2.0, account: &Account{Id: "test"}},
|
||||
&Balance{Value: 1.0},
|
||||
&Balance{Value: 3.0},
|
||||
}
|
||||
sg := &SharedGroup{AccountParameters: map[string]*SharingParameters{
|
||||
"test": &SharingParameters{Strategy: STRATEGY_MINE_HIGHEST}},
|
||||
}
|
||||
sbc := sg.GetBalancesByStrategy(bc[0], bc)
|
||||
if len(sbc) != 3 ||
|
||||
sbc[0].Value != 2.0 ||
|
||||
sbc[1].Value != 3.0 {
|
||||
t.Error("Error sorting balance chain: ", sbc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedPopBalanceByStrategyRandomHigh(t *testing.T) {
|
||||
bc := BalanceChain{
|
||||
&Balance{Uuid: "uuuu", Value: 2.0, account: &Account{Id: "test"}},
|
||||
&Balance{Value: 1.0},
|
||||
&Balance{Value: 3.0},
|
||||
}
|
||||
sg := &SharedGroup{AccountParameters: map[string]*SharingParameters{
|
||||
"test": &SharingParameters{Strategy: STRATEGY_RANDOM}},
|
||||
}
|
||||
x := bc[0]
|
||||
sbc := sg.GetBalancesByStrategy(bc[0], bc)
|
||||
firstTest := (sbc[0].Uuid == x.Uuid)
|
||||
sbc = sg.GetBalancesByStrategy(bc[0], bc)
|
||||
secondTest := (sbc[0].Uuid == x.Uuid)
|
||||
if firstTest && secondTest {
|
||||
t.Error("Something is wrong with balance randomizer")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user