diff --git a/engine/accountlock.go b/engine/accountlock.go
index 27349135b..6ea5c104d 100644
--- a/engine/accountlock.go
+++ b/engine/accountlock.go
@@ -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
+}
diff --git a/engine/sharedgroup.go b/engine/sharedgroup.go
index 632544e1c..7980fea96 100644
--- a/engine/sharedgroup.go
+++ b/engine/sharedgroup.go
@@ -18,6 +18,18 @@ along with this program. If not, see
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
+}
diff --git a/engine/sharedgroup_test.go b/engine/sharedgroup_test.go
new file mode 100644
index 000000000..384570dce
--- /dev/null
+++ b/engine/sharedgroup_test.go
@@ -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
+*/
+
+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)
+ }
+}
diff --git a/engine/userbalance.go b/engine/userbalance.go
index 824a5cff1..e82150589 100644
--- a/engine/userbalance.go
+++ b/engine/userbalance.go
@@ -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() {