shared group strategy fully customizable

This commit is contained in:
Radu Ioan Fericean
2014-03-11 18:15:12 +02:00
parent c8553e9611
commit 0855d09fea
4 changed files with 169 additions and 98 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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")
}
}