diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index b63c6f31f..820a31cd9 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -65,7 +65,7 @@ var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path h var storDbType = flag.String("stordb_type", "mysql", "The type of the storDb database ") var waitRater = flag.Int("wait_rater", 500, "Number of miliseconds to wait for rater to start and cache") -func TestLoadConfig(t *testing.T) { +func TestApierLoadConfig(t *testing.T) { if !*testLocal { return } @@ -76,7 +76,7 @@ func TestLoadConfig(t *testing.T) { } } -func TestCreateDirs(t *testing.T) { +func TestApierCreateDirs(t *testing.T) { if !*testLocal { return } @@ -92,7 +92,7 @@ func TestCreateDirs(t *testing.T) { } // Empty tables before using them -func TestCreateTables(t *testing.T) { +func TestApierCreateTables(t *testing.T) { if !*testLocal { return } @@ -118,7 +118,7 @@ func TestCreateTables(t *testing.T) { } } -func TestInitDataDb(t *testing.T) { +func TestApierInitDataDb(t *testing.T) { if !*testLocal { return } @@ -128,7 +128,7 @@ func TestInitDataDb(t *testing.T) { } // Finds cgr-engine executable and starts it with default configuration -func TestStartEngine(t *testing.T) { +func TestApierStartEngine(t *testing.T) { if !*testLocal { return } @@ -146,7 +146,7 @@ func TestStartEngine(t *testing.T) { } // Connect rpc client to rater -func TestRpcConn(t *testing.T) { +func TestApierRpcConn(t *testing.T) { if !*testLocal { return } @@ -1205,7 +1205,7 @@ func TestApierGetAccount(t *testing.T) { } // Start with initial balance, top-up to test max_balance -func TestTriggersExecute(t *testing.T) { +func TestApierTriggersExecute(t *testing.T) { if !*testLocal { return } @@ -1225,11 +1225,11 @@ func TestTriggersExecute(t *testing.T) { } // Start fresh before loading from folder -func TestResetDataBeforeLoadFromFolder(t *testing.T) { +func TestApierResetDataBeforeLoadFromFolder(t *testing.T) { if !*testLocal { return } - TestInitDataDb(t) + TestApierInitDataDb(t) reply := "" arc := new(utils.ApiReloadCache) // Simple test that command is executed without errors @@ -1272,7 +1272,7 @@ func TestApierLoadTariffPlanFromFolder(t *testing.T) { time.Sleep(time.Duration(*waitRater) * time.Millisecond) } -func TestResetDataAfterLoadFromFolder(t *testing.T) { +func TestApierResetDataAfterLoadFromFolder(t *testing.T) { if !*testLocal { return } @@ -1310,7 +1310,7 @@ func TestApierGetAccountAfterLoad(t *testing.T) { } // Test here ResponderGetCost -func TestResponderGetCost(t *testing.T) { +func TestApierResponderGetCost(t *testing.T) { if !*testLocal { return } @@ -1337,7 +1337,7 @@ func TestResponderGetCost(t *testing.T) { } // Test here ResponderGetCost -func TestGetCallCostLog(t *testing.T) { +func TestApierGetCallCostLog(t *testing.T) { if !*testLocal { return } @@ -1354,7 +1354,7 @@ func TestGetCallCostLog(t *testing.T) { } } -func TestMaxDebitInexistentAcnt(t *testing.T) { +func TestApierMaxDebitInexistentAcnt(t *testing.T) { if !*testLocal { return } @@ -1378,7 +1378,7 @@ func TestMaxDebitInexistentAcnt(t *testing.T) { } -func TestCdrServer(t *testing.T) { +func TestApierCdrServer(t *testing.T) { if !*testLocal { return } @@ -1400,7 +1400,7 @@ func TestCdrServer(t *testing.T) { } /* -func TestExportCdrsToFile(t *testing.T) { +func TestApierExportCdrsToFile(t *testing.T) { if !*testLocal { return } @@ -1437,7 +1437,7 @@ func TestExportCdrsToFile(t *testing.T) { } */ -func TestLocalGetCdrs(t *testing.T) { +func TestApierLocalGetCdrs(t *testing.T) { if !*testLocal { return } @@ -1450,7 +1450,7 @@ func TestLocalGetCdrs(t *testing.T) { } } -func TestLocalProcessCdr(t *testing.T) { +func TestApierLocalProcessCdr(t *testing.T) { if !*testLocal { return } @@ -1460,7 +1460,7 @@ func TestLocalProcessCdr(t *testing.T) { SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", } - if err := rater.Call("CDRSV1.ProcessCdr", cdr, &reply); err != nil { + if err := rater.Call("CdrsV1.ProcessCdr", cdr, &reply); err != nil { t.Error("Unexpected error: ", err.Error()) } else if reply != utils.OK { t.Error("Unexpected reply received: ", reply) @@ -1474,7 +1474,7 @@ func TestLocalProcessCdr(t *testing.T) { } } -func TestLocalSetDC(t *testing.T) { +func TestApierLocalSetDC(t *testing.T) { if !*testLocal { return } @@ -1493,7 +1493,7 @@ func TestLocalSetDC(t *testing.T) { } } -func TestLocalGetDC(t *testing.T) { +func TestApierLocalGetDC(t *testing.T) { if !*testLocal { return } @@ -1512,7 +1512,7 @@ func TestLocalGetDC(t *testing.T) { } } -func TestLocalRemDC(t *testing.T) { +func TestApierLocalRemDC(t *testing.T) { if !*testLocal { return } @@ -1525,7 +1525,7 @@ func TestLocalRemDC(t *testing.T) { } } -func TestLocalGetRatingSubjectAliases(t *testing.T) { +func TestApierLocalGetRatingSubjectAliases(t *testing.T) { if !*testLocal { return } @@ -1537,7 +1537,7 @@ func TestLocalGetRatingSubjectAliases(t *testing.T) { } } -func TestLocalAddRatingSubjectAliases(t *testing.T) { +func TestApierLocalAddRatingSubjectAliases(t *testing.T) { if !*testLocal { return } @@ -1562,7 +1562,7 @@ func TestLocalAddRatingSubjectAliases(t *testing.T) { } } -func TestLocalRemRatingSubjectAliases(t *testing.T) { +func TestApierLocalRemRatingSubjectAliases(t *testing.T) { if !*testLocal { return } @@ -1581,7 +1581,7 @@ func TestLocalRemRatingSubjectAliases(t *testing.T) { } } -func TestLocalGetAccountAliases(t *testing.T) { +func TestApierLocalGetAccountAliases(t *testing.T) { if !*testLocal { return } @@ -1594,7 +1594,7 @@ func TestLocalGetAccountAliases(t *testing.T) { } } -func TestLocalAddAccountAliases(t *testing.T) { +func TestApierLocalAddAccountAliases(t *testing.T) { if !*testLocal { return } @@ -1619,7 +1619,7 @@ func TestLocalAddAccountAliases(t *testing.T) { } } -func TestLocalRemAccountAliases(t *testing.T) { +func TestApierLocalRemAccountAliases(t *testing.T) { if !*testLocal { return } @@ -1638,7 +1638,7 @@ func TestLocalRemAccountAliases(t *testing.T) { } } -func TestLocalGetScheduledActions(t *testing.T) { +func TestApierLocalGetScheduledActions(t *testing.T) { if !*testLocal { return } @@ -1648,7 +1648,7 @@ func TestLocalGetScheduledActions(t *testing.T) { } } -func TestLocalGetDataCost(t *testing.T) { +func TestApierLocalGetDataCost(t *testing.T) { if !*testLocal { return } @@ -1662,7 +1662,7 @@ func TestLocalGetDataCost(t *testing.T) { } // Simply kill the engine after we are done with tests within this file -func TestStopEngine(t *testing.T) { +func TestApierStopEngine(t *testing.T) { if !*testLocal { return } diff --git a/apier/v1/cdrstatsv1_local_test.go b/apier/v1/cdrstatsv1_local_test.go index 3d0d266c8..a4f83f31a 100644 --- a/apier/v1/cdrstatsv1_local_test.go +++ b/apier/v1/cdrstatsv1_local_test.go @@ -26,7 +26,6 @@ import ( "net/http" "net/rpc" "net/rpc/jsonrpc" - "os/exec" "path" "reflect" "testing" @@ -37,7 +36,7 @@ var cdrstCfgPath string var cdrstCfg *config.CGRConfig var cdrstRpc *rpc.Client -func TestCDRStatsLoadConfig(t *testing.T) { +func TestCDRStatsLclLoadConfig(t *testing.T) { if !*testLocal { return } @@ -61,17 +60,9 @@ func TestCDRStatsLclStartEngine(t *testing.T) { if !*testLocal { return } - enginePath, err := exec.LookPath("cgr-engine") - if err != nil { - t.Fatal("Cannot find cgr-engine executable") + if _, err := engine.StopStartEngine(cdrstCfgPath, *waitRater); err != nil { + t.Fatal(err) } - exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - engine := exec.Command(enginePath, "-config_dir", cdrstCfgPath) - if err := engine.Start(); err != nil { - t.Fatal("Cannot start cgr-engine: ", err.Error()) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time to rater to fire up } // Connect rpc client to rater diff --git a/apier/v1/cdrsv1.go b/apier/v1/cdrsv1.go index 657edcd98..c3a4652ce 100644 --- a/apier/v1/cdrsv1.go +++ b/apier/v1/cdrsv1.go @@ -22,23 +22,51 @@ import ( "fmt" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" + "time" ) // Receive CDRs via RPC methods -type CDRSV1 struct { - CdrSrv *engine.CDRS +type CdrsV1 struct { + CdrSrv *engine.CdrServer } -func (cdrsrv *CDRSV1) ProcessCdr(cdr *engine.StoredCdr, reply *string) error { - if err := cdrsrv.CdrSrv.ProcessCdr(cdr); err != nil { +// Designed for CGR internal usage +func (self *CdrsV1) ProcessCdr(cdr *engine.StoredCdr, reply *string) error { + if err := self.CdrSrv.ProcessCdr(cdr); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } *reply = utils.OK return nil } -func (cdrsrv *CDRSV1) ProcessExternalCdr(cdr *engine.ExternalCdr, reply *string) error { - if err := cdrsrv.CdrSrv.ProcessExternalCdr(cdr); err != nil { +// Designed for external programs feeding CDRs to CGRateS +func (self *CdrsV1) ProcessExternalCdr(cdr *engine.ExternalCdr, reply *string) error { + if err := self.CdrSrv.ProcessExternalCdr(cdr); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + *reply = utils.OK + return nil +} + +// Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog +func (self *CdrsV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error { + var tStart, tEnd time.Time + var err error + if len(attrs.TimeStart) != 0 { + if tStart, err = utils.ParseTimeDetectLayout(attrs.TimeStart); err != nil { + return err + } + } + if len(attrs.TimeEnd) != 0 { + if tEnd, err = utils.ParseTimeDetectLayout(attrs.TimeEnd); err != nil { + return err + } + } + //RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string, + //orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool) + if err := self.CdrSrv.RateCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions, + attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects, + attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } *reply = utils.OK diff --git a/apier/v1/mediator.go b/apier/v1/mediator.go index ba6523766..674c6cfd7 100644 --- a/apier/v1/mediator.go +++ b/apier/v1/mediator.go @@ -17,43 +17,3 @@ along with this program. If not, see */ package v1 - -import ( - "fmt" - "time" - - "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/utils" -) - -type MediatorV1 struct { - Medi *engine.Mediator -} - -// Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog -func (self *MediatorV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error { - if self.Medi == nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "MediatorNotRunning") - } - var tStart, tEnd time.Time - var err error - if len(attrs.TimeStart) != 0 { - if tStart, err = utils.ParseTimeDetectLayout(attrs.TimeStart); err != nil { - return err - } - } - if len(attrs.TimeEnd) != 0 { - if tEnd, err = utils.ParseTimeDetectLayout(attrs.TimeEnd); err != nil { - return err - } - } - //RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string, - //orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool) - if err := self.Medi.RateCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions, - attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects, - attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - *reply = utils.OK - return nil -} diff --git a/apier/v2/cdrs.go b/apier/v2/cdrs.go index 3f433d460..262f9cfce 100644 --- a/apier/v2/cdrs.go +++ b/apier/v2/cdrs.go @@ -60,5 +60,5 @@ func (apier *ApierV2) CountCdrs(attrs utils.RpcCdrsFilter, reply *int64) error { // Receive CDRs via RPC methods, not included with APIer because it has way less dependencies and can be standalone type CdrsV2 struct { - v1.CDRSV1 + v1.CdrsV1 } diff --git a/apier/v2/cdrs_mysql_local_test.go b/apier/v2/cdrs_mysql_local_test.go index 881440742..1c382eca6 100644 --- a/apier/v2/cdrs_mysql_local_test.go +++ b/apier/v2/cdrs_mysql_local_test.go @@ -25,7 +25,6 @@ import ( "github.com/cgrates/cgrates/utils" "net/rpc" "net/rpc/jsonrpc" - "os/exec" "path" "testing" "time" @@ -39,8 +38,6 @@ var cdrsCfgPath string var cdrsCfg *config.CGRConfig var cdrsRpc *rpc.Client -var cmdEngineCdrsMysql *exec.Cmd - func TestV2CdrsMysqlInitConfig(t *testing.T) { if !*testLocal { return @@ -98,8 +95,7 @@ func TestV2CdrsMysqlStartEngine(t *testing.T) { if !*testLocal { return } - var err error - if cmdEngineCdrsMysql, err = engine.StopStartEngine(cdrsCfgPath, *waitRater); err != nil { + if _, err := engine.StopStartEngine(cdrsCfgPath, *waitRater); err != nil { t.Fatal(err) } } diff --git a/cdrc/cdrc.go b/cdrc/cdrc.go index c98da3908..0ee31ca31 100644 --- a/cdrc/cdrc.go +++ b/cdrc/cdrc.go @@ -81,12 +81,12 @@ func populateStoredCdrField(cdr *engine.StoredCdr, fieldId, fieldVal string) err return nil } -func NewCdrc(cdrcCfgs map[string]*config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engine.CDRS, exitChan chan struct{}) (*Cdrc, error) { +func NewCdrc(cdrcCfgs map[string]*config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engine.CdrServer, exitChan chan struct{}) (*Cdrc, error) { var cdrcCfg *config.CdrcConfig for _, cdrcCfg = range cdrcCfgs { // Take the first config out, does not matter which one break } - cdrc := &Cdrc{cdrsAddress: cdrcCfg.CdrsAddress, CdrFormat: cdrcCfg.CdrFormat, cdrInDir: cdrcCfg.CdrInDir, cdrOutDir: cdrcCfg.CdrOutDir, + cdrc := &Cdrc{cdrsAddress: cdrcCfg.Cdrs, CdrFormat: cdrcCfg.CdrFormat, cdrInDir: cdrcCfg.CdrInDir, cdrOutDir: cdrcCfg.CdrOutDir, cdrSourceId: cdrcCfg.CdrSourceId, runDelay: cdrcCfg.RunDelay, csvSep: cdrcCfg.FieldSeparator, duMultiplyFactor: cdrcCfg.DataUsageMultiplyFactor, httpSkipTlsCheck: httpSkipTlsCheck, cdrServer: cdrServer, exitChan: exitChan} cdrc.cdrFilters = make([]utils.RSRFields, len(cdrcCfgs)) @@ -119,7 +119,7 @@ type Cdrc struct { cdrFilters []utils.RSRFields // Should be in sync with cdrFields on indexes cdrFields [][]*config.CfgCdrField // Profiles directly connected with cdrFilters httpSkipTlsCheck bool - cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case + cdrServer *engine.CdrServer // Reference towards internal cdrServer if that is the case httpClient *http.Client exitChan chan struct{} } diff --git a/cdrc/cdrc_local_test.go b/cdrc/cdrc_local_test.go index 9f887548b..f53e39120 100644 --- a/cdrc/cdrc_local_test.go +++ b/cdrc/cdrc_local_test.go @@ -170,8 +170,8 @@ func TestProcessCdrDir(t *testing.T) { for _, cdrcCfg = range cdrcCfgs { // Take the first config out, does not matter which one break } - if cdrcCfg.CdrsAddress == utils.INTERNAL { // For now we only test over network - cdrcCfg.CdrsAddress = "127.0.0.1:2013" + if cdrcCfg.Cdrs == utils.INTERNAL { // For now we only test over network + cdrcCfg.Cdrs = "127.0.0.1:2013" } if err := startEngine(); err != nil { t.Fatal(err.Error()) @@ -206,8 +206,8 @@ func TestProcessCdr3Dir(t *testing.T) { if !*testLocal { return } - if cdrcCfg.CdrsAddress == utils.INTERNAL { // For now we only test over network - cdrcCfg.CdrsAddress = "127.0.0.1:2013" + if cdrcCfg.Cdrs == utils.INTERNAL { // For now we only test over network + cdrcCfg.Cdrs = "127.0.0.1:2013" } if err := startEngine(); err != nil { t.Fatal(err.Error()) diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 173e35b6f..3d5b90f71 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -55,23 +55,21 @@ const ( ) var ( - cfgDir = flag.String("config_dir", utils.CONFIG_DIR, "Configuration directory path.") - version = flag.Bool("version", false, "Prints the application version.") - raterEnabled = flag.Bool("rater", false, "Enforce starting of the rater daemon overwriting config") - schedEnabled = flag.Bool("scheduler", false, "Enforce starting of the scheduler daemon .overwriting config") - cdrsEnabled = flag.Bool("cdrs", false, "Enforce starting of the cdrs daemon overwriting config") - mediatorEnabled = flag.Bool("mediator", false, "Enforce starting of the mediator service overwriting config") - pidFile = flag.String("pid", "", "Write pid file") - bal = balancer2go.NewBalancer() - exitChan = make(chan bool) - server = &engine.Server{} - scribeServer history.Scribe - cdrServer *engine.CDRS - cdrStats *engine.Stats - sm sessionmanager.SessionManager - medi *engine.Mediator - cfg *config.CGRConfig - err error + cfgDir = flag.String("config_dir", utils.CONFIG_DIR, "Configuration directory path.") + version = flag.Bool("version", false, "Prints the application version.") + raterEnabled = flag.Bool("rater", false, "Enforce starting of the rater daemon overwriting config") + schedEnabled = flag.Bool("scheduler", false, "Enforce starting of the scheduler daemon .overwriting config") + cdrsEnabled = flag.Bool("cdrs", false, "Enforce starting of the cdrs daemon overwriting config") + pidFile = flag.String("pid", "", "Write pid file") + bal = balancer2go.NewBalancer() + exitChan = make(chan bool) + server = &engine.Server{} + scribeServer history.Scribe + cdrServer *engine.CdrServer + cdrStats *engine.Stats + sm sessionmanager.SessionManager + cfg *config.CGRConfig + err error ) func cacheData(ratingDb engine.RatingStorage, accountDb engine.AccountingStorage, doneChan chan struct{}) { @@ -88,6 +86,7 @@ func cacheData(ratingDb engine.RatingStorage, accountDb engine.AccountingStorage close(doneChan) } +/* func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrDb engine.CdrStorage, cacheChan, chanDone chan struct{}) { var connector engine.Connector if cfg.MediatorRater == utils.INTERNAL { @@ -123,14 +122,15 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD close(chanDone) } +*/ // Fires up a cdrc instance -func startCdrc(cdrsChan chan struct{}, cdrcCfgs map[string]*config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engine.CDRS, closeChan chan struct{}) { +func startCdrc(cdrsChan chan struct{}, cdrcCfgs map[string]*config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engine.CdrServer, closeChan chan struct{}) { var cdrcCfg *config.CdrcConfig for _, cdrcCfg = range cdrcCfgs { // Take the first config out, does not matter which one break } - if cdrcCfg.CdrsAddress == utils.INTERNAL { + if cdrcCfg.Cdrs == utils.INTERNAL { <-cdrsChan // Wait for CDRServer to come up before start processing } cdrc, err := cdrc.NewCdrc(cdrcCfgs, httpSkipTlsCheck, cdrServer, closeChan) @@ -285,21 +285,60 @@ func startSmOpenSIPS(responder *engine.Responder, loggerDb engine.LogStorage, ca exitChan <- true } -func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage, mediChan, doneChan chan struct{}) { - if cfg.CDRSMediator == utils.INTERNAL { - <-mediChan // Deadlock if mediator not started - if medi == nil { - engine.Logger.Crit(" Could not connect to mediator, exiting.") +func startCDRS(logDb engine.LogStorage, cdrDb engine.CdrStorage, responder *engine.Responder, stats *engine.Stats, responderReady, doneChan chan struct{}) { + var err error + var client *rpcclient.RpcClient + // Rater connection init + var raterConn engine.Connector + if cfg.CDRSRater == utils.INTERNAL { + <-responderReady // Wait for the cache to init before start doing queries + raterConn = responder + } else if len(cfg.CDRSRater) != 0 { + for i := 0; i < cfg.CDRSReconnects; i++ { + client, err = rpcclient.NewRpcClient("tcp", cfg.CDRSRater, cfg.CDRSReconnects, utils.GOB) + if err == nil { //Connected so no need to reiterate + break + } + time.Sleep(time.Duration(i+1) * time.Second) + } + if err != nil { + engine.Logger.Crit(fmt.Sprintf(" Could not connect to rater: %s", err.Error())) exitChan <- true return } + raterConn = &engine.RPCClientConnector{Client: client} } - cdrServer = engine.NewCdrS(cdrDb, medi, cdrStats, cfg) + // Stats connection init + var statsConn engine.StatsInterface + if cfg.CDRSStats == utils.INTERNAL { + statsConn = stats + } else if len(cfg.CDRSStats) != 0 { + if cfg.CDRSRater == cfg.CDRSStats { + statsConn = &engine.ProxyStats{Client: client} + } else { + for i := 0; i < cfg.CDRSReconnects; i++ { + client, err = rpcclient.NewRpcClient("tcp", cfg.CDRSStats, cfg.CDRSReconnects, utils.GOB) + if err == nil { //Connected so no need to reiterate + break + } + time.Sleep(time.Duration(i+1) * time.Second) + } + if err != nil { + engine.Logger.Crit(fmt.Sprintf(" Could not connect to stats server: %s", err.Error())) + exitChan <- true + return + } + statsConn = &engine.ProxyStats{Client: client} + } + } + + cdrServer, _ := engine.NewCdrServer(cfg, logDb, cdrDb, raterConn, statsConn) + engine.Logger.Info("Registering CDRS HTTP Handlers.") cdrServer.RegisterHanlersToServer(server) engine.Logger.Info("Registering CDRS RPC service.") - cdrSrv := v1.CDRSV1{CdrSrv: cdrServer} + cdrSrv := v1.CdrsV1{CdrSrv: cdrServer} server.RpcRegister(&cdrSrv) - server.RpcRegister(&v2.CdrsV2{CDRSV1: cdrSrv}) + server.RpcRegister(&v2.CdrsV2{CdrsV1: cdrSrv}) responder.CdrSrv = cdrServer // Make the cdrserver available for internal communication close(doneChan) } @@ -373,10 +412,6 @@ func checkConfigSanity() error { engine.Logger.Crit("The balancer is enabled so it cannot connect to another balancer (change rater/balancer to disabled)!") return errors.New("Improperly configured balancer") } - if cfg.CDRSEnabled && cfg.CDRSMediator == utils.INTERNAL && !cfg.MediatorEnabled { - engine.Logger.Crit("CDRS cannot connect to mediator, Mediator not enabled in configuration!") - return errors.New("Internal Mediator required by CDRS") - } if cfg.HistoryServerEnabled && cfg.HistoryServer == utils.INTERNAL && !cfg.HistoryServerEnabled { engine.Logger.Crit("The history agent is enabled and internal and history server is disabled!") return errors.New("Improperly configured history service") @@ -422,9 +457,6 @@ func main() { if *cdrsEnabled { cfg.CDRSEnabled = *cdrsEnabled } - if *mediatorEnabled { - cfg.MediatorEnabled = *mediatorEnabled - } // some consitency checks errCfg := checkConfigSanity() @@ -455,7 +487,7 @@ func main() { defer accountDb.Close() engine.SetAccountingStorage(accountDb) } - if cfg.RaterEnabled || cfg.CDRSEnabled || cfg.MediatorEnabled { // Only connect to storDb if necessary + if cfg.RaterEnabled || cfg.CDRSEnabled { // Only connect to storDb if necessary if cfg.StorDBType == SAME { logDb = ratingDb.(engine.LogStorage) } else { @@ -556,19 +588,12 @@ func main() { go startHistoryAgent(histServChan) } - var medChan chan struct{} - if cfg.MediatorEnabled { - engine.Logger.Info("Starting CGRateS Mediator service.") - medChan = make(chan struct{}) - go startMediator(responder, logDb, cdrDb, cacheChan, medChan) - } - var cdrsChan chan struct{} if cfg.CDRSEnabled { engine.Logger.Info("Starting CGRateS CDRS service.") cdrsChan = make(chan struct{}) httpWait = append(httpWait, cdrsChan) - go startCDRS(responder, cdrDb, medChan, cdrsChan) + go startCDRS(logDb, cdrDb, responder, cdrStats, cacheChan, cdrsChan) } if cfg.SmFsConfig.Enabled { diff --git a/config/cdrcconfig.go b/config/cdrcconfig.go index 4ee3502fd..13eeea24b 100644 --- a/config/cdrcconfig.go +++ b/config/cdrcconfig.go @@ -26,7 +26,7 @@ import ( type CdrcConfig struct { Enabled bool // Enable/Disable the profile - CdrsAddress string // The address where CDRs can be reached + Cdrs string // The address where CDRs can be reached CdrFormat string // The type of CDR file to process FieldSeparator rune // The separator to use when reading csvs DataUsageMultiplyFactor float64 // Conversion factor for data usage @@ -46,11 +46,8 @@ func (self *CdrcConfig) loadFromJsonCfg(jsnCfg *CdrcJsonCfg) error { if jsnCfg.Enabled != nil { self.Enabled = *jsnCfg.Enabled } - if jsnCfg.Cdrs_address != nil { - self.CdrsAddress = *jsnCfg.Cdrs_address - } - if jsnCfg.Cdrs_address != nil { - self.CdrsAddress = *jsnCfg.Cdrs_address + if jsnCfg.Cdrs != nil { + self.Cdrs = *jsnCfg.Cdrs } if jsnCfg.Cdr_format != nil { self.CdrFormat = *jsnCfg.Cdr_format @@ -91,7 +88,7 @@ func (self *CdrcConfig) loadFromJsonCfg(jsnCfg *CdrcJsonCfg) error { func (self *CdrcConfig) Clone() *CdrcConfig { clnCdrc := new(CdrcConfig) clnCdrc.Enabled = self.Enabled - clnCdrc.CdrsAddress = self.CdrsAddress + clnCdrc.Cdrs = self.Cdrs clnCdrc.CdrFormat = self.CdrFormat clnCdrc.FieldSeparator = self.FieldSeparator clnCdrc.DataUsageMultiplyFactor = self.DataUsageMultiplyFactor diff --git a/config/cfg_data.json b/config/cfg_data.json index 67d83ee7f..9b03c0c26 100644 --- a/config/cfg_data.json +++ b/config/cfg_data.json @@ -18,7 +18,7 @@ "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario "cdr_replication":[ // replicate the rated CDR to a number of servers {"transport": "*http_post", "server": "1.2.3.4:2080/cdr_post"}, - {"transport": "*http_jsonrpc", "server": "2.3.4.5:2080/jsonrpc", "synchronous": true}, + {"transport": "*http_jsonrpc", "server": "2.3.4.5:2080/jsonrpc", "synchronous": true, "cdr_filter": "~account:s/(.+)/1001/"}, ], }, diff --git a/config/config.go b/config/config.go index 2983ee6a1..cdac6ece1 100644 --- a/config/config.go +++ b/config/config.go @@ -185,9 +185,10 @@ type CGRConfig struct { SchedulerEnabled bool CDRSEnabled bool // Enable CDR Server service CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs - CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal> - CDRSStats string // Address where to reach the Mediator. <""|intenal> - CDRSStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario + CDRSStoreCdrs bool // store cdrs in storDb + CDRSRater string // address where to reach the Rater for cost calculation: <""|internal|x.y.z.y:1234> + CDRSStats string // address where to reach the cdrstats service. Empty to disable stats gathering <""|internal|x.y.z.y:1234> + CDRSReconnects int // number of reconnects to remote services before giving up CDRSCdrReplication []*CdrReplicationCfg // Replicate raw CDRs to a number of servers CDRStatsEnabled bool // Enable CDR Stats service CDRStatConfig *CdrStatsConfig // Active cdr stats configuration instances, platform level @@ -196,23 +197,17 @@ type CGRConfig struct { SmFsConfig *SmFsConfig // SM-FreeSWITCH configuration SmKamConfig *SmKamConfig // SM-Kamailio Configuration SmOsipsConfig *SmOsipsConfig // SM-OpenSIPS Configuration - MediatorEnabled bool // Starts Mediator service: . - MediatorReconnects int // Number of reconnects to rater before giving up. - MediatorRater string - MediatorStats string // Address where to reach the Rater: - MediatorStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario - MediCdrReplication []*CdrReplicationCfg // Replicate CDRs to a number of servers - HistoryAgentEnabled bool // Starts History as an agent: . - HistoryServer string // Address where to reach the master history server: - HistoryServerEnabled bool // Starts History as server: . - HistoryDir string // Location on disk where to store history files. - HistorySaveInterval time.Duration // The timout duration between history writes - MailerServer string // The server to use when sending emails out - MailerAuthUser string // Authenticate to email server using this user - MailerAuthPass string // Authenticate to email server with this password - MailerFromAddr string // From address used when sending emails out - DataFolderPath string // Path towards data folder, for tests internal usage, not loading out of .json options - ConfigReloads map[string]chan struct{} // Signals to specific entities that a config reload should occur + HistoryAgentEnabled bool // Starts History as an agent: . + HistoryServer string // Address where to reach the master history server: + HistoryServerEnabled bool // Starts History as server: . + HistoryDir string // Location on disk where to store history files. + HistorySaveInterval time.Duration // The timout duration between history writes + MailerServer string // The server to use when sending emails out + MailerAuthUser string // Authenticate to email server using this user + MailerAuthPass string // Authenticate to email server with this password + MailerFromAddr string // From address used when sending emails out + DataFolderPath string // Path towards data folder, for tests internal usage, not loading out of .json options + ConfigReloads map[string]chan struct{} // Signals to specific entities that a config reload should occur // Cache defaults loaded from json and needing clones dfltCdreProfile *CdreConfig // Default cdreConfig profile dfltCdrcProfile *CdrcConfig // Default cdrcConfig profile @@ -240,9 +235,6 @@ func (self *CGRConfig) checkConfigSanity() error { if self.CDRSStats == utils.INTERNAL && !self.CDRStatsEnabled { return errors.New("CDRStats not enabled but requested by CDRS component.") } - if self.MediatorStats == utils.INTERNAL && !self.CDRStatsEnabled { - return errors.New("CDRStats not enabled but requested by Mediator.") - } /* if self.SMCdrS == utils.INTERNAL && !self.CDRSEnabled { return errors.New("CDRS not enabled but requested by SessionManager") @@ -253,83 +245,98 @@ func (self *CGRConfig) checkConfigSanity() error { // Loads from json configuration object, will be used for defaults, config from file and reload, might need lock func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { + // Load sections out of JSON config, stop on error jsnGeneralCfg, err := jsnCfg.GeneralJsonCfg() if err != nil { return err } + jsnListenCfg, err := jsnCfg.ListenJsonCfg() if err != nil { return err } + jsnRatingDbCfg, err := jsnCfg.DbJsonCfg(RATINGDB_JSN) if err != nil { return err } + jsnAccountingDbCfg, err := jsnCfg.DbJsonCfg(ACCOUNTINGDB_JSN) if err != nil { return err } + jsnStorDbCfg, err := jsnCfg.DbJsonCfg(STORDB_JSN) if err != nil { return err } + jsnBalancerCfg, err := jsnCfg.BalancerJsonCfg() if err != nil { return err } + jsnRaterCfg, err := jsnCfg.RaterJsonCfg() if err != nil { return err } + jsnSchedCfg, err := jsnCfg.SchedulerJsonCfg() if err != nil { return err } + jsnCdrsCfg, err := jsnCfg.CdrsJsonCfg() if err != nil { return err } - jsnMediatorCfg, err := jsnCfg.MediatorJsonCfg() - if err != nil { - return err - } + jsnCdrstatsCfg, err := jsnCfg.CdrStatsJsonCfg() if err != nil { return err } + jsnCdreCfg, err := jsnCfg.CdreJsonCfgs() if err != nil { return err } + jsnCdrcCfg, err := jsnCfg.CdrcJsonCfg() if err != nil { return err } + jsnSmFsCfg, err := jsnCfg.SmFsJsonCfg() if err != nil { return err } + jsnSmKamCfg, err := jsnCfg.SmKamJsonCfg() if err != nil { return err } + jsnSmOsipsCfg, err := jsnCfg.SmOsipsJsonCfg() if err != nil { return err } + jsnHistServCfg, err := jsnCfg.HistServJsonCfg() if err != nil { return err } + jsnHistAgentCfg, err := jsnCfg.HistAgentJsonCfg() if err != nil { return err } + jsnMailerCfg, err := jsnCfg.MailerJsonCfg() if err != nil { return err } + // All good, start populating config variables if jsnRatingDbCfg != nil { if jsnRatingDbCfg.Db_type != nil { @@ -351,6 +358,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.RatingDBPass = *jsnRatingDbCfg.Db_passwd } } + if jsnAccountingDbCfg != nil { if jsnAccountingDbCfg.Db_type != nil { self.AccountDBType = *jsnAccountingDbCfg.Db_type @@ -371,6 +379,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.AccountDBPass = *jsnAccountingDbCfg.Db_passwd } } + if jsnStorDbCfg != nil { if jsnStorDbCfg.Db_type != nil { self.StorDBType = *jsnStorDbCfg.Db_type @@ -397,6 +406,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.StorDBMaxIdleConns = *jsnStorDbCfg.Max_idle_conns } } + if jsnGeneralCfg != nil { if jsnGeneralCfg.Dbdata_encoding != nil { self.DBDataEncoding = *jsnGeneralCfg.Dbdata_encoding @@ -423,6 +433,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.TpExportPath = *jsnGeneralCfg.Tpexport_dir } } + if jsnListenCfg != nil { if jsnListenCfg.Rpc_json != nil { self.RPCJSONListen = *jsnListenCfg.Rpc_json @@ -434,6 +445,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.HTTPListen = *jsnListenCfg.Http } } + if jsnRaterCfg != nil { if jsnRaterCfg.Enabled != nil { self.RaterEnabled = *jsnRaterCfg.Enabled @@ -442,12 +454,15 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.RaterBalancer = *jsnRaterCfg.Balancer } } + if jsnBalancerCfg != nil && jsnBalancerCfg.Enabled != nil { self.BalancerEnabled = *jsnBalancerCfg.Enabled } + if jsnSchedCfg != nil && jsnSchedCfg.Enabled != nil { self.SchedulerEnabled = *jsnSchedCfg.Enabled } + if jsnCdrsCfg != nil { if jsnCdrsCfg.Enabled != nil { self.CDRSEnabled = *jsnCdrsCfg.Enabled @@ -457,14 +472,17 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { return err } } - if jsnCdrsCfg.Mediator != nil { - self.CDRSMediator = *jsnCdrsCfg.Mediator + if jsnCdrsCfg.Store_cdrs != nil { + self.CDRSStoreCdrs = *jsnCdrsCfg.Store_cdrs + } + if jsnCdrsCfg.Rater != nil { + self.CDRSRater = *jsnCdrsCfg.Rater } if jsnCdrsCfg.Cdrstats != nil { self.CDRSStats = *jsnCdrsCfg.Cdrstats } - if jsnCdrsCfg.Store_disable != nil { - self.CDRSStoreDisable = *jsnCdrsCfg.Store_disable + if jsnCdrsCfg.Reconnects != nil { + self.CDRSReconnects = *jsnCdrsCfg.Reconnects } if jsnCdrsCfg.Cdr_replication != nil { self.CDRSCdrReplication = make([]*CdrReplicationCfg, len(*jsnCdrsCfg.Cdr_replication)) @@ -479,9 +497,15 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { if rplJsonCfg.Synchronous != nil { self.CDRSCdrReplication[idx].Synchronous = *rplJsonCfg.Synchronous } + if rplJsonCfg.Cdr_filter != nil { + if self.CDRSCdrReplication[idx].CdrFilter, err = utils.ParseRSRFields(*rplJsonCfg.Cdr_filter, utils.INFIELD_SEP); err != nil { + return err + } + } } } } + if jsnCdrstatsCfg != nil { if jsnCdrstatsCfg.Enabled != nil { self.CDRStatsEnabled = *jsnCdrstatsCfg.Enabled @@ -495,6 +519,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } } + if jsnCdreCfg != nil { if self.CdreProfiles == nil { self.CdreProfiles = make(map[string]*CdreConfig) @@ -511,6 +536,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } } + if jsnCdrcCfg != nil { if self.CdrcProfiles == nil { self.CdrcProfiles = make(map[string]map[string]*CdrcConfig) @@ -531,53 +557,24 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } } + if jsnSmFsCfg != nil { if err := self.SmFsConfig.loadFromJsonCfg(jsnSmFsCfg); err != nil { return err } } + if jsnSmKamCfg != nil { if err := self.SmKamConfig.loadFromJsonCfg(jsnSmKamCfg); err != nil { return err } } + if jsnSmOsipsCfg != nil { if err := self.SmOsipsConfig.loadFromJsonCfg(jsnSmOsipsCfg); err != nil { return err } } - if jsnMediatorCfg != nil { - if jsnMediatorCfg.Enabled != nil { - self.MediatorEnabled = *jsnMediatorCfg.Enabled - } - if jsnMediatorCfg.Reconnects != nil { - self.MediatorReconnects = *jsnMediatorCfg.Reconnects - } - if jsnMediatorCfg.Rater != nil { - self.MediatorRater = *jsnMediatorCfg.Rater - } - if jsnMediatorCfg.Cdrstats != nil { - self.MediatorStats = *jsnMediatorCfg.Cdrstats - } - if jsnMediatorCfg.Store_disable != nil { - self.MediatorStoreDisable = *jsnMediatorCfg.Store_disable - } - if jsnMediatorCfg.Cdr_replication != nil { - self.MediCdrReplication = make([]*CdrReplicationCfg, len(*jsnMediatorCfg.Cdr_replication)) - for idx, rplJsonCfg := range *jsnMediatorCfg.Cdr_replication { - self.MediCdrReplication[idx] = new(CdrReplicationCfg) - if rplJsonCfg.Transport != nil { - self.MediCdrReplication[idx].Transport = *rplJsonCfg.Transport - } - if rplJsonCfg.Server != nil { - self.MediCdrReplication[idx].Server = *rplJsonCfg.Server - } - if rplJsonCfg.Synchronous != nil { - self.MediCdrReplication[idx].Synchronous = *rplJsonCfg.Synchronous - } - } - } - } if jsnHistAgentCfg != nil { if jsnHistAgentCfg.Enabled != nil { @@ -587,6 +584,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { self.HistoryServer = *jsnHistAgentCfg.Server } } + if jsnHistServCfg != nil { if jsnHistServCfg.Enabled != nil { self.HistoryServerEnabled = *jsnHistServCfg.Enabled @@ -600,6 +598,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } } + if jsnMailerCfg != nil { if jsnMailerCfg.Server != nil { self.MailerServer = *jsnMailerCfg.Server diff --git a/config/config_defaults.go b/config/config_defaults.go index 0e7a5a6ae..89b0c5220 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -32,7 +32,7 @@ const CGRATES_CFG_JSON = ` "rounding_decimals": 10, // system level precision for floats "dbdata_encoding": "msgpack", // encoding used to store object data in strings: "tpexport_dir": "/var/log/cgrates/tpe", // path towards export folder for offline Tariff Plans - "default_reqtype": "*rated", // default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated> + "default_reqtype": "*rated", // default request type to consider when missing from requests: <""|*prepaid|*postpaid|*pseudoprepaid|*rated> "default_category": "call", // default Type of Record to consider when missing from requests "default_tenant": "cgrates.org", // default Tenant to consider when missing from requests "default_subject": "cgrates", // default rating Subject to consider when missing from requests @@ -97,24 +97,15 @@ const CGRATES_CFG_JSON = ` "cdrs": { "enabled": false, // start the CDR Server service: "extra_fields": [], // extra fields to store in CDRs for non-generic CDRs - "mediator": "", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> - "cdrstats": "", // address where to reach the cdrstats service. Empty to disable stats gathering from raw CDRs <""|internal|x.y.z.y:1234> - "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario + "store_cdrs": true, // store cdrs in storDb + "rater": "", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> + "cdrstats": "", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234> + "reconnects": 5, // number of reconnect attempts to rater or cdrs "cdr_replication":[], // replicate the raw CDR to a number of servers }, -"mediator": { - "enabled": false, // starts Mediator service: . - "reconnects": 3, // number of reconnects to rater/cdrs before giving up. - "rater": "internal", // address where to reach the Rater: - "cdrstats": "", // address where to reach the cdrstats service. Empty to disable stats gathering out of mediated CDRs <""|internal|x.y.z.y:1234> - "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario - "cdr_replication":[], // replicate the rated CDR to a number of servers -}, - - -"cdrstats": { +"cdr_stats": { "enabled": false, // starts the cdrstats service: "queue_length": 50, // number of items in the stats buffer "time_window": "1h", // will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow @@ -176,7 +167,7 @@ const CGRATES_CFG_JSON = ` "cdrc": { "*default": { "enabled": false, // enable CDR client functionality - "cdrs_address": "internal", // address where to reach CDR server. + "cdrs": "internal", // address where to reach CDR server. "cdr_format": "csv", // CDR file format "field_separator": ",", // separator used in case of csv files "run_delay": 0, // sleep interval in seconds between consecutive runs, 0 to use automation via inotify diff --git a/config/config_json.go b/config/config_json.go index 359c9070e..dfb67fd2e 100644 --- a/config/config_json.go +++ b/config/config_json.go @@ -36,7 +36,7 @@ const ( SCHEDULER_JSN = "scheduler" CDRS_JSN = "cdrs" MEDIATOR_JSN = "mediator" - CDRSTATS_JSN = "cdrstats" + CDRSTATS_JSN = "cdr_stats" CDRE_JSN = "cdre" CDRC_JSN = "cdrc" SMFS_JSN = "sm_freeswitch" @@ -158,18 +158,6 @@ func (self CgrJsonCfg) CdrsJsonCfg() (*CdrsJsonCfg, error) { return cfg, nil } -func (self CgrJsonCfg) MediatorJsonCfg() (*MediatorJsonCfg, error) { - rawCfg, hasKey := self[MEDIATOR_JSN] - if !hasKey { - return nil, nil - } - cfg := new(MediatorJsonCfg) - if err := json.Unmarshal(*rawCfg, cfg); err != nil { - return nil, err - } - return cfg, nil -} - func (self CgrJsonCfg) CdrStatsJsonCfg() (*CdrStatsJsonCfg, error) { rawCfg, hasKey := self[CDRSTATS_JSN] if !hasKey { diff --git a/config/config_json_test.go b/config/config_json_test.go index 28f4f72dd..3e356dc8a 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -139,31 +139,16 @@ func TestDfCdrsJsonCfg(t *testing.T) { eCfg := &CdrsJsonCfg{ Enabled: utils.BoolPointer(false), Extra_fields: utils.StringSlicePointer([]string{}), - Mediator: utils.StringPointer(""), + Store_cdrs: utils.BoolPointer(true), + Rater: utils.StringPointer(""), Cdrstats: utils.StringPointer(""), - Store_disable: utils.BoolPointer(false), + Reconnects: utils.IntPointer(5), Cdr_replication: &[]*CdrReplicationJsonCfg{}, } if cfg, err := dfCgrJsonCfg.CdrsJsonCfg(); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCfg, cfg) { - t.Error("Received: ", cfg) - } -} - -func TestDfMediatorJsonCfg(t *testing.T) { - eCfg := &MediatorJsonCfg{ - Enabled: utils.BoolPointer(false), - Reconnects: utils.IntPointer(3), - Rater: utils.StringPointer("internal"), - Cdrstats: utils.StringPointer(""), - Store_disable: utils.BoolPointer(false), - Cdr_replication: &[]*CdrReplicationJsonCfg{}, - } - if cfg, err := dfCgrJsonCfg.MediatorJsonCfg(); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eCfg, cfg) { - t.Error("Received: ", cfg) + t.Error("Received: ", *cfg) } } @@ -317,7 +302,7 @@ func TestDfCdrcJsonCfg(t *testing.T) { eCfg := map[string]*CdrcJsonCfg{ "*default": &CdrcJsonCfg{ Enabled: utils.BoolPointer(false), - Cdrs_address: utils.StringPointer("internal"), + Cdrs: utils.StringPointer("internal"), Cdr_format: utils.StringPointer("csv"), Field_separator: utils.StringPointer(","), Run_delay: utils.IntPointer(0), @@ -457,22 +442,24 @@ func TestNewCgrJsonCfgFromFile(t *testing.T) { } else if !reflect.DeepEqual(eCfg, gCfg) { t.Error("Received: ", gCfg) } - eCfgMedi := &MediatorJsonCfg{ - Enabled: utils.BoolPointer(true), - Reconnects: utils.IntPointer(5), - Rater: utils.StringPointer("internal"), - Cdrstats: utils.StringPointer(""), - Store_disable: utils.BoolPointer(false), - Cdr_replication: &[]*CdrReplicationJsonCfg{ - &CdrReplicationJsonCfg{Transport: utils.StringPointer(utils.META_HTTP_POST), Server: utils.StringPointer("1.2.3.4:2080/cdr_post")}, - &CdrReplicationJsonCfg{Transport: utils.StringPointer(utils.META_HTTP_JSONRPC), Server: utils.StringPointer("2.3.4.5:2080/jsonrpc"), Synchronous: utils.BoolPointer(true)}, - }, - } - if mediCfg, err := cgrJsonCfg.MediatorJsonCfg(); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eCfgMedi, mediCfg) { - t.Error("Received: ", mediCfg) - } + /* + eCfgMedi := &MediatorJsonCfg{ + Enabled: utils.BoolPointer(true), + Reconnects: utils.IntPointer(5), + Rater: utils.StringPointer("internal"), + Cdrstats: utils.StringPointer(""), + Store_disable: utils.BoolPointer(false), + Cdr_replication: &[]*CdrReplicationJsonCfg{ + &CdrReplicationJsonCfg{Transport: utils.StringPointer(utils.META_HTTP_POST), Server: utils.StringPointer("1.2.3.4:2080/cdr_post")}, + &CdrReplicationJsonCfg{Transport: utils.StringPointer(utils.META_HTTP_JSONRPC), Server: utils.StringPointer("2.3.4.5:2080/jsonrpc"), Synchronous: utils.BoolPointer(true), Cdr_filter: utils.StringPointer("~account:s/(.+)/1001/")}, + }, + } + if mediCfg, err := cgrJsonCfg.MediatorJsonCfg(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCfgMedi, mediCfg) { + t.Error("Received: ", mediCfg) + } + */ cdrFields := []*CdrFieldJsonCfg{ &CdrFieldJsonCfg{Cdr_field_id: utils.StringPointer("tor"), Value: utils.StringPointer("~7:s/^(voice|data|sms)$/*$1/")}, &CdrFieldJsonCfg{Cdr_field_id: utils.StringPointer("answer_time"), Value: utils.StringPointer("1")}, diff --git a/config/libconfig.go b/config/libconfig.go index e1b0ea898..23aebef03 100644 --- a/config/libconfig.go +++ b/config/libconfig.go @@ -18,8 +18,13 @@ along with this program. If not, see package config +import ( + "github.com/cgrates/cgrates/utils" +) + type CdrReplicationCfg struct { Transport string Server string Synchronous bool + CdrFilter utils.RSRFields // Only replicate if the filters here are matching } diff --git a/config/libconfig_json.go b/config/libconfig_json.go index ac7022b77..49f5b4b95 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -69,9 +69,10 @@ type SchedulerJsonCfg struct { type CdrsJsonCfg struct { Enabled *bool Extra_fields *[]string - Mediator *string + Store_cdrs *bool + Rater *string Cdrstats *string - Store_disable *bool + Reconnects *int Cdr_replication *[]*CdrReplicationJsonCfg } @@ -79,16 +80,7 @@ type CdrReplicationJsonCfg struct { Transport *string Server *string Synchronous *bool -} - -// Mediator config section -type MediatorJsonCfg struct { - Enabled *bool - Reconnects *int - Rater *string - Cdrstats *string - Store_disable *bool - Cdr_replication *[]*CdrReplicationJsonCfg + Cdr_filter *string } // Cdrstats config section @@ -149,7 +141,7 @@ type CdreJsonCfg struct { // Cdrc config section type CdrcJsonCfg struct { Enabled *bool - Cdrs_address *string + Cdrs *string Cdr_format *string Field_separator *string Run_delay *int diff --git a/data/conf/cgrates/cgrates.json b/data/conf/cgrates/cgrates.json index e7df8f1a9..568a6ab7c 100644 --- a/data/conf/cgrates/cgrates.json +++ b/data/conf/cgrates/cgrates.json @@ -11,7 +11,7 @@ // "rounding_decimals": 10, // system level precision for floats // "dbdata_encoding": "msgpack", // encoding used to store object data in strings: // "tpexport_dir": "/var/log/cgrates/tpe", // path towards export folder for offline Tariff Plans -// "default_reqtype": "*rated", // default request type to consider when missing from requests: <""|*prepaid|*postpaid|*pseudoprepaid|*rated> +// "default_reqtype": "*rated", // default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated> // "default_category": "call", // default Type of Record to consider when missing from requests // "default_tenant": "cgrates.org", // default Tenant to consider when missing from requests // "default_subject": "cgrates", // default rating Subject to consider when missing from requests @@ -76,22 +76,16 @@ //"cdrs": { // "enabled": false, // start the CDR Server service: // "extra_fields": [], // extra fields to store in CDRs for non-generic CDRs -// "mediator": "", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> -// "cdrstats": "", // address where to reach the cdrstats service. Empty to disable stats gathering from raw CDRs <""|internal|x.y.z.y:1234> -// "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario +// "rate_cdrs": true, // enable CDR rating calculation +// "store_cdrs": true, // store cdrs in storDb +// "rater": "", // address where to reach the Rater for cost calculation: <""|internal|x.y.z.y:1234> +// "cdrstats": "", // address where to reach the cdrstats service. Empty to disable stats gathering <""|internal|x.y.z.y:1234> +// "reconnects": 5, // number of reconnect attempts to rater or cdrs +// "cdr_replication":[], // replicate the raw CDR to a number of servers //}, -//"mediator": { -// "enabled": false, // starts Mediator service: . -// "reconnects": 3, // number of reconnects to rater/cdrs before giving up. -// "rater": "internal", // address where to reach the Rater: -// "cdrstats": "", // address where to reach the cdrstats service. Empty to disable stats gathering out of mediated CDRs <""|internal|x.y.z.y:1234> -// "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario -//}, - - -//"cdrstats": { +//"cdr_stats": { // "enabled": false, // starts the cdrstats service: // "queue_length": 50, // number of items in the stats buffer // "time_window": "1h", // will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow @@ -153,7 +147,7 @@ //"cdrc": { // "*default": { // "enabled": false, // enable CDR client functionality -// "cdrs_address": "internal", // address where to reach CDR server. +// "cdrs": "internal", // address where to reach CDR server. // "cdr_format": "csv", // CDR file format // "field_separator": ",", // separator used in case of csv files // "run_delay": 0, // sleep interval in seconds between consecutive runs, 0 to use automation via inotify @@ -179,6 +173,7 @@ // } //}, + //"sm_freeswitch": { // "enabled": false, // starts SessionManager service: // "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013> @@ -193,7 +188,7 @@ // "empty_balance_context": "", // if defined, prepaid calls will be transfered to this context on empty balance // "empty_balance_ann_file": "", // file to be played before disconnecting prepaid calls on empty balance (applies only if no context defined) // "connections":[ // instantiate connections to multiple FreeSWITCH servers -// {"server": "127.0.0.1:8021", "password": "ClueCon", "reconnects": -1} // reconnects -1 to indefinitely connect +// {"server": "127.0.0.1:8021", "password": "ClueCon", "reconnects": 5} // ], //}, @@ -207,7 +202,7 @@ // "min_call_duration": "0s", // only authorize calls with allowed duration higher than this // "max_call_duration": "3h", // maximum call duration a prepaid call can last // "connections":[ // instantiate connections to multiple Kamailio servers -// {"evapi_addr": "127.0.0.1:8448", "reconnects": -1} // reconnects -1 to indefinitely connect +// {"evapi_addr": "127.0.0.1:8448", "reconnects": 5} // ], //}, @@ -222,7 +217,7 @@ // "max_call_duration": "3h", // maximum call duration a prepaid call can last // "events_subscribe_interval": "60s", // automatic events subscription to OpenSIPS, 0 to disable it // "mi_addr": "127.0.0.1:8020", // address where to reach OpenSIPS MI to send session disconnects -// "reconnects": -1, // reconnects -1 to indefinitely connect +// "reconnects": 5, // number of reconnects if connection is lost //}, @@ -247,4 +242,4 @@ //}, -//} \ No newline at end of file +} \ No newline at end of file diff --git a/data/conf/samples/apier/apier.json b/data/conf/samples/apier/apier.json index 6bb268430..22fd7733b 100644 --- a/data/conf/samples/apier/apier.json +++ b/data/conf/samples/apier/apier.json @@ -14,7 +14,7 @@ "cdrs": { "enabled": true, // start the CDR Server service: - "mediator": "internal", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> }, "cdre": { @@ -23,9 +23,4 @@ } }, -"mediator": { - "enabled": true, // starts Mediator service: . - -}, - } \ No newline at end of file diff --git a/data/conf/samples/cdrsreplicationmaster/cdrsreplicationmaster.json b/data/conf/samples/cdrsreplicationmaster/cdrsreplicationmaster.json index 0df2d5ab7..adc8300ef 100644 --- a/data/conf/samples/cdrsreplicationmaster/cdrsreplicationmaster.json +++ b/data/conf/samples/cdrsreplicationmaster/cdrsreplicationmaster.json @@ -9,9 +9,9 @@ }, "cdrs": { - "enabled": true, // start the CDR Server service: - "store_disable": true, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario - "cdr_replication":[ // replicate the rated CDR to a number of servers + "enabled": true, // start the CDR Server service: + "store_cdrs": false, // store cdrs in storDb + "cdr_replication":[ // replicate the rated CDR to a number of servers {"transport": "*http_jsonrpc", "server": "http://127.0.0.1:12080/jsonrpc"}, ], }, diff --git a/data/conf/samples/cdrsreplicationslave/cdrsreplicationslave.json b/data/conf/samples/cdrsreplicationslave/cdrsreplicationslave.json index 8441b8632..2d06fb5a8 100644 --- a/data/conf/samples/cdrsreplicationslave/cdrsreplicationslave.json +++ b/data/conf/samples/cdrsreplicationslave/cdrsreplicationslave.json @@ -16,7 +16,6 @@ "cdrs": { "enabled": true, // start the CDR Server service: - }, } \ No newline at end of file diff --git a/data/conf/samples/cdrstats/cdrstats.json b/data/conf/samples/cdrstats/cdrstats.json index 5259b69d5..16bd11ca9 100644 --- a/data/conf/samples/cdrstats/cdrstats.json +++ b/data/conf/samples/cdrstats/cdrstats.json @@ -10,17 +10,12 @@ "cdrs": { "enabled": true, // start the CDR Server service: - "mediator": "internal", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> - "store_disable": true, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario -}, - -"mediator": { - "enabled": true, // starts Mediator service: . + "store_cdrs": false, // store cdrs in storDb + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> "cdrstats": "internal", // address where to reach the cdrstats service. Empty to disable stats gathering out of mediated CDRs <""|internal|x.y.z.y:1234> - "store_disable": true, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario }, -"cdrstats": { +"cdr_stats": { "enabled": true, // starts the cdrstats service: "queue_length": 5, // number of items in the stats buffer "time_window": "0", // will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow diff --git a/data/conf/samples/cdrsv2mysql/cdrsv2mysql.json b/data/conf/samples/cdrsv2mysql/cdrsv2mysql.json index 7fa519eb2..e05ac0535 100644 --- a/data/conf/samples/cdrsv2mysql/cdrsv2mysql.json +++ b/data/conf/samples/cdrsv2mysql/cdrsv2mysql.json @@ -10,12 +10,7 @@ "cdrs": { "enabled": true, // start the CDR Server service: - "mediator": "internal", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> -}, - -"mediator": { - "enabled": true, // starts Mediator service: . - + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> }, } \ No newline at end of file diff --git a/data/conf/samples/cdrsv2psql/cdrsv2psql.json b/data/conf/samples/cdrsv2psql/cdrsv2psql.json index e7b4cc281..9f78c6df8 100644 --- a/data/conf/samples/cdrsv2psql/cdrsv2psql.json +++ b/data/conf/samples/cdrsv2psql/cdrsv2psql.json @@ -16,12 +16,7 @@ "cdrs": { "enabled": true, // start the CDR Server service: - "mediator": "internal", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> -}, - -"mediator": { - "enabled": true, // starts Mediator service: . - + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> }, } \ No newline at end of file diff --git a/data/conf/samples/mediator1/mediator_test1.json b/data/conf/samples/mediator1/mediator_test1.json index f446edd2a..d02f64603 100644 --- a/data/conf/samples/mediator1/mediator_test1.json +++ b/data/conf/samples/mediator1/mediator_test1.json @@ -14,7 +14,7 @@ "cdrs": { "enabled": true, // start the CDR Server service: - "mediator": "internal", // address where to reach the Mediator. Empty for disabling mediation. <""|internal> + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> }, "cdre": { @@ -23,8 +23,4 @@ } }, -"mediator": { - "enabled": true, // starts Mediator service: . -}, - } \ No newline at end of file diff --git a/data/tutorials/fs_evsock/cgrates/etc/cgrates/cgrates.json b/data/tutorials/fs_evsock/cgrates/etc/cgrates/cgrates.json index a23401dd2..345f968dd 100644 --- a/data/tutorials/fs_evsock/cgrates/etc/cgrates/cgrates.json +++ b/data/tutorials/fs_evsock/cgrates/etc/cgrates/cgrates.json @@ -82,17 +82,17 @@ // "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario }, - -"mediator": { - "enabled": true, // starts Mediator service: . -// "reconnects": 3, // number of reconnects to rater/cdrs before giving up. - "rater": "internal", // address where to reach the Rater: - "cdrstats": "internal", // address where to reach the cdrstats service. Empty to disable stats gathering out of mediated CDRs <""|internal|x.y.z.y:1234> -// "store_disable": false, // when true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario +"cdrs": { + "enabled": true, // start the CDR Server service: +// "extra_fields": [], // extra fields to store in CDRs for non-generic CDRs +// "store_cdrs": true, // store cdrs in storDb + "rater": "internal", // address where to reach the Rater for cost calculation, empty to disable functionality: <""|internal|x.y.z.y:1234> + "cdrstats": "internal", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234> +// "reconnects": 5, // number of reconnect attempts to rater or cdrs +// "cdr_replication":[], // replicate the raw CDR to a number of servers }, - -"cdrstats": { +"cdr_stats": { "enabled": true, // starts the cdrstats service: // "queue_length": 50, // number of items in the stats buffer // "time_window": "1h", // will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow diff --git a/engine/calldesc.go b/engine/calldesc.go index 310742029..613a7a7e2 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -408,7 +408,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } err := cd.LoadRatingPlans() if err != nil { - Logger.Err(fmt.Sprintf("error getting cost for key %s: %v", cd.GetKey(cd.Subject), err)) + Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error())) return &CallCost{Cost: -1}, err } @@ -503,7 +503,7 @@ func (origCD *CallDescriptor) getMaxSessionDuration(origAcc *Account) (time.Dura func (cd *CallDescriptor) GetMaxSessionDuration() (duration time.Duration, err error) { if account, err := cd.getAccount(); err != nil || account == nil { - Logger.Err(fmt.Sprintf("Could not get user balance for %s: %s.", cd.GetAccountKey(), err.Error())) + Logger.Err(fmt.Sprintf("Could not get user balance for <%s>: %s.", cd.GetAccountKey(), err.Error())) return 0, err } else { if memberIds, err := account.GetUniqueSharedGroupMembers(cd.Destination, cd.Direction, cd.Category, cd.TOR); err == nil { @@ -531,7 +531,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) cc, err = account.debitCreditBalance(cd, !dryRun, dryRun, goNegative) //log.Print("HERE: ", cc, err) if err != nil { - Logger.Err(fmt.Sprintf(" Error getting cost for account key %v: %v", cd.GetAccountKey(), err)) + Logger.Err(fmt.Sprintf(" Error getting cost for account key <%s>: %s", cd.GetAccountKey(), err.Error())) //return } cost := 0.0 @@ -552,7 +552,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { // lock all group members if account, err := cd.getAccount(); err != nil || account == nil { - Logger.Err(fmt.Sprintf("Could not get user balance for %s: %s.", cd.GetAccountKey(), err.Error())) + Logger.Err(fmt.Sprintf("Could not get user balance for <%s>: %s.", cd.GetAccountKey(), err.Error())) return nil, err } else { if memberIds, err := account.GetUniqueSharedGroupMembers(cd.Destination, cd.Direction, cd.Category, cd.TOR); err == nil { @@ -573,7 +573,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { // by the GetMaxSessionTime method. The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { if account, err := cd.getAccount(); err != nil || account == nil { - Logger.Err(fmt.Sprintf("Could not get user balance for %s: %s.", cd.GetAccountKey(), err.Error())) + Logger.Err(fmt.Sprintf("Could not get user balance for <%s>: %s.", cd.GetAccountKey(), err.Error())) return nil, err } else { //log.Printf("ACC: %+v", account) diff --git a/engine/cdrs.go b/engine/cdrs.go index 4cabb0987..0c87836af 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -22,48 +22,291 @@ import ( "fmt" "io/ioutil" "net/http" + "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) -var ( - cfg *config.CGRConfig // Share the configuration with the rest of the package - storage CdrStorage - medi *Mediator - stats StatsInterface -) +var cdrServer *CdrServer // Share the server so we can use it in http handlers + +// Handler for generic cgr cdr http +func cgrCdrHandler(w http.ResponseWriter, r *http.Request) { + cgrCdr, err := NewCgrCdrFromHttpReq(r) + if err != nil { + Logger.Err(fmt.Sprintf(" Could not create CDR entry: %s", err.Error())) + } + if err := cdrServer.rateStoreStatsReplicate(cgrCdr.AsStoredCdr()); err != nil { + Logger.Err(fmt.Sprintf(" Errors when storing CDR entry: %s", err.Error())) + } +} + +// Handler for fs http +func fsCdrHandler(w http.ResponseWriter, r *http.Request) { + body, _ := ioutil.ReadAll(r.Body) + fsCdr, err := NewFSCdr(body, cdrServer.cgrCfg) + if err != nil { + Logger.Err(fmt.Sprintf(" Could not create CDR entry: %s", err.Error())) + } + if err := cdrServer.rateStoreStatsReplicate(fsCdr.AsStoredCdr()); err != nil { + Logger.Err(fmt.Sprintf(" Errors when storing CDR entry: %s", err.Error())) + } +} + +func NewCdrServer(cgrCfg *config.CGRConfig, logDb LogStorage, cdrDb CdrStorage, rater Connector, stats StatsInterface) (*CdrServer, error) { + return &CdrServer{cgrCfg: cgrCfg, logDb: logDb, cdrDb: cdrDb, rater: rater, stats: stats}, nil + /* + if cfg.CDRSStats != "" { + if cfg.CDRSStats != utils.INTERNAL { + if s, err := NewProxyStats(cfg.CDRSStats); err == nil { + stats = s + } else { + Logger.Err(fmt.Sprintf(" Errors connecting to CDRS stats service : %s", err.Error())) + } + } + } else { + // disable stats for cdrs + stats = nil + } + */ +} + +type CdrServer struct { + cgrCfg *config.CGRConfig + logDb LogStorage + cdrDb CdrStorage + rater Connector + stats StatsInterface +} + +func (self *CdrServer) RegisterHanlersToServer(server *Server) { + cdrServer = self // Share the server object for handlers + server.RegisterHttpFunc("/cdr_post", cgrCdrHandler) + server.RegisterHttpFunc("/freeswitch_json", fsCdrHandler) +} + +// RPC method, used to internally process CDR +func (self *CdrServer) ProcessCdr(cdr *StoredCdr) error { + return self.rateStoreStatsReplicate(cdr) +} + +// RPC method, used to process external CDRs +func (self *CdrServer) ProcessExternalCdr(cdr *ExternalCdr) error { + storedCdr, err := NewStoredCdrFromExternalCdr(cdr) + if err != nil { + return err + } + return self.rateStoreStatsReplicate(storedCdr) +} + +// Called by rate/re-rate API +func (self *CdrServer) RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects []string, + orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated, sendToStats bool) error { + var costStart, costEnd *float64 + if rerateErrors { + costStart = utils.Float64Pointer(-1.0) + if !rerateRated { + costEnd = utils.Float64Pointer(0.0) + } + } else if rerateRated { + costStart = utils.Float64Pointer(0.0) + } + cdrs, _, err := self.cdrDb.GetStoredCdrs(&utils.CdrsFilter{CgrIds: cgrIds, RunIds: runIds, Tors: tors, CdrHosts: cdrHosts, CdrSources: cdrSources, ReqTypes: reqTypes, Directions: directions, + Tenants: tenants, Categories: categories, Accounts: accounts, Subjects: subjects, DestPrefixes: destPrefixes, RatedAccounts: ratedAccounts, RatedSubjects: ratedSubjects, + OrderIdStart: orderIdStart, OrderIdEnd: orderIdEnd, AnswerTimeStart: &timeStart, AnswerTimeEnd: &timeEnd, CostStart: costStart, CostEnd: costEnd, IgnoreDerived: true}) + if err != nil { + return err + } + for _, cdr := range cdrs { + if err := self.rateStoreStatsReplicate(cdr); err != nil { + Logger.Err(fmt.Sprintf(" Processing CDR %+v, got error: %s", cdr, err.Error())) + } + } + return nil +} // Returns error if not able to properly store the CDR, mediation is async since we can always recover offline -func storeAndMediate(storedCdr *StoredCdr) error { - if !cfg.CDRSStoreDisable { - if err := storage.SetCdr(storedCdr); err != nil { +func (self *CdrServer) rateStoreStatsReplicate(storedCdr *StoredCdr) (err error) { + cdrs := []*StoredCdr{storedCdr} + if self.rater != nil && !storedCdr.Rated { // Rate CDR + if cdrs, err = self.deriveAndRateCdr(storedCdr); err != nil { return err } } - if stats != nil { - go func(storedCdr *StoredCdr) { - if err := stats.AppendCDR(storedCdr, nil); err != nil { - Logger.Err(fmt.Sprintf(" Could not append cdr to stats: %s", err.Error())) + if self.cgrCfg.CDRSStoreCdrs { // Store CDRs + // Store RawCdr + if err := self.cdrDb.SetCdr(storedCdr); err != nil { // Only original CDR stored in primary table, no derived + Logger.Err(fmt.Sprintf(" Storing primary CDR %+v, got error: %s", storedCdr, err.Error())) + } + // Store rated CDRs (including derived) + for _, cdr := range cdrs { + if err := self.cdrDb.SetRatedCdr(cdr); err != nil { + Logger.Err(fmt.Sprintf(" Storing rated CDR %+v, got error: %s", cdr, err.Error())) } - }(storedCdr) - } - if cfg.CDRSCdrReplication != nil { - replicateCdr(storedCdr, cfg.CDRSCdrReplication) - } - if cfg.CDRSMediator == utils.INTERNAL { - go func(storedCdr *StoredCdr) { - if err := medi.RateCdr(storedCdr, true); err != nil { - Logger.Err(fmt.Sprintf(" Could not run mediation on CDR: %s", err.Error())) + // Store CostDetails + if cdr.Rated || utils.IsSliceMember([]string{utils.RATED, utils.META_RATED}, cdr.ReqType) { // Account related CDRs are saved automatically, so save the others here if requested + if err := self.logDb.LogCallCost(cdr.CgrId, utils.CDRS_SOURCE, cdr.MediationRunId, storedCdr.CostDetails); err != nil { + Logger.Err(fmt.Sprintf(" Storing costs for CDR %+v, costDetails: %+v, got error: %s", cdr, cdr.CostDetails, err.Error())) + } } - }(storedCdr) + } + } + if self.stats != nil { // Send CDR to stats + for _, cdr := range cdrs { + go func(storedCdr *StoredCdr) { + if err := self.stats.AppendCDR(storedCdr, nil); err != nil { + Logger.Err(fmt.Sprintf(" Could not append cdr to stats: %s", err.Error())) + } + }(cdr) + } + } + if self.cgrCfg.CDRSCdrReplication != nil { + for _, cdr := range cdrs { + self.replicateCdr(cdr) + } + } + return nil +} + +// Derive the original CDR based on derivedCharging rules and calculate costs for each. Returns the results +func (self *CdrServer) deriveAndRateCdr(storedCdr *StoredCdr) ([]*StoredCdr, error) { + cdrRuns, err := self.deriveCdrs(storedCdr) + if err != nil { + return nil, err + } + for _, cdr := range cdrRuns { + if err := self.rateCDR(cdr); err != nil { + cdr.Cost = -1.0 // If there was an error, mark the CDR + cdr.ExtraInfo = err.Error() + } + } + return cdrRuns, nil +} + +// Retrive the cost from logging database, nil in case of no log +func (self *CdrServer) getCostsFromDB(cgrid, runId string) (cc *CallCost, err error) { + for i := 0; i < 3; i++ { // Mechanism to avoid concurrency between SessionManager writing the costs and mediator picking them up + cc, err = self.logDb.GetCallCostLog(cgrid, SESSION_MANAGER_SOURCE, runId) + if cc != nil { + break + } + time.Sleep(time.Duration((i+1)*10) * time.Millisecond) + } + return +} + +// Retrive the cost from engine +func (self *CdrServer) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error) { + if storedCdr.Usage == time.Duration(0) { // failed call, nil cost + return nil, nil // No costs present, better than empty call cost since could lead us to 0 costs + } + cc := new(CallCost) + var err error + cd := CallDescriptor{ + TOR: storedCdr.TOR, + Direction: storedCdr.Direction, + Tenant: storedCdr.Tenant, + Category: storedCdr.Category, + Subject: storedCdr.Subject, + Account: storedCdr.Account, + Destination: storedCdr.Destination, + TimeStart: storedCdr.AnswerTime, + TimeEnd: storedCdr.AnswerTime.Add(storedCdr.Usage), + DurationIndex: storedCdr.Usage, + } + if utils.IsSliceMember([]string{utils.META_PSEUDOPREPAID, utils.META_POSTPAID, utils.PSEUDOPREPAID, utils.POSTPAID}, storedCdr.ReqType) { + if err = self.rater.Debit(cd, cc); err == nil { // Debit has occured, we are forced to write the log, even if CDR store is disabled + self.logDb.LogCallCost(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, cc) + } + } else { + err = self.rater.GetCost(cd, cc) + } + if err != nil { + return nil, err + } + return cc, nil +} + +func (self *CdrServer) deriveCdrs(storedCdr *StoredCdr) ([]*StoredCdr, error) { + if len(storedCdr.MediationRunId) == 0 { + storedCdr.MediationRunId = utils.META_DEFAULT + } + cdrRuns := []*StoredCdr{storedCdr} + if storedCdr.Rated { // Do not derive already rated CDRs since they should be already derived + return cdrRuns, nil + } + attrsDC := utils.AttrDerivedChargers{Tenant: storedCdr.Tenant, Category: storedCdr.Category, Direction: storedCdr.Direction, + Account: storedCdr.Account, Subject: storedCdr.Subject} + var dcs utils.DerivedChargers + if err := self.rater.GetDerivedChargers(attrsDC, &dcs); err != nil { + Logger.Err(fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", storedCdr.CgrId, err.Error())) + return nil, err + } + for _, dc := range dcs { + runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP) + matchingAllFilters := true + for _, dcRunFilter := range runFilters { + if fltrPass, _ := storedCdr.PassesFieldFilter(dcRunFilter); !fltrPass { + matchingAllFilters = false + break + } + } + if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched + continue + } + dcReqTypeFld, _ := utils.NewRSRField(dc.ReqTypeField) + dcDirFld, _ := utils.NewRSRField(dc.DirectionField) + dcTenantFld, _ := utils.NewRSRField(dc.TenantField) + dcCategoryFld, _ := utils.NewRSRField(dc.CategoryField) + dcAcntFld, _ := utils.NewRSRField(dc.AccountField) + dcSubjFld, _ := utils.NewRSRField(dc.SubjectField) + dcDstFld, _ := utils.NewRSRField(dc.DestinationField) + dcSTimeFld, _ := utils.NewRSRField(dc.SetupTimeField) + dcATimeFld, _ := utils.NewRSRField(dc.AnswerTimeField) + dcDurFld, _ := utils.NewRSRField(dc.UsageField) + forkedCdr, err := storedCdr.ForkCdr(dc.RunId, dcReqTypeFld, dcDirFld, dcTenantFld, dcCategoryFld, dcAcntFld, dcSubjFld, dcDstFld, dcSTimeFld, dcATimeFld, dcDurFld, + []*utils.RSRField{}, true) + if err != nil { // Errors on fork, cannot calculate further, write that into db for later analysis + Logger.Err(fmt.Sprintf("Could not fork CGR with cgrid %s, run: %s, error: %s", storedCdr.CgrId, dc.RunId, err.Error())) + //forkedCdr = &StoredCdr{CgrId: storedCdr.CgrId, CdrSource: utils.FORKED_CDR, MediationRunId: dc.RunId, Cost: -1, CostExtraInfo: err.Error()} + continue // do not add it to the forked CDR list + } + cdrRuns = append(cdrRuns, forkedCdr) + } + return cdrRuns, nil +} + +func (self *CdrServer) rateCDR(storedCdr *StoredCdr) error { + var qryCC *CallCost + var errCost error + if utils.IsSliceMember([]string{utils.META_PREPAID, utils.PREPAID}, storedCdr.ReqType) { // ToDo: Get rid of PREPAID as soon as we don't want to support it backwards + // Should be previously calculated and stored in DB + qryCC, errCost = self.getCostsFromDB(storedCdr.CgrId, storedCdr.MediationRunId) + } else { + qryCC, errCost = self.getCostFromRater(storedCdr) + } + if errCost != nil { + return errCost + } else if qryCC != nil { + storedCdr.Cost = qryCC.Cost + storedCdr.CostDetails = qryCC } return nil } // ToDo: Add websocket support -func replicateCdr(cdr *StoredCdr, replCfgs []*config.CdrReplicationCfg) error { - for _, rplCfg := range replCfgs { +func (self *CdrServer) replicateCdr(cdr *StoredCdr) error { + for _, rplCfg := range self.cgrCfg.CDRSCdrReplication { + passesFilters := true + for _, cdfFltr := range rplCfg.CdrFilter { + if fltrPass, _ := cdr.PassesFieldFilter(cdfFltr); !fltrPass { + passesFilters = false + break + } + } + if !passesFilters { // Not passes filters, ignore this replication + continue + } switch rplCfg.Transport { case utils.META_HTTP_POST: httpClient := new(http.Client) @@ -82,67 +325,3 @@ func replicateCdr(cdr *StoredCdr, replCfgs []*config.CdrReplicationCfg) error { } return nil } - -// Handler for generic cgr cdr http -func cgrCdrHandler(w http.ResponseWriter, r *http.Request) { - cgrCdr, err := NewCgrCdrFromHttpReq(r) - if err != nil { - Logger.Err(fmt.Sprintf(" Could not create CDR entry: %s", err.Error())) - } - if err := storeAndMediate(cgrCdr.AsStoredCdr()); err != nil { - Logger.Err(fmt.Sprintf(" Errors when storing CDR entry: %s", err.Error())) - } -} - -// Handler for fs http -func fsCdrHandler(w http.ResponseWriter, r *http.Request) { - body, _ := ioutil.ReadAll(r.Body) - fsCdr, err := NewFSCdr(body) - if err != nil { - Logger.Err(fmt.Sprintf(" Could not create CDR entry: %s", err.Error())) - } - if err := storeAndMediate(fsCdr.AsStoredCdr()); err != nil { - Logger.Err(fmt.Sprintf(" Errors when storing CDR entry: %s", err.Error())) - } -} - -type CDRS struct{} - -func NewCdrS(s CdrStorage, m *Mediator, st *Stats, c *config.CGRConfig) *CDRS { - storage = s - medi = m - cfg = c - stats = st - if cfg.CDRSStats != "" { - if cfg.CDRSStats != utils.INTERNAL { - if s, err := NewProxyStats(cfg.CDRSStats); err == nil { - stats = s - } else { - Logger.Err(fmt.Sprintf(" Errors connecting to CDRS stats service : %s", err.Error())) - } - } - } else { - // disable stats for cdrs - stats = nil - } - return &CDRS{} -} - -func (cdrs *CDRS) RegisterHanlersToServer(server *Server) { - server.RegisterHttpFunc("/cdr_post", cgrCdrHandler) - server.RegisterHttpFunc("/freeswitch_json", fsCdrHandler) -} - -// Used to internally process CDR -func (cdrs *CDRS) ProcessCdr(cdr *StoredCdr) error { - return storeAndMediate(cdr) -} - -// Used to process external CDR -func (cdrs *CDRS) ProcessExternalCdr(cdr *ExternalCdr) error { - storedCdr, err := NewStoredCdrFromExternalCdr(cdr) - if err != nil { - return err - } - return storeAndMediate(storedCdr) -} diff --git a/engine/cdrs_local_test.go b/engine/cdrs_local_test.go index 564e6c27b..b799400cf 100644 --- a/engine/cdrs_local_test.go +++ b/engine/cdrs_local_test.go @@ -109,12 +109,3 @@ func TestCdrsHttpJsonRpcCdrReplication(t *testing.T) { t.Error("Unexpected number of CDRs returned: ", len(rcvedCdrs)) } } - -/* -&StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", - CdrHost: "192.168.1.1", CdrSource: "test", ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", - SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, - Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, - RatedAccount: "dan", RatedSubject: "dans", Rated: true, - } -*/ diff --git a/engine/fscdr.go b/engine/fscdr.go index f15e4df3e..741f61c18 100644 --- a/engine/fscdr.go +++ b/engine/fscdr.go @@ -24,6 +24,7 @@ import ( "reflect" "strings" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) @@ -50,9 +51,8 @@ const ( FS_SIP_REQUSER = "sip_req_user" // Apps like FusionPBX do not set dialed_extension, alternative being destination_number but that comes in customer profile, not in vars ) -func NewFSCdr(body []byte) (*FSCdr, error) { - fsCdr := new(FSCdr) - fsCdr.vars = make(map[string]string) +func NewFSCdr(body []byte, cgrCfg *config.CGRConfig) (*FSCdr, error) { + fsCdr := &FSCdr{cgrCfg: cgrCfg, vars: make(map[string]string)} var err error if err = json.Unmarshal(body, &fsCdr.body); err == nil { if variables, ok := fsCdr.body[FS_CDR_MAP]; ok { @@ -68,8 +68,9 @@ func NewFSCdr(body []byte) (*FSCdr, error) { } type FSCdr struct { - vars map[string]string - body map[string]interface{} // keeps the loaded body for extra field search + cgrCfg *config.CGRConfig + vars map[string]string + body map[string]interface{} // keeps the loaded body for extra field search } func (fsCdr FSCdr) getCgrId() string { @@ -78,8 +79,8 @@ func (fsCdr FSCdr) getCgrId() string { } func (fsCdr FSCdr) getExtraFields() map[string]string { - extraFields := make(map[string]string, len(cfg.CDRSExtraFields)) - for _, field := range cfg.CDRSExtraFields { + extraFields := make(map[string]string, len(fsCdr.cgrCfg.CDRSExtraFields)) + for _, field := range fsCdr.cgrCfg.CDRSExtraFields { origFieldVal, foundInVars := fsCdr.vars[field.Id] if strings.HasPrefix(field.Id, utils.STATIC_VALUE_PREFIX) { // Support for static values injected in the CDRS. it will show up as {^value:value} foundInVars = true @@ -127,10 +128,10 @@ func (fsCdr FSCdr) AsStoredCdr() *StoredCdr { storCdr.AccId = fsCdr.vars[FS_UUID] storCdr.CdrHost = fsCdr.vars[FS_IP] storCdr.CdrSource = FS_CDR_SOURCE - storCdr.ReqType = utils.FirstNonEmpty(fsCdr.vars[FS_REQTYPE], cfg.DefaultReqType) + storCdr.ReqType = utils.FirstNonEmpty(fsCdr.vars[FS_REQTYPE], fsCdr.cgrCfg.DefaultReqType) storCdr.Direction = "*out" - storCdr.Tenant = utils.FirstNonEmpty(fsCdr.vars[FS_CSTMID], cfg.DefaultTenant) - storCdr.Category = utils.FirstNonEmpty(fsCdr.vars[FS_CATEGORY], cfg.DefaultCategory) + storCdr.Tenant = utils.FirstNonEmpty(fsCdr.vars[FS_CSTMID], fsCdr.cgrCfg.DefaultTenant) + storCdr.Category = utils.FirstNonEmpty(fsCdr.vars[FS_CATEGORY], fsCdr.cgrCfg.DefaultCategory) storCdr.Account = utils.FirstNonEmpty(fsCdr.vars[FS_ACCOUNT], fsCdr.vars[FS_USERNAME]) storCdr.Subject = utils.FirstNonEmpty(fsCdr.vars[FS_SUBJECT], fsCdr.vars[FS_USERNAME]) storCdr.Destination = utils.FirstNonEmpty(fsCdr.vars[FS_DESTINATION], fsCdr.vars[FS_CALL_DEST_NR], fsCdr.vars[FS_SIP_REQUSER]) diff --git a/engine/fscdr_test.go b/engine/fscdr_test.go index 535c31cef..5213de2a7 100644 --- a/engine/fscdr_test.go +++ b/engine/fscdr_test.go @@ -29,13 +29,15 @@ import ( ) var body = []byte(`{"core-uuid":"844715f9-d8a1-44d6-a4bf-358bec5e10b8","channel_data":{"state":"CS_REPORTING","direction":"inbound","state_number":"11","flags":"0=1;1=1;3=1;19=1;23=1;36=1;37=1;39=1;42=1;47=1;52=1","caps":"1=1;2=1;3=1;4=1;5=1;6=1"},"variables":{"direction":"inbound","uuid":"01df56f4-d99a-4ef6-b7fe-b924b2415b7f","session_id":"33","sip_from_user":"dan","sip_from_uri":"dan@ipbx.itsyscom.com","sip_from_host":"ipbx.itsyscom.com","channel_name":"sofia/ipbxas/dan@ipbx.itsyscom.com","sip_local_network_addr":"127.0.0.1","sip_network_ip":"2.3.4.5","sip_network_port":"5060","sip_received_ip":"2.3.4.5","sip_received_port":"5060","sip_via_protocol":"udp","sip_from_user_stripped":"dan","sofia_profile_name":"ipbxas","recovery_profile_name":"ipbxas","sip_invite_record_route":"","sip_req_user":"+4986517174963","sip_req_port":"5080","sip_req_uri":"+4986517174963@127.0.0.1:5080","sip_req_host":"127.0.0.1","sip_to_user":"+4986517174963","sip_to_uri":"+4986517174963@ipbx.itsyscom.com","sip_to_host":"ipbx.itsyscom.com","sip_contact_params":"alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com","sip_contact_user":"dan","sip_contact_port":"5060","sip_contact_uri":"dan@10.10.10.154:5060","sip_contact_host":"10.10.10.154","sip_user_agent":"Jitsi2.2.4603.9615Linux","sip_via_host":"2.3.4.5","presence_id":"dan@ipbx.itsyscom.com","sip_h_X-AuthType":"SUA","sip_h_X-AuthUser":"dan","sip_h_X-AuthDomain":"ipbx.itsyscom.com","sip_h_X-BalancerIP":"2.3.4.5","switch_r_sdp":"v=0\r\no=dan 0 0 IN IP4 10.10.10.154\r\ns=-\r\nc=IN IP4 10.10.10.154\r\nt=0 0\r\nm=audio 5004 RTP/AVP 96 8 0\r\na=rtpmap:96 opus/48000\r\na=fmtp:96 usedtx=1\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:0 PCMU/8000\r\na=extmap:1 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\nm=video 5006 RTP/AVP 97 99\r\na=rtpmap:97 H264/90000\r\na=fmtp:97 profile-level-id=4DE01f;packetization-mode=1\r\na=rtpmap:99 H264/90000\r\na=fmtp:99 profile-level-id=4DE01f\r\na=recvonly\r\na=imageattr:97 send [x=[0-640],y=[0-480]] recv [x=[0-1280],y=[0-800]]\r\na=imageattr:99 send [x=[0-640],y=[0-480]] recv [x=[0-1280],y=[0-800]]\r\n","ep_codec_string":"PCMA@8000h@20i@64000b,PCMU@8000h@20i@64000b,H264@90000h","effective_caller_id_number":"+4986517174960","hangup_after_bridge":"true","continue_on_fail":"true","cgr_tenant":"ipbx.itsyscom.com","cgr_tor":"call","cgr_account":"dan","cgr_subject":"dan","cgr_destination":"+4986517174963","sip_redirect_contact_0":";q=1","sip_redirected_to":";q=1","sip_redirect_contact_user_0":"dan","sip_redirect_contact_host_0":"10.10.10.141","sip_redirect_contact_params_0":"alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072","sip_redirect_dialstring_0":"sofia/ipbxas/sip:dan@10.10.10.141:3072;alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072","sip_redirect_contact_1":"","sip_redirect_contact_user_1":"dan","sip_redirect_contact_host_1":"10.10.10.154","sip_redirect_contact_params_1":"alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060","sip_redirect_dialstring_1":"sofia/ipbxas/sip:dan@10.10.10.154:5060;alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060","sip_redirect_dialstring":"sofia/ipbxas/sip:dan@10.10.10.141:3072;alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072,sofia/ipbxas/sip:dan@10.10.10.154:5060;alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060","max_forwards":"15","transfer_history":"1375609854:d2300128-6724-471c-a495-a3f7a985a2b6:bl_xfer:dan/redirected/XML","transfer_source":"1375609854:d2300128-6724-471c-a495-a3f7a985a2b6:bl_xfer:dan/redirected/XML","call_uuid":"01df56f4-d99a-4ef6-b7fe-b924b2415b7f","current_application_data":"sofia/ipbxas/sip:dan@10.10.10.154:5060;alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060;fs_path=sip:2.3.4.5,sofia/ipbxas/sip:dan@10.10.10.141:3072;alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072;fs_path=sip:2.3.4.5","current_application":"bridge","originated_legs":"ARRAY::e377c077-0f1f-4b7d-b036-1aeef13eff32;Outbound Call;dan|:8f7c860f-0619-4d3c-9515-cc23b0fa3997;Outbound Call;dan","switch_m_sdp":"v=0\r\no=root 975388641 975388642 IN IP4 10.10.10.141\r\ns=call\r\nc=IN IP4 10.10.10.141\r\nt=0 0\r\nm=audio 59976 RTP/AVP 8 0 9 3 101\r\na=rtpmap:8 pcma/8000\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:9 g722/8000\r\na=rtpmap:3 gsm/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-16\r\na=ptime:20\r\nm=video 0 RTP/AVP 98 99\r\na=rtpmap:98 H264/90000\r\na=fmtp:98 profile-level-id=4DE01f\r\na=rtpmap:99 H264/90000\r\na=fmtp:99 profile-level-id=4DE01f\r\n","rtp_use_codec_string":"G722,G722.1,G729,PCMU,PCMA,GSM,H264","sip_audio_recv_pt":"0","sip_use_codec_name":"PCMU","sip_use_codec_rate":"8000","sip_use_codec_ptime":"20","read_codec":"PCMU","read_rate":"8000","write_codec":"PCMU","write_rate":"8000","dtmf_type":"info","video_possible":"true","remote_video_ip":"10.10.10.154","remote_video_port":"5006","sip_video_fmtp":"profile-level-id=4DE01f;packetization-mode=1","sip_video_pt":"97","sip_video_recv_pt":"97","video_read_codec":"H264","video_read_rate":"90000","video_write_codec":"H264","video_write_rate":"90000","sip_use_video_codec_name":"H264","sip_use_video_codec_fmtp":"profile-level-id=4DE01f;packetization-mode=1","sip_use_video_codec_rate":"90000","sip_use_video_codec_ptime":"0","local_media_ip":"2.3.4.5","local_media_port":"29452","advertised_media_ip":"2.3.4.5","sip_use_pt":"0","rtp_use_ssrc":"1408273224","local_video_ip":"2.3.4.5","local_video_port":"22648","sip_use_video_pt":"97","rtp_use_video_ssrc":"1408273224","sip_local_sdp_str":"v=0\no=iPBXCell 1375580404 1375580405 IN IP4 2.3.4.5\ns=iPBXCell\nc=IN IP4 2.3.4.5\nt=0 0\nm=audio 29452 RTP/AVP 0\na=rtpmap:0 PCMU/8000\na=silenceSupp:off - - - -\na=ptime:20\na=sendrecv\nm=video 22648 RTP/AVP 97\na=rtpmap:97 H264/90000\n","endpoint_disposition":"ANSWER","originate_disposition":"SUCCESS","DIALSTATUS":"SUCCESS","originate_causes":"ARRAY::e377c077-0f1f-4b7d-b036-1aeef13eff32;LOSE_RACE|:8f7c860f-0619-4d3c-9515-cc23b0fa3997;NONE","last_bridge_to":"8f7c860f-0619-4d3c-9515-cc23b0fa3997","bridge_channel":"sofia/ipbxas/sip:dan@10.10.10.141:3072","bridge_uuid":"8f7c860f-0619-4d3c-9515-cc23b0fa3997","signal_bond":"8f7c860f-0619-4d3c-9515-cc23b0fa3997","sip_to_tag":"4X345vQvQyetD","sip_from_tag":"9f90cc40","sip_cseq":"2","sip_call_id":"ca9c5e20caeaa6596be8cf66261f13e5@0:0:0:0:0:0:0:0","sip_full_via":"SIP/2.0/UDP 2.3.4.5;branch=z9hG4bKcydzigwkX,SIP/2.0/UDP 10.10.10.154:5060;rport=5060;received=1.2.3.4;branch=z9hG4bK-313937-b78ce2a1daafe532fc34b1b3735727ac","sip_from_display":"dan","sip_full_from":"\"dan\" ;tag=9f90cc40","sip_full_to":";tag=4X345vQvQyetD","last_sent_callee_id_name":"Outbound Call","last_sent_callee_id_number":"dan","remote_media_ip_reported":"10.10.10.154","remote_media_ip":"1.2.3.4","remote_media_port_reported":"5004","remote_media_port":"5004","rtp_auto_adjust":"true","sip_hangup_phrase":"OK","last_bridge_hangup_cause":"NORMAL_CLEARING","last_bridge_proto_specific_hangup_cause":"sip:200","bridge_hangup_cause":"NORMAL_CLEARING","hangup_cause":"NORMAL_CLEARING","hangup_cause_q850":"16","digits_dialed":"none","start_stamp":"2013-08-04 11:50:54","profile_start_stamp":"2013-08-04 11:50:54","answer_stamp":"2013-08-04 11:50:56","bridge_stamp":"2013-08-04 11:50:56","progress_stamp":"2013-08-04 11:50:54","progress_media_stamp":"2013-08-04 11:50:56","end_stamp":"2013-08-04 11:51:00","start_epoch":"1375609854","start_uepoch":"1375609854385581","profile_start_epoch":"1375609854","profile_start_uepoch":"1375609854385581","answer_epoch":"1375609856","answer_uepoch":"1375609856285587","bridge_epoch":"1375609856","bridge_uepoch":"1375609856285587","last_hold_epoch":"0","last_hold_uepoch":"0","hold_accum_seconds":"0","hold_accum_usec":"0","hold_accum_ms":"0","resurrect_epoch":"0","resurrect_uepoch":"0","progress_epoch":"1375609854","progress_uepoch":"1375609854505584","progress_media_epoch":"1375609856","progress_media_uepoch":"1375609856285587","end_epoch":"1375609860","end_uepoch":"1375609860205563","last_app":"bridge","last_arg":"sofia/ipbxas/sip:dan@10.10.10.154:5060;alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060;fs_path=sip:2.3.4.5,sofia/ipbxas/sip:dan@10.10.10.141:3072;alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072;fs_path=sip:2.3.4.5","caller_id":"\"dan\" ","duration":"6","billsec":"4","progresssec":"0","answersec":"2","waitsec":"2","progress_mediasec":"2","flow_billsec":"6","mduration":"5820","billmsec":"3920","progressmsec":"120","answermsec":"1900","waitmsec":"1900","progress_mediamsec":"1900","flow_billmsec":"5820","uduration":"5819982","billusec":"3919976","progressusec":"120003","answerusec":"1900006","waitusec":"1900006","progress_mediausec":"1900006","flow_billusec":"5819982","sip_hangup_disposition":"send_bye","rtp_audio_in_raw_bytes":"32968","rtp_audio_in_media_bytes":"32960","rtp_audio_in_packet_count":"207","rtp_audio_in_media_packet_count":"205","rtp_audio_in_skip_packet_count":"6","rtp_audio_in_jb_packet_count":"0","rtp_audio_in_dtmf_packet_count":"0","rtp_audio_in_cng_packet_count":"0","rtp_audio_in_flush_packet_count":"2","rtp_audio_in_largest_jb_size":"0","rtp_audio_out_raw_bytes":"31648","rtp_audio_out_media_bytes":"31648","rtp_audio_out_packet_count":"184","rtp_audio_out_media_packet_count":"184","rtp_audio_out_skip_packet_count":"0","rtp_audio_out_dtmf_packet_count":"0","rtp_audio_out_cng_packet_count":"0","rtp_audio_rtcp_packet_count":"0","rtp_audio_rtcp_octet_count":"0","rtp_video_in_raw_bytes":"0","rtp_video_in_media_bytes":"0","rtp_video_in_packet_count":"0","rtp_video_in_media_packet_count":"0","rtp_video_in_skip_packet_count":"0","rtp_video_in_jb_packet_count":"0","rtp_video_in_dtmf_packet_count":"0","rtp_video_in_cng_packet_count":"0","rtp_video_in_flush_packet_count":"0","rtp_video_in_largest_jb_size":"0","rtp_video_out_raw_bytes":"0","rtp_video_out_media_bytes":"0","rtp_video_out_packet_count":"0","rtp_video_out_media_packet_count":"0","rtp_video_out_skip_packet_count":"0","rtp_video_out_dtmf_packet_count":"0","rtp_video_out_cng_packet_count":"0","rtp_video_rtcp_packet_count":"0","rtp_video_rtcp_octet_count":"0"},"app_log":{"applications":[{"app_name":"set","app_data":"effective_caller_id_number=+4986517174960"},{"app_name":"set","app_data":"hangup_after_bridge=true"},{"app_name":"set","app_data":"continue_on_fail=true"},{"app_name":"set","app_data":"cgr_tenant=ipbx.itsyscom.com"},{"app_name":"set","app_data":"cgr_tor=call"},{"app_name":"set","app_data":"cgr_account=dan"},{"app_name":"set","app_data":"cgr_subject=dan"},{"app_name":"set","app_data":"cgr_destination=+4986517174963"},{"app_name":"bridge","app_data":"{presence_id=dan@ipbx.itsyscom.com,sip_redirect_fork=true}sofia/ipbxas/dan@ipbx.itsyscom.com;fs_path=sip:2.3.4.5"},{"app_name":"bridge","app_data":"sofia/ipbxas/sip:dan@10.10.10.154:5060;alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060;fs_path=sip:2.3.4.5,sofia/ipbxas/sip:dan@10.10.10.141:3072;alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072;fs_path=sip:2.3.4.5"}]},"callflow":{"dialplan":"XML","profile_index":"2","extension":{"name":"Redirected call","number":"dan","applications":[{"app_name":"bridge","app_data":"sofia/ipbxas/sip:dan@10.10.10.154:5060;alias=1.2.3.4~5060~1;transport=udp;registering_acc=ipbx_itsyscom_com;rcv=sip:1.2.3.4:5060;fs_path=sip:2.3.4.5,sofia/ipbxas/sip:dan@10.10.10.141:3072;alias=1.2.3.4~3072~1;line=x81npwse;rcv=sip:1.2.3.4:3072;fs_path=sip:2.3.4.5"}]},"caller_profile":{"username":"dan","dialplan":"XML","caller_id_name":"dan","ani":"dan","aniii":"","caller_id_number":"dan","network_addr":"2.3.4.5","rdnis":"+4986517174963","destination_number":"dan","uuid":"01df56f4-d99a-4ef6-b7fe-b924b2415b7f","source":"mod_sofia","context":"redirected","chan_name":"sofia/ipbxas/dan@ipbx.itsyscom.com","originatee":{"originatee_caller_profiles":[{"username":"dan","dialplan":"XML","caller_id_name":"dan","ani":"dan","aniii":"","caller_id_number":"+4986517174960","network_addr":"2.3.4.5","rdnis":"+4986517174963","destination_number":"dan","uuid":"8f7c860f-0619-4d3c-9515-cc23b0fa3997","source":"mod_sofia","context":"redirected","chan_name":"sofia/ipbxas/sip:dan@10.10.10.141:3072"}]}},"times":{"created_time":"1375609854385581","profile_created_time":"1375609854385581","progress_time":"1375609854505584","progress_media_time":"1375609856285587","answered_time":"1375609856285587","hangup_time":"1375609860205563","resurrect_time":"0","transfer_time":"0"}},"callflow":{"dialplan":"XML","profile_index":"1","extension":{"name":"OnNet Call","number":"+4986517174963","applications":[{"app_name":"set","app_data":"effective_caller_id_number=+4986517174960"},{"app_name":"set","app_data":"hangup_after_bridge=true"},{"app_name":"set","app_data":"continue_on_fail=true"},{"app_name":"set","app_data":"cgr_tenant=ipbx.itsyscom.com"},{"app_name":"set","app_data":"cgr_tor=call"},{"app_name":"set","app_data":"cgr_account=dan"},{"app_name":"set","app_data":"cgr_subject=dan"},{"app_name":"set","app_data":"cgr_destination=+4986517174963"},{"app_name":"bridge","app_data":"{presence_id=dan@ipbx.itsyscom.com,sip_redirect_fork=true}sofia/ipbxas/dan@ipbx.itsyscom.com;fs_path=sip:2.3.4.5"}]},"caller_profile":{"username":"dan","dialplan":"XML","caller_id_name":"dan","ani":"dan","aniii":"","caller_id_number":"dan","network_addr":"2.3.4.5","rdnis":"","destination_number":"+4986517174963","uuid":"01df56f4-d99a-4ef6-b7fe-b924b2415b7f","source":"mod_sofia","context":"ipbxas","chan_name":"sofia/ipbxas/dan@ipbx.itsyscom.com"},"times":{"created_time":"1375609854385581","profile_created_time":"1375609854385581","progress_time":"0","progress_media_time":"0","answered_time":"0","hangup_time":"0","resurrect_time":"0","transfer_time":"1375609854385581"}}}`) +var fsCdrCfg *config.CGRConfig func TestFsCdrInterfaces(t *testing.T) { var _ RawCdr = new(FSCdr) } func TestFirstNonEmpty(t *testing.T) { - fsCdr, err := NewFSCdr(body) + fsCdrCfg, _ = config.NewDefaultCGRConfig() + fsCdr, err := NewFSCdr(body, fsCdrCfg) if err != nil { t.Errorf("Error loading cdr: %v", err) } @@ -46,12 +48,8 @@ func TestFirstNonEmpty(t *testing.T) { } func TestCDRFields(t *testing.T) { - cfg, err = config.NewDefaultCGRConfig() - if err != nil { - t.Error(err) - } - cfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "sip_user_agent"}} - fsCdr, err := NewFSCdr(body) + fsCdrCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "sip_user_agent"}} + fsCdr, err := NewFSCdr(body, fsCdrCfg) if err != nil { t.Errorf("Error loading cdr: %v", err) } @@ -67,7 +65,7 @@ func TestCDRFields(t *testing.T) { } func TestSearchExtraFieldLast(t *testing.T) { - fsCdr, _ := NewFSCdr(body) + fsCdr, _ := NewFSCdr(body, fsCdrCfg) value := fsCdr.searchExtraField("transfer_time", fsCdr.body) if value != "1375609854385581" { t.Error("Error finding extra field: ", value) @@ -75,10 +73,10 @@ func TestSearchExtraFieldLast(t *testing.T) { } func TestSearchExtraField(t *testing.T) { - fsCdr, _ := NewFSCdr(body) + fsCdr, _ := NewFSCdr(body, fsCdrCfg) rsrSt1, _ := utils.NewRSRField("^injected_value") rsrSt2, _ := utils.NewRSRField("^injected_hdr::injected_value/") - cfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "caller_id_name"}, rsrSt1, rsrSt2} + fsCdrCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "caller_id_name"}, rsrSt1, rsrSt2} extraFields := fsCdr.getExtraFields() if len(extraFields) != 3 || extraFields["caller_id_name"] != "dan" || extraFields["injected_value"] != "injected_value" || @@ -88,7 +86,7 @@ func TestSearchExtraField(t *testing.T) { } func TestSearchExtraFieldInSlice(t *testing.T) { - fsCdr, _ := NewFSCdr(body) + fsCdr, _ := NewFSCdr(body, fsCdrCfg) value := fsCdr.searchExtraField("app_data", fsCdr.body) if value != "effective_caller_id_number=+4986517174960" { t.Error("Error finding extra field: ", value) @@ -96,11 +94,10 @@ func TestSearchExtraFieldInSlice(t *testing.T) { } func TestSearchReplaceInExtraFields(t *testing.T) { - cfg, _ = config.NewDefaultCGRConfig() - cfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "read_codec"}, + fsCdrCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "read_codec"}, &utils.RSRField{Id: "sip_user_agent", RSRules: []*utils.ReSearchReplace{&utils.ReSearchReplace{SearchRegexp: regexp.MustCompile(`([A-Za-z]*).+`), ReplaceTemplate: "$1"}}}, &utils.RSRField{Id: "write_codec"}} - fsCdr, _ := NewFSCdr(body) + fsCdr, _ := NewFSCdr(body, fsCdrCfg) extraFields := fsCdr.getExtraFields() if len(extraFields) != 3 { t.Error("Error parsing extra fields: ", extraFields) @@ -146,14 +143,14 @@ func TestDDazRSRExtraFields(t *testing.T) { } }`) var err error - cfg, err = config.NewCGRConfigFromJsonString(eFieldsCfg) + fsCdrCfg, err = config.NewCGRConfigFromJsonString(eFieldsCfg) if err != nil { t.Error("Could not parse the config", err.Error()) - } else if !reflect.DeepEqual(cfg.CDRSExtraFields, []*utils.RSRField{&utils.RSRField{Id: "effective_caller_id_number", + } else if !reflect.DeepEqual(fsCdrCfg.CDRSExtraFields, []*utils.RSRField{&utils.RSRField{Id: "effective_caller_id_number", RSRules: []*utils.ReSearchReplace{&utils.ReSearchReplace{SearchRegexp: regexp.MustCompile(`(\d+)`), ReplaceTemplate: "+$1"}}}}) { - t.Errorf("Unexpected value for config CdrsExtraFields: %v", cfg.CDRSExtraFields) + t.Errorf("Unexpected value for config CdrsExtraFields: %v", fsCdrCfg.CDRSExtraFields) } - fsCdr, err := NewFSCdr(simpleJsonCdr) + fsCdr, err := NewFSCdr(simpleJsonCdr, fsCdrCfg) if err != nil { t.Error("Could not parse cdr", err.Error()) } diff --git a/engine/mediator.go b/engine/mediator.go index 7010c75c6..dfcc6bff3 100644 --- a/engine/mediator.go +++ b/engine/mediator.go @@ -18,15 +18,7 @@ along with this program. If not, see package engine -import ( - "errors" - "fmt" - "time" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/utils" -) - +/* func NewMediator(connector Connector, logDb LogStorage, cdrDb CdrStorage, st StatsInterface, cfg *config.CGRConfig) (m *Mediator, err error) { m = &Mediator{ connector: connector, @@ -49,169 +41,4 @@ func NewMediator(connector Connector, logDb LogStorage, cdrDb CdrStorage, st Sta } return m, nil } - -type Mediator struct { - connector Connector - logDb LogStorage - cdrDb CdrStorage - stats StatsInterface - cgrCfg *config.CGRConfig -} - -// Retrive the cost from logging database, nil in case of no log -func (self *Mediator) getCostsFromDB(cgrid, runId string) (cc *CallCost, err error) { - for i := 0; i < 3; i++ { // Mechanism to avoid concurrency between SessionManager writing the costs and mediator picking them up - cc, err = self.logDb.GetCallCostLog(cgrid, SESSION_MANAGER_SOURCE, runId) - if cc != nil { - break - } - time.Sleep(time.Duration(i) * time.Second) - } - return -} - -// Retrive the cost from engine -func (self *Mediator) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error) { - cc := &CallCost{} - var err error - if storedCdr.Usage == time.Duration(0) { // failed call, returning empty callcost, no error - return cc, nil - } - cd := CallDescriptor{ - TOR: storedCdr.TOR, - Direction: storedCdr.Direction, - Tenant: storedCdr.Tenant, - Category: storedCdr.Category, - Subject: storedCdr.Subject, - Account: storedCdr.Account, - Destination: storedCdr.Destination, - TimeStart: storedCdr.AnswerTime, - TimeEnd: storedCdr.AnswerTime.Add(storedCdr.Usage), - DurationIndex: storedCdr.Usage, - } - if utils.IsSliceMember([]string{utils.META_PSEUDOPREPAID, utils.META_POSTPAID, utils.PSEUDOPREPAID, utils.POSTPAID}, storedCdr.ReqType) { - err = self.connector.Debit(cd, cc) - } else { - err = self.connector.GetCost(cd, cc) - } - if err != nil { - self.logDb.LogError(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, err.Error()) - } else { - // If the mediator calculated a price it will write it to logdb - self.logDb.LogCallCost(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, cc) - } - return cc, err -} - -func (self *Mediator) rateCDR(storedCdr *StoredCdr) error { - var qryCC *CallCost - var errCost error - if utils.IsSliceMember([]string{utils.META_PREPAID, utils.PREPAID}, storedCdr.ReqType) { // ToDo: Get rid of PREPAID as soon as we don't want to support it backwards - // Should be previously calculated and stored in DB - qryCC, errCost = self.getCostsFromDB(storedCdr.CgrId, storedCdr.MediationRunId) - } else { - qryCC, errCost = self.getCostFromRater(storedCdr) - } - if errCost != nil { - return errCost - } else if qryCC == nil { - return errors.New("No cost returned from rater") - } - storedCdr.Cost = qryCC.Cost - return nil -} - -func (self *Mediator) RateCdr(storedCdr *StoredCdr, sendToStats bool) error { - storedCdr.MediationRunId = utils.META_DEFAULT - cdrRuns := []*StoredCdr{storedCdr} // Start with initial storCdr, will add here all to be mediated - attrsDC := utils.AttrDerivedChargers{Tenant: storedCdr.Tenant, Category: storedCdr.Category, Direction: storedCdr.Direction, - Account: storedCdr.Account, Subject: storedCdr.Subject} - var dcs utils.DerivedChargers - if err := self.connector.GetDerivedChargers(attrsDC, &dcs); err != nil { - errText := fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", storedCdr.CgrId, err.Error()) - Logger.Err(errText) - return errors.New(errText) - } - for _, dc := range dcs { - runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP) - matchingAllFilters := true - for _, dcRunFilter := range runFilters { - if fltrPass, _ := storedCdr.PassesFieldFilter(dcRunFilter); !fltrPass { - matchingAllFilters = false - break - } - } - if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched - continue - } - dcReqTypeFld, _ := utils.NewRSRField(dc.ReqTypeField) - dcDirFld, _ := utils.NewRSRField(dc.DirectionField) - dcTenantFld, _ := utils.NewRSRField(dc.TenantField) - dcCategoryFld, _ := utils.NewRSRField(dc.CategoryField) - dcAcntFld, _ := utils.NewRSRField(dc.AccountField) - dcSubjFld, _ := utils.NewRSRField(dc.SubjectField) - dcDstFld, _ := utils.NewRSRField(dc.DestinationField) - dcSTimeFld, _ := utils.NewRSRField(dc.SetupTimeField) - dcATimeFld, _ := utils.NewRSRField(dc.AnswerTimeField) - dcDurFld, _ := utils.NewRSRField(dc.UsageField) - forkedCdr, err := storedCdr.ForkCdr(dc.RunId, dcReqTypeFld, dcDirFld, dcTenantFld, dcCategoryFld, dcAcntFld, dcSubjFld, dcDstFld, dcSTimeFld, dcATimeFld, dcDurFld, - []*utils.RSRField{}, true) - if err != nil { // Errors on fork, cannot calculate further, write that into db for later analysis - self.cdrDb.SetRatedCdr(&StoredCdr{CgrId: storedCdr.CgrId, CdrSource: utils.FORKED_CDR, MediationRunId: dc.RunId, Cost: -1}, - err.Error()) // Cannot fork CDR, important just runid and error - continue - } - cdrRuns = append(cdrRuns, forkedCdr) - } - for _, cdr := range cdrRuns { - extraInfo := "" - if cdr.MediationRunId != utils.META_DEFAULT || !cdr.Rated { // Do not rate calls which are out of default run and marked as rated already, eg premium SMSes - if err := self.rateCDR(cdr); err != nil { - cdr.Cost = -1.0 // If there was an error, mark the CDR as it is - extraInfo = err.Error() - } - } - if !self.cgrCfg.MediatorStoreDisable { - if err := self.cdrDb.SetRatedCdr(cdr, extraInfo); err != nil { - Logger.Err(fmt.Sprintf(" Could not record cost for cgrid: <%s>, ERROR: <%s>, cost: %f, extraInfo: %s", - cdr.CgrId, err.Error(), cdr.Cost, extraInfo)) - } - } - if sendToStats && self.stats != nil { // We send to stats only after saving to db since there are chances we cannot store and then no way to reproduce stats offline - go func(cdr *StoredCdr) { // Pass it by value since the variable will be overwritten by for - if err := self.stats.AppendCDR(cdr, nil); err != nil { - Logger.Err(fmt.Sprintf(" Could not append cdr to stats: %s", err.Error())) - } - }(cdr) - } - if cfg.MediCdrReplication != nil { - replicateCdr(cdr, cfg.MediCdrReplication) - } - } - return nil -} - -func (self *Mediator) RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects []string, - orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated, sendToStats bool) error { - var costStart, costEnd *float64 - if rerateErrors { - costStart = utils.Float64Pointer(-1.0) - if !rerateRated { - costEnd = utils.Float64Pointer(0.0) - } - } else if rerateRated { - costStart = utils.Float64Pointer(0.0) - } - cdrs, _, err := self.cdrDb.GetStoredCdrs(&utils.CdrsFilter{CgrIds: cgrIds, RunIds: runIds, Tors: tors, CdrHosts: cdrHosts, CdrSources: cdrSources, ReqTypes: reqTypes, Directions: directions, - Tenants: tenants, Categories: categories, Accounts: accounts, Subjects: subjects, DestPrefixes: destPrefixes, RatedAccounts: ratedAccounts, RatedSubjects: ratedSubjects, - OrderIdStart: orderIdStart, OrderIdEnd: orderIdEnd, AnswerTimeStart: &timeStart, AnswerTimeEnd: &timeEnd, CostStart: costStart, CostEnd: costEnd, IgnoreDerived: true}) - if err != nil { - return err - } - for _, cdr := range cdrs { - if err := self.RateCdr(cdr, sendToStats); err != nil { - return err - } - } - return nil -} +*/ diff --git a/engine/mediator_local_test.go b/engine/mediator_local_test.go index 54bab0d6f..dda92f0d0 100644 --- a/engine/mediator_local_test.go +++ b/engine/mediator_local_test.go @@ -18,6 +18,7 @@ along with this program. If not, see package engine +/* import ( "flag" "fmt" @@ -34,19 +35,7 @@ import ( "github.com/cgrates/cgrates/utils" ) -/* -README: - Enable local tests by passing '-local' to the go test command - It is expected that the data folder of CGRateS exists at path /usr/share/cgrates/data or passed via command arguments. - Prior running the tests, create database and users by running: - mysql -pyourrootpwd < /usr/share/cgrates/data/storage/mysql/create_db_with_users.sql - What these tests do: - * Flush tables in storDb to start clean. - * Start engine with default configuration and give it some time to listen (here caching can slow down, hence the command argument parameter). - * Connect rpc client depending on encoding defined in configuration. - * Execute remote Apis and test their replies(follow prepaid1cent scenario so we can test load in dataDb also). -*/ var cgrCfg *config.CGRConfig var cgrRpc *rpc.Client @@ -57,7 +46,7 @@ var storDbType = flag.String("stordb_type", utils.MYSQL, "The type of the storDb var startDelay = flag.Int("delay_start", 300, "Number of miliseconds to it for rater to start and cache") var cfgPath = path.Join(*dataDir, "conf", "samples", "mediator1") -func TestMediInitRatingDb(t *testing.T) { +func TestMediInitConfig(t *testing.T) { if !*testLocal { return } @@ -66,12 +55,14 @@ func TestMediInitRatingDb(t *testing.T) { if err != nil { t.Fatal("Got config error: ", err.Error()) } - ratingDb, err := ConfigureRatingStorage(cgrCfg.RatingDBType, cgrCfg.RatingDBHost, cgrCfg.RatingDBPort, cgrCfg.RatingDBName, cgrCfg.RatingDBUser, cgrCfg.RatingDBPass, cgrCfg.DBDataEncoding) - if err != nil { - t.Fatal("Cannot connect to dataDb", err) +} + +func TestMediInitDataDb(t *testing.T) { + if !*testLocal { + return } - if err := ratingDb.Flush(""); err != nil { - t.Fatal("Cannot reset dataDb", err) + if err := InitDataDb(cgrCfg); err != nil { + t.Fatal(err) } } @@ -107,18 +98,9 @@ func TestMediStartEngine(t *testing.T) { if !*testLocal { return } - enginePath, err := exec.LookPath("cgr-engine") - if err != nil { - t.Fatal("Cannot find cgr-engine executable") + if _, err := StartEngine(cfgPath, *startDelay); err != nil { + t.Fatal(err) } - exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it - time.Sleep(time.Duration(*startDelay) * time.Millisecond) - engine := exec.Command(enginePath, "-config_dir", cfgPath) - if err := engine.Start(); err != nil { - t.Fatal("Cannot start cgr-engine: ", err.Error()) - } - - time.Sleep(time.Duration(*startDelay) * time.Millisecond) // Give time to rater to fire up httpClient = new(http.Client) } @@ -142,13 +124,13 @@ func TestMediPostCdrs(t *testing.T) { cdrForm1 := url.Values{utils.TOR: []string{utils.VOICE}, utils.ACCID: []string{"dsafdsaf"}, utils.CDRHOST: []string{"192.168.1.1"}, utils.REQTYPE: []string{utils.META_RATED}, utils.DIRECTION: []string{"*out"}, utils.TENANT: []string{"cgrates.org"}, utils.CATEGORY: []string{"call"}, utils.ACCOUNT: []string{"2001"}, utils.SUBJECT: []string{"2001"}, utils.DESTINATION: []string{"+4986517174963"}, - utils.ANSWER_TIME: []string{"2013-11-07T08:42:26Z"}, utils.USAGE: []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} + utils.ANSWER_TIME: []string{"2014-11-07T08:42:26Z"}, utils.USAGE: []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} cdrForm2 := url.Values{utils.TOR: []string{utils.VOICE}, utils.ACCID: []string{"adsafdsaf"}, utils.CDRHOST: []string{"192.168.1.1"}, utils.REQTYPE: []string{utils.META_RATED}, utils.DIRECTION: []string{"*out"}, utils.TENANT: []string{"itsyscom.com"}, utils.CATEGORY: []string{"call"}, utils.ACCOUNT: []string{"1003"}, utils.SUBJECT: []string{"1003"}, utils.DESTINATION: []string{"+4986517174964"}, - utils.ANSWER_TIME: []string{"2013-11-07T08:42:26Z"}, utils.USAGE: []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} + utils.ANSWER_TIME: []string{"2014-11-07T08:42:26Z"}, utils.USAGE: []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} cdrFormData1 := url.Values{utils.TOR: []string{utils.DATA}, utils.ACCID: []string{"616350843"}, utils.CDRHOST: []string{"192.168.1.1"}, utils.REQTYPE: []string{utils.META_RATED}, utils.DIRECTION: []string{"*out"}, utils.TENANT: []string{"cgrates.org"}, utils.CATEGORY: []string{"data"}, - utils.ACCOUNT: []string{"1010"}, utils.SUBJECT: []string{"1010"}, utils.ANSWER_TIME: []string{"2013-11-07T08:42:26Z"}, + utils.ACCOUNT: []string{"1010"}, utils.SUBJECT: []string{"1010"}, utils.ANSWER_TIME: []string{"2014-11-07T08:42:26Z"}, utils.USAGE: []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} for _, cdrForm := range []url.Values{cdrForm1, cdrForm2, cdrFormData1} { cdrForm.Set(utils.CDRSOURCE, TEST_SQL) @@ -174,12 +156,12 @@ func TestMediInjectCdrs(t *testing.T) { if !*testLocal { return } - cgrCdr1 := CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "aaaaadsafdsaf", "cdrsource": TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: utils.META_RATED, utils.DIRECTION: "*out", + cgrCdr1 := CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "aaaaadsafdsaf", "cdrsource": "TEST_INJECT", utils.CDRHOST: "192.168.1.1", utils.REQTYPE: utils.META_RATED, utils.DIRECTION: "*out", utils.TENANT: "cgrates.org", utils.CATEGORY: "call", utils.ACCOUNT: "dan", utils.SUBJECT: "dan", utils.DESTINATION: "+4986517174963", - utils.ANSWER_TIME: "2013-11-07T08:42:26Z", utils.USAGE: "10"} - cgrCdr2 := CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "baaaadsafdsaf", "cdrsource": TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: utils.META_RATED, utils.DIRECTION: "*out", + utils.ANSWER_TIME: "2014-11-07T08:42:26Z", utils.USAGE: "10"} + cgrCdr2 := CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "baaaadsafdsaf", "cdrsource": "TEST_INJECT", utils.CDRHOST: "192.168.1.1", utils.REQTYPE: utils.META_RATED, utils.DIRECTION: "*out", utils.TENANT: "cgrates.org", utils.CATEGORY: "call", utils.ACCOUNT: "dan", utils.SUBJECT: "dan", utils.DESTINATION: "+4986517173964", - utils.ANSWER_TIME: "2013-11-07T09:42:26Z", utils.USAGE: "20"} + utils.ANSWER_TIME: "2014-11-07T09:42:26Z", utils.USAGE: "20"} for _, cdr := range []CgrCdr{cgrCdr1, cgrCdr2} { if err := cdrStor.SetCdr(cdr.AsStoredCdr()); err != nil { t.Error(err) @@ -204,7 +186,7 @@ func TestMediLoadTariffPlanFromFolder(t *testing.T) { } reply := "" // Simple test that command is executed without errors - attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "prepaid1centpsec")} + attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")} if err := cgrRpc.Call("ApierV1.LoadTariffPlanFromFolder", attrs, &reply); err != nil { t.Error("Got error on ApierV1.LoadTariffPlanFromFolder: ", err.Error()) } else if reply != utils.OK { @@ -212,12 +194,13 @@ func TestMediLoadTariffPlanFromFolder(t *testing.T) { } } +/* func TestMediRateCdrs(t *testing.T) { if !*testLocal { return } var reply string - if err := cgrRpc.Call("MediatorV1.RateCdrs", utils.AttrRateCdrs{}, &reply); err != nil { + if err := cgrRpc.Call("CdrsV1.RateCdrs", utils.AttrRateCdrs{}, &reply); err != nil { t.Error(err.Error()) } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) @@ -232,7 +215,7 @@ func TestMediRateCdrs(t *testing.T) { } else if len(errRatedCdrs) != 1 { t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) } - if err := cgrRpc.Call("MediatorV1.RateCdrs", utils.AttrRateCdrs{RerateErrors: true}, &reply); err != nil { + if err := cgrRpc.Call("CdrsV1.RateCdrs", utils.AttrRateCdrs{RerateErrors: true}, &reply); err != nil { t.Error(err.Error()) } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) @@ -244,41 +227,6 @@ func TestMediRateCdrs(t *testing.T) { } } -/* -func TestMediatePseudoprepaid(t *testing.T) { - if !*testLocal { - return - } - var reply *engine.Account - attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1003", Direction: "*out"} - if err := cgrRpc.Call("ApierV1.GetAccount", attrs, &reply); err != nil { - t.Error("Got error on ApierV1.GetAccount: ", err.Error()) - } else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 11 { - t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue()) - } - voiceCdr := &utils.StoredCdr{TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test", ReqType: utils.META_PSEUDOPREPAID, Direction: utils.OUT, - Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "+4986517174963", - SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), - Usage: time.Duration(5) * time.Second} - dataCdr := &utils.StoredCdr{TOR: utils.DATA, AccId: "6163508432", CdrHost: "192.168.1.1", CdrSource: "test", ReqType: utils.META_PSEUDOPREPAID, Direction: utils.OUT, - Tenant: "cgrates.org", Category: "data", Account: "1003", Subject: "1003", - SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), - Usage: time.Duration(10) * time.Second} - for _, cdrForm := range []url.Values{voiceCdr.AsHttpForm(), dataCdr.AsHttpForm()} { - cdrForm.Set(utils.CDRSOURCE, engine.TEST_SQL) - if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cdr_post", cfg.HTTPListen), cdrForm); err != nil { - t.Error(err.Error()) - } - } - time.Sleep(time.Duration(*startDelay) * time.Millisecond) // Give time for debits to happen - expectBalance := 5.998 - if err := cgrRpc.Call("ApierV1.GetAccount", attrs, &reply); err != nil { - t.Error("Got error on ApierV1.GetAccount: ", err.Error()) - } else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != expectBalance { // 5 from voice, 0.002 from DATA - t.Errorf("Calling ApierV1.GetBalance expected: %f, received: %f", expectBalance, reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue()) - } -} -*/ // Simply kill the engine after we are done with tests within this file func TestMediStopEngine(t *testing.T) { @@ -287,3 +235,4 @@ func TestMediStopEngine(t *testing.T) { } exec.Command("pkill", "cgr-engine").Run() } +*/ diff --git a/engine/responder.go b/engine/responder.go index 889109ed2..c95a68c83 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -43,7 +43,7 @@ type SessionRun struct { type Responder struct { Bal *balancer2go.Balancer ExitChan chan bool - CdrSrv *CDRS + CdrSrv *CdrServer } /* diff --git a/engine/stats.go b/engine/stats.go index f5f9b416f..512cd4646 100644 --- a/engine/stats.go +++ b/engine/stats.go @@ -21,10 +21,10 @@ package engine import ( "errors" "fmt" - "net/rpc" "sync" "github.com/cgrates/cgrates/utils" + "github.com/cgrates/rpcclient" ) type StatsInterface interface { @@ -173,12 +173,11 @@ func (s *Stats) AppendCDR(cdr *StoredCdr, out *int) error { } type ProxyStats struct { - Client *rpc.Client + Client *rpcclient.RpcClient } -func NewProxyStats(addr string) (*ProxyStats, error) { - client, err := rpc.Dial("tcp", addr) - +func NewProxyStats(addr string, reconnects int) (*ProxyStats, error) { + client, err := rpcclient.NewRpcClient("tcp", addr, reconnects, utils.GOB) if err != nil { return nil, err } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 08f052df5..b389436a7 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -113,7 +113,7 @@ type AccountingStorage interface { type CdrStorage interface { Storage SetCdr(*StoredCdr) error - SetRatedCdr(*StoredCdr, string) error + SetRatedCdr(*StoredCdr) error GetStoredCdrs(*utils.CdrsFilter) ([]*StoredCdr, int64, error) RemStoredCdrs([]string) error } diff --git a/engine/storage_mysql.go b/engine/storage_mysql.go index 05b79c9c9..162614041 100644 --- a/engine/storage_mysql.go +++ b/engine/storage_mysql.go @@ -75,6 +75,9 @@ func (self *MySQLStorage) SetTPTiming(tm *utils.ApierTPTiming) error { } func (self *MySQLStorage) LogCallCost(cgrid, source, runid string, cc *CallCost) (err error) { + if cc == nil { + return nil + } tss, err := json.Marshal(cc.Timespans) if err != nil { Logger.Err(fmt.Sprintf("Error marshalling timespans to json: %v", err)) @@ -103,7 +106,7 @@ func (self *MySQLStorage) LogCallCost(cgrid, source, runid string, cc *CallCost) return nil } -func (self *MySQLStorage) SetRatedCdr(storedCdr *StoredCdr, extraInfo string) (err error) { +func (self *MySQLStorage) SetRatedCdr(storedCdr *StoredCdr) (err error) { _, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s (cgrid,runid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,`usage`,cost,extra_info,created_at) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s',%v,%f,'%s','%s') ON DUPLICATE KEY UPDATE reqtype=values(reqtype),direction=values(direction),tenant=values(tenant),category=values(category),account=values(account),subject=values(subject),destination=values(destination),setup_time=values(setup_time),answer_time=values(answer_time),`usage`=values(`usage`),cost=values(cost),extra_info=values(extra_info), updated_at='%s'", utils.TBL_RATED_CDRS, storedCdr.CgrId, @@ -119,7 +122,7 @@ func (self *MySQLStorage) SetRatedCdr(storedCdr *StoredCdr, extraInfo string) (e storedCdr.AnswerTime, storedCdr.Usage.Seconds(), storedCdr.Cost, - extraInfo, + storedCdr.ExtraInfo, time.Now().Format(time.RFC3339), time.Now().Format(time.RFC3339))) if err != nil { diff --git a/engine/storage_mysql_local_test.go b/engine/storage_mysql_local_test.go index ec00d2169..adc586e9b 100644 --- a/engine/storage_mysql_local_test.go +++ b/engine/storage_mysql_local_test.go @@ -505,7 +505,7 @@ func TestMySQLSetRatedCdr(t *testing.T) { strCdr3.CgrId = utils.Sha1(strCdr3.AccId, strCdr3.SetupTime.String()) for _, cdr := range []*StoredCdr{strCdr1, strCdr2, strCdr3} { - if err := mysqlDb.SetRatedCdr(cdr, ""); err != nil { + if err := mysqlDb.SetRatedCdr(cdr); err != nil { t.Error(err.Error()) } } diff --git a/engine/storage_postgres.go b/engine/storage_postgres.go index 324fd34eb..f6d357eaf 100644 --- a/engine/storage_postgres.go +++ b/engine/storage_postgres.go @@ -84,6 +84,9 @@ func (self *PostgresStorage) SetTPTiming(tm *utils.ApierTPTiming) error { } func (self *PostgresStorage) LogCallCost(cgrid, source, runid string, cc *CallCost) (err error) { + if cc == nil { + return nil + } tss, err := json.Marshal(cc.Timespans) if err != nil { Logger.Err(fmt.Sprintf("Error marshalling timespans to json: %v", err)) @@ -120,7 +123,7 @@ func (self *PostgresStorage) LogCallCost(cgrid, source, runid string, cc *CallCo return nil } -func (self *PostgresStorage) SetRatedCdr(cdr *StoredCdr, extraInfo string) (err error) { +func (self *PostgresStorage) SetRatedCdr(cdr *StoredCdr) (err error) { tx := self.db.Begin() saved := tx.Save(&TblRatedCdr{ Cgrid: cdr.CgrId, @@ -136,7 +139,7 @@ func (self *PostgresStorage) SetRatedCdr(cdr *StoredCdr, extraInfo string) (err AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Cost: cdr.Cost, - ExtraInfo: extraInfo, + ExtraInfo: cdr.ExtraInfo, CreatedAt: time.Now(), }) if saved.Error != nil { @@ -144,7 +147,7 @@ func (self *PostgresStorage) SetRatedCdr(cdr *StoredCdr, extraInfo string) (err tx = self.db.Begin() updated := tx.Model(TblRatedCdr{}).Where(&TblRatedCdr{Cgrid: cdr.CgrId, Runid: cdr.MediationRunId}).Updates(&TblRatedCdr{Reqtype: cdr.ReqType, Direction: cdr.Direction, Tenant: cdr.Tenant, Category: cdr.Category, Account: cdr.Account, Subject: cdr.Subject, Destination: cdr.Destination, - SetupTime: cdr.SetupTime, AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Cost: cdr.Cost, ExtraInfo: extraInfo, UpdatedAt: time.Now()}) + SetupTime: cdr.SetupTime, AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Cost: cdr.Cost, ExtraInfo: cdr.ExtraInfo, UpdatedAt: time.Now()}) if updated.Error != nil { tx.Rollback() return updated.Error diff --git a/engine/storage_psql_local_test.go b/engine/storage_psql_local_test.go index daf7cad99..a27145281 100644 --- a/engine/storage_psql_local_test.go +++ b/engine/storage_psql_local_test.go @@ -551,7 +551,7 @@ func TestPSQLSetRatedCdr(t *testing.T) { strCdr3.CgrId = utils.Sha1(strCdr3.AccId, strCdr3.SetupTime.String()) for _, cdr := range []*StoredCdr{strCdr1, strCdr2, strCdr3} { - if err := psqlDb.SetRatedCdr(cdr, ""); err != nil { + if err := psqlDb.SetRatedCdr(cdr); err != nil { t.Error(err.Error()) } } diff --git a/engine/storage_redis_local_test.go b/engine/storage_redis_local_test.go index 7f4793992..0b562d6a9 100644 --- a/engine/storage_redis_local_test.go +++ b/engine/storage_redis_local_test.go @@ -34,7 +34,7 @@ func TestConnectRedis(t *testing.T) { if !*testLocal { return } - cfg, _ = config.NewDefaultCGRConfig() + cfg, _ := config.NewDefaultCGRConfig() rds, err = NewRedisStorage(fmt.Sprintf("%s:%s", cfg.RatingDBHost, cfg.RatingDBPort), 4, cfg.RatingDBPass, cfg.DBDataEncoding) if err != nil { t.Fatal("Could not connect to Redis", err.Error()) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index c24bd1db3..f9b6e8664 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -752,7 +752,7 @@ func (self *SQLStorage) SetCdr(cdr *StoredCdr) error { return nil } -func (self *SQLStorage) SetRatedCdr(storedCdr *StoredCdr, extraInfo string) error { +func (self *SQLStorage) SetRatedCdr(storedCdr *StoredCdr) error { return errors.New(utils.ERR_NOT_IMPLEMENTED) } diff --git a/engine/storedcdr.go b/engine/storedcdr.go index a5923660c..5374dc29e 100644 --- a/engine/storedcdr.go +++ b/engine/storedcdr.go @@ -76,6 +76,7 @@ type StoredCdr struct { RatedAccount string // Populated out of rating data RatedSubject string Cost float64 + ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation CostDetails *CallCost // Attach the cost details to CDR when possible Rated bool // Mark the CDR as rated so we do not process it during mediation } diff --git a/general_tests/fsevcorelate_test.go b/general_tests/fsevcorelate_test.go index d56194b63..1a186777d 100644 --- a/general_tests/fsevcorelate_test.go +++ b/general_tests/fsevcorelate_test.go @@ -214,13 +214,12 @@ variable_sip_local_sdp_str: v%3D0%0Ao%3DFreeSWITCH%201396951687%201396951689%20I var jsonCdr = []byte(`{"core-uuid":"feef0b51-7fdf-4c4a-878e-aff233752de2","channel_data":{"state":"CS_REPORTING","direction":"inbound","state_number":"11","flags":"0=1;1=1;3=1;36=1;37=1;39=1;42=1;47=1;52=1;73=1;75=1;94=1","caps":"1=1;2=1;3=1;4=1;5=1;6=1"},"variables":{"direction":"inbound","uuid":"86cfd6e2-dbda-45a3-b59d-f683ec368e8b","session_id":"5","sip_from_user":"1001","sip_from_uri":"1001@192.168.56.74","sip_from_host":"192.168.56.74","channel_name":"sofia/internal/1001@192.168.56.74","sip_local_network_addr":"192.168.56.74","sip_network_ip":"192.168.56.1","sip_network_port":"5060","sip_received_ip":"192.168.56.1","sip_received_port":"5060","sip_via_protocol":"udp","sip_authorized":"true","Event-Name":"REQUEST_PARAMS","Core-UUID":"feef0b51-7fdf-4c4a-878e-aff233752de2","FreeSWITCH-Hostname":"CGRTest","FreeSWITCH-Switchname":"CGRTest","FreeSWITCH-IPv4":"192.168.178.32","FreeSWITCH-IPv6":"::1","Event-Date-Local":"2014-04-08 21:10:21","Event-Date-GMT":"Tue, 08 Apr 2014 19:10:21 GMT","Event-Date-Timestamp":"1396984221278217","Event-Calling-File":"sofia.c","Event-Calling-Function":"sofia_handle_sip_i_invite","Event-Calling-Line-Number":"8076","Event-Sequence":"1423","sip_number_alias":"1001","sip_auth_username":"1001","sip_auth_realm":"192.168.56.74","number_alias":"1001","requested_domain_name":"192.168.56.66","record_stereo":"true","default_gateway":"example.com","default_areacode":"918","transfer_fallback_extension":"operator","toll_allow":"domestic,international,local","accountcode":"1001","user_context":"default","effective_caller_id_name":"Extension 1001","effective_caller_id_number":"1001","outbound_caller_id_name":"FreeSWITCH","outbound_caller_id_number":"0000000000","callgroup":"techsupport","user_name":"1001","domain_name":"192.168.56.66","sip_from_user_stripped":"1001","sofia_profile_name":"internal","recovery_profile_name":"internal","sip_req_user":"1002","sip_req_uri":"1002@192.168.56.74","sip_req_host":"192.168.56.74","sip_to_user":"1002","sip_to_uri":"1002@192.168.56.74","sip_to_host":"192.168.56.74","sip_contact_params":"transport=udp;registering_acc=192_168_56_74","sip_contact_user":"1001","sip_contact_port":"5060","sip_contact_uri":"1001@192.168.56.1:5060","sip_contact_host":"192.168.56.1","sip_via_host":"192.168.56.1","sip_via_port":"5060","presence_id":"1001@192.168.56.74","ep_codec_string":"G722@8000h@20i@64000b,PCMU@8000h@20i@64000b,PCMA@8000h@20i@64000b,GSM@8000h@20i@13200b","cgr_notify":"+AUTH_OK","max_forwards":"69","transfer_history":"1396984221:caefc538-5da4-4245-8716-112c706383d8:bl_xfer:1002/default/XML","transfer_source":"1396984221:caefc538-5da4-4245-8716-112c706383d8:bl_xfer:1002/default/XML","DP_MATCH":"ARRAY::1002|:1002","call_uuid":"86cfd6e2-dbda-45a3-b59d-f683ec368e8b","RFC2822_DATE":"Tue, 08 Apr 2014 21:10:21 +0200","dialed_extension":"1002","export_vars":"RFC2822_DATE,RFC2822_DATE,dialed_extension","ringback":"%(2000,4000,440,480)","transfer_ringback":"local_stream://moh","call_timeout":"30","hangup_after_bridge":"true","continue_on_fail":"true","called_party_callgroup":"techsupport","current_application_data":"user/1002@192.168.56.66","current_application":"bridge","dialed_user":"1002","dialed_domain":"192.168.56.66","inherit_codec":"true","originated_legs":"ARRAY::402f0929-fa14-4a5f-9642-3a1311bb4ddd;Outbound Call;1002|:402f0929-fa14-4a5f-9642-3a1311bb4ddd;Outbound Call;1002","rtp_use_codec_string":"G722,PCMU,PCMA,GSM","sip_use_codec_name":"G722","sip_use_codec_rate":"8000","sip_use_codec_ptime":"20","write_codec":"G722","write_rate":"16000","video_possible":"true","local_media_ip":"192.168.56.74","local_media_port":"32534","advertised_media_ip":"192.168.56.74","sip_use_pt":"9","rtp_use_ssrc":"1431080133","zrtp_secure_media_confirmed_audio":"true","zrtp_sas1_string_audio":"j6ff","switch_m_sdp":"v=0\r\no=1002 0 0 IN IP4 192.168.56.1\r\ns=-\r\nc=IN IP4 192.168.56.1\r\nt=0 0\r\nm=audio 5020 RTP/AVP 9 0 8 3 101\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:3 GSM/8000\r\na=rtpmap:101 telephone-event/8000\r\n","read_codec":"G722","read_rate":"16000","endpoint_disposition":"ANSWER","originate_causes":"ARRAY::402f0929-fa14-4a5f-9642-3a1311bb4ddd;NONE|:402f0929-fa14-4a5f-9642-3a1311bb4ddd;NONE","originate_disposition":"SUCCESS","DIALSTATUS":"SUCCESS","last_bridge_to":"402f0929-fa14-4a5f-9642-3a1311bb4ddd","bridge_channel":"sofia/internal/sip:1002@192.168.56.1:5060","bridge_uuid":"402f0929-fa14-4a5f-9642-3a1311bb4ddd","signal_bond":"402f0929-fa14-4a5f-9642-3a1311bb4ddd","last_sent_callee_id_name":"Outbound Call","last_sent_callee_id_number":"1002","cgr_reqtype":"*prepaid","sip_reinvite_sdp":"v=0\r\no=1001 0 1 IN IP4 192.168.56.1\r\ns=-\r\nc=IN IP4 192.168.56.1\r\nt=0 0\r\nm=audio 5016 RTP/AVP 96 97 98 9 100 102 0 8 103 3 104 101\r\na=sendonly\r\na=rtpmap:96 opus/48000/2\r\na=fmtp:96 usedtx=1\r\na=rtpmap:97 SILK/24000\r\na=rtpmap:98 SILK/16000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:100 speex/32000\r\na=rtpmap:102 speex/16000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:103 iLBC/8000\r\na=rtpmap:3 GSM/8000\r\na=rtpmap:104 speex/8000\r\na=rtpmap:101 telephone-event/8000\r\na=extmap:1 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=zrtp-hash:1.10 722d57097aaabea2749ea8938472478f8d88645b23521fa5f8005a7a2bed8286\r\nm=video 0 RTP/AVP 105 99\r\n","switch_r_sdp":"v=0\r\no=1001 0 1 IN IP4 192.168.56.1\r\ns=-\r\nc=IN IP4 192.168.56.1\r\nt=0 0\r\nm=audio 5016 RTP/AVP 96 97 98 9 100 102 0 8 103 3 104 101\r\na=rtpmap:96 opus/48000/2\r\na=fmtp:96 usedtx=1\r\na=rtpmap:97 SILK/24000\r\na=rtpmap:98 SILK/16000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:100 speex/32000\r\na=rtpmap:102 speex/16000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:103 iLBC/8000\r\na=rtpmap:3 GSM/8000\r\na=rtpmap:104 speex/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendonly\r\na=extmap:1 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=zrtp-hash:1.10 722d57097aaabea2749ea8938472478f8d88645b23521fa5f8005a7a2bed8286\r\nm=video 0 RTP/AVP 105 99\r\n","r_sdp_audio_zrtp_hash":"1.10 722d57097aaabea2749ea8938472478f8d88645b23521fa5f8005a7a2bed8286","remote_media_ip":"192.168.56.1","remote_media_port":"5016","sip_audio_recv_pt":"9","dtmf_type":"rfc2833","sip_2833_send_payload":"101","sip_2833_recv_payload":"101","sip_local_sdp_str":"v=0\no=FreeSWITCH 1396951687 1396951690 IN IP4 192.168.56.74\ns=FreeSWITCH\nc=IN IP4 192.168.56.74\nt=0 0\nm=audio 32534 RTP/AVP 9 101\na=rtpmap:9 G722/8000\na=rtpmap:101 telephone-event/8000\na=fmtp:101 0-16\na=ptime:20\na=sendrecv\n","sip_to_tag":"rXc9vZpv9eFaF","sip_from_tag":"1afc7eca","sip_cseq":"3","sip_call_id":"6691dbf8ffdc02bdacee02bc305d5c71@0:0:0:0:0:0:0:0","sip_full_via":"SIP/2.0/UDP 192.168.56.1:5060;branch=z9hG4bK-323133-5d083abc0d3f327b9101586e71b5fce4","sip_from_display":"1001","sip_full_from":"\"1001\" ;tag=1afc7eca","sip_full_to":";tag=rXc9vZpv9eFaF","sip_term_status":"200","proto_specific_hangup_cause":"sip:200","sip_term_cause":"16","last_bridge_role":"originator","sip_user_agent":"Jitsi2.5.5065Linux","sip_hangup_disposition":"recv_bye","bridge_hangup_cause":"NORMAL_CLEARING","hangup_cause":"NORMAL_CLEARING","hangup_cause_q850":"16","digits_dialed":"none","start_stamp":"2014-04-08 21:10:21","profile_start_stamp":"2014-04-08 21:10:21","answer_stamp":"2014-04-08 21:10:27","bridge_stamp":"2014-04-08 21:10:27","hold_stamp":"2014-04-08 21:10:27","progress_stamp":"2014-04-08 21:10:21","progress_media_stamp":"2014-04-08 21:10:21","hold_events":"{{1396984227824182,1396984242247995}}","end_stamp":"2014-04-08 21:10:42","start_epoch":"1396984221","start_uepoch":"1396984221278217","profile_start_epoch":"1396984221","profile_start_uepoch":"1396984221377035","answer_epoch":"1396984227","answer_uepoch":"1396984227717006","bridge_epoch":"1396984227","bridge_uepoch":"1396984227737268","last_hold_epoch":"1396984227","last_hold_uepoch":"1396984227824167","hold_accum_seconds":"14","hold_accum_usec":"14423816","hold_accum_ms":"14423","resurrect_epoch":"0","resurrect_uepoch":"0","progress_epoch":"1396984221","progress_uepoch":"1396984221497331","progress_media_epoch":"1396984221","progress_media_uepoch":"1396984221517042","end_epoch":"1396984242","end_uepoch":"1396984242257026","last_app":"bridge","last_arg":"user/1002@192.168.56.66","caller_id":"\"1001\" <1001>","duration":"21","billsec":"15","progresssec":"0","answersec":"6","waitsec":"6","progress_mediasec":"0","flow_billsec":"21","mduration":"20979","billmsec":"14540","progressmsec":"219","answermsec":"6439","waitmsec":"6459","progress_mediamsec":"239","flow_billmsec":"20979","uduration":"20978809","billusec":"14540020","progressusec":"219114","answerusec":"6438789","waitusec":"6459051","progress_mediausec":"238825","flow_billusec":"20978809","rtp_audio_in_raw_bytes":"181360","rtp_audio_in_media_bytes":"180304","rtp_audio_in_packet_count":"1031","rtp_audio_in_media_packet_count":"1025","rtp_audio_in_skip_packet_count":"45","rtp_audio_in_jb_packet_count":"0","rtp_audio_in_dtmf_packet_count":"0","rtp_audio_in_cng_packet_count":"0","rtp_audio_in_flush_packet_count":"6","rtp_audio_in_largest_jb_size":"0","rtp_audio_out_raw_bytes":"165780","rtp_audio_out_media_bytes":"165780","rtp_audio_out_packet_count":"942","rtp_audio_out_media_packet_count":"942","rtp_audio_out_skip_packet_count":"0","rtp_audio_out_dtmf_packet_count":"0","rtp_audio_out_cng_packet_count":"0","rtp_audio_rtcp_packet_count":"0","rtp_audio_rtcp_octet_count":"0"},"app_log":{"applications":[{"app_name":"hash","app_data":"insert/192.168.56.66-spymap/1001/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial/1001/1002"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial/global/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"export","app_data":"RFC2822_DATE=Tue, 08 Apr 2014 21:10:21 +0200"},{"app_name":"park","app_data":""},{"app_name":"hash","app_data":"insert/192.168.56.66-spymap/1001/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial/1001/1002"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial/global/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"export","app_data":"RFC2822_DATE=Tue, 08 Apr 2014 21:10:21 +0200"},{"app_name":"export","app_data":"dialed_extension=1002"},{"app_name":"bind_meta_app","app_data":"1 b s execute_extension::dx XML features"},{"app_name":"bind_meta_app","app_data":"2 b s record_session::/var/lib/freeswitch/recordings/1001.2014-04-08-21-10-21.wav"},{"app_name":"bind_meta_app","app_data":"3 b s execute_extension::cf XML features"},{"app_name":"bind_meta_app","app_data":"4 b s execute_extension::att_xfer XML features"},{"app_name":"set","app_data":"ringback=%(2000,4000,440,480)"},{"app_name":"set","app_data":"transfer_ringback=local_stream://moh"},{"app_name":"set","app_data":"call_timeout=30"},{"app_name":"set","app_data":"hangup_after_bridge=true"},{"app_name":"set","app_data":"continue_on_fail=true"},{"app_name":"hash","app_data":"insert/192.168.56.66-call_return/1002/1001"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial_ext/1002/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"set","app_data":"called_party_callgroup=techsupport"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial_ext/techsupport/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial_ext/global/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"hash","app_data":"insert/192.168.56.66-last_dial/techsupport/86cfd6e2-dbda-45a3-b59d-f683ec368e8b"},{"app_name":"bridge","app_data":"user/1002@192.168.56.66"}]},"callflow":{"dialplan":"XML","profile_index":"2","extension":{"name":"global","number":"1002","applications":[{"app_name":"hash","app_data":"insert/${domain_name}-spymap/${caller_id_number}/${uuid}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial/${caller_id_number}/${destination_number}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial/global/${uuid}"},{"app_name":"export","app_data":"RFC2822_DATE=${strftime(%a, %d %b %Y %T %z)}"},{"app_name":"export","app_data":"dialed_extension=1002"},{"app_name":"bind_meta_app","app_data":"1 b s execute_extension::dx XML features"},{"app_name":"bind_meta_app","app_data":"2 b s record_session::/var/lib/freeswitch/recordings/${caller_id_number}.${strftime(%Y-%m-%d-%H-%M-%S)}.wav"},{"app_name":"bind_meta_app","app_data":"3 b s execute_extension::cf XML features"},{"app_name":"bind_meta_app","app_data":"4 b s execute_extension::att_xfer XML features"},{"app_name":"set","app_data":"ringback=${us-ring}"},{"app_name":"set","app_data":"transfer_ringback=local_stream://moh"},{"app_name":"set","app_data":"call_timeout=30"},{"app_name":"set","app_data":"hangup_after_bridge=true"},{"app_name":"set","app_data":"continue_on_fail=true"},{"app_name":"hash","app_data":"insert/${domain_name}-call_return/${dialed_extension}/${caller_id_number}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial_ext/${dialed_extension}/${uuid}"},{"app_name":"set","app_data":"called_party_callgroup=${user_data(${dialed_extension}@${domain_name} var callgroup)}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial_ext/${called_party_callgroup}/${uuid}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial_ext/global/${uuid}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial/${called_party_callgroup}/${uuid}"},{"app_name":"bridge","app_data":"user/${dialed_extension}@${domain_name}"},{"last_executed":"true","app_name":"answer","app_data":""},{"app_name":"sleep","app_data":"1000"},{"app_name":"bridge","app_data":"loopback/app=voicemail:default ${domain_name} ${dialed_extension}"}],"current_app":"answer"},"caller_profile":{"username":"1001","dialplan":"XML","caller_id_name":"1001","ani":"1001","aniii":"","caller_id_number":"1001","network_addr":"192.168.56.1","rdnis":"1002","destination_number":"1002","uuid":"86cfd6e2-dbda-45a3-b59d-f683ec368e8b","source":"mod_sofia","context":"default","chan_name":"sofia/internal/1001@192.168.56.74","originatee":{"originatee_caller_profiles":[{"username":"1001","dialplan":"XML","caller_id_name":"Extension 1001","ani":"1001","aniii":"","caller_id_number":"1001","network_addr":"192.168.56.1","rdnis":"1002","destination_number":"1002","uuid":"402f0929-fa14-4a5f-9642-3a1311bb4ddd","source":"mod_sofia","context":"default","chan_name":"sofia/internal/sip:1002@192.168.56.1:5060"},{"username":"1001","dialplan":"XML","caller_id_name":"Extension 1001","ani":"1001","aniii":"","caller_id_number":"1001","network_addr":"192.168.56.1","rdnis":"1002","destination_number":"1002","uuid":"402f0929-fa14-4a5f-9642-3a1311bb4ddd","source":"mod_sofia","context":"default","chan_name":"sofia/internal/sip:1002@192.168.56.1:5060"}]}},"times":{"created_time":"1396984221278217","profile_created_time":"1396984221377035","progress_time":"1396984221497331","progress_media_time":"1396984221517042","answered_time":"1396984227717006","hangup_time":"1396984242257026","resurrect_time":"0","transfer_time":"0"}},"callflow":{"dialplan":"XML","profile_index":"1","extension":{"name":"global","number":"1002","applications":[{"app_name":"hash","app_data":"insert/${domain_name}-spymap/${caller_id_number}/${uuid}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial/${caller_id_number}/${destination_number}"},{"app_name":"hash","app_data":"insert/${domain_name}-last_dial/global/${uuid}"},{"app_name":"export","app_data":"RFC2822_DATE=${strftime(%a, %d %b %Y %T %z)}"},{"app_name":"park","app_data":""}]},"caller_profile":{"username":"1001","dialplan":"XML","caller_id_name":"1001","ani":"1001","aniii":"","caller_id_number":"1001","network_addr":"192.168.56.1","rdnis":"","destination_number":"1002","uuid":"86cfd6e2-dbda-45a3-b59d-f683ec368e8b","source":"mod_sofia","context":"default","chan_name":"sofia/internal/1001@192.168.56.74"},"times":{"created_time":"1396984221278217","profile_created_time":"1396984221278217","progress_time":"0","progress_media_time":"0","answered_time":"0","hangup_time":"0","resurrect_time":"0","transfer_time":"1396984221377035"}}}`) func TestEvCorelate(t *testing.T) { - cfg, _ := config.NewDefaultCGRConfig() - engine.NewCdrS(nil, nil, nil, cfg) // So we can set the package cfg answerEv := new(sessionmanager.FSEvent).AsEvent(answerEvent) if answerEv.GetName() != "CHANNEL_ANSWER" { t.Error("Event not parsed correctly: ", answerEv) } - cdrEv, err := engine.NewFSCdr(jsonCdr) + cfg, _ := config.NewDefaultCGRConfig() + cdrEv, err := engine.NewFSCdr(jsonCdr, cfg) if err != nil { t.Errorf("Error loading cdr: %v", err.Error()) } else if cdrEv.AsStoredCdr().AccId != "86cfd6e2-dbda-45a3-b59d-f683ec368e8b" { diff --git a/general_tests/tutorial_fs_calls_test.go b/general_tests/tutorial_fs_calls_test.go index 4c8da0b63..49aa2cb45 100644 --- a/general_tests/tutorial_fs_calls_test.go +++ b/general_tests/tutorial_fs_calls_test.go @@ -179,8 +179,8 @@ func TestTutFsCallsCdrs1001(t *testing.T) { if reply[0].Usage != "67" { // Usage as seconds t.Errorf("Unexpected Usage for CDR: %+v", reply[0]) } - if reply[0].Cost != 0.0159 { - t.Errorf("Unexpected Cost for CDR: %+v", reply[0]) + if reply[0].Cost != 0.0218 { + t.Errorf("Unexpected Cost for CDR: %+v", reply[0].Cost) } } req = utils.RpcCdrsFilter{Accounts: []string{"1001"}, RunIds: []string{"derived_run1"}} @@ -195,7 +195,7 @@ func TestTutFsCallsCdrs1001(t *testing.T) { if reply[0].Subject != "1002" { t.Errorf("Unexpected Subject for CDR: %+v", reply[0]) } - if reply[0].Cost != 1.2059 { + if reply[0].Cost != 1.2334 { t.Errorf("Unexpected Cost for CDR: %+v", reply[0].Cost) } } diff --git a/sessionmanager/fsevent.go b/sessionmanager/fsevent.go index 47afd52f8..114c39ff6 100644 --- a/sessionmanager/fsevent.go +++ b/sessionmanager/fsevent.go @@ -164,6 +164,9 @@ func (fsev FSEvent) GetReqType(fieldName string) string { return utils.FirstNonEmpty(fsev[fieldName], fsev[REQTYPE], config.CgrConfig().DefaultReqType) } func (fsev FSEvent) MissingParameter() bool { + fmt.Printf("Uuid: <%s>, Direction: <%s>, Account: <%s>, Subject: <%s>, Destination: <%s>, Category: <%s>, Tenant: <%s>, CallDestNr: %s\n", fsev.GetUUID(), + fsev.GetDirection(utils.META_DEFAULT), fsev.GetAccount(utils.META_DEFAULT), fsev.GetSubject(utils.META_DEFAULT), fsev.GetDestination(utils.META_DEFAULT), + fsev.GetCategory(utils.META_DEFAULT), fsev.GetTenant(utils.META_DEFAULT), fsev.GetCallDestNr(utils.META_DEFAULT)) return strings.TrimSpace(fsev.GetDirection(utils.META_DEFAULT)) == "" || strings.TrimSpace(fsev.GetSubject(utils.META_DEFAULT)) == "" || strings.TrimSpace(fsev.GetAccount(utils.META_DEFAULT)) == "" || diff --git a/utils/consts.go b/utils/consts.go index b71fdc159..9e6f20c26 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -165,6 +165,7 @@ const ( LOG_MEDIATED_CDR = "mcd_" SESSION_MANAGER_SOURCE = "SMR" MEDIATOR_SOURCE = "MED" + CDRS_SOURCE = "CDRS" SCHED_SOURCE = "SCH" RATER_SOURCE = "RAT" CREATE_CDRS_TABLES_SQL = "create_cdrs_tables.sql"