From 727337e61745257ec414c9ac1f8203c41a4d96fb Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 3 Feb 2014 18:50:26 +0200 Subject: [PATCH] start shared groups cvs loading modified shared group structure --- apier/v1/apier.go | 1 + apier/v1/apier_local_test.go | 21 +++++++------- engine/action.go | 1 + engine/loader_csv.go | 53 ++++++++++++++++++++++++++++++------ engine/loader_csv_test.go | 10 +++++-- engine/loader_helpers.go | 4 +++ engine/loader_local_test.go | 1 + engine/sharedgroup.go | 17 ++++++++---- engine/sharedgroup_test.go | 12 +++++--- engine/userbalance.go | 5 ++-- utils/consts.go | 4 ++- 11 files changed, 95 insertions(+), 34 deletions(-) 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