started adding test for shared group and created utility methods for

getting shared balances
This commit is contained in:
Radu Ioan Fericean
2014-01-09 20:06:32 +02:00
parent c8fb090c11
commit 1471c47bda
4 changed files with 178 additions and 25 deletions

View File

@@ -68,3 +68,21 @@ func (cm *AccountLock) Guard(name string, handler func() (float64, error)) (repl
<-lock
return
}
func (cm *AccountLock) GuardMany(names []string, handler func() (float64, error)) (reply float64, err error) {
for _, name := range names {
cm.RLock()
lock, exists := AccLock.queue[name]
cm.RUnlock()
if !exists {
cm.Lock()
lock = make(chan bool, 1)
AccLock.queue[name] = lock
cm.Unlock()
}
lock <- true
reply, err = handler()
<-lock
}
return
}

View File

@@ -18,6 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"math"
"math/rand"
"time"
)
const (
STRATEGY_LOWEST_FIRST = "*lowest_first"
STRATEGY_HIGHEST_FIRST = "*highest_first"
STRATEGY_RANDOM = "*random"
)
type SharedGroup struct {
Id string
Strategy string
@@ -26,3 +38,46 @@ type SharedGroup struct {
Weight float64
Members []string
}
func (sg *SharedGroup) GetMembersExceptUser(ubId string) []string {
for i, m := range sg.Members {
if m == ubId {
a := make([]string, len(sg.Members))
copy(a, sg.Members)
a[i], a = a[len(a)-1], a[:len(a)-1]
return a
}
}
return sg.Members
}
func (sg *SharedGroup) PopBalanceByStrategy(balanceChain *BalanceChain) (bal *Balance) {
bc := *balanceChain
if len(bc) == 0 {
return
}
index := 0
switch sg.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 {
if b.Value < minVal {
minVal = b.Value
index = i
}
}
case STRATEGY_HIGHEST_FIRST:
maxVal := math.SmallestNonzeroFloat64
for i, b := range bc {
if b.Value > maxVal {
maxVal = b.Value
index = i
}
}
}
bal, bc[index], *balanceChain = bc[index], bc[len(bc)-1], bc[:len(bc)-1]
return
}

View File

@@ -0,0 +1,75 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"reflect"
"testing"
)
func TestSharedGroupGetMembersExcept(t *testing.T) {
sg := &SharedGroup{
Members: []string{"1", "2", "3"},
}
a1 := sg.GetMembersExceptUser("1")
a2 := sg.GetMembersExceptUser("2")
a3 := sg.GetMembersExceptUser("3")
if !reflect.DeepEqual(a1, []string{"3", "2"}) ||
!reflect.DeepEqual(a2, []string{"1", "3"}) ||
!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{Value: 3.0},
}
sg := &SharedGroup{Strategy: STRATEGY_LOWEST_FIRST}
b := sg.PopBalanceByStrategy(&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)
}
}
func TestSharedPopBalanceByStrategyHigh(t *testing.T) {
bc := BalanceChain{
&Balance{Value: 2.0},
&Balance{Value: 1.0},
&Balance{Value: 3.0},
}
sg := &SharedGroup{Strategy: STRATEGY_HIGHEST_FIRST}
b := sg.PopBalanceByStrategy(&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)
}
}

View File

@@ -189,31 +189,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
} else {
// chack if it's shared
if balance.SharedGroup != "" {
sharedGroup, err := accountingStorage.GetSharedGroup(balance.SharedGroup, false)
if err != nil {
Logger.Warning(fmt.Sprintf("Could not get shared group: %s", balance.SharedGroup))
continue
}
for _, ubId := range sharedGroup.Members {
if ubId == ub.Id { // skip the initiating user
continue
}
AccLock.Guard(ubId, func() (float64, error) {
nUb, err := accountingStorage.GetUserBalance(ubId)
if err != nil {
Logger.Warning(fmt.Sprintf("Could not get user balance: %s", ubId))
}
sharedMinuteBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[MINUTES+cc.Direction], balance.SharedGroup)
sharedMoneyBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[CREDIT+cc.Direction], balance.SharedGroup)
for _, sharedBalance := range sharedMinuteBalances {
// FIXME: insert money balances after users balances
allMoneyBalances := append(usefulMoneyBalances, sharedMoneyBalances)
sharedBalance.DebitMinutes(cc, count, nUb, allMoneyBalances)
// FIXME: save nUb
}
return 0, nil
})
}
ub.debitFromSharedBalances(balance.SharedGroup, cc, usefulMoneyBalances, count)
}
}
}
@@ -265,6 +241,35 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
return returnError
}
func (ub *UserBalance) debitFromSharedBalances(sharedGroupName string, cc *CallCost, moneyBalances BalanceChain, count bool) {
sharedGroup, err := accountingStorage.GetSharedGroup(sharedGroupName, false)
if err != nil {
Logger.Warning(fmt.Sprintf("Could not get shared group: %s", sharedGroup))
return
}
sharingMembers := sharedGroup.GetMembersExceptUser(ub.Id)
AccLock.GuardMany(sharingMembers, func() (float64, error) {
for _, ubId := range sharingMembers {
if ubId == ub.Id { // skip the initiating user
continue
}
nUb, err := accountingStorage.GetUserBalance(ubId)
if err != nil {
Logger.Warning(fmt.Sprintf("Could not get user balance: %s", ubId))
}
sharedMinuteBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[MINUTES+cc.Direction], sharedGroupName)
sharedMoneyBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[CREDIT+cc.Direction], sharedGroupName)
for _, sharedBalance := range sharedMinuteBalances {
allMoneyBalances := append(moneyBalances, sharedMoneyBalances...)
sharedBalance.DebitMinutes(cc, count, nUb, allMoneyBalances)
accountingStorage.SetUserBalance(nUb)
}
}
return 0, nil
})
}
func (ub *UserBalance) GetDefaultMoneyBalance(direction string) *Balance {
for _, balance := range ub.BalanceMap[CREDIT+direction] {
if balance.IsDefault() {