diff --git a/apier/v1/apier.go b/apier/v1/apier.go
index 35e947ba2..d275e9f76 100644
--- a/apier/v1/apier.go
+++ b/apier/v1/apier.go
@@ -543,6 +543,7 @@ func (self *ApierV1) LoadTariffPlanFromFolder(attrs AttrLoadTPFromFolder, reply
path.Join(attrs.FolderPath, utils.DESTINATION_RATES_CSV),
path.Join(attrs.FolderPath, utils.RATING_PLANS_CSV),
path.Join(attrs.FolderPath, utils.RATING_PROFILES_CSV),
+ path.Join(attrs.FolderPath, utils.SHARED_GROUPS_CSV),
path.Join(attrs.FolderPath, utils.ACTIONS_CSV),
path.Join(attrs.FolderPath, utils.ACTION_PLANS_CSV),
path.Join(attrs.FolderPath, utils.ACTION_TRIGGERS_CSV),
diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go
index 8649d0d6f..dbd0457df 100644
--- a/apier/v1/apier_local_test.go
+++ b/apier/v1/apier_local_test.go
@@ -19,20 +19,21 @@ along with this program. If not, see
package apier
import (
- "path"
- "reflect"
- "os/exec"
- "testing"
- "time"
- "strings"
- "net/url"
- "net/http"
- "net/rpc"
"flag"
"fmt"
+ "net/http"
+ "net/rpc"
+ "net/url"
+ "os/exec"
+ "path"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
- "github.com/cgrates/cgrates/utils"
"github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
)
// ToDo: Replace rpc.Client with internal rpc server and Apier using internal map as both data and stor so we can run the tests non-local
diff --git a/engine/action.go b/engine/action.go
index 459c6c30d..0f839d554 100644
--- a/engine/action.go
+++ b/engine/action.go
@@ -45,6 +45,7 @@ type Action struct {
ExpirationString string
Weight float64
Balance *Balance
+ SharedGroup string
}
const (
diff --git a/engine/loader_csv.go b/engine/loader_csv.go
index 7a1da7962..880af4511 100644
--- a/engine/loader_csv.go
+++ b/engine/loader_csv.go
@@ -45,12 +45,13 @@ type CSVReader struct {
destinationRates map[string]*utils.TPDestinationRate
ratingPlans map[string]*RatingPlan
ratingProfiles map[string]*RatingProfile
+ sharedGroups map[string]*SharedGroup
// file names
destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn,
- actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string
+ sharedGroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string
}
-func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingStorage, sep rune, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string) *CSVReader {
+func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingStorage, sep rune, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedGroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string) *CSVReader {
c := new(CSVReader)
c.sep = sep
c.dataStorage = dataStorage
@@ -63,15 +64,16 @@ func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingSto
c.timings = make(map[string]*utils.TPTiming)
c.ratingPlans = make(map[string]*RatingPlan)
c.ratingProfiles = make(map[string]*RatingProfile)
+ c.sharedGroups = make(map[string]*SharedGroup)
c.readerFunc = openFileCSVReader
c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn,
- c.actionsFn, c.actiontimingsFn, c.actiontriggersFn, c.accountactionsFn = destinationsFn, timingsFn,
- ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn
+ c.sharedGroupsFn, c.actionsFn, c.actiontimingsFn, c.actiontriggersFn, c.accountactionsFn = destinationsFn, timingsFn,
+ ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedGroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn
return c
}
-func NewStringCSVReader(dataStorage RatingStorage, accountingStorage AccountingStorage, sep rune, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string) *CSVReader {
- c := NewFileCSVReader(dataStorage, accountingStorage, sep, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn)
+func NewStringCSVReader(dataStorage RatingStorage, accountingStorage AccountingStorage, sep rune, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedGroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string) *CSVReader {
+ c := NewFileCSVReader(dataStorage, accountingStorage, sep, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedGroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn)
c.readerFunc = openStringCSVReader
return c
}
@@ -441,6 +443,40 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) {
return
}
+func (csvr *CSVReader) LoadSharedGroups() (err error) {
+ csvReader, fp, err := csvr.readerFunc(csvr.sharedGroupsFn, csvr.sep, utils.SHARED_GROUPS_NRCOLS)
+ if err != nil {
+ log.Print("Could not load shared groups file: ", err)
+ // allow writing of the other values
+ return nil
+ }
+ if fp != nil {
+ defer fp.Close()
+ }
+ for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
+ tag := record[0]
+ sg, found := csvr.sharedGroups[tag]
+ if found {
+ sg.AccountParameters[record[1]] = &SharingParameters{
+ Strategy: record[2],
+ RateSubject: record[3],
+ }
+ } else {
+ sg = &SharedGroup{
+ Id: tag,
+ AccountParameters: map[string]*SharingParameters{
+ record[1]: &SharingParameters{
+ Strategy: record[2],
+ RateSubject: record[3],
+ },
+ },
+ }
+ }
+ csvr.sharedGroups[tag] = sg
+ }
+ return
+}
+
func (csvr *CSVReader) LoadActions() (err error) {
csvReader, fp, err := csvr.readerFunc(csvr.actionsFn, csvr.sep, utils.ACTIONS_NRCOLS)
if err != nil {
@@ -471,7 +507,7 @@ func (csvr *CSVReader) LoadActions() (err error) {
return errors.New(fmt.Sprintf("Could not parse action balance weight: %v", err))
}
}
- weight, err := strconv.ParseFloat(record[10], 64)
+ weight, err := strconv.ParseFloat(record[11], 64)
if err != nil {
return errors.New(fmt.Sprintf("Could not parse action weight: %v", err))
}
@@ -482,7 +518,7 @@ func (csvr *CSVReader) LoadActions() (err error) {
Direction: record[3],
Weight: weight,
ExpirationString: record[5],
- ExtraParameters: record[9],
+ ExtraParameters: record[10],
Balance: &Balance{
Uuid: utils.GenUUID(),
Value: units,
@@ -490,6 +526,7 @@ func (csvr *CSVReader) LoadActions() (err error) {
DestinationId: record[6],
RateSubject: record[7],
},
+ SharedGroup: record[9],
}
if _, err := utils.ParseDate(a.ExpirationString); err != nil {
return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err))
diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go
index e54bc4876..99aff8d00 100644
--- a/engine/loader_csv_test.go
+++ b/engine/loader_csv_test.go
@@ -110,9 +110,12 @@ vdf,0,*out,fallback1,2013-11-18T13:46:00Z,G,fallback2
vdf,0,*out,fallback1,2013-11-18T13:47:00Z,G,fallback2
vdf,0,*out,fallback2,2013-11-18T13:45:00Z,R,rif
`
+ sharedGroups = `
+`
+
actions = `
-MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,10
-MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,10
+MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,,10
+MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,,10
`
actionTimings = `
MORE_MINUTES,MINI,ONE_TIME_RUN,10
@@ -129,13 +132,14 @@ vdf,minitsboy,*out,MORE_MINUTES,STANDARD_TRIGGER
var csvr *CSVReader
func init() {
- csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, destinationRateTimings, ratingProfiles, actions, actionTimings, actionTriggers, accountActions)
+ csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, destinationRateTimings, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions)
csvr.LoadDestinations()
csvr.LoadTimings()
csvr.LoadRates()
csvr.LoadDestinationRates()
csvr.LoadRatingPlans()
csvr.LoadRatingProfiles()
+ csvr.LoadSharedGroups()
csvr.LoadActions()
csvr.LoadActionTimings()
csvr.LoadActionTriggers()
diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go
index 602436237..2cde59aa7 100644
--- a/engine/loader_helpers.go
+++ b/engine/loader_helpers.go
@@ -40,6 +40,7 @@ type TPLoader interface {
LoadTimings() error
LoadRatingPlans() error
LoadRatingProfiles() error
+ LoadSharedGroups() error
LoadActions() error
LoadActionTimings() error
LoadActionTriggers() error
@@ -198,6 +199,9 @@ var FileValidators = map[string]*FileLineRegexValidator{
utils.RATING_PROFILES_CSV: &FileLineRegexValidator{utils.RATE_PROFILES_NRCOLS,
regexp.MustCompile(`(?:\w+\s*,\s*){2}(?:\*out\s*,\s*){1}(?:\*any\s*,\s*|\w+\s*,\s*){1}(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z){1}(?:\w*\s*,?\s*){2}$`),
"Tenant([0-9A-Za-z_]),TOR([0-9A-Za-z_]),Direction(*out),Subject([0-9A-Za-z_]|*all),RatesFallbackSubject([0-9A-Za-z_]|),RatesTimingTag([0-9A-Za-z_]),ActivationTime([0-9T:X])"},
+ utils.SHARED_GROUPS_CSV: &FileLineRegexValidator{utils.SHARED_GROUPS_NRCOLS,
+ regexp.MustCompile(``),
+ ""},
utils.ACTIONS_CSV: &FileLineRegexValidator{utils.ACTIONS_NRCOLS,
regexp.MustCompile(`(?:\w+\s*),(?:\*\w+\s*),(?:\*\w+\s*)?,(?:\*out\s*)?,(?:\d+\s*)?,(?:\*\w+\s*|\+\d+[smh]\s*|\d+\s*)?,(?:\*any|\w+\s*)?,(?:\*\w+\s*)?,(?:\d+\.?\d*\s*)?,(?:\S+\s*)?,(?:\d+\.?\d*\s*)$`),
"Tag([0-9A-Za-z_]),Action([0-9A-Za-z_]),BalanceType([*a-z_]),Direction(*out),Units([0-9]),ExpiryTime(*[a-z_]|+[0-9][smh]|[0-9])DestinationTag([0-9A-Za-z_]|*all),RatingSubject([0-9A-Za-z_]),BalanceWeight([0-9.]),ExtraParameters([0-9A-Za-z_:;]),Weight([0-9.])"},
diff --git a/engine/loader_local_test.go b/engine/loader_local_test.go
index b358e5bea..7bbc0e868 100644
--- a/engine/loader_local_test.go
+++ b/engine/loader_local_test.go
@@ -126,6 +126,7 @@ func TestLoadFromCSV(t *testing.T) {
path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.DESTINATION_RATES_CSV),
path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.RATING_PLANS_CSV),
path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.RATING_PROFILES_CSV),
+ path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.SHARED_GROUPS_CSV),
path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.ACTIONS_CSV),
path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.ACTION_PLANS_CSV),
path.Join(*dataDir, "tariffplans", *tpCsvScenario, utils.ACTION_TRIGGERS_CSV),
diff --git a/engine/sharedgroup.go b/engine/sharedgroup.go
index 7980fea96..de4bf738a 100644
--- a/engine/sharedgroup.go
+++ b/engine/sharedgroup.go
@@ -31,12 +31,14 @@ const (
)
type SharedGroup struct {
- Id string
+ Id string
+ AccountParameters map[string]*SharingParameters
+ Members []string
+}
+
+type SharingParameters struct {
Strategy string
- Account string
RateSubject string
- Weight float64
- Members []string
}
func (sg *SharedGroup) GetMembersExceptUser(ubId string) []string {
@@ -51,19 +53,21 @@ func (sg *SharedGroup) GetMembersExceptUser(ubId string) []string {
return sg.Members
}
-func (sg *SharedGroup) PopBalanceByStrategy(balanceChain *BalanceChain) (bal *Balance) {
+func (sg *SharedGroup) PopBalanceByStrategy(account string, balanceChain *BalanceChain) (bal *Balance) {
bc := *balanceChain
if len(bc) == 0 {
return
}
index := 0
- switch sg.Strategy {
+ sharingParameters := sg.AccountParameters[account]
+ switch sharingParameters.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
@@ -72,6 +76,7 @@ func (sg *SharedGroup) PopBalanceByStrategy(balanceChain *BalanceChain) (bal *Ba
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
diff --git a/engine/sharedgroup_test.go b/engine/sharedgroup_test.go
index 384570dce..b893e903e 100644
--- a/engine/sharedgroup_test.go
+++ b/engine/sharedgroup_test.go
@@ -44,8 +44,10 @@ func TestSharedPopBalanceByStrategyLow(t *testing.T) {
&Balance{Value: 1.0},
&Balance{Value: 3.0},
}
- sg := &SharedGroup{Strategy: STRATEGY_LOWEST_FIRST}
- b := sg.PopBalanceByStrategy(&bc)
+ sg := &SharedGroup{AccountParameters: map[string]*SharingParameters{
+ "test": &SharingParameters{Strategy: STRATEGY_LOWEST_FIRST}},
+ }
+ b := sg.PopBalanceByStrategy("test", &bc)
if b.Value != 1.0 {
t.Error("Error popping the right balance according to strategy: ", b, bc)
}
@@ -62,8 +64,10 @@ func TestSharedPopBalanceByStrategyHigh(t *testing.T) {
&Balance{Value: 1.0},
&Balance{Value: 3.0},
}
- sg := &SharedGroup{Strategy: STRATEGY_HIGHEST_FIRST}
- b := sg.PopBalanceByStrategy(&bc)
+ sg := &SharedGroup{AccountParameters: map[string]*SharingParameters{
+ "test": &SharingParameters{Strategy: STRATEGY_HIGHEST_FIRST}},
+ }
+ b := sg.PopBalanceByStrategy("test", &bc)
if b.Value != 3.0 {
t.Error("Error popping the right balance according to strategy: ", b, bc)
}
diff --git a/engine/userbalance.go b/engine/userbalance.go
index 85870dfa1..fa35b8340 100644
--- a/engine/userbalance.go
+++ b/engine/userbalance.go
@@ -281,7 +281,8 @@ func (ub *UserBalance) debitMinutesFromSharedBalances(sharedGroupName string, cc
sharedMinuteBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[MINUTES+cc.Direction], sharedGroupName)
allMinuteSharedBalances = append(allMinuteSharedBalances, sharedMinuteBalances...)
}
- for sharedBalance := sharedGroup.PopBalanceByStrategy(&allMinuteSharedBalances); sharedBalance != nil; sharedBalance = sharedGroup.PopBalanceByStrategy(&allMinuteSharedBalances) {
+ for sharedBalance := sharedGroup.PopBalanceByStrategy(ub.Id, &allMinuteSharedBalances); sharedBalance != nil; sharedBalance = sharedGroup.PopBalanceByStrategy(ub.Id,
+ &allMinuteSharedBalances) {
initialValue := sharedBalance.Value
sharedBalance.DebitMinutes(cc, count, sharedBalance.userBalance, moneyBalances)
if sharedBalance.Value != initialValue {
@@ -320,7 +321,7 @@ func (ub *UserBalance) debitMoneyFromSharedBalances(sharedGroupName string, cc *
sharedMoneyBalances := nUb.getBalancesForPrefix(cc.Destination, nUb.BalanceMap[CREDIT+cc.Direction], sharedGroupName)
allMoneySharedBalances = append(allMoneySharedBalances, sharedMoneyBalances...)
}
- for sharedBalance := sharedGroup.PopBalanceByStrategy(&allMoneySharedBalances); sharedBalance != nil; sharedBalance = sharedGroup.PopBalanceByStrategy(&allMoneySharedBalances) {
+ for sharedBalance := sharedGroup.PopBalanceByStrategy(ub.Id, &allMoneySharedBalances); sharedBalance != nil; sharedBalance = sharedGroup.PopBalanceByStrategy(ub.Id, &allMoneySharedBalances) {
initialValue := sharedBalance.Value
sharedBalance.DebitMoney(cc, count, sharedBalance.userBalance)
if sharedBalance.Value != initialValue {
diff --git a/utils/consts.go b/utils/consts.go
index 8cadcd6ef..58c45b80c 100644
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -40,6 +40,7 @@ const (
DESTINATION_RATES_CSV = "DestinationRates.csv"
RATING_PLANS_CSV = "RatingPlans.csv"
RATING_PROFILES_CSV = "RatingProfiles.csv"
+ SHARED_GROUPS_CSV = "SharedGroups.csv"
ACTIONS_CSV = "Actions.csv"
ACTION_PLANS_CSV = "ActionPlans.csv"
ACTION_TRIGGERS_CSV = "ActionTriggers.csv"
@@ -50,7 +51,8 @@ const (
DESTINATION_RATES_NRCOLS = 3
DESTRATE_TIMINGS_NRCOLS = 4
RATE_PROFILES_NRCOLS = 7
- ACTIONS_NRCOLS = 11
+ SHARED_GROUPS_NRCOLS = 5
+ ACTIONS_NRCOLS = 12
ACTION_PLANS_NRCOLS = 4
ACTION_TRIGGERS_NRCOLS = 8
ACCOUNT_ACTIONS_NRCOLS = 5