diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index edea9fd10..9a4872938 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -211,31 +211,54 @@ CREATE TABLE `tp_account_actions` ( UNIQUE KEY `unique_tp_account` (`tpid`,`loadid`,`tenant`,`account`,`direction`) ); +-- +-- Table structure for table `tp_lcrs` +-- + +DROP TABLE IF EXISTS tp_lcrs; +CREATE TABLE tp_lcrs ( + `tbid` int(11) NOT NULL AUTO_INCREMENT, + `tpid` varchar(64) NOT NULL, + `direction` varchar(8) NOT NULL, + `tenant` varchar(64) NOT NULL, + `customer` varchar(64) NOT NULL, + `destination`_id varchar(64) NOT NULL, + `category` varchar(16) NOT NULL, + `strategy` varchar(16) NOT NULL, + `suppliers` varchar(64) NOT NULL, + `activation_time` varchar(24) NOT NULL, + `weight` double(8,2) NOT NULL, + PRIMARY KEY (`tbid`), + KEY `tpid` (`tpid`) +); + -- -- Table structure for table `tp_derived_chargers` -- DROP TABLE IF EXISTS tp_derived_chargers; CREATE TABLE tp_derived_chargers ( - tbid int(11) NOT NULL AUTO_INCREMENT, - tpid varchar(64) NOT NULL, - loadid varchar(64) NOT NULL, - direction varchar(8) NOT NULL, - tenant varchar(64) NOT NULL, - tor varchar(16) NOT NULL, - account varchar(24) NOT NULL, - subject varchar(64) NOT NULL, - runid_field varchar(24) NOT NULL, - reqtype_field varchar(24) NOT NULL, - direction_field varchar(24) NOT NULL, - tenant_field varchar(24) NOT NULL, - tor_field varchar(24) NOT NULL, - account_field varchar(24) NOT NULL, - subject_field varchar(24) NOT NULL, - destination_field varchar(24) NOT NULL, - setup_time_field varchar(24) NOT NULL, - answer_time_field varchar(24) NOT NULL, - duration_field varchar(24) NOT NULL, + `tbid` int(11) NOT NULL AUTO_INCREMENT, + `tpid` varchar(64) NOT NULL, + `loadid` varchar(64) NOT NULL, + `direction` varchar(8) NOT NULL, + `tenant` varchar(64) NOT NULL, + `category` varchar(16) NOT NULL, + `account` varchar(24) NOT NULL, + `subject` varchar(64) NOT NULL, + `runid_field` varchar(24) NOT NULL, + `reqtype_field` varchar(24) NOT NULL, + `direction_field` varchar(24) NOT NULL, + `tenant_field` varchar(24) NOT NULL, + `tor_field` varchar(24) NOT NULL, + `account_field` varchar(24) NOT NULL, + `subject_field` varchar(24) NOT NULL, + `destination_field` varchar(24) NOT NULL, + `setup_time`_field varchar(24) NOT NULL, + `answer_time`_field varchar(24) NOT NULL, + `duration_field` varchar(24) NOT NULL, PRIMARY KEY (`tbid`), KEY `tpid` (`tpid`) ); + + diff --git a/engine/calldesc.go b/engine/calldesc.go index 17278fd7c..032fab205 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -724,7 +724,7 @@ func (cd *CallDescriptor) GetLCR() (*LCRCost, error) { } } else { // find rating profiles - ratingProfileSearchKey := fmt.Sprintf("%s:%s:%s:", lcr.Direction, lcr.Tenant, ts.Entry.TOR) + ratingProfileSearchKey := fmt.Sprintf("%s:%s:%s:", lcr.Direction, lcr.Tenant, ts.Entry.Category) suppliers := cache2go.GetEntriesKeys(LCR_PREFIX + ratingProfileSearchKey) for _, supplier := range suppliers { split := strings.Split(supplier, ":") diff --git a/engine/lcr.go b/engine/lcr.go index 1261e7da5..863fab158 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -34,9 +34,9 @@ const ( ) type LCR struct { + Direction string Tenant string Customer string - Direction string Activations []*LCRActivation } type LCRActivation struct { @@ -45,7 +45,7 @@ type LCRActivation struct { } type LCREntry struct { DestinationId string - TOR string + Category string Strategy string Suppliers string Weight float64 diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 217d5ba3f..0573843c1 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -54,11 +54,11 @@ type CSVReader struct { derivedChargers map[string]utils.DerivedChargers // file names destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn, - sharedgroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn string + sharedgroupsFn, lcrFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn string } func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingStorage, sep rune, - destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedgroupsFn, + destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedgroupsFn, lcrFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn string) *CSVReader { c := new(CSVReader) c.sep = sep @@ -80,16 +80,16 @@ func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingSto c.rpAliases = make(map[string]string) c.accAliases = make(map[string]string) c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn, - c.sharedgroupsFn, c.actionsFn, c.actiontimingsFn, c.actiontriggersFn, c.accountactionsFn, c.derivedChargersFn = destinationsFn, timingsFn, - ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedgroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn + c.sharedgroupsFn, c.lcrFn, c.actionsFn, c.actiontimingsFn, c.actiontriggersFn, c.accountactionsFn, c.derivedChargersFn = destinationsFn, timingsFn, + ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedgroupsFn, lcrFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn return c } func NewStringCSVReader(dataStorage RatingStorage, accountingStorage AccountingStorage, sep rune, - destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedgroupsFn, + destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, ratingprofilesFn, sharedgroupsFn, lcrFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn string) *CSVReader { c := NewFileCSVReader(dataStorage, accountingStorage, sep, destinationsFn, timingsFn, ratesFn, destinationratesFn, destinationratetimingsFn, - ratingprofilesFn, sharedgroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn) + ratingprofilesFn, sharedgroupsFn, lcrFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn) c.readerFunc = openStringCSVReader return c } @@ -167,6 +167,8 @@ func (csvr *CSVReader) ShowStatistics() { log.Print("Account actions: ", len(csvr.accountActions)) // derivedChargers log.Print("DerivedChargers: ", len(csvr.derivedChargers)) + // lcr rules + log.Print("LCR rules: ", len(csvr.lcrs)) } func (csvr *CSVReader) WriteToDatabase(flush, verbose bool) (err error) { @@ -238,6 +240,18 @@ func (csvr *CSVReader) WriteToDatabase(flush, verbose bool) (err error) { log.Println(k) } } + if verbose { + log.Print("LCR Rules") + } + for k, lcr := range csvr.lcrs { + err = dataStorage.SetLCR(lcr) + if err != nil { + return err + } + if verbose { + log.Println(k) + } + } if verbose { log.Print("Actions") } @@ -558,6 +572,60 @@ func (csvr *CSVReader) LoadSharedGroups() (err error) { return } +func (csvr *CSVReader) LoadLCRs() (err error) { + csvReader, fp, err := csvr.readerFunc(csvr.lcrFn, csvr.sep, utils.LCRS_NRCOLS) + if err != nil { + log.Print("Could not load LCR rules 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() { + direction, tenant, customer := record[0], record[1], record[2] + id := fmt.Sprintf("%s:%s:%s", direction, tenant, customer) + lcr, found := csvr.lcrs[id] + activationTime, err := utils.ParseTimeDetectLayout(record[7]) + if err != nil { + return fmt.Errorf("Could not parse LCR activation time: %v", err) + } + weight, err := strconv.ParseFloat(record[8], 64) + if err != nil { + return fmt.Errorf("Could not parse LCR weight: %v", err) + } + if !found { + lcr = &LCR{ + Direction: direction, + Tenant: tenant, + Customer: customer, + } + } + var act *LCRActivation + for _, existingAct := range lcr.Activations { + if existingAct.ActivationTime.Equal(activationTime) { + act = existingAct + break + } + } + if act == nil { + act = &LCRActivation{ + ActivationTime: activationTime, + } + lcr.Activations = append(lcr.Activations, act) + } + act.Entries = append(act.Entries, &LCREntry{ + DestinationId: record[3], + Category: record[4], + Strategy: record[5], + Suppliers: record[6], + Weight: weight, + }) + csvr.lcrs[id] = lcr + } + return +} + func (csvr *CSVReader) LoadActions() (err error) { csvReader, fp, err := csvr.readerFunc(csvr.actionsFn, csvr.sep, utils.ACTIONS_NRCOLS) if err != nil { diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index f8d12e9af..ccd0139ee 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -133,6 +133,11 @@ RP_DATA,DATA_RATE,ALWAYS,10 SG1,*any,*lowest, SG2,*any,*lowest,one SG3,*any,*lowest, +` + + lcrs = ` +*in,cgrates.org,*any,EU_LANDLINE,LCR_STANDARD,*static,ivo;dan;rif,2012-01-01T00:00:00Z,10 +*in,cgrates.org,*any,*any,LCR_STANDARD,*lowest_cost,,2012-01-01T00:00:00Z,20 ` actions = ` @@ -184,7 +189,7 @@ var csvr *CSVReader func init() { csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, ratingPlans, ratingProfiles, - sharedGroups, actions, actionTimings, actionTriggers, accountActions, derivedCharges) + sharedGroups, lcrs, actions, actionTimings, actionTriggers, accountActions, derivedCharges) csvr.LoadDestinations() csvr.LoadTimings() csvr.LoadRates() @@ -192,6 +197,7 @@ func init() { csvr.LoadRatingPlans() csvr.LoadRatingProfiles() csvr.LoadSharedGroups() + csvr.LoadLCRs() csvr.LoadActions() csvr.LoadActionTimings() csvr.LoadActionTriggers() @@ -709,7 +715,7 @@ func TestLoadActions(t *testing.T) { func TestLoadSharedGroups(t *testing.T) { if len(csvr.sharedGroups) != 3 { - t.Error("Failed to load actions: ", csvr.sharedGroups) + t.Error("Failed to shared groups: ", csvr.sharedGroups) } sg1 := csvr.sharedGroups["SG1"] @@ -754,6 +760,43 @@ func TestLoadSharedGroups(t *testing.T) { }*/ } +func TestLoadLCRs(t *testing.T) { + if len(csvr.lcrs) != 1 { + t.Error("Failed to load LCRs: ", csvr.lcrs) + } + + lcr := csvr.lcrs["*in:cgrates.org:*any"] + expected := &LCR{ + Direction: "*in", + Tenant: "cgrates.org", + Customer: "*any", + Activations: []*LCRActivation{ + &LCRActivation{ + ActivationTime: time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Entries: []*LCREntry{ + &LCREntry{ + DestinationId: "EU_LANDLINE", + Category: "LCR_STANDARD", + Strategy: "*static", + Suppliers: "ivo;dan;rif", + Weight: 10, + }, + &LCREntry{ + DestinationId: "*any", + Category: "LCR_STANDARD", + Strategy: "*lowest_cost", + Suppliers: "", + Weight: 20, + }, + }, + }, + }, + } + if !reflect.DeepEqual(lcr, expected) { + t.Errorf("Error loading lcr %+v: ", lcr.Activations) + } +} + func TestLoadActionTimings(t *testing.T) { if len(csvr.actionsTimings) != 5 { t.Error("Failed to load action timings: ", csvr.actionsTimings) diff --git a/engine/loader_db.go b/engine/loader_db.go index c23e41c5f..6365aa4c6 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "log" + "strconv" "strings" "github.com/cgrates/cgrates/utils" @@ -47,6 +48,7 @@ type DbReader struct { ratingPlans map[string]*RatingPlan ratingProfiles map[string]*RatingProfile sharedGroups map[string]*SharedGroup + lcrs map[string]*LCR derivedChargers map[string]utils.DerivedChargers } @@ -62,6 +64,7 @@ func NewDbReader(storDB LoadStorage, ratingDb RatingStorage, accountDb Accountin c.ratingPlans = make(map[string]*RatingPlan) c.ratingProfiles = make(map[string]*RatingProfile) c.sharedGroups = make(map[string]*SharedGroup) + c.lcrs = make(map[string]*LCR) c.rpAliases = make(map[string]string) c.accAliases = make(map[string]string) c.accountActions = make(map[string]*Account) @@ -121,6 +124,8 @@ func (dbr *DbReader) ShowStatistics() { log.Print("Account actions: ", len(dbr.accountActions)) // derivedChargers log.Print("DerivedChargers: ", len(dbr.derivedChargers)) + // lcr rules + log.Print("LCR rules: ", len(dbr.lcrs)) } func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) { @@ -188,6 +193,18 @@ func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) { log.Println(k) } } + if verbose { + log.Print("LCR Rules") + } + for k, lcr := range dbr.lcrs { + err = dataStorage.SetLCR(lcr) + if err != nil { + return err + } + if verbose { + log.Println(k) + } + } if verbose { log.Print("Actions") } @@ -456,6 +473,62 @@ func (dbr *DbReader) LoadSharedGroups() (err error) { return err } +func (dbr *DbReader) LoadLCRs() (err error) { + dbr.sharedGroups, err = dbr.storDb.GetTpSharedGroups(dbr.tpid, "") + return err + csvReader, fp, err := csvr.readerFunc(csvr.lcrFn, csvr.sep, utils.LCRS_NRCOLS) + if err != nil { + log.Print("Could not load LCR rules 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() { + direction, tenant, customer := record[0], record[1], record[2] + id := fmt.Sprintf("%s:%s:%s", direction, tenant, customer) + lcr, found := csvr.lcrs[id] + activationTime, err := utils.ParseTimeDetectLayout(record[7]) + if err != nil { + return fmt.Errorf("Could not parse LCR activation time: %v", err) + } + weight, err := strconv.ParseFloat(record[8], 64) + if err != nil { + return fmt.Errorf("Could not parse LCR weight: %v", err) + } + if !found { + lcr = &LCR{ + Direction: direction, + Tenant: tenant, + Customer: customer, + } + } + var act *LCRActivation + for _, existingAct := range lcr.Activations { + if existingAct.ActivationTime.Equal(activationTime) { + act = existingAct + break + } + } + if act == nil { + act = &LCRActivation{ + ActivationTime: activationTime, + } + lcr.Activations = append(lcr.Activations, act) + } + act.Entries = append(act.Entries, &LCREntry{ + DestinationId: record[3], + Category: record[4], + Strategy: record[5], + Suppliers: record[6], + Weight: weight, + }) + csvr.lcrs[id] = lcr + } + return +} + func (dbr *DbReader) LoadActions() (err error) { storActs, err := dbr.storDb.GetTpActions(dbr.tpid, "") if err != nil { diff --git a/engine/loader_local_test.go b/engine/loader_local_test.go index 3c85c510f..98cc5da5b 100644 --- a/engine/loader_local_test.go +++ b/engine/loader_local_test.go @@ -127,6 +127,7 @@ func TestLoadFromCSV(t *testing.T) { 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.LCRS_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/storage_interface.go b/engine/storage_interface.go index d6277cb80..048885a52 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -155,6 +155,9 @@ type LoadStorage interface { SetTPSharedGroups(string, map[string]*SharedGroup) error GetTpSharedGroups(string, string) (map[string]*SharedGroup, error) + SetTPLCRs(string, map[string]*LCR) error + GetTpLCRs(string, string) (map[string]*LCR, error) + SetTPActions(string, map[string][]*utils.TPAction) error GetTpActions(string, string) (map[string][]*utils.TPAction, error) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 555adda4d..45833bc9f 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -305,6 +305,31 @@ func (self *SQLStorage) SetTPSharedGroups(tpid string, sgs map[string]*SharedGro return nil } +func (self *SQLStorage) SetTPLCRs(tpid string, lcrs map[string]*LCR) error { + if len(lcrs) == 0 { + return nil //Nothing to set + } + var buffer bytes.Buffer + buffer.WriteString(fmt.Sprintf("INSERT INTO %s (tpid,direction,tenant,customer,destination_id,category,strategy,suppliers,activation_time,weight) VALUES ", utils.TBL_TP_LCRS)) + i := 0 + for _, lcr := range lcrs { + for _, act := range lcr.Activations { + for _, entry := range act.Entries { + if i != 0 { //Consecutive values after the first will be prefixed with "," as separator + buffer.WriteRune(',') + } + buffer.WriteString(fmt.Sprintf("('%s','%s','%s','%s','%s','%s','%s','%v','%v')", + tpid, lcr.Direction, lcr.Tenant, lcr.Customer, entry.DestinationId, entry.Category, entry.Strategy, entry.Suppliers, act.ActivationTime, entry.Weight)) + i++ + } + } + } + if _, err := self.Db.Exec(buffer.String()); err != nil { + return err + } + return nil +} + func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*utils.TPAction) error { if len(acts) == 0 { return nil //Nothing to set @@ -1086,6 +1111,59 @@ func (self *SQLStorage) GetTpSharedGroups(tpid, tag string) (map[string]*SharedG return sgs, nil } +func (self *SQLStorage) GetTpLCRs(tpid, tag string) (map[string]*LCR, error) { + lcrs := make(map[string]*LCR) + q := fmt.Sprintf("SELECT * FROM %s WHERE tpid='%s'", utils.TBL_TP_LCRS, tpid) + if tag != "" { + q += fmt.Sprintf(" AND id='%s'", tag) + } + rows, err := self.Db.Query(q) + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var id int + var tpid, direction, tenant, customer, destinationId, category, strategy, suppliers, activationTimeString string + var weight float64 + if err := rows.Scan(&id, &tpid, &direction, &tenant, &customer, &destinationId, &category, &strategy, &suppliers, &activationTimeString, &weight); err != nil { + return nil, err + } + tag := fmt.Sprintf("%s:%s:%s", direction, tenant, customer) + lcr, found := lcrs[tag] + activationTime, _ := utils.ParseTimeDetectLayout(activationTimeString) + if !found { + lcr = &LCR{ + Direction: direction, + Tenant: tenant, + Customer: customer, + } + } + var act *LCRActivation + for _, existingAct := range lcr.Activations { + if existingAct.ActivationTime.Equal(activationTime) { + act = existingAct + break + } + } + if act == nil { + act = &LCRActivation{ + ActivationTime: activationTime, + } + lcr.Activations = append(lcr.Activations, act) + } + act.Entries = append(act.Entries, &LCREntry{ + DestinationId: destinationId, + Category: category, + Strategy: strategy, + Suppliers: suppliers, + Weight: weight, + }) + lcrs[tag] = lcr + } + return lcrs, nil +} + func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*utils.TPAction, error) { as := make(map[string][]*utils.TPAction) q := fmt.Sprintf("SELECT * FROM %s WHERE tpid='%s'", utils.TBL_TP_ACTIONS, tpid) diff --git a/utils/consts.go b/utils/consts.go index 150c92d39..2e566bffc 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -27,6 +27,7 @@ const ( TBL_TP_RATING_PLANS = "tp_rating_plans" TBL_TP_RATE_PROFILES = "tp_rating_profiles" TBL_TP_SHARED_GROUPS = "tp_shared_groups" + TBL_TP_LCRS = "tp_lcrs" TBL_TP_ACTIONS = "tp_actions" TBL_TP_ACTION_PLANS = "tp_action_plans" TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" @@ -42,6 +43,7 @@ const ( RATING_PLANS_CSV = "RatingPlans.csv" RATING_PROFILES_CSV = "RatingProfiles.csv" SHARED_GROUPS_CSV = "SharedGroups.csv" + LCRS_CSV = "LCRRules.csv" ACTIONS_CSV = "Actions.csv" ACTION_PLANS_CSV = "ActionPlans.csv" ACTION_TRIGGERS_CSV = "ActionTriggers.csv" @@ -54,6 +56,7 @@ const ( DESTRATE_TIMINGS_NRCOLS = 4 RATE_PROFILES_NRCOLS = 7 SHARED_GROUPS_NRCOLS = 4 + LCRS_NRCOLS = 9 ACTIONS_NRCOLS = 12 ACTION_PLANS_NRCOLS = 4 ACTION_TRIGGERS_NRCOLS = 9