diff --git a/README.md b/README.md index 215409f56..845d9cd8d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,6 @@ PDF, Epub, Manpage http://readthedocs.org/projects/cgrates/downloads/ API reference [godoc](http://godoc.org/github.com/cgrates/cgrates/apier) -Also check irc.freenode.net#cgrates and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support. +Also check [irc.freenode.net #cgrates](irc://irc.freenode.net:6667/cgrates) ([Webchat](http://webchat.freenode.net?randomnick=1&channels=%23cgrates)) and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support. [![Analytics](https://ga-beacon.appspot.com/UA-10073547-11/cgrates/readme)](https://github.com/igrigorik/ga-beacon) diff --git a/apier/apier.go b/apier/apier.go index 51fe0db8b..84d710377 100644 --- a/apier/apier.go +++ b/apier/apier.go @@ -55,6 +55,12 @@ func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) err return nil } +type AttrSetDestination struct { //ToDo + Id string + Prefixes []string + Overwrite bool +} + func (self *ApierV1) GetRatingPlan(rplnId string, reply *engine.RatingPlan) error { if rpln, err := self.RatingDb.GetRatingPlan(rplnId, false); err != nil { return errors.New(utils.ERR_NOT_FOUND) @@ -77,20 +83,21 @@ func (self *ApierV1) GetAccount(attr *utils.AttrGetAccount, reply *engine.Accoun } type AttrAddBalance struct { - Tenant string - Account string - BalanceType string - Direction string - Value float64 - ExpirationDate string - RatingSubject string - DestinationId string - Weight float64 - Overwrite bool // When true it will reset if the balance is already there + Tenant string + Account string + BalanceType string + Direction string + Value float64 + ExpiryTime string + RatingSubject string + DestinationId string + Weight float64 + SharedGroup string + Overwrite bool // When true it will reset if the balance is already there } func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { - expTime, err := utils.ParseDate(attr.ExpirationDate) + expTime, err := utils.ParseDate(attr.ExpiryTime) if err != nil { *reply = err.Error() return err @@ -131,6 +138,7 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { RatingSubject: attr.RatingSubject, DestinationId: attr.DestinationId, Weight: attr.Weight, + SharedGroup: attr.SharedGroup, }, }, }) @@ -419,17 +427,19 @@ func (self *ApierV1) SetActionPlan(attrs AttrSetActionPlan, reply *string) error } type AttrAddActionTrigger struct { - Tenant string - Account string - Direction string - BalanceType string - ThresholdType string - ThresholdValue float64 - DestinationId string - BalanceWeight float64 - BalanceExpiryTime string - Weight float64 - ActionsId string + Tenant string + Account string + Direction string + BalanceType string + ThresholdType string + ThresholdValue float64 + DestinationId string + BalanceRatingSubject string //ToDo + BalanceWeight float64 + BalanceExpiryTime string + BalanceSharedGroup string //ToDo + Weight float64 + ActionsId string } func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error { diff --git a/apier/apier_local_test.go b/apier/apier_local_test.go index d43291b81..a469f9ef7 100644 --- a/apier/apier_local_test.go +++ b/apier/apier_local_test.go @@ -1442,6 +1442,30 @@ func TestLocalGetCdrs(t *testing.T) { } } +func TestLocalProcessCdr(t *testing.T) { + if !*testLocal { + return + } + var reply string + cdr := utils.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: "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", + } + 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) + } + var cdrs []*utils.StoredCdr + req := utils.AttrGetCdrs{} + if err := rater.Call("ApierV1.GetCdrs", req, &cdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if len(cdrs) != 3 { + t.Error("Unexpected number of CDRs returned: ", len(cdrs)) + } +} + func TestLocalSetDC(t *testing.T) { if !*testLocal { return diff --git a/apier/cdre.go b/apier/cdre.go index f1f63933d..f98e655df 100644 --- a/apier/cdre.go +++ b/apier/cdre.go @@ -121,8 +121,9 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E if attr.MaskLength != nil { maskLen = *attr.MaskLength } - cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunId, attr.TOR, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction, - attr.Tenant, attr.Category, attr.Account, attr.Subject, attr.DestinationPrefix, attr.OrderIdStart, attr.OrderIdEnd, tStart, tEnd, attr.SkipErrors, attr.SkipRated, false) + cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunIds, attr.TORs, attr.CdrHosts, attr.CdrSources, attr.ReqTypes, attr.Directions, + attr.Tenants, attr.Categories, attr.Accounts, attr.Subjects, attr.DestinationPrefixes, attr.RatedAccounts, attr.RatedSubjects, attr.OrderIdStart, attr.OrderIdEnd, + tStart, tEnd, attr.SkipErrors, attr.SkipRated, false) if err != nil { return err } else if len(cdrs) == 0 { @@ -141,8 +142,11 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E if err := cdrexp.WriteToFile(filePath); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } - *reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), TotalCost: cdrexp.TotalCost(), - ExportedCgrIds: cdrexp.PositiveExports(), UnexportedCgrIds: cdrexp.NegativeExports(), FirstOrderId: cdrexp.FirstOrderId(), LastOrderId: cdrexp.LastOrderId()} + *reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), TotalCost: cdrexp.TotalCost(), FirstOrderId: cdrexp.FirstOrderId(), LastOrderId: cdrexp.LastOrderId()} + if !attr.SuppressCgrIds { + reply.ExportedCgrIds = cdrexp.PositiveExports() + reply.UnexportedCgrIds = cdrexp.NegativeExports() + } return nil } diff --git a/apier/cdrs.go b/apier/cdrs.go index 4676869b2..7aadaab9d 100644 --- a/apier/cdrs.go +++ b/apier/cdrs.go @@ -59,8 +59,8 @@ func (apier *ApierV1) GetCdrs(attrs utils.AttrGetCdrs, reply *[]*utils.CgrCdrOut return err } } - if cdrs, err := apier.CdrDb.GetStoredCdrs(attrs.CgrIds, attrs.MediationRunId, attrs.TOR, attrs.CdrHost, attrs.CdrSource, attrs.ReqType, attrs.Direction, - attrs.Tenant, attrs.Category, attrs.Account, attrs.Subject, attrs.DestinationPrefix, + if cdrs, err := apier.CdrDb.GetStoredCdrs(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.SkipErrors, attrs.SkipRated, false); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } else { diff --git a/mediator/mediator_test.go b/apier/cdrsv1.go similarity index 55% rename from mediator/mediator_test.go rename to apier/cdrsv1.go index ed1406934..3b638ddfd 100644 --- a/mediator/mediator_test.go +++ b/apier/cdrsv1.go @@ -1,6 +1,6 @@ /* Real-time Charging System for Telecom & ISP environments -Copyright (C) 2012-2014 ITsysCOM GmbH +Copyright (C) ITsysCOM GmbH This program is free software: you can Storagetribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,4 +16,26 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -package mediator +package apier + +import ( + "fmt" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// Receive CDRs via RPC methods +type CDRSV1 struct { + CdrSrv *engine.CDRS +} + +func (cdrsrv *CDRSV1) ProcessCdr(cdr *utils.StoredCdr, reply *string) error { + if cdrsrv.CdrSrv == nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "CDRS_NOT_RUNNING") + } + if err := cdrsrv.CdrSrv.ProcessCdr(cdr); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + *reply = utils.OK + return nil +} diff --git a/apier/mediator.go b/apier/mediator.go index 6f44e388f..e5cc8c0a7 100644 --- a/apier/mediator.go +++ b/apier/mediator.go @@ -22,12 +22,12 @@ import ( "fmt" "time" - "github.com/cgrates/cgrates/mediator" + "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) type MediatorV1 struct { - Medi *mediator.Mediator + Medi *engine.Mediator } // Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog @@ -47,7 +47,11 @@ func (self *MediatorV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error return err } } - if err := self.Medi.RateCdrs(tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil { + //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 diff --git a/cdrc/cdrc.go b/cdrc/cdrc.go index 5399f68e5..209ca1360 100644 --- a/cdrc/cdrc.go +++ b/cdrc/cdrc.go @@ -32,7 +32,6 @@ import ( "time" "unicode/utf8" - "github.com/cgrates/cgrates/cdrs" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" "github.com/howeyc/fsnotify" @@ -43,7 +42,7 @@ const ( FS_CSV = "freeswitch_csv" ) -func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string]*utils.RSRField, cdrServer *cdrs.CDRS) (*Cdrc, error) { +func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField, cdrServer *engine.CDRS) (*Cdrc, error) { if len(csvSep) != 1 { return nil, fmt.Errorf("Unsupported csv separator: %s", csvSep) } @@ -68,8 +67,8 @@ type Cdrc struct { cdrSourceId string runDelay time.Duration csvSep rune - cdrFields map[string]*utils.RSRField - cdrServer *cdrs.CDRS // Reference towards internal cdrServer if that is the case + cdrFields map[string][]*utils.RSRField + cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case httpClient *http.Client } @@ -89,16 +88,18 @@ func (self *Cdrc) Run() error { func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) { storedCdr := &utils.StoredCdr{CdrHost: "0.0.0.0", CdrSource: self.cdrSourceId, ExtraFields: make(map[string]string), Cost: -1} var err error - for cfgFieldName, cfgFieldRSR := range self.cdrFields { + for cfgFieldName, cfgFieldRSRs := range self.cdrFields { var fieldVal string if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) { - if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) { - fieldVal = cfgFieldRSR.ParseValue("PLACEHOLDER") - } else { // Dynamic value extracted using index - if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx { - return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName) - } else { - fieldVal = cfgFieldRSR.ParseValue(record[cfgFieldIdx]) + for _, cfgFieldRSR := range cfgFieldRSRs { + if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) { + fieldVal += cfgFieldRSR.ParseValue("PLACEHOLDER") + } else { // Dynamic value extracted using index + if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx { + return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName) + } else { + fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx]) + } } } } else { // Modify here when we add more supported cdr formats @@ -218,7 +219,7 @@ func (self *Cdrc) processFile(filePath string) error { continue } if self.cdrsAddress == utils.INTERNAL { - if err := self.cdrServer.ProcessRawCdr(storedCdr); err != nil { + if err := self.cdrServer.ProcessCdr(storedCdr); err != nil { engine.Logger.Err(fmt.Sprintf(" Failed posting CDR, row: %d, error: %s", procRowNr, err.Error())) continue } diff --git a/cdrc/cdrc_test.go b/cdrc/cdrc_test.go index 7874c1e59..37b923477 100644 --- a/cdrc/cdrc_test.go +++ b/cdrc/cdrc_test.go @@ -22,8 +22,8 @@ import ( //"bytes" //"encoding/csv" //"fmt" - "github.com/cgrates/cgrates/cdrs" "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" //"io" "reflect" @@ -34,10 +34,10 @@ import ( func TestRecordForkCdr(t *testing.T) { cgrConfig, _ := config.NewDefaultCGRConfig() - cgrConfig.CdrcCdrFields["supplier"] = &utils.RSRField{Id: "14"} + cgrConfig.CdrcCdrFields["supplier"] = []*utils.RSRField{&utils.RSRField{Id: "14"}} csvSepRune, _ := utf8.DecodeRune([]byte(cgrConfig.CdrcCsvSep)) cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, csvSepRune, - cgrConfig.CdrcCdrFields, new(cdrs.CDRS), nil} + cgrConfig.CdrcCdrFields, new(engine.CDRS), nil} cdrRow := []string{"firstField", "secondField"} _, err := cdrc.recordToStoredCdr(cdrRow) if err == nil { diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index da8530dab..d2a0be847 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -31,11 +31,9 @@ import ( "github.com/cgrates/cgrates/apier" "github.com/cgrates/cgrates/balancer2go" "github.com/cgrates/cgrates/cdrc" - "github.com/cgrates/cgrates/cdrs" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/history" - "github.com/cgrates/cgrates/mediator" "github.com/cgrates/cgrates/scheduler" "github.com/cgrates/cgrates/sessionmanager" "github.com/cgrates/cgrates/utils" @@ -51,6 +49,7 @@ const ( REDIS = "redis" SAME = "same" FS = "freeswitch" + OSIPS = "opensips" ) var ( @@ -66,10 +65,10 @@ var ( exitChan = make(chan bool) server = &engine.Server{} scribeServer history.Scribe - cdrServer *cdrs.CDRS + cdrServer *engine.CDRS cdrStats *engine.Stats sm sessionmanager.SessionManager - medi *mediator.Mediator + medi *engine.Mediator cfg *config.CGRConfig err error ) @@ -97,8 +96,8 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD var client *rpcclient.RpcClient var err error - for i := 0; i < cfg.MediatorRaterReconnects; i++ { - client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorRaterReconnects, utils.GOB) + for i := 0; i < cfg.MediatorReconnects; i++ { + client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorReconnects, utils.GOB) if err == nil { //Connected so no need to reiterate break } @@ -112,7 +111,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD connector = &engine.RPCClientConnector{Client: client} } var err error - medi, err = mediator.NewMediator(connector, loggerDb, cdrDb, cdrStats, cfg) + medi, err = engine.NewMediator(connector, loggerDb, cdrDb, cdrStats, cfg) if err != nil { engine.Logger.Crit(fmt.Sprintf("Mediator config parsing error: %v", err)) exitChan <- true @@ -125,7 +124,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD } // Fires up a cdrc instance -func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string]*utils.RSRField) { +func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField) { if cdrsAddress == utils.INTERNAL { <-cdrsChan // Wait for CDRServer to come up before start processing } @@ -150,8 +149,8 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage var client *rpcclient.RpcClient var err error - for i := 0; i < cfg.SMRaterReconnects; i++ { - client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMRaterReconnects, utils.GOB) + for i := 0; i < cfg.SMReconnects; i++ { + client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMReconnects, utils.GOB) if err == nil { //Connected so no need to reiterate break } @@ -166,14 +165,15 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage switch cfg.SMSwitchType { case FS: dp, _ := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval)) - sm = sessionmanager.NewFSSessionManager(loggerDb, connector, dp) - errConn := sm.Connect(cfg) - if errConn != nil { - engine.Logger.Err(fmt.Sprintf(" error: %s!", errConn)) - } + sm = sessionmanager.NewFSSessionManager(cfg, loggerDb, connector, dp) + case OSIPS: + sm, _ = sessionmanager.NewOSipsSessionManager(cfg, connector) default: engine.Logger.Err(fmt.Sprintf(" Unsupported session manger type: %s!", cfg.SMSwitchType)) } + if err = sm.Connect(); err != nil { + engine.Logger.Err(fmt.Sprintf(" error: %s!", err)) + } exitChan <- true } @@ -186,8 +186,11 @@ func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage, mediChan, d return } } - cdrServer = cdrs.New(cdrDb, medi, cdrStats, cfg) + cdrServer = engine.NewCdrS(cdrDb, medi, cdrStats, cfg) cdrServer.RegisterHanlersToServer(server) + engine.Logger.Info("Registering CDRS RPC service.") + server.RpcRegister(&apier.CDRSV1{CdrSrv: cdrServer}) + responder.CdrSrv = cdrServer // Make the cdrserver available for internal communication close(doneChan) } diff --git a/config/config.go b/config/config.go index 7e0cf853e..0a7399871 100644 --- a/config/config.go +++ b/config/config.go @@ -55,71 +55,80 @@ func SetCgrConfig(cfg *CGRConfig) { // Holds system configuration, defaults are overwritten with values from config file if found type CGRConfig struct { - RatingDBType string - RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets. - RatingDBPort string // The port to bind to. - RatingDBName string // The name of the database to connect to. - RatingDBUser string // The user to sign in as. - RatingDBPass string // The user's password. - AccountDBType string - AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets. - AccountDBPort string // The port to bind to. - AccountDBName string // The name of the database to connect to. - AccountDBUser string // The user to sign in as. - AccountDBPass string // The user's password. - StorDBType string // Should reflect the database type used to store logs - StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets. - StorDBPort string // Th e port to bind to. - StorDBName string // The name of the database to connect to. - StorDBUser string // The user to sign in as. - StorDBPass string // The user's password. - DBDataEncoding string // The encoding used to store object data in strings: - RPCJSONListen string // RPC JSON listening address - RPCGOBListen string // RPC GOB listening address - HTTPListen string // HTTP listening address - DefaultReqType string // Use this request type if not defined on top - DefaultCategory string // set default type of record - DefaultTenant string // set default tenant - DefaultSubject string // set default rating subject, useful in case of fallback - RoundingDecimals int // Number of decimals to round end prices at - HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate - XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document - RaterEnabled bool // start standalone server (no balancer) - RaterBalancer string // balancer address host:port - BalancerEnabled bool - 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> + RatingDBType string + RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets. + RatingDBPort string // The port to bind to. + RatingDBName string // The name of the database to connect to. + RatingDBUser string // The user to sign in as. + RatingDBPass string // The user's password. + AccountDBType string + AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets. + AccountDBPort string // The port to bind to. + AccountDBName string // The name of the database to connect to. + AccountDBUser string // The user to sign in as. + AccountDBPass string // The user's password. + StorDBType string // Should reflect the database type used to store logs + StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets. + StorDBPort string // Th e port to bind to. + StorDBName string // The name of the database to connect to. + StorDBUser string // The user to sign in as. + StorDBPass string // The user's password. + DBDataEncoding string // The encoding used to store object data in strings: + RPCJSONListen string // RPC JSON listening address + RPCGOBListen string // RPC GOB listening address + HTTPListen string // HTTP listening address + DefaultReqType string // Use this request type if not defined on top + DefaultCategory string // set default type of record + DefaultTenant string // set default tenant + DefaultSubject string // set default rating subject, useful in case of fallback + RoundingDecimals int // Number of decimals to round end prices at + HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate + XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document + RaterEnabled bool // start standalone server (no balancer) + RaterBalancer string // balancer address host:port + BalancerEnabled bool + 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> CDRStatsEnabled bool // Enable CDR Stats service - //CdrStats []*cdrstats.CdrStats // Active cdr stats configuration instances - CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API - CdrcEnabled bool // Enable CDR client functionality - CdrcCdrs string // Address where to reach CDR server - CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify - CdrcCdrType string // CDR file format . - CdrcCsvSep string // Separator used in case of csv files. One character only supported. - CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored. - CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved. - CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database. - CdrcCdrFields map[string]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs. + //CdrStats []*cdrstats.CdrStats // Active cdr stats configuration instances + CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API + CdrcEnabled bool // Enable CDR client functionality + CdrcCdrs string // Address where to reach CDR server + CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify + CdrcCdrType string // CDR file format . + CdrcCsvSep string // Separator used in case of csv files. One character only supported. + CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored. + CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved. + CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database. + CdrcCdrFields map[string][]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs. SMEnabled bool SMSwitchType string SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer - SMRaterReconnects int // Number of reconnect attempts to rater + SMReconnects int // Number of reconnect attempts to rater SMDebitInterval int // the period to be debited in advanced during a call (in seconds) SMMaxCallDuration time.Duration // The maximum duration of a call SMMinCallDuration time.Duration // Only authorize calls with allowed duration bigger than this MediatorEnabled bool // Starts Mediator service: . - MediatorRater string // Address where to reach the Rater: - MediatorStats string // Address where to reach the stats service: - MediatorRaterReconnects int // Number of reconnects to rater before giving up. + MediatorRater string + MediatorStats string // Address where to reach the Rater: + MediatorReconnects int // Number of reconnects to rater before giving up. DerivedChargers utils.DerivedChargers // System wide derived chargers, added to the account level ones CombinedDerivedChargers bool // Combine accounts specific derived_chargers with server configured FreeswitchServer string // freeswitch address host:port FreeswitchPass string // FS socket password FreeswitchReconnects int // number of times to attempt reconnect after connect fails + FSMinDurLowBalance time.Duration // Threshold which will trigger low balance warnings + FSLowBalanceAnnFile string // File to be played when low balance is reached + FSEmptyBalanceContext string // If defined, call will be transfered to this context on empty balance + FSEmptyBalanceAnnFile string // File to be played before disconnecting prepaid calls (applies only if no context defined) + OsipsListenUdp string // Address where to listen for event datagrams coming from OpenSIPS + OsipsMiAddr string // Adress where to reach OpenSIPS mi_datagram module + OsipsEvSubscInterval time.Duration // Refresh event subscription at this interval + OsipCDRS string // Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013> + OsipsReconnects int // Number of attempts on connect failure. HistoryAgentEnabled bool // Starts History as an agent: . HistoryServer string // Address where to reach the master history server: HistoryServerEnabled bool // Starts History as server: . @@ -179,36 +188,45 @@ func (self *CGRConfig) setDefaults() error { self.CdrcCdrInDir = "/var/log/cgrates/cdrc/in" self.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out" self.CdrcSourceId = "csv" - self.CdrcCdrFields = map[string]*utils.RSRField{ - utils.TOR: &utils.RSRField{Id: "2"}, - utils.ACCID: &utils.RSRField{Id: "3"}, - utils.REQTYPE: &utils.RSRField{Id: "4"}, - utils.DIRECTION: &utils.RSRField{Id: "5"}, - utils.TENANT: &utils.RSRField{Id: "6"}, - utils.CATEGORY: &utils.RSRField{Id: "7"}, - utils.ACCOUNT: &utils.RSRField{Id: "8"}, - utils.SUBJECT: &utils.RSRField{Id: "9"}, - utils.DESTINATION: &utils.RSRField{Id: "10"}, - utils.SETUP_TIME: &utils.RSRField{Id: "11"}, - utils.ANSWER_TIME: &utils.RSRField{Id: "12"}, - utils.USAGE: &utils.RSRField{Id: "13"}, + self.CdrcCdrFields = map[string][]*utils.RSRField{ + utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}}, + utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}}, + utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}}, + utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}}, + utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}}, + utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}}, + utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}}, + utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}}, + utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}}, + utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}}, + utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}}, + utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}}, } self.MediatorEnabled = false self.MediatorRater = utils.INTERNAL - self.MediatorRaterReconnects = 3 + self.MediatorReconnects = 3 self.MediatorStats = utils.INTERNAL self.DerivedChargers = make(utils.DerivedChargers, 0) self.CombinedDerivedChargers = true self.SMEnabled = false self.SMSwitchType = FS self.SMRater = utils.INTERNAL - self.SMRaterReconnects = 3 + self.SMReconnects = 3 self.SMDebitInterval = 10 self.SMMaxCallDuration = time.Duration(3) * time.Hour self.SMMinCallDuration = time.Duration(0) self.FreeswitchServer = "127.0.0.1:8021" self.FreeswitchPass = "ClueCon" self.FreeswitchReconnects = 5 + self.FSMinDurLowBalance = time.Duration(5) * time.Second + self.FSLowBalanceAnnFile = "" + self.FSEmptyBalanceContext = "" + self.FSEmptyBalanceAnnFile = "" + self.OsipsListenUdp = "127.0.0.1:2020" + self.OsipsMiAddr = "127.0.0.1:8020" + self.OsipsEvSubscInterval = time.Duration(60) * time.Second + self.OsipCDRS = "internal" + self.OsipsReconnects = 3 self.HistoryAgentEnabled = false self.HistoryServerEnabled = false self.HistoryServer = utils.INTERNAL @@ -227,9 +245,11 @@ func (self *CGRConfig) checkConfigSanity() error { return errors.New("CdrC enabled but no fields to be processed defined!") } if self.CdrcCdrType == utils.CSV { - for _, rsrFld := range self.CdrcCdrFields { - if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil { - return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id) + for _, rsrFldLst := range self.CdrcCdrFields { + for _, rsrFld := range rsrFldLst { + if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil { + return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id) + } } } } @@ -503,8 +523,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { if hasOpt = c.HasOption("mediator", "rater"); hasOpt { cfg.MediatorRater, _ = c.GetString("mediator", "rater") } - if hasOpt = c.HasOption("mediator", "rater_reconnects"); hasOpt { - cfg.MediatorRaterReconnects, _ = c.GetInt("mediator", "rater_reconnects") + if hasOpt = c.HasOption("mediator", "reconnects"); hasOpt { + cfg.MediatorReconnects, _ = c.GetInt("mediator", "reconnects") } if hasOpt = c.HasOption("mediator", "stats"); hasOpt { cfg.MediatorStats, _ = c.GetString("mediator", "stats") @@ -518,8 +538,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { if hasOpt = c.HasOption("session_manager", "rater"); hasOpt { cfg.SMRater, _ = c.GetString("session_manager", "rater") } - if hasOpt = c.HasOption("session_manager", "rater_reconnects"); hasOpt { - cfg.SMRaterReconnects, _ = c.GetInt("session_manager", "rater_reconnects") + if hasOpt = c.HasOption("session_manager", "reconnects"); hasOpt { + cfg.SMReconnects, _ = c.GetInt("session_manager", "reconnects") } if hasOpt = c.HasOption("session_manager", "debit_interval"); hasOpt { cfg.SMDebitInterval, _ = c.GetInt("session_manager", "debit_interval") @@ -531,7 +551,7 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { } } if hasOpt = c.HasOption("session_manager", "max_call_duration"); hasOpt { - maxCallDurStr, _ := c.GetString("session_manager", "min_call_duration") + maxCallDurStr, _ := c.GetString("session_manager", "max_call_duration") if cfg.SMMaxCallDuration, err = utils.ParseDurationWithSecs(maxCallDurStr); err != nil { return nil, err } @@ -545,6 +565,39 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { if hasOpt = c.HasOption("freeswitch", "reconnects"); hasOpt { cfg.FreeswitchReconnects, _ = c.GetInt("freeswitch", "reconnects") } + if hasOpt = c.HasOption("freeswitch", "min_dur_low_balance"); hasOpt { + minDurStr, _ := c.GetString("freeswitch", "min_dur_low_balance") + if cfg.FSMinDurLowBalance, err = utils.ParseDurationWithSecs(minDurStr); err != nil { + return nil, err + } + } + if hasOpt = c.HasOption("freeswitch", "low_balance_ann_file"); hasOpt { + cfg.FSLowBalanceAnnFile, _ = c.GetString("freeswitch", "low_balance_ann_file") + } + if hasOpt = c.HasOption("freeswitch", "empty_balance_context"); hasOpt { + cfg.FSEmptyBalanceContext, _ = c.GetString("freeswitch", "empty_balance_context") + } + if hasOpt = c.HasOption("freeswitch", "empty_balance_ann_file"); hasOpt { + cfg.FSEmptyBalanceAnnFile, _ = c.GetString("freeswitch", "empty_balance_ann_file") + } + if hasOpt = c.HasOption("opensips", "listen_udp"); hasOpt { + cfg.OsipsListenUdp, _ = c.GetString("opensips", "listen_udp") + } + if hasOpt = c.HasOption("opensips", "mi_addr"); hasOpt { + cfg.OsipsMiAddr, _ = c.GetString("opensips", "mi_addr") + } + if hasOpt = c.HasOption("opensips", "events_subscribe_interval"); hasOpt { + evSubscIntervalStr, _ := c.GetString("opensips", "events_subscribe_interval") + if cfg.OsipsEvSubscInterval, err = utils.ParseDurationWithSecs(evSubscIntervalStr); err != nil { + return nil, err + } + } + if hasOpt = c.HasOption("opensips", "cdrs"); hasOpt { + cfg.OsipCDRS, _ = c.GetString("opensips", "cdrs") + } + if hasOpt = c.HasOption("opensips", "reconnects"); hasOpt { + cfg.OsipsReconnects, _ = c.GetInt("opensips", "reconnects") + } if cfg.DerivedChargers, err = ParseCfgDerivedCharging(c); err != nil { return nil, err } diff --git a/config/config_test.go b/config/config_test.go index 4150f6720..991d916e1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -92,19 +92,19 @@ func TestDefaults(t *testing.T) { eCfg.CdrcCdrInDir = "/var/log/cgrates/cdrc/in" eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out" eCfg.CdrcSourceId = "csv" - eCfg.CdrcCdrFields = map[string]*utils.RSRField{ - utils.TOR: &utils.RSRField{Id: "2"}, - utils.ACCID: &utils.RSRField{Id: "3"}, - utils.REQTYPE: &utils.RSRField{Id: "4"}, - utils.DIRECTION: &utils.RSRField{Id: "5"}, - utils.TENANT: &utils.RSRField{Id: "6"}, - utils.CATEGORY: &utils.RSRField{Id: "7"}, - utils.ACCOUNT: &utils.RSRField{Id: "8"}, - utils.SUBJECT: &utils.RSRField{Id: "9"}, - utils.DESTINATION: &utils.RSRField{Id: "10"}, - utils.SETUP_TIME: &utils.RSRField{Id: "11"}, - utils.ANSWER_TIME: &utils.RSRField{Id: "12"}, - utils.USAGE: &utils.RSRField{Id: "13"}, + eCfg.CdrcCdrFields = map[string][]*utils.RSRField{ + utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}}, + utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}}, + utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}}, + utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}}, + utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}}, + utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}}, + utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}}, + utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}}, + utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}}, + utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}}, + utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}}, + utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}}, } eCfg.MediatorEnabled = false eCfg.MediatorRater = utils.INTERNAL @@ -113,13 +113,22 @@ func TestDefaults(t *testing.T) { eCfg.SMEnabled = false eCfg.SMSwitchType = FS eCfg.SMRater = utils.INTERNAL - eCfg.SMRaterReconnects = 3 + eCfg.SMReconnects = 3 eCfg.SMDebitInterval = 10 eCfg.SMMinCallDuration = time.Duration(0) eCfg.SMMaxCallDuration = time.Duration(3) * time.Hour eCfg.FreeswitchServer = "127.0.0.1:8021" eCfg.FreeswitchPass = "ClueCon" eCfg.FreeswitchReconnects = 5 + eCfg.FSMinDurLowBalance = time.Duration(5) * time.Second + eCfg.FSLowBalanceAnnFile = "" + eCfg.FSEmptyBalanceContext = "" + eCfg.FSEmptyBalanceAnnFile = "" + eCfg.OsipsListenUdp = "127.0.0.1:2020" + eCfg.OsipsMiAddr = "127.0.0.1:8020" + eCfg.OsipsEvSubscInterval = time.Duration(60) * time.Second + eCfg.OsipCDRS = "internal" + eCfg.OsipsReconnects = 3 eCfg.DerivedChargers = make(utils.DerivedChargers, 0) eCfg.CombinedDerivedChargers = true eCfg.HistoryAgentEnabled = false @@ -153,11 +162,11 @@ func TestSanityCheck(t *testing.T) { t.Error("Failed to detect missing CDR fields definition") } cfg.CdrcCdrType = utils.CSV - cfg.CdrcCdrFields = map[string]*utils.RSRField{utils.ACCID: &utils.RSRField{Id: "test"}} + cfg.CdrcCdrFields = map[string][]*utils.RSRField{utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}}} if err := cfg.checkConfigSanity(); err == nil { t.Error("Failed to detect improper use of CDR field names") } - cfg.CdrcCdrFields = map[string]*utils.RSRField{"extra1": &utils.RSRField{Id: "test"}} + cfg.CdrcCdrFields = map[string][]*utils.RSRField{"extra1": []*utils.RSRField{&utils.RSRField{Id: "test"}}} if err := cfg.checkConfigSanity(); err == nil { t.Error("Failed to detect improper use of CDR field names") } @@ -229,35 +238,44 @@ func TestConfigFromFile(t *testing.T) { eCfg.CdrcCdrInDir = "test" eCfg.CdrcCdrOutDir = "test" eCfg.CdrcSourceId = "test" - eCfg.CdrcCdrFields = map[string]*utils.RSRField{ - utils.TOR: &utils.RSRField{Id: "test"}, - utils.ACCID: &utils.RSRField{Id: "test"}, - utils.REQTYPE: &utils.RSRField{Id: "test"}, - utils.DIRECTION: &utils.RSRField{Id: "test"}, - utils.TENANT: &utils.RSRField{Id: "test"}, - utils.CATEGORY: &utils.RSRField{Id: "test"}, - utils.ACCOUNT: &utils.RSRField{Id: "test"}, - utils.SUBJECT: &utils.RSRField{Id: "test"}, - utils.DESTINATION: &utils.RSRField{Id: "test"}, - utils.SETUP_TIME: &utils.RSRField{Id: "test"}, - utils.ANSWER_TIME: &utils.RSRField{Id: "test"}, - utils.USAGE: &utils.RSRField{Id: "test"}, - "test": &utils.RSRField{Id: "test"}, + eCfg.CdrcCdrFields = map[string][]*utils.RSRField{ + utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "test"}}, + "test": []*utils.RSRField{&utils.RSRField{Id: "test"}}, } eCfg.MediatorEnabled = true eCfg.MediatorRater = "test" - eCfg.MediatorRaterReconnects = 99 + eCfg.MediatorReconnects = 99 eCfg.MediatorStats = "test" eCfg.SMEnabled = true eCfg.SMSwitchType = "test" eCfg.SMRater = "test" - eCfg.SMRaterReconnects = 99 + eCfg.SMReconnects = 99 eCfg.SMDebitInterval = 99 - eCfg.SMMinCallDuration = time.Duration(99) * time.Second + eCfg.SMMinCallDuration = time.Duration(98) * time.Second eCfg.SMMaxCallDuration = time.Duration(99) * time.Second eCfg.FreeswitchServer = "test" eCfg.FreeswitchPass = "test" eCfg.FreeswitchReconnects = 99 + eCfg.FSMinDurLowBalance = time.Duration(99) * time.Second + eCfg.FSLowBalanceAnnFile = "test" + eCfg.FSEmptyBalanceContext = "test" + eCfg.FSEmptyBalanceAnnFile = "test" + eCfg.OsipsListenUdp = "test" + eCfg.OsipsMiAddr = "test" + eCfg.OsipsEvSubscInterval = time.Duration(99) * time.Second + eCfg.OsipCDRS = "test" + eCfg.OsipsReconnects = 99 eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", RunFilters: "", ReqTypeField: "test", DirectionField: "test", TenantField: "test", CategoryField: "test", AccountField: "test", SubjectField: "test", DestinationField: "test", SetupTimeField: "test", AnswerTimeField: "test", UsageField: "test"}} eCfg.CombinedDerivedChargers = true diff --git a/config/helpers.go b/config/helpers.go index 53522e241..baeb6e422 100644 --- a/config/helpers.go +++ b/config/helpers.go @@ -120,8 +120,8 @@ func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err } func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld, - setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string]*utils.RSRField, error) { - cdrcCdrFlds := make(map[string]*utils.RSRField) + setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string][]*utils.RSRField, error) { + cdrcCdrFlds := make(map[string][]*utils.RSRField) if len(extraFlds) != 0 { if sepExtraFlds, err := ConfigSlice(extraFlds); err != nil { return nil, err @@ -131,10 +131,10 @@ func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, c if spltLbl := strings.Split(fldStr, utils.CONCATENATED_KEY_SEP); len(spltLbl) != 2 { return nil, fmt.Errorf("Wrong format for cdrc.extra_fields: %s", fldStr) } else { - if rsrFld, err := utils.NewRSRField(spltLbl[1]); err != nil { + if rsrFlds, err := utils.ParseRSRFields(spltLbl[1], utils.INFIELD_SEP); err != nil { return nil, err } else { - cdrcCdrFlds[spltLbl[0]] = rsrFld + cdrcCdrFlds[spltLbl[0]] = rsrFlds } } } @@ -144,10 +144,10 @@ func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, c utils.CATEGORY: categoryFld, utils.ACCOUNT: acntFld, utils.SUBJECT: subjectFld, utils.DESTINATION: destFld, utils.SETUP_TIME: setupTimeFld, utils.ANSWER_TIME: answerTimeFld, utils.USAGE: durFld} { if len(fldVal) != 0 { - if rsrFld, err := utils.NewRSRField(fldVal); err != nil { + if rsrFlds, err := utils.ParseRSRFields(fldVal, utils.INFIELD_SEP); err != nil { return nil, err } else { - cdrcCdrFlds[fldTag] = rsrFld + cdrcCdrFlds[fldTag] = rsrFlds } } } diff --git a/config/helpers_test.go b/config/helpers_test.go index 11f0177bc..8f0d462ae 100644 --- a/config/helpers_test.go +++ b/config/helpers_test.go @@ -116,21 +116,21 @@ answer_time_field = answertime1 usage_field = duration1 extra_fields = extra1:extraval1,extra2:extraval1 `) - eCdrcCdrFlds := map[string]*utils.RSRField{ - utils.TOR: &utils.RSRField{Id: "tor1"}, - utils.ACCID: &utils.RSRField{Id: "accid1"}, - utils.REQTYPE: &utils.RSRField{Id: "reqtype1"}, - utils.DIRECTION: &utils.RSRField{Id: "direction1"}, - utils.TENANT: &utils.RSRField{Id: "tenant1"}, - utils.CATEGORY: &utils.RSRField{Id: "category1"}, - utils.ACCOUNT: &utils.RSRField{Id: "account1"}, - utils.SUBJECT: &utils.RSRField{Id: "subject1"}, - utils.DESTINATION: &utils.RSRField{Id: "destination1"}, - utils.SETUP_TIME: &utils.RSRField{Id: "setuptime1"}, - utils.ANSWER_TIME: &utils.RSRField{Id: "answertime1"}, - utils.USAGE: &utils.RSRField{Id: "duration1"}, - "extra1": &utils.RSRField{Id: "extraval1"}, - "extra2": &utils.RSRField{Id: "extraval1"}, + eCdrcCdrFlds := map[string][]*utils.RSRField{ + utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "tor1"}}, + utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "accid1"}}, + utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "reqtype1"}}, + utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "direction1"}}, + utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "tenant1"}}, + utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "category1"}}, + utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "account1"}}, + utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "subject1"}}, + utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "destination1"}}, + utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "setuptime1"}}, + utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "answertime1"}}, + utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "duration1"}}, + "extra1": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}}, + "extra2": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}}, } if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil { t.Error("Could not parse the config", err.Error()) diff --git a/config/test_data.txt b/config/test_data.txt index f45b32c2e..080e51800 100644 --- a/config/test_data.txt +++ b/config/test_data.txt @@ -87,22 +87,33 @@ extra_fields = test:test # Field identifiers of the fields to add in extra field [mediator] enabled = true # Starts Mediacategory service: . rater = test # Address where to reach the Rater: -rater_reconnects = 99 # Number of reconnects to rater before giving up. +reconnects = 99 # Number of reconnects to rater before giving up. stats = test # Address where to reach the stats service: [session_manager] enabled = true # Starts SessionManager service: . switch_type = test # Defines the type of switch behind: . rater = test # Address where to reach the Rater. -rater_reconnects = 99 # Number of reconnects to rater before giving up. +reconnects = 99 # Number of reconnects to rater before giving up. debit_interval = 99 # Interval to perform debits on. -max_call_duration = 99 # Maximum call duration a prepaid call can last -min_call_duration = 99 # Only authorize calls with allowed duration bigger than this +min_call_duration = 98 # Only authorize calls with allowed duration bigger than this +max_call_duration = 99 # Maximum call duration a prepaid call can last [freeswitch] -server = test # Adress where to connect to FreeSWITCH socket. -passwd = test # FreeSWITCH socket password. -reconnects = 99 # Number of attempts on connect failure. +server = test # Adress where to connect to FreeSWITCH socket. +passwd = test # FreeSWITCH socket password. +reconnects = 99 # Number of attempts on connect failure. +min_dur_low_balance = 99 # Threshold which will trigger low balance warnings +low_balance_ann_file = test # File to be played when low balance is reached +empty_balance_context = test # If defined, call will be transfered to this context on empty balance +empty_balance_ann_file = test # File to be played before disconnecting prepaid calls (applies only if no context defined) + +[opensips] +listen_udp = test # Address where to listen for event datagrams coming from OpenSIPS +mi_addr = test # Adress where to reach OpenSIPS mi_datagram module +events_subscribe_interval = 99 # Automatic events subscription to OpenSIPS, 0 to disable it +cdrs = test # Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013> +reconnects = 99 # Number of attempts on connect failure. [derived_charging] run_ids = test # Identifiers of additional sessions control. diff --git a/config/xmlcdrc.go b/config/xmlcdrc.go index 68d406622..01bdd0896 100644 --- a/config/xmlcdrc.go +++ b/config/xmlcdrc.go @@ -57,30 +57,30 @@ func (cdrcCfg *CgrXmlCdrcCfg) setDefaults() error { cdrcCfg.CdrSourceId = dfCfg.CdrcSourceId } if len(cdrcCfg.CdrFields) == 0 { - for key, cfgRsrField := range dfCfg.CdrcCdrFields { - cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: cfgRsrField.Id, rsrField: cfgRsrField}) + for key, cfgRsrFields := range dfCfg.CdrcCdrFields { + cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: "PLACEHOLDER", rsrFields: cfgRsrFields}) } } return nil } -func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string]*utils.RSRField { - rsrFields := make(map[string]*utils.RSRField) +func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string][]*utils.RSRField { + rsrFields := make(map[string][]*utils.RSRField) for _, fld := range cdrcCfg.CdrFields { - rsrFields[fld.Id] = fld.rsrField + rsrFields[fld.Id] = fld.rsrFields } return rsrFields } type CdrcField struct { - XMLName xml.Name `xml:"field"` - Id string `xml:"id,attr"` - Value string `xml:"value,attr"` - rsrField *utils.RSRField + XMLName xml.Name `xml:"field"` + Id string `xml:"id,attr"` + Value string `xml:"value,attr"` + rsrFields []*utils.RSRField } -func (cdrcFld *CdrcField) PopulateRSRField() (err error) { - if cdrcFld.rsrField, err = utils.NewRSRField(cdrcFld.Value); err != nil { +func (cdrcFld *CdrcField) PopulateRSRFields() (err error) { + if cdrcFld.rsrFields, err = utils.ParseRSRFields(cdrcFld.Value, utils.INFIELD_SEP); err != nil { return err } return nil diff --git a/config/xmlcdrc_test.go b/config/xmlcdrc_test.go index 9fcd1a9ba..c7f3e8d53 100644 --- a/config/xmlcdrc_test.go +++ b/config/xmlcdrc_test.go @@ -30,15 +30,15 @@ var cfgDocCdrc *CgrXmlCfgDocument // Will be populated by first test func TestPopulateRSRFIeld(t *testing.T) { cdrcField := CdrcField{Id: "TEST1", Value: `~effective_caller_id_number:s/(\d+)/+$1/`} - if err := cdrcField.PopulateRSRField(); err != nil { + if err := cdrcField.PopulateRSRFields(); err != nil { t.Error("Unexpected error: ", err.Error()) - } else if cdrcField.rsrField == nil { + } else if cdrcField.rsrFields == nil { t.Error("Failed loading the RSRField") } cdrcField = CdrcField{Id: "TEST2", Value: `99`} - if err := cdrcField.PopulateRSRField(); err != nil { + if err := cdrcField.PopulateRSRFields(); err != nil { t.Error("Unexpected error: ", err.Error()) - } else if cdrcField.rsrField == nil { + } else if cdrcField.rsrFields == nil { t.Error("Failed loading the RSRField") } } @@ -87,7 +87,7 @@ func TestParseXmlCdrcConfig(t *testing.T) { /var/log/cgrates/cdrc/out freeswitch_csv - + @@ -123,7 +123,7 @@ func TestGetCdrcCfgs(t *testing.T) { expectCdrc := &CgrXmlCdrcCfg{Enabled: true, CdrsAddress: "internal", CdrType: "csv", CsvSeparator: ",", RunDelay: 0, CdrInDir: "/var/log/cgrates/cdrc/in", CdrOutDir: "/var/log/cgrates/cdrc/out", CdrSourceId: "freeswitch_csv"} cdrFlds := []*CdrcField{ - &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0"}, + &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0;13"}, &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.REQTYPE, Value: "1"}, &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DIRECTION, Value: "2"}, &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.TENANT, Value: "3"}, @@ -137,7 +137,7 @@ func TestGetCdrcCfgs(t *testing.T) { &CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr1", Value: "11"}, &CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr2", Value: "12"}} for _, fld := range cdrFlds { - fld.PopulateRSRField() + fld.PopulateRSRFields() } expectCdrc.CdrFields = cdrFlds if !reflect.DeepEqual(expectCdrc, cdrcfgs["CDRC-CSV1"]) { @@ -150,20 +150,20 @@ func TestCdrRSRFields(t *testing.T) { if cdrcfgs == nil { t.Error("No config instance returned") } - eRSRFields := map[string]*utils.RSRField{ - utils.ACCID: &utils.RSRField{Id: "0"}, - utils.REQTYPE: &utils.RSRField{Id: "1"}, - utils.DIRECTION: &utils.RSRField{Id: "2"}, - utils.TENANT: &utils.RSRField{Id: "3"}, - utils.CATEGORY: &utils.RSRField{Id: "4"}, - utils.ACCOUNT: &utils.RSRField{Id: "5"}, - utils.SUBJECT: &utils.RSRField{Id: "6"}, - utils.DESTINATION: &utils.RSRField{Id: "7"}, - utils.SETUP_TIME: &utils.RSRField{Id: "8"}, - utils.ANSWER_TIME: &utils.RSRField{Id: "9"}, - utils.USAGE: &utils.RSRField{Id: "10"}, - "extr1": &utils.RSRField{Id: "11"}, - "extr2": &utils.RSRField{Id: "12"}, + eRSRFields := map[string][]*utils.RSRField{ + utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "0"}, &utils.RSRField{Id: "13"}}, + utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "1"}}, + utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "2"}}, + utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "3"}}, + utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "4"}}, + utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "5"}}, + utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "6"}}, + utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "7"}}, + utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "8"}}, + utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "9"}}, + utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "10"}}, + "extr1": []*utils.RSRField{&utils.RSRField{Id: "11"}}, + "extr2": []*utils.RSRField{&utils.RSRField{Id: "12"}}, } if rsrFields := cdrcfgs["CDRC-CSV1"].CdrRSRFields(); !reflect.DeepEqual(rsrFields, eRSRFields) { t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFields) diff --git a/config/xmlconfig.go b/config/xmlconfig.go index 906947c3f..5c2c903cd 100644 --- a/config/xmlconfig.go +++ b/config/xmlconfig.go @@ -85,7 +85,7 @@ func (xmlCfg *CgrXmlCfgDocument) cacheCdrcCfgs() error { } // Cache rsr fields for _, fld := range cdrcCfg.CdrFields { - if err := fld.PopulateRSRField(); err != nil { + if err := fld.PopulateRSRFields(); err != nil { return fmt.Errorf("Populating field %s, error: %s", fld.Id, err.Error()) } } diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg index ece39cde0..b4a117534 100644 --- a/data/conf/cgrates.cfg +++ b/data/conf/cgrates.cfg @@ -1,5 +1,5 @@ # Real-time Charging System for Telecom & ISP environments -# Copyright (C) 2012-2014 ITsysCOM GmbH +# Copyright (C) ITsysCOM GmbH # # This file contains the default configuration hardcoded into CGRateS. # This is what you get when you load CGRateS with an empty configuration file. @@ -90,23 +90,33 @@ # rater_reconnects = 3 # Number of reconnects to rater before giving up. [session_manager] -# enabled = false # Starts SessionManager service: . -# switch_type = freeswitch # Defines the type of switch behind: . -# rater = internal # Address where to reach the Rater. -# rater_reconnects = 3 # Number of reconnects to rater before giving up. +# enabled = false # Starts SessionManager service: +# switch_type = freeswitch # Defines the type of switch behind: +# rater = internal # Address where to reach the Rater +# reconnects = 3 # Number of reconnects to rater/cdrs before giving up. # debit_interval = 10 # Interval to perform debits on. -# min_call_duration = 0s # Only authorize calls with allowed duration bigger than this +# min_call_duration = 0s # Only authorize calls with allowed duration bigger than this # max_call_duration = 3h # Maximum call duration a prepaid call can last - [freeswitch] # server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket. # passwd = ClueCon # FreeSWITCH socket password. # reconnects = 5 # Number of attempts on connect failure. +# min_dur_low_balance = 5s # Threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval) +# low_balance_ann_file = # File to be played when low balance is reached for prepaid calls +# 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) + +[opensips] +# listen_udp = 127.0.0.1:2020 # Address where to listen for datagram events coming from OpenSIPS +# mi_addr = 127.0.0.1:8020 # Adress where to reach OpenSIPS mi_datagram module +# events_subscribe_interval = 60s # Automatic events subscription to OpenSIPS, 0 to disable it +# cdrs = internal # Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013> +# reconnects = 3 # Number of attempts on connect failure. [derived_charging] # run_ids = # Identifiers of additional sessions control. -# run_filters = # List of cdr field filters for each run. +# run_filters = # List of cdr field filters for each run. # reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>. # direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>. # tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>. diff --git a/data/storage/mysql/create_cdrs_tables.sql b/data/storage/mysql/create_cdrs_tables.sql index 8765d1d46..b8d2312e8 100644 --- a/data/storage/mysql/create_cdrs_tables.sql +++ b/data/storage/mysql/create_cdrs_tables.sql @@ -34,7 +34,8 @@ CREATE TABLE cdrs_extra ( tbid int(11) NOT NULL AUTO_INCREMENT, cgrid char(40) NOT NULL, extra_fields text NOT NULL, - PRIMARY KEY (tbid) + PRIMARY KEY (tbid), + UNIQUE KEY cgrid (cgrid) ); -- diff --git a/cdrs/cdrs.go b/engine/cdrs.go similarity index 77% rename from cdrs/cdrs.go rename to engine/cdrs.go index df3b44163..e449d1038 100644 --- a/cdrs/cdrs.go +++ b/engine/cdrs.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -package cdrs +package engine import ( "fmt" @@ -25,14 +25,13 @@ import ( "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/mediator" "github.com/cgrates/cgrates/utils" ) var ( cfg *config.CGRConfig // Share the configuration with the rest of the package storage engine.CdrStorage - medi *mediator.Mediator + medi *Mediator stats engine.StatsInterface ) @@ -51,7 +50,7 @@ func storeAndMediate(storedCdr *utils.StoredCdr) error { if cfg.CDRSMediator == utils.INTERNAL { go func() { if err := medi.RateCdr(storedCdr, true); err != nil { - engine.Logger.Err(fmt.Sprintf("Could not run mediation on CDR: %s", err.Error())) + Logger.Err(fmt.Sprintf("Could not run mediation on CDR: %s", err.Error())) } }() } @@ -62,10 +61,10 @@ func storeAndMediate(storedCdr *utils.StoredCdr) error { func cgrCdrHandler(w http.ResponseWriter, r *http.Request) { cgrCdr, err := utils.NewCgrCdrFromHttpReq(r) if err != nil { - engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error())) + Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error())) } if err := storeAndMediate(cgrCdr.AsStoredCdr()); err != nil { - engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error())) + Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error())) } } @@ -74,16 +73,16 @@ func fsCdrHandler(w http.ResponseWriter, r *http.Request) { body, _ := ioutil.ReadAll(r.Body) fsCdr, err := NewFSCdr(body) if err != nil { - engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error())) + Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error())) } if err := storeAndMediate(fsCdr.AsStoredCdr()); err != nil { - engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error())) + Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error())) } } type CDRS struct{} -func New(s engine.CdrStorage, m *mediator.Mediator, st *engine.Stats, c *config.CGRConfig) *CDRS { +func NewCdrS(s CdrStorage, m *Mediator, st *Stats, c *config.CGRConfig) *CDRS { storage = s medi = m cfg = c @@ -103,12 +102,12 @@ func New(s engine.CdrStorage, m *mediator.Mediator, st *engine.Stats, c *config. return &CDRS{} } -func (cdrs *CDRS) RegisterHanlersToServer(server *engine.Server) { +func (cdrs *CDRS) RegisterHanlersToServer(server *Server) { server.RegisterHttpFunc("/cgr", cgrCdrHandler) server.RegisterHttpFunc("/freeswitch_json", fsCdrHandler) } // Used to internally process CDR -func (cdrs *CDRS) ProcessRawCdr(rawCdr utils.RawCdr) error { - return storeAndMediate(rawCdr.AsStoredCdr()) +func (cdrs *CDRS) ProcessCdr(cdr *utils.StoredCdr) error { + return storeAndMediate(cdr) } diff --git a/cdrs/fscdr.go b/engine/fscdr.go similarity index 95% rename from cdrs/fscdr.go rename to engine/fscdr.go index f7437661c..417d96b58 100644 --- a/cdrs/fscdr.go +++ b/engine/fscdr.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -package cdrs +package engine import ( "encoding/json" @@ -24,7 +24,6 @@ import ( "reflect" "strings" - "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) @@ -111,11 +110,11 @@ func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) ( return } } else { - engine.Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item))) + Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item))) } } default: - engine.Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v))) + Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v))) } } return diff --git a/cdrs/fscdr_test.go b/engine/fscdr_test.go similarity index 99% rename from cdrs/fscdr_test.go rename to engine/fscdr_test.go index b26995c26..b137ecf3f 100644 --- a/cdrs/fscdr_test.go +++ b/engine/fscdr_test.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -package cdrs +package engine import ( "reflect" diff --git a/engine/loader_local_test.go b/engine/loader_local_test.go index 7baa27baa..9997bbb2b 100644 --- a/engine/loader_local_test.go +++ b/engine/loader_local_test.go @@ -44,7 +44,7 @@ README: var ratingDbCsv, ratingDbStor, ratingDbApier RatingStorage // Each ratingDb will have it's own sources to collect data var accountDbCsv, accountDbStor, accountDbApier AccountingStorage // Each ratingDb will have it's own sources to collect data var storDb LoadStorage -var cfg *config.CGRConfig +var lCfg *config.CGRConfig // Arguments received via test command var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args @@ -57,27 +57,27 @@ func TestConnDataDbs(t *testing.T) { if !*testLocal { return } - cfg, _ = config.NewDefaultCGRConfig() + lCfg, _ = config.NewDefaultCGRConfig() var err error - if ratingDbCsv, err = ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, "4", cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding); err != nil { + if ratingDbCsv, err = ConfigureRatingStorage(lCfg.RatingDBType, lCfg.RatingDBHost, lCfg.RatingDBPort, "4", lCfg.RatingDBUser, lCfg.RatingDBPass, lCfg.DBDataEncoding); err != nil { t.Fatal("Error on ratingDb connection: ", err.Error()) } - if ratingDbStor, err = ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, "5", cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding); err != nil { + if ratingDbStor, err = ConfigureRatingStorage(lCfg.RatingDBType, lCfg.RatingDBHost, lCfg.RatingDBPort, "5", lCfg.RatingDBUser, lCfg.RatingDBPass, lCfg.DBDataEncoding); err != nil { t.Fatal("Error on ratingDb connection: ", err.Error()) } - if ratingDbApier, err = ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, "6", cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding); err != nil { + if ratingDbApier, err = ConfigureRatingStorage(lCfg.RatingDBType, lCfg.RatingDBHost, lCfg.RatingDBPort, "6", lCfg.RatingDBUser, lCfg.RatingDBPass, lCfg.DBDataEncoding); err != nil { t.Fatal("Error on ratingDb connection: ", err.Error()) } - if accountDbCsv, err = ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort, "7", - cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding); err != nil { + if accountDbCsv, err = ConfigureAccountingStorage(lCfg.AccountDBType, lCfg.AccountDBHost, lCfg.AccountDBPort, "7", + lCfg.AccountDBUser, lCfg.AccountDBPass, lCfg.DBDataEncoding); err != nil { t.Fatal("Error on ratingDb connection: ", err.Error()) } - if accountDbStor, err = ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort, "8", - cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding); err != nil { + if accountDbStor, err = ConfigureAccountingStorage(lCfg.AccountDBType, lCfg.AccountDBHost, lCfg.AccountDBPort, "8", + lCfg.AccountDBUser, lCfg.AccountDBPass, lCfg.DBDataEncoding); err != nil { t.Fatal("Error on ratingDb connection: ", err.Error()) } - if accountDbApier, err = ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort, "9", - cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding); err != nil { + if accountDbApier, err = ConfigureAccountingStorage(lCfg.AccountDBType, lCfg.AccountDBHost, lCfg.AccountDBPort, "9", + lCfg.AccountDBUser, lCfg.AccountDBPass, lCfg.DBDataEncoding); err != nil { t.Fatal("Error on ratingDb connection: ", err.Error()) } for _, db := range []Storage{ratingDbCsv, ratingDbStor, ratingDbApier, accountDbCsv, accountDbStor, accountDbApier} { @@ -94,7 +94,7 @@ func TestCreateStorTpTables(t *testing.T) { return } var db *MySQLStorage - if d, err := NewMySQLStorage(cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass); err != nil { + if d, err := NewMySQLStorage(lCfg.StorDBHost, lCfg.StorDBPort, lCfg.StorDBName, lCfg.StorDBUser, lCfg.StorDBPass); err != nil { t.Error("Error on opening database connection: ", err) return } else { diff --git a/mediator/mediator.go b/engine/mediator.go similarity index 80% rename from mediator/mediator.go rename to engine/mediator.go index dc732f08d..2ee412986 100644 --- a/mediator/mediator.go +++ b/engine/mediator.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -package mediator +package engine import ( "errors" @@ -28,7 +28,7 @@ import ( "github.com/cgrates/cgrates/utils" ) -func NewMediator(connector engine.Connector, logDb engine.LogStorage, cdrDb engine.CdrStorage, st engine.StatsInterface, cfg *config.CGRConfig) (m *Mediator, err error) { +func NewMediator(connector Connector, logDb LogStorage, cdrDb CdrStorage, st StatsInterface, cfg *config.CGRConfig) (m *Mediator, err error) { m = &Mediator{ connector: connector, logDb: logDb, @@ -52,17 +52,17 @@ func NewMediator(connector engine.Connector, logDb engine.LogStorage, cdrDb engi } type Mediator struct { - connector engine.Connector - logDb engine.LogStorage - cdrDb engine.CdrStorage - stats engine.StatsInterface + 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 *engine.CallCost, err error) { +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, engine.SESSION_MANAGER_SOURCE, runId) + cc, err = self.logDb.GetCallCostLog(cgrid, SESSION_MANAGER_SOURCE, runId) if cc != nil { break } @@ -72,13 +72,13 @@ func (self *Mediator) getCostsFromDB(cgrid, runId string) (cc *engine.CallCost, } // Retrive the cost from engine -func (self *Mediator) getCostFromRater(storedCdr *utils.StoredCdr) (*engine.CallCost, error) { - cc := &engine.CallCost{} +func (self *Mediator) getCostFromRater(storedCdr *utils.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 := engine.CallDescriptor{ + cd := CallDescriptor{ TOR: storedCdr.TOR, Direction: storedCdr.Direction, Tenant: storedCdr.Tenant, @@ -96,16 +96,16 @@ func (self *Mediator) getCostFromRater(storedCdr *utils.StoredCdr) (*engine.Call err = self.connector.GetCost(cd, cc) } if err != nil { - self.logDb.LogError(storedCdr.CgrId, engine.MEDIATOR_SOURCE, storedCdr.MediationRunId, err.Error()) + 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, engine.MEDIATOR_SOURCE, storedCdr.MediationRunId, cc) + self.logDb.LogCallCost(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, cc) } return cc, err } func (self *Mediator) rateCDR(storedCdr *utils.StoredCdr) error { - var qryCC *engine.CallCost + var qryCC *CallCost var errCost error if storedCdr.ReqType == utils.PREPAID { // Should be previously calculated and stored in DB @@ -130,7 +130,7 @@ func (self *Mediator) RateCdr(storedCdr *utils.StoredCdr, sendToStats bool) erro 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()) - engine.Logger.Err(errText) + Logger.Err(errText) return errors.New(errText) } for _, dc := range dcs { @@ -177,15 +177,17 @@ func (self *Mediator) RateCdr(storedCdr *utils.StoredCdr, sendToStats bool) erro }() } if err := self.cdrDb.SetRatedCdr(cdr, extraInfo); err != nil { - engine.Logger.Err(fmt.Sprintf(" Could not record cost for cgrid: <%s>, ERROR: <%s>, cost: %f, extraInfo: %s", + Logger.Err(fmt.Sprintf(" Could not record cost for cgrid: <%s>, ERROR: <%s>, cost: %f, extraInfo: %s", cdr.CgrId, err.Error(), cdr.Cost, extraInfo)) } } return nil } -func (self *Mediator) RateCdrs(timeStart, timeEnd time.Time, rerateErrors, rerateRated bool, sentToStats bool) error { - cdrs, err := self.cdrDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, !rerateErrors, !rerateRated, true) +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 bool, sendToStats bool) error { + cdrs, err := self.cdrDb.GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects, + orderIdStart, orderIdEnd, timeStart, timeEnd, !rerateErrors, !rerateRated, true) if err != nil { return err } diff --git a/mediator/mediator_local_test.go b/engine/mediator_local_test.go similarity index 84% rename from mediator/mediator_local_test.go rename to engine/mediator_local_test.go index 3ca4f84a1..05fcf0405 100644 --- a/mediator/mediator_local_test.go +++ b/engine/mediator_local_test.go @@ -16,13 +16,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -package mediator +package engine import ( "flag" "fmt" "net/http" "net/rpc" + "net/rpc/jsonrpc" "net/url" "os/exec" "path" @@ -30,7 +31,6 @@ import ( "time" "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) @@ -48,13 +48,11 @@ README: * Execute remote Apis and test their replies(follow prepaid1cent scenario so we can test load in dataDb also). */ -var cfg *config.CGRConfig +var cgrCfg *config.CGRConfig var cgrRpc *rpc.Client -var cdrStor engine.CdrStorage +var cdrStor CdrStorage var httpClient *http.Client -var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args -var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here") var storDbType = flag.String("stordb_type", utils.MYSQL, "The type of the storDb database ") 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", "mediator_test1.cfg") @@ -64,11 +62,11 @@ func TestInitRatingDb(t *testing.T) { return } var err error - cfg, err = config.NewCGRConfigFromFile(&cfgPath) + cgrCfg, err = config.NewCGRConfigFromFile(&cfgPath) if err != nil { t.Fatal("Got config error: ", err.Error()) } - ratingDb, err := engine.ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, cfg.RatingDBName, cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding) + 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) } @@ -85,14 +83,14 @@ func TestInitStorDb(t *testing.T) { if *storDbType != utils.MYSQL { t.Fatal("Unsupported storDbType") } - var mysql *engine.MySQLStorage + var mysql *MySQLStorage var err error - if cdrStor, err = engine.ConfigureCdrStorage(cfg.StorDBType, cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass); err != nil { + if cdrStor, err = ConfigureCdrStorage(cgrCfg.StorDBType, cgrCfg.StorDBHost, cgrCfg.StorDBPort, cgrCfg.StorDBName, cgrCfg.StorDBUser, cgrCfg.StorDBPass); err != nil { t.Fatal("Error on opening database connection: ", err) } else { - mysql = cdrStor.(*engine.MySQLStorage) + mysql = cdrStor.(*MySQLStorage) } - if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, engine.CREATE_CDRS_TABLES_SQL)); err != nil { + if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, CREATE_CDRS_TABLES_SQL)); err != nil { t.Fatal("Error on mysql creation: ", err.Error()) return // No point in going further } @@ -127,7 +125,8 @@ func TestRpcConn(t *testing.T) { return } var err error - cgrRpc, err = rpc.Dial("tcp", cfg.RPCGOBListen) //ToDo: Fix with automatic config + //cgrRpc, err = rpc.Dial("tcp", cfg.RPCGOBListen) //ToDo: Fix with automatic config + cgrRpc, err = jsonrpc.Dial("tcp", cgrCfg.RPCJSONListen) if err != nil { t.Fatal("Could not connect to CGR GOB-RPC Server: ", err.Error()) } @@ -149,18 +148,18 @@ func TestPostCdrs(t *testing.T) { utils.ACCOUNT: []string{"1010"}, utils.SUBJECT: []string{"1010"}, utils.ANSWER_TIME: []string{"2013-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, engine.TEST_SQL) - if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", cfg.HTTPListen), cdrForm); err != nil { + cdrForm.Set(utils.CDRSOURCE, TEST_SQL) + if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", cgrCfg.HTTPListen), cdrForm); err != nil { t.Error(err.Error()) } } time.Sleep(100 * time.Millisecond) // Give time for CDRs to reach database - if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil { + if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil { t.Error(err) } else if len(storedCdrs) != 6 { // Make sure CDRs made it into StorDb t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs))) } - if nonErrorCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, false, false); err != nil { + if nonErrorCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, false, false); err != nil { t.Error(err) } else if len(nonErrorCdrs) != 0 { t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(nonErrorCdrs))) @@ -172,10 +171,10 @@ func TestInjectCdrs(t *testing.T) { if !*testLocal { return } - cgrCdr1 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "aaaaadsafdsaf", "cdrsource": engine.TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "rated", utils.DIRECTION: "*out", + cgrCdr1 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "aaaaadsafdsaf", "cdrsource": TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "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 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "baaaadsafdsaf", "cdrsource": engine.TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "rated", utils.DIRECTION: "*out", + cgrCdr2 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "baaaadsafdsaf", "cdrsource": TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "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"} for _, cdr := range []utils.CgrCdr{cgrCdr1, cgrCdr2} { @@ -183,12 +182,12 @@ func TestInjectCdrs(t *testing.T) { t.Error(err) } } - if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil { + if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil { t.Error(err) } else if len(storedCdrs) != 8 { // Make sure CDRs made it into StorDb t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs))) } - if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil { + if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil { t.Error(err) } else if len(nonRatedCdrs) != 2 { // Just two of them should be non-rated t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs))) @@ -220,12 +219,12 @@ func TestRateCdrs(t *testing.T) { } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) } - if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil { + if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil { t.Error(err) } else if len(nonRatedCdrs) != 0 { // All CDRs should be rated t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs))) } - if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil { + if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil { t.Error(err) } else if len(errRatedCdrs) != 8 { // The first 2 with errors should be still there before rerating t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) @@ -235,7 +234,7 @@ func TestRateCdrs(t *testing.T) { } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) } - if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil { + if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil { t.Error(err) } else if len(errRatedCdrs) != 4 { t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) diff --git a/engine/responder.go b/engine/responder.go index d0b879411..906a343a7 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -36,6 +36,7 @@ import ( type Responder struct { Bal *balancer2go.Balancer ExitChan chan bool + CdrSrv *CDRS } /* @@ -128,6 +129,17 @@ func (rs *Responder) GetDerivedChargers(attrs utils.AttrDerivedChargers, dcs *ut return nil } +func (rs *Responder) ProcessCdr(cdr *utils.StoredCdr, reply *string) error { + if rs.CdrSrv == nil { + return errors.New("CdrServerNotRunning") + } + if err := rs.CdrSrv.ProcessCdr(cdr); err != nil { + return err + } + *reply = utils.OK + return nil +} + func (rs *Responder) FlushCache(arg CallDescriptor, reply *float64) (err error) { if rs.Bal != nil { *reply, err = rs.callMethod(&arg, "Responder.FlushCache") @@ -287,6 +299,7 @@ type Connector interface { RefundIncrements(CallDescriptor, *float64) error GetMaxSessionTime(CallDescriptor, *float64) error GetDerivedChargers(utils.AttrDerivedChargers, *utils.DerivedChargers) error + ProcessCdr(*utils.StoredCdr, *string) error } type RPCClientConnector struct { @@ -316,3 +329,7 @@ func (rcc *RPCClientConnector) GetMaxSessionTime(cd CallDescriptor, resp *float6 func (rcc *RPCClientConnector) GetDerivedChargers(attrs utils.AttrDerivedChargers, dcs *utils.DerivedChargers) error { return rcc.Client.Call("ApierV1.GetDerivedChargers", attrs, dcs) } + +func (rcc *RPCClientConnector) ProcessCdr(cdr *utils.StoredCdr, reply *string) error { + return rcc.Client.Call("CDRSV1.ProcessCdr", cdr, reply) +} diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 48548b0e7..b0f3929e7 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -116,7 +116,7 @@ type CdrStorage interface { Storage SetCdr(*utils.StoredCdr) error SetRatedCdr(*utils.StoredCdr, string) error - GetStoredCdrs([]string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, + GetStoredCdrs([]string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, int64, int64, time.Time, time.Time, bool, bool, bool) ([]*utils.StoredCdr, error) RemStoredCdrs([]string) error } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 7120a367b..bf4ce822b 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -609,12 +609,12 @@ func (self *SQLStorage) SetRatedCdr(storedCdr *utils.StoredCdr, extraInfo string // Return a slice of CDRs from storDb using optional filters.a // ignoreErr - do not consider cdrs with rating errors // ignoreRated - do not consider cdrs which were already rated, including here the ones with errors -func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string, orderIdStart, orderIdEnd int64, - timeStart, timeEnd time.Time, ignoreErr, ignoreRated, ignoreDerived bool) ([]*utils.StoredCdr, error) { +func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects []string, + orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, ignoreErr, ignoreRated, ignoreDerived bool) ([]*utils.StoredCdr, error) { var cdrs []*utils.StoredCdr var q *bytes.Buffer // Need to query differently since in case of primary, unmediated CDRs some values will be missing if ignoreDerived { - q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid", + q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.account,%s.subject,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid AND %s.runid=%s.runid", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, @@ -633,6 +633,8 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS, + utils.TBL_COST_DETAILS, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, @@ -640,9 +642,14 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, - utils.TBL_RATED_CDRS)) + utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS, + utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS, + utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS)) } else { - q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid", + q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.account,%s.subject,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid AND %s.runid=%s.runid", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, @@ -661,6 +668,8 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources utils.TBL_RATED_CDRS, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS, + utils.TBL_COST_DETAILS, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, @@ -668,7 +677,12 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, - utils.TBL_RATED_CDRS)) + utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS, + utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS, + utils.TBL_RATED_CDRS, + utils.TBL_COST_DETAILS)) } fltr := new(bytes.Buffer) if len(cgrIds) != 0 { @@ -839,6 +853,34 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources } fltr.Write(qIds.Bytes()) } + if len(ratedAccounts) != 0 { + qIds := bytes.NewBufferString(" (") + for idx, ratedAccount := range ratedAccounts { + if idx != 0 { + qIds.WriteString(" OR") + } + qIds.WriteString(fmt.Sprintf(" %s.account='%s'", utils.TBL_COST_DETAILS, ratedAccount)) + } + qIds.WriteString(" )") + if fltr.Len() != 0 { + fltr.WriteString(" AND") + } + fltr.Write(qIds.Bytes()) + } + if len(ratedSubjects) != 0 { + qIds := bytes.NewBufferString(" (") + for idx, ratedSubject := range ratedSubjects { + if idx != 0 { + qIds.WriteString(" OR") + } + qIds.WriteString(fmt.Sprintf(" %s.subject='%s'", utils.TBL_COST_DETAILS, ratedSubject)) + } + qIds.WriteString(" )") + if fltr.Len() != 0 { + fltr.WriteString(" AND") + } + fltr.Write(qIds.Bytes()) + } if orderIdStart != 0 { if fltr.Len() != 0 { fltr.WriteString(" AND") @@ -893,14 +935,14 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources } defer rows.Close() for rows.Next() { - var cgrid, tor, accid, cdrhost, cdrsrc, reqtype, direction, tenant, category, account, subject, destination, runid sql.NullString + var cgrid, tor, accid, cdrhost, cdrsrc, reqtype, direction, tenant, category, account, subject, destination, runid, ratedAccount, ratedSubject sql.NullString var extraFields []byte var setupTime, answerTime mysql.NullTime var orderid int64 var usage, cost sql.NullFloat64 var extraFieldsMp map[string]string if err := rows.Scan(&cgrid, &orderid, &tor, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &category, &account, &subject, &destination, &setupTime, &answerTime, &usage, - &extraFields, &runid, &cost); err != nil { + &extraFields, &runid, &ratedAccount, &ratedSubject, &cost); err != nil { return nil, err } if len(extraFields) != 0 { @@ -914,7 +956,7 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources Direction: direction.String, Tenant: tenant.String, Category: category.String, Account: account.String, Subject: subject.String, Destination: destination.String, SetupTime: setupTime.Time, AnswerTime: answerTime.Time, Usage: usageDur, - ExtraFields: extraFieldsMp, MediationRunId: runid.String, Cost: cost.Float64, + ExtraFields: extraFieldsMp, MediationRunId: runid.String, RatedAccount: ratedAccount.String, RatedSubject: ratedSubject.String, Cost: cost.Float64, } if !cost.Valid { //There was no cost provided, will fakely insert 0 if we do not handle it and reflect on re-rating storCdr.Cost = -1 diff --git a/engine/storage_sql_local_test.go b/engine/storage_sql_local_test.go index 4ff49b062..316f1194a 100644 --- a/engine/storage_sql_local_test.go +++ b/engine/storage_sql_local_test.go @@ -217,13 +217,47 @@ func TestSetRatedCdr(t *testing.T) { } } +func TestCallCost(t *testing.T) { + if !*testLocal { + return + } + cgrId := utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()) + cc := &CallCost{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "91001", + Account: "8001", + Destination: "1002", + TOR: utils.VOICE, + Timespans: []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 13, 40, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC), + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 13, 41, 30, 0, time.UTC), + }, + }, + } + if err := mysqlDb.LogCallCost(cgrId, TEST_SQL, utils.DEFAULT_RUNID, cc); err != nil { + t.Error(err.Error()) + } + if ccRcv, err := mysqlDb.GetCallCostLog(cgrId, TEST_SQL, utils.DEFAULT_RUNID); err != nil { + t.Error(err.Error()) + } else if !reflect.DeepEqual(cc, ccRcv) { + t.Errorf("Expecting call cost: %v, received: %v", cc, ccRcv) + } +} + func TestGetStoredCdrs(t *testing.T) { if !*testLocal { return } var timeStart, timeEnd time.Time // All CDRs, no filter - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) @@ -231,7 +265,7 @@ func TestGetStoredCdrs(t *testing.T) { // Filter on cgrids if storedCdrs, err := mysqlDb.GetStoredCdrs([]string{utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), utils.Sha1("bbb2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String())}, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) @@ -239,41 +273,41 @@ func TestGetStoredCdrs(t *testing.T) { // Filter on cgrids plus reqType if storedCdrs, err := mysqlDb.GetStoredCdrs([]string{utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), utils.Sha1("bbb2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String())}, - nil, nil, nil, nil, []string{"prepaid"}, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, []string{"prepaid"}, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on runId if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, []string{utils.DEFAULT_RUNID}, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on TOR if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, []string{utils.SMS}, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 0 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple TOR if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, []string{utils.SMS, utils.VOICE}, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on cdrHost if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, []string{"192.168.1.2"}, - nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple cdrHost - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, []string{"192.168.1.1", "192.168.1.2"}, nil, nil, nil, nil, nil, nil, nil, nil, + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, []string{"192.168.1.1", "192.168.1.2"}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { @@ -281,46 +315,46 @@ func TestGetStoredCdrs(t *testing.T) { } // Filter on cdrSource if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, []string{"UNKNOWN"}, - nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple cdrSource if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, []string{"UNKNOWN", "UNKNOWN2"}, - nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on reqType if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"prepaid"}, - nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple reqType - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"prepaid", "pseudoprepaid"}, nil, nil, nil, nil, nil, nil, + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"prepaid", "pseudoprepaid"}, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on direction - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, []string{"*out"}, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, []string{"*out"}, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on tenant - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com"}, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com"}, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple tenants - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com", "cgrates.org"}, nil, nil, nil, nil, + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com", "cgrates.org"}, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { @@ -328,69 +362,83 @@ func TestGetStoredCdrs(t *testing.T) { } // Filter on tor if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, []string{"premium_call"}, - nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple tor if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, []string{"premium_call", "call"}, - nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on account if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1002"}, - nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple account if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1001", "1002"}, - nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on subject if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1000"}, - nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple subject if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1000", "1002"}, - nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on destPrefix if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, []string{"+498651"}, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, nil, nil, []string{"+498651"}, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on multiple destPrefixes - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1001", "+498651"}, + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1001", "+498651"}, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 4 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } + // Filter on ratedAccount + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, []string{"8001"}, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + t.Error(err.Error()) + } else if len(storedCdrs) != 1 { + t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) + } + // Filter on ratedSubject + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, []string{"91001"}, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + t.Error(err.Error()) + } else if len(storedCdrs) != 1 { + t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) + } // Filter on ignoreErr - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, true, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, true, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on ignoreRated var orderIdStart, orderIdEnd int64 // Capture also orderIds for the next test - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, true, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, true, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 5 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) @@ -405,13 +453,13 @@ func TestGetStoredCdrs(t *testing.T) { } } // Filter on orderIdStart - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on orderIdStart and orderIdEnd - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, orderIdEnd+1, timeStart, timeEnd, + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, orderIdEnd+1, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 5 { @@ -419,60 +467,33 @@ func TestGetStoredCdrs(t *testing.T) { } // Filter on timeStart timeStart = time.Date(2013, 11, 8, 8, 0, 0, 0, time.UTC) - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 5 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on timeStart and timeEnd timeEnd = time.Date(2013, 12, 1, 8, 0, 0, 0, time.UTC) - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Combined filter if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"rated"}, nil, nil, nil, nil, nil, - nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on ignoreDerived - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, true); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, true); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { // ToDo: Recheck this value t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } } -func TestCallCost(t *testing.T) { - if !*testLocal { - return - } - cgrId := utils.Sha1("bbb1", "123") - cc := &CallCost{ - Timespans: []*TimeSpan{ - &TimeSpan{ - TimeStart: time.Date(2013, 9, 10, 13, 40, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC), - }, - &TimeSpan{ - TimeStart: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 10, 13, 41, 30, 0, time.UTC), - }, - }, - } - if err := mysqlDb.LogCallCost(cgrId, TEST_SQL, TEST_SQL, cc); err != nil { - t.Error(err.Error()) - } - if ccRcv, err := mysqlDb.GetCallCostLog(cgrId, TEST_SQL, TEST_SQL); err != nil { - t.Error(err.Error()) - } else if !reflect.DeepEqual(cc, ccRcv) { - t.Errorf("Expecting call cost: %v, received: %v", cc, ccRcv) - } -} - func TestRemStoredCdrs(t *testing.T) { if !*testLocal { return @@ -482,7 +503,7 @@ func TestRemStoredCdrs(t *testing.T) { if err := mysqlDb.RemStoredCdrs([]string{cgrIdB1}); err != nil { t.Error(err.Error()) } - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 7 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) @@ -503,7 +524,7 @@ func TestRemStoredCdrs(t *testing.T) { cgrIdB2, cgrIdB3}); err != nil { t.Error(err.Error()) } - if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { + if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 0 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) diff --git a/general_tests/fsevcorelate_test.go b/general_tests/fsevcorelate_test.go index bccdcba41..394080910 100644 --- a/general_tests/fsevcorelate_test.go +++ b/general_tests/fsevcorelate_test.go @@ -21,8 +21,8 @@ package general_tests import ( "testing" - "github.com/cgrates/cgrates/cdrs" "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/sessionmanager" ) @@ -215,12 +215,12 @@ var jsonCdr = []byte(`{"core-uuid":"feef0b51-7fdf-4c4a-878e-aff233752de2","chann func TestEvCorelate(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() - cdrs.New(nil, nil, nil, cfg) // So we can set the package cfg + engine.NewCdrS(nil, nil, nil, cfg) // So we can set the package cfg answerEv := new(sessionmanager.FSEvent).New(answerEvent) if answerEv.GetName() != "CHANNEL_ANSWER" { t.Error("Event not parsed correctly: ", answerEv) } - cdrEv, err := cdrs.NewFSCdr(jsonCdr) + cdrEv, err := engine.NewFSCdr(jsonCdr) if err != nil { t.Errorf("Error loading cdr: %v", err.Error()) } else if cdrEv.AsStoredCdr().AccId != "86cfd6e2-dbda-45a3-b59d-f683ec368e8b" { diff --git a/local_test.sh b/local_test.sh index fbfea7c13..b34696cb6 100755 --- a/local_test.sh +++ b/local_test.sh @@ -8,8 +8,6 @@ go test github.com/cgrates/cgrates/engine -local en=$? go test github.com/cgrates/cgrates/cdrc -local cdrc=$? -go test github.com/cgrates/cgrates/mediator -local -med=$? go test github.com/cgrates/cgrates/config -local cfg=$? go test github.com/cgrates/cgrates/utils -local @@ -20,5 +18,5 @@ utl=$? -exit $gen && $ap && $en && $cdrc && $med && $cfg && $utl +exit $gen && $ap && $en && $cdrc && $cfg && $utl diff --git a/mediator/fsfilecsvcdr.go b/mediator/fsfilecsvcdr.go deleted file mode 100644 index be3e004b3..000000000 --- a/mediator/fsfilecsvcdr.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -Rating system designed to be used in VoIP Carriers World -Copyright (C) 2013 ITsysCOM - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see -*/ - -package mediator - -import ( - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/utils" - "strconv" - "time" -) - -type FScsvCDR struct { - rowData []string // The original row extracted form csv file - accIdIdx, - subjectIdx, - reqtypeIdx, - directionIdx, - tenantIdx, - torIdx, - accountIdx, - destinationIdx, - setupTimeIdx, - answerTimeIdx, - durationIdx int // Field indexes - cgrCfg *config.CGRConfig // CGR Config instance -} - -func NewFScsvCDR(cdrRow []string, accIdIdx, subjectIdx, reqtypeIdx, directionIdx, tenantIdx, torIdx, - accountIdx, destinationIdx, setupTimeIdx, answerTimeIdx, durationIdx int, cfg *config.CGRConfig) (*FScsvCDR, error) { - fscdr := FScsvCDR{cdrRow, accIdIdx, subjectIdx, reqtypeIdx, directionIdx, tenantIdx, - torIdx, accountIdx, destinationIdx, setupTimeIdx, answerTimeIdx, durationIdx, cfg} - return &fscdr, nil -} - -func (self *FScsvCDR) GetCgrId() string { - return utils.Sha1(self.rowData[self.accIdIdx], self.rowData[self.setupTimeIdx]) -} - -func (self *FScsvCDR) GetAccId() string { - return self.rowData[self.accIdIdx] -} - -func (self *FScsvCDR) GetCdrHost() string { - return utils.LOCALHOST // ToDo: Maybe extract dynamically the external IP address here -} - -func (self *FScsvCDR) GetDirection() string { - return "*out" -} - -func (self *FScsvCDR) GetSubject() string { - return self.rowData[self.subjectIdx] -} - -func (self *FScsvCDR) GetAccount() string { - return self.rowData[self.accountIdx] -} - -func (self *FScsvCDR) GetDestination() string { - return self.rowData[self.destinationIdx] -} - -func (self *FScsvCDR) GetTOR() string { - return self.rowData[self.torIdx] -} - -func (self *FScsvCDR) GetTenant() string { - return self.rowData[self.tenantIdx] -} - -func (self *FScsvCDR) GetReqType() string { - if self.reqtypeIdx == -1 { - return self.cgrCfg.DefaultReqType - } - return self.rowData[self.reqtypeIdx] -} - -func (self *FScsvCDR) GetAnswerTime() (time.Time, error) { - return time.Parse("2006-01-02 15:04:05", self.rowData[self.answerTimeIdx]) -} - -func (self *FScsvCDR) GetDuration() int64 { - dur, _ := strconv.ParseInt(self.rowData[self.durationIdx], 0, 64) - return dur -} - -func (self *FScsvCDR) GetExtraFields() map[string]string { - return nil -} diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 412715fe5..6b924cee6 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -1,6 +1,6 @@ /* Real-time Charging System for Telecom & ISP environments -Copyright (C) 2012-2014 ITsysCOM GmbH +Copyright (C) ITsysCOM GmbH This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,14 +45,14 @@ type FSSessionManager struct { loggerDB engine.LogStorage } -func NewFSSessionManager(storage engine.LogStorage, connector engine.Connector, debitPeriod time.Duration) *FSSessionManager { +func NewFSSessionManager(cgrCfg *config.CGRConfig, storage engine.LogStorage, connector engine.Connector, debitPeriod time.Duration) *FSSessionManager { + cfg = cgrCfg // make config global return &FSSessionManager{loggerDB: storage, connector: connector, debitPeriod: debitPeriod} } // Connects to the freeswitch mod_event_socket server and starts // listening for events. -func (sm *FSSessionManager) Connect(cgrCfg *config.CGRConfig) (err error) { - cfg = cgrCfg // make config global +func (sm *FSSessionManager) Connect() (err error) { eventFilters := map[string]string{"Call-Direction": "inbound"} if fsock.FS, err = fsock.NewFSock(cfg.FreeswitchServer, cfg.FreeswitchPass, cfg.FreeswitchReconnects, sm.createHandlers(), eventFilters, engine.Logger.(*syslog.Writer)); err != nil { return err @@ -99,14 +99,24 @@ func (sm *FSSessionManager) GetSession(uuid string) *Session { } // Disconnects a session by sending hangup command to freeswitch -func (sm *FSSessionManager) DisconnectSession(uuid string, notify string) { - // engine.Logger.Debug(fmt.Sprintf("Session: %+v", s.uuid)) - _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_notify %s\n\n", uuid, notify)) - if err != nil { - engine.Logger.Err(fmt.Sprintf(" Could not send disconect api notification to freeswitch: %v", err)) +func (sm *FSSessionManager) DisconnectSession(uuid, notify, destnr string) { + if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_notify %s\n\n", uuid, notify)); err != nil { + engine.Logger.Err(fmt.Sprintf(" Could not send disconect api notification to freeswitch: %s", err.Error())) } - err = fsock.FS.SendMsgCmd(uuid, map[string]string{"call-command": "hangup", "hangup-cause": "MANAGER_REQUEST"}) // without + sign - if err != nil { + if notify == INSUFFICIENT_FUNDS { + if len(cfg.FSEmptyBalanceContext) != 0 { + if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_transfer %s %s %s\n\n", uuid, destnr, cfg.FSEmptyBalanceContext)); err != nil { + engine.Logger.Err(" Could not transfer the call to empty balance context") + } + return + } else if len(cfg.FSEmptyBalanceAnnFile) != 0 { + if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_broadcast %s playback!manager_request::%s aleg\n\n", uuid, cfg.FSEmptyBalanceAnnFile)); err != nil { + engine.Logger.Err(fmt.Sprintf(" Could not send uuid_broadcast to freeswitch: %s", err.Error())) + } + return + } + } + if err := fsock.FS.SendMsgCmd(uuid, map[string]string{"call-command": "hangup", "hangup-cause": "MANAGER_REQUEST"}); err != nil { engine.Logger.Err(fmt.Sprintf(" Could not send disconect msg to freeswitch: %v", err)) } return @@ -124,7 +134,8 @@ func (sm *FSSessionManager) RemoveSession(uuid string) { // Sets the call timeout valid of starting of the call func (sm *FSSessionManager) setMaxCallDuration(uuid string, maxDur time.Duration) error { - _, err := fsock.FS.SendApiCmd(fmt.Sprintf("sched_hangup +%d %s\n\n", int(maxDur.Seconds()), uuid)) + // _, err := fsock.FS.SendApiCmd(fmt.Sprintf("sched_hangup +%d %s\n\n", int(maxDur.Seconds()), uuid)) + _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s execute_on_answer sched_hangup +%d alloted_timeout\n\n", uuid, int(maxDur.Seconds()))) if err != nil { engine.Logger.Err("could not send sched_hangup command to freeswitch") return err @@ -138,8 +149,7 @@ func (sm *FSSessionManager) unparkCall(uuid, call_dest_nb, notify string) { if err != nil { engine.Logger.Err(" Could not send unpark api notification to freeswitch") } - _, err = fsock.FS.SendApiCmd(fmt.Sprintf("uuid_transfer %s %s\n\n", uuid, call_dest_nb)) - if err != nil { + if _, err = fsock.FS.SendApiCmd(fmt.Sprintf("uuid_transfer %s %s\n\n", uuid, call_dest_nb)); err != nil { engine.Logger.Err(" Could not send unpark api call to freeswitch") } } @@ -223,7 +233,7 @@ func (sm *FSSessionManager) OnChannelPark(ev Event) { func (sm *FSSessionManager) OnChannelAnswer(ev Event) { if ev.MissingParameter() { - sm.DisconnectSession(ev.GetUUID(), MISSING_PARAMETER) + sm.DisconnectSession(ev.GetUUID(), MISSING_PARAMETER, "") } if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_reqtype %s\n\n", ev.GetUUID(), ev.GetReqType(""))); err != nil { engine.Logger.Err(fmt.Sprintf("Error on attempting to overwrite cgr_type in chan variables: %v", err)) @@ -233,7 +243,7 @@ func (sm *FSSessionManager) OnChannelAnswer(ev Event) { var dcs utils.DerivedChargers if err := sm.connector.GetDerivedChargers(attrsDC, &dcs); err != nil { engine.Logger.Err(fmt.Sprintf(" OnAnswer: could not get derived charging for event %s: %s", ev.GetUUID(), err.Error())) - sm.DisconnectSession(ev.GetUUID(), SYSTEM_ERROR) // Disconnect the session since we are not able to process sessions + sm.DisconnectSession(ev.GetUUID(), SYSTEM_ERROR, "") // Disconnect the session since we are not able to process sessions return } dcs, _ = dcs.AppendDefaultRun() diff --git a/sessionmanager/osipsevent.go b/sessionmanager/osipsevent.go new file mode 100644 index 000000000..37431f991 --- /dev/null +++ b/sessionmanager/osipsevent.go @@ -0,0 +1,217 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package sessionmanager + +import ( + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/osipsdagram" + "strings" + "time" +) + +/* +/*&{Name:E_ACC_CDR AttrValues:map[to_tag:5ec6e925 cgr_account:dan setuptime:1 created:1406312794 method:INVITE callid:Y2I5ZDYzMDkzM2YzYjhlZjA2Y2ZhZTJmZTc4MGU4NDI. + // sip_reason:OK time:1406312795 cgr_reqtype:prepaid cgr_destination:dan cgr_subject:dan sip_code:200 duration:7 from_tag:a5716471] Values:[]}*/ + +const ( + FROM_TAG = "from_tag" + TO_TAG = "to_tag" + CALLID = "callid" + CGR_CATEGORY = "cgr_category" + CGR_REQTYPE = "cgr_reqtype" + CGR_TENANT = "cgr_tenant" + CGR_SUBJECT = "cgr_subject" + CGR_ACCOUNT = "cgr_account" + CGR_DESTINATION = "cgr_destination" + TIME = "time" + SETUP_DURATION = "setuptime" + OSIPS__SETUP_TIME = "created" + OSIPS_DURATION = "duration" +) + +func NewOsipsEvent(osipsDagramEvent *osipsdagram.OsipsEvent) (*OsipsEvent, error) { + return &OsipsEvent{osipsEvent: osipsDagramEvent}, nil +} + +type OsipsEvent struct { + osipsEvent *osipsdagram.OsipsEvent +} + +func (osipsev *OsipsEvent) New(evStr string) Event { + return osipsev +} + +func (osipsev *OsipsEvent) GetName() string { + return osipsev.osipsEvent.Name +} + +func (osipsev *OsipsEvent) GetCgrId() string { + setupTime, _ := osipsev.GetSetupTime(utils.META_DEFAULT) + return utils.Sha1(osipsev.GetUUID(), setupTime.UTC().String()) +} + +func (osipsev *OsipsEvent) GetUUID() string { + return osipsev.osipsEvent.AttrValues[CALLID] + ";" + osipsev.osipsEvent.AttrValues[FROM_TAG] + ";" + osipsev.osipsEvent.AttrValues[TO_TAG] +} + +func (osipsev *OsipsEvent) GetDirection(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.OUT +} + +func (osipsev *OsipsEvent) GetSubject(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_SUBJECT]) +} + +func (osipsev *OsipsEvent) GetAccount(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_ACCOUNT]) +} + +func (osipsev *OsipsEvent) GetDestination(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_DESTINATION]) +} + +func (osipsev *OsipsEvent) GetCallDestNr(fieldName string) string { + return osipsev.GetDestination(fieldName) +} + +func (osipsev *OsipsEvent) GetCategory(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_CATEGORY], config.CgrConfig().DefaultCategory) +} + +func (osipsev *OsipsEvent) GetTenant(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_TENANT], config.CgrConfig().DefaultTenant) +} +func (osipsev *OsipsEvent) GetReqType(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_REQTYPE], config.CgrConfig().DefaultReqType) +} +func (osipsev *OsipsEvent) GetSetupTime(fieldName string) (time.Time, error) { + sTimeStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[OSIPS__SETUP_TIME]) + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + sTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):] + } else if fieldName == utils.META_DEFAULT { + sTimeStr = osipsev.osipsEvent.AttrValues[OSIPS__SETUP_TIME] + } + return utils.ParseTimeDetectLayout(sTimeStr) +} +func (osipsev *OsipsEvent) GetAnswerTime(fieldName string) (time.Time, error) { + aTimeStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[TIME]) + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + aTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):] + } else if fieldName == utils.META_DEFAULT { + aTimeStr = osipsev.osipsEvent.AttrValues[TIME] + } + return utils.ParseTimeDetectLayout(aTimeStr) +} +func (osipsev *OsipsEvent) GetEndTime() (time.Time, error) { + var nilTime time.Time + aTime, err := osipsev.GetAnswerTime(utils.META_DEFAULT) + if err != nil { + return nilTime, err + } + dur, err := osipsev.GetDuration(utils.META_DEFAULT) + if err != nil { + return nilTime, err + } + return aTime.Add(dur), nil +} +func (osipsev *OsipsEvent) GetDuration(fieldName string) (time.Duration, error) { + durStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[OSIPS_DURATION]) + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + durStr = fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.ParseDurationWithSecs(durStr) +} +func (osipsev *OsipsEvent) MissingParameter() bool { + var nilTime time.Time + var nilDur time.Duration + aTime, _ := osipsev.GetAnswerTime(utils.META_DEFAULT) + dur, _ := osipsev.GetDuration(utils.META_DEFAULT) + return len(osipsev.GetUUID()) == 0 || + len(osipsev.GetAccount(utils.META_DEFAULT)) == 0 || + len(osipsev.GetSubject(utils.META_DEFAULT)) == 0 || + len(osipsev.GetDestination(utils.META_DEFAULT)) == 0 || + aTime == nilTime || + dur == nilDur +} +func (osipsev *OsipsEvent) ParseEventValue(*utils.RSRField) string { + return "" +} +func (osipsev *OsipsEvent) PassesFieldFilter(*utils.RSRField) (bool, string) { + return false, "" +} +func (osipsev *OsipsEvent) GetExtraFields() map[string]string { + primaryFields := []string{"to_tag", "setuptime", "created", "method", "callid", "sip_reason", "time", "sip_code", "duration", "from_tag", + "cgr_tenant", "cgr_category", "cgr_reqtype", "cgr_account", "cgr_subject", "cgr_destination"} + extraFields := make(map[string]string) + for field, val := range osipsev.osipsEvent.AttrValues { + if !utils.IsSliceMember(primaryFields, field) { + extraFields[field] = val + } + } + return extraFields +} +func (osipsEv *OsipsEvent) GetOriginatorIP() string { + if osipsEv.osipsEvent == nil || osipsEv.osipsEvent.OriginatorAddress == nil { + return "" + } + return osipsEv.osipsEvent.OriginatorAddress.IP.String() +} +func (osipsEv *OsipsEvent) AsStoredCdr() *utils.StoredCdr { + storCdr := new(utils.StoredCdr) + storCdr.CgrId = osipsEv.GetCgrId() + storCdr.TOR = utils.VOICE + storCdr.AccId = osipsEv.GetUUID() + storCdr.CdrHost = osipsEv.GetOriginatorIP() + storCdr.CdrSource = "OSIPS_" + osipsEv.GetName() + storCdr.ReqType = osipsEv.GetReqType(utils.META_DEFAULT) + storCdr.Direction = osipsEv.GetDirection(utils.META_DEFAULT) + storCdr.Tenant = osipsEv.GetTenant(utils.META_DEFAULT) + storCdr.Category = osipsEv.GetCategory(utils.META_DEFAULT) + storCdr.Account = osipsEv.GetAccount(utils.META_DEFAULT) + storCdr.Subject = osipsEv.GetSubject(utils.META_DEFAULT) + storCdr.Destination = osipsEv.GetDestination(utils.META_DEFAULT) + storCdr.SetupTime, _ = osipsEv.GetSetupTime(utils.META_DEFAULT) + storCdr.AnswerTime, _ = osipsEv.GetAnswerTime(utils.META_DEFAULT) + storCdr.Usage, _ = osipsEv.GetDuration(utils.META_DEFAULT) + storCdr.ExtraFields = osipsEv.GetExtraFields() + storCdr.Cost = -1 + return storCdr +} diff --git a/sessionmanager/osipsevent_test.go b/sessionmanager/osipsevent_test.go new file mode 100644 index 000000000..5925341a5 --- /dev/null +++ b/sessionmanager/osipsevent_test.go @@ -0,0 +1,135 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package sessionmanager + +import ( + "net" + "reflect" + "testing" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/osipsdagram" +) + +var addr, _ = net.ResolveUDPAddr("udp", "172.16.254.77:42574") +var osipsEv = &OsipsEvent{osipsEvent: &osipsdagram.OsipsEvent{Name: "E_ACC_CDR", + AttrValues: map[string]string{"to_tag": "4ea9687f", "cgr_account": "dan", "setuptime": "7", "created": "1406370492", "method": "INVITE", "callid": "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", + "sip_reason": "OK", "time": "1406370499", "cgr_reqtype": "prepaid", "cgr_subject": "dan", "cgr_destination": "+4986517174963", "cgr_tenant": "itsyscom.com", "sip_code": "200", + "duration": "20", "from_tag": "eb082607", "extra1": "val1", "extra2": "val2"}, OriginatorAddress: addr}} + +func TestOsipsEventInterface(t *testing.T) { + var _ Event = Event(osipsEv) +} + +func TestOsipsEventParseStatic(t *testing.T) { + setupTime, _ := osipsEv.GetSetupTime("^2013-12-07 08:42:24") + answerTime, _ := osipsEv.GetAnswerTime("^2013-12-07 08:42:24") + dur, _ := osipsEv.GetDuration("^60s") + if osipsEv.GetReqType("^test") != "test" || + osipsEv.GetDirection("^test") != "test" || + osipsEv.GetTenant("^test") != "test" || + osipsEv.GetCategory("^test") != "test" || + osipsEv.GetAccount("^test") != "test" || + osipsEv.GetSubject("^test") != "test" || + osipsEv.GetDestination("^test") != "test" || + setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || + answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || + dur != time.Duration(60)*time.Second { + t.Error("Values out of static not matching", + osipsEv.GetReqType("^test") != "test", + osipsEv.GetDirection("^test") != "test", + osipsEv.GetTenant("^test") != "test", + osipsEv.GetCategory("^test") != "test", + osipsEv.GetAccount("^test") != "test", + osipsEv.GetSubject("^test") != "test", + osipsEv.GetDestination("^test") != "test", + setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), + answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), + dur != time.Duration(60)*time.Second) + } +} + +func TestOsipsEventGetValues(t *testing.T) { + cfg, _ = config.NewDefaultCGRConfig() + config.SetCgrConfig(cfg) + setupTime, _ := osipsEv.GetSetupTime(utils.META_DEFAULT) + answerTime, _ := osipsEv.GetAnswerTime(utils.META_DEFAULT) + endTime, _ := osipsEv.GetEndTime() + dur, _ := osipsEv.GetDuration(utils.META_DEFAULT) + if osipsEv.GetName() != "E_ACC_CDR" || + osipsEv.GetCgrId() != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ"+";"+"eb082607"+";"+"4ea9687f", setupTime.UTC().String()) || + osipsEv.GetUUID() != "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f" || + osipsEv.GetDirection(utils.META_DEFAULT) != utils.OUT || + osipsEv.GetSubject(utils.META_DEFAULT) != "dan" || + osipsEv.GetAccount(utils.META_DEFAULT) != "dan" || + osipsEv.GetDestination(utils.META_DEFAULT) != "+4986517174963" || + osipsEv.GetCallDestNr(utils.META_DEFAULT) != "+4986517174963" || + osipsEv.GetCategory(utils.META_DEFAULT) != cfg.DefaultCategory || + osipsEv.GetTenant(utils.META_DEFAULT) != "itsyscom.com" || + osipsEv.GetReqType(utils.META_DEFAULT) != "prepaid" || + setupTime != time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local) || + answerTime != time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local) || + endTime != time.Date(2014, 7, 26, 12, 28, 39, 0, time.Local) || + dur != time.Duration(20*time.Second) || + osipsEv.GetOriginatorIP() != "172.16.254.77" { + t.Error("GetValues not matching: ", osipsEv.GetName() != "E_ACC_CDR", + osipsEv.GetCgrId() != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ"+";"+"eb082607"+";"+"4ea9687f", setupTime.UTC().String()), + osipsEv.GetUUID() != "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", + osipsEv.GetDirection(utils.META_DEFAULT) != utils.OUT, + osipsEv.GetSubject(utils.META_DEFAULT) != "dan", + osipsEv.GetAccount(utils.META_DEFAULT) != "dan", + osipsEv.GetDestination(utils.META_DEFAULT) != "+4986517174963", + osipsEv.GetCallDestNr(utils.META_DEFAULT) != "+4986517174963", + osipsEv.GetCategory(utils.META_DEFAULT) != cfg.DefaultCategory, + osipsEv.GetTenant(utils.META_DEFAULT) != "itsyscom.com", + osipsEv.GetReqType(utils.META_DEFAULT) != "prepaid", + setupTime != time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local), + answerTime != time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local), + endTime != time.Date(2014, 7, 26, 12, 28, 39, 0, time.Local), + dur != time.Duration(20*time.Second), + osipsEv.GetOriginatorIP() != "172.16.254.77", + ) + } +} + +func TestOsipsEventMissingParameter(t *testing.T) { + if osipsEv.MissingParameter() { + t.Errorf("Wrongly detected missing parameter: %+v", osipsEv) + } + osipsEv2 := &OsipsEvent{osipsEvent: &osipsdagram.OsipsEvent{Name: "E_ACC_CDR", + AttrValues: map[string]string{"to_tag": "4ea9687f", "cgr_account": "dan", "setuptime": "7", "created": "1406370492", "method": "INVITE", "callid": "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", + "sip_reason": "OK", "time": "1406370499", "cgr_reqtype": "prepaid", "cgr_subject": "dan", "cgr_tenant": "itsyscom.com", "sip_code": "200", + "duration": "20", "from_tag": "eb082607"}}} + if !osipsEv2.MissingParameter() { + t.Error("Failed to detect missing parameter.") + } +} + +func TestOsipsEventAsStoredCdr(t *testing.T) { + eStoredCdr := &utils.StoredCdr{CgrId: utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local).UTC().String()), + TOR: utils.VOICE, AccId: "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", CdrHost: "172.16.254.77", CdrSource: "OSIPS_E_ACC_CDR", ReqType: "prepaid", + Direction: utils.OUT, Tenant: "itsyscom.com", Category: "call", Account: "dan", Subject: "dan", + Destination: "+4986517174963", SetupTime: time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local), AnswerTime: time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local), + Usage: time.Duration(20) * time.Second, ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1} + if storedCdr := osipsEv.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) { + t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) + } +} diff --git a/sessionmanager/osipssm.go b/sessionmanager/osipssm.go new file mode 100644 index 000000000..20db4cac8 --- /dev/null +++ b/sessionmanager/osipssm.go @@ -0,0 +1,146 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package sessionmanager + +import ( + "bytes" + "errors" + "fmt" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/osipsdagram" + "strings" + "time" +) + +func NewOSipsSessionManager(cfg *config.CGRConfig, cdrsrv engine.Connector) (*OsipsSessionManager, error) { + osm := &OsipsSessionManager{cgrCfg: cfg, cdrsrv: cdrsrv} + osm.eventHandlers = map[string][]func(*osipsdagram.OsipsEvent){ + "E_OPENSIPS_START": []func(*osipsdagram.OsipsEvent){osm.OnOpensipsStart}, + "E_ACC_CDR": []func(*osipsdagram.OsipsEvent){osm.OnCdr}, + } + return osm, nil +} + +type OsipsSessionManager struct { + cgrCfg *config.CGRConfig + cdrsrv engine.Connector + eventHandlers map[string][]func(*osipsdagram.OsipsEvent) + evSubscribeStop *chan struct{} // Reference towards the channel controlling subscriptions, keep it as reference so we do not need to copy it + stopServing chan struct{} // Stop serving datagrams + miConn *osipsdagram.OsipsMiDatagramConnector +} + +func (osm *OsipsSessionManager) Connect() (err error) { + osm.stopServing = make(chan struct{}) + if osm.miConn, err = osipsdagram.NewOsipsMiDatagramConnector(osm.cgrCfg.OsipsMiAddr, osm.cgrCfg.OsipsReconnects); err != nil { + return fmt.Errorf("Cannot connect to OpenSIPS at %s, error: %s", osm.cgrCfg.OsipsMiAddr, err.Error()) + } + evSubscribeStop := make(chan struct{}) + osm.evSubscribeStop = &evSubscribeStop + defer close(*osm.evSubscribeStop) // Stop subscribing on disconnect + go osm.SubscribeEvents(evSubscribeStop) + evsrv, err := osipsdagram.NewEventServer(osm.cgrCfg.OsipsListenUdp, osm.eventHandlers) + if err != nil { + engine.Logger.Err(fmt.Sprintf(" Cannot initialize datagram server, error: <%s>", err.Error())) + return + } + engine.Logger.Info(fmt.Sprintf(" Listening for datagram events at <%s>", osm.cgrCfg.OsipsListenUdp)) + evsrv.ServeEvents(osm.stopServing) // Will break through stopServing on error in other places + return errors.New(" Stopped reading events") +} + +func (osm *OsipsSessionManager) DisconnectSession(uuid, notify, destnr string) { + return +} +func (osm *OsipsSessionManager) RemoveSession(uuid string) { + return +} +func (osm *OsipsSessionManager) MaxDebit(cd *engine.CallDescriptor, cc *engine.CallCost) error { + return nil +} +func (osm *OsipsSessionManager) GetDebitPeriod() time.Duration { + var nilDuration time.Duration + return nilDuration +} +func (osm *OsipsSessionManager) GetDbLogger() engine.LogStorage { + return nil +} +func (osm *OsipsSessionManager) Shutdown() error { + return nil +} + +// Event Handlers + +// Automatic subscribe to OpenSIPS for events, trigered on Connect or OpenSIPS restart +func (osm *OsipsSessionManager) SubscribeEvents(evStop chan struct{}) error { + for { + select { + case <-evStop: // Break this loop from outside + return nil + default: + subscribeInterval := osm.cgrCfg.OsipsEvSubscInterval + time.Duration(1)*time.Second // Avoid concurrency on expiry + listenAddrSplt := strings.Split(osm.cgrCfg.OsipsListenUdp, ":") + portListen := listenAddrSplt[1] + addrListen := listenAddrSplt[0] + if len(addrListen) == 0 { //Listen on all addresses, try finding out from mi connection + if localAddr := osm.miConn.LocallAddr(); localAddr != nil { + addrListen = strings.Split(localAddr.String(), ":")[1] + } + } + for eventName := range osm.eventHandlers { + if eventName == "E_OPENSIPS_START" { // Do not subscribe for start since this should be hardcoded + continue + } + cmd := fmt.Sprintf(":event_subscribe:\n%s\nudp:%s:%s\n%d\n", eventName, addrListen, portListen, int(subscribeInterval.Seconds())) + success := false + for attempts := 0; attempts < osm.cgrCfg.OsipsReconnects; attempts++ { + if reply, err := osm.miConn.SendCommand([]byte(cmd)); err == nil && bytes.HasPrefix(reply, []byte("200 OK")) { + success = true + break + } + time.Sleep(time.Duration((attempts+1)/2) * time.Second) // Allow OpenSIPS to recover from errors + continue // Try again + } + if !success { + close(osm.stopServing) // Do not serve anymore since we got errors on subscribing + return errors.New("Failed subscribing to OpenSIPS events") + } + } + time.Sleep(osm.cgrCfg.OsipsEvSubscInterval) + } + } + return nil +} + +func (osm *OsipsSessionManager) OnOpensipsStart(cdrDagram *osipsdagram.OsipsEvent) { + close(*osm.evSubscribeStop) // Cancel previous subscribes + evStop := make(chan struct{}) + osm.evSubscribeStop = &evStop + go osm.SubscribeEvents(evStop) +} + +func (osm *OsipsSessionManager) OnCdr(cdrDagram *osipsdagram.OsipsEvent) { + var reply string + osipsEv, _ := NewOsipsEvent(cdrDagram) + storedCdr := osipsEv.AsStoredCdr() + if err := osm.cdrsrv.ProcessCdr(storedCdr, &reply); err != nil { + engine.Logger.Err(fmt.Sprintf(" Failed processing CDR, cgrid: %s, accid: %s, error: <%s>", storedCdr.CgrId, storedCdr.AccId, err.Error())) + } +} diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 792902d56..c80ceff4b 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -25,6 +25,7 @@ import ( "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" + "github.com/cgrates/fsock" ) // Session type holding the call information fields, a session delegate for specific @@ -110,13 +111,18 @@ func (s *Session) debitLoop(runIdx int) { cc := new(engine.CallCost) if err := s.sessionManager.MaxDebit(&nextCd, cc); err != nil { engine.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) - s.sessionManager.DisconnectSession(s.uuid, SYSTEM_ERROR) + s.sessionManager.DisconnectSession(s.uuid, SYSTEM_ERROR, "") return } if cc.GetDuration() == 0 { - s.sessionManager.DisconnectSession(s.uuid, INSUFFICIENT_FUNDS) + s.sessionManager.DisconnectSession(s.uuid, INSUFFICIENT_FUNDS, nextCd.Destination) return } + if cc.GetDuration() <= cfg.FSMinDurLowBalance && len(cfg.FSLowBalanceAnnFile) != 0 { + if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_broadcast %s %s aleg\n\n", s.uuid, cfg.FSLowBalanceAnnFile)); err != nil { + engine.Logger.Err(fmt.Sprintf(" Could not send uuid_broadcast to freeswitch: %s", err.Error())) + } + } s.sessionRuns[runIdx].callCosts = append(s.sessionRuns[runIdx].callCosts, cc) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd // update call duration with real debited duration diff --git a/sessionmanager/sessionmanager.go b/sessionmanager/sessionmanager.go index 2367c0a6b..c05dab8d2 100644 --- a/sessionmanager/sessionmanager.go +++ b/sessionmanager/sessionmanager.go @@ -1,6 +1,6 @@ /* Real-time Charging System for Telecom & ISP environments -Copyright (C) 2012-2014 ITsysCOM GmbH +Copyright (C) ITsysCOM GmbH This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,13 +21,12 @@ package sessionmanager import ( "time" - "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" ) type SessionManager interface { - Connect(*config.CGRConfig) error - DisconnectSession(string, string) + Connect() error + DisconnectSession(string, string, string) RemoveSession(string) MaxDebit(*engine.CallDescriptor, *engine.CallCost) error GetDebitPeriod() time.Duration diff --git a/test.sh b/test.sh index 984caa3ae..5497f0aaf 100755 --- a/test.sh +++ b/test.sh @@ -1,13 +1,11 @@ -#! /usr/bin/env sh +1#! /usr/bin/env sh go test -i github.com/cgrates/cgrates/engine go test -i github.com/cgrates/cgrates/sessionmanager go test -i github.com/cgrates/cgrates/config go test -i github.com/cgrates/cgrates/cmd/cgr-engine -go test -i github.com/cgrates/cgrates/mediator go test -i github.com/cgrates/fsock go test -i github.com/cgrates/cgrates/cache2go -go test -i github.com/cgrates/cgrates/cdrs go test -i github.com/cgrates/cgrates/cdrc go test -i github.com/cgrates/cgrates/utils go test -i github.com/cgrates/cgrates/history @@ -23,10 +21,6 @@ go test github.com/cgrates/cgrates/config cfg=$? go test github.com/cgrates/cgrates/cmd/cgr-engine cr=$? -go test github.com/cgrates/cgrates/mediator -md=$? -go test github.com/cgrates/cgrates/cdrs -cdrs=$? go test github.com/cgrates/cgrates/cdrc cdrcs=$? go test github.com/cgrates/cgrates/utils @@ -40,4 +34,5 @@ c2g=$? go test github.com/cgrates/cgrates/cdre cdre=$? -exit $en && $gt && $sm && $cfg && $bl && $cr && $md && $cdrs && $cdrc && $fs && $ut && $hs && $c2g && $cdre +exit $en && $gt && $sm && $cfg && $bl && $cr && $cdrc && $fs && $ut && $hs && $c2g && $cdre + diff --git a/update_external_libs.sh b/update_external_libs.sh index 5d9939702..605b8194c 100755 --- a/update_external_libs.sh +++ b/update_external_libs.sh @@ -4,6 +4,7 @@ go get -v -u github.com/bmizerany/pq go get -v -u github.com/ugorji/go/codec go get -v -u labix.org/v2/mgo go get -v -u github.com/cgrates/fsock +go get -v -u github.com/cgrates/osipsdagram go get -u -v github.com/go-sql-driver/mysql go get -u -v github.com/hoisie/redis go get -u -v github.com/howeyc/fsnotify diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 5be838c35..3686af663 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -340,23 +340,26 @@ type AttrExpFileCdrs struct { MaskDestinationId *string // Overwrite configured MaskDestId MaskLength *int // Overwrite configured MaskLength, -1 to use general config ones CgrIds []string // If provided, it will filter based on the cgrids present in list - MediationRunId []string // If provided, it will filter on mediation runid - TOR []string // If provided, filter on TypeOfRecord - CdrHost []string // If provided, it will filter cdrhost - CdrSource []string // If provided, it will filter cdrsource - ReqType []string // If provided, it will fiter reqtype - Direction []string // If provided, it will fiter direction - Tenant []string // If provided, it will filter tenant - Category []string // If provided, it will filter çategory - Account []string // If provided, it will filter account - Subject []string // If provided, it will filter the rating subject - DestinationPrefix []string // If provided, it will filter on destination prefix + MediationRunIds []string // If provided, it will filter on mediation runid + TORs []string // If provided, filter on TypeOfRecord + CdrHosts []string // If provided, it will filter cdrhost + CdrSources []string // If provided, it will filter cdrsource + ReqTypes []string // If provided, it will fiter reqtype + Directions []string // If provided, it will fiter direction + Tenants []string // If provided, it will filter tenant + Categories []string // If provided, it will filter çategory + Accounts []string // If provided, it will filter account + Subjects []string // If provided, it will filter the rating subject + DestinationPrefixes []string // If provided, it will filter on destination prefix + RatedAccounts []string // If provided, it will filter ratedaccount + RatedSubjects []string // If provided, it will filter the ratedsubject OrderIdStart int64 // Export from this order identifier OrderIdEnd int64 // Export smaller than this order identifier TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) TimeEnd string // If provided, it will represent the end of the CDRs interval (<) SkipErrors bool // Do not export errored CDRs SkipRated bool // Do not export rated CDRs + SuppressCgrIds bool // Disable CgrIds reporting in reply/ExportedCgrIds and reply/UnexportedCgrIds } type ExportedFileCdrs struct { @@ -370,24 +373,26 @@ type ExportedFileCdrs struct { } type AttrGetCdrs struct { - CgrIds []string // If provided, it will filter based on the cgrids present in list - MediationRunId []string // If provided, it will filter on mediation runid - TOR []string // If provided, filter on TypeOfRecord - CdrHost []string // If provided, it will filter cdrhost - CdrSource []string // If provided, it will filter cdrsource - ReqType []string // If provided, it will fiter reqtype - Direction []string // If provided, it will fiter direction - Tenant []string // If provided, it will filter tenant - Category []string // If provided, it will filter çategory - Account []string // If provided, it will filter account - Subject []string // If provided, it will filter the rating subject - DestinationPrefix []string // If provided, it will filter on destination prefix - OrderIdStart int64 // Export from this order identifier - OrderIdEnd int64 // Export smaller than this order identifier - TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) - TimeEnd string // If provided, it will represent the end of the CDRs interval (<) - SkipErrors bool // Do not export errored CDRs - SkipRated bool // Do not export rated CDRs + CgrIds []string // If provided, it will filter based on the cgrids present in list + MediationRunIds []string // If provided, it will filter on mediation runid + TORs []string // If provided, filter on TypeOfRecord + CdrHosts []string // If provided, it will filter cdrhost + CdrSources []string // If provided, it will filter cdrsource + ReqTypes []string // If provided, it will fiter reqtype + Directions []string // If provided, it will fiter direction + Tenants []string // If provided, it will filter tenant + Categories []string // If provided, it will filter çategory + Accounts []string // If provided, it will filter account + Subjects []string // If provided, it will filter the rating subject + DestinationPrefixes []string // If provided, it will filter on destination prefix + RatedAccounts []string // If provided, it will filter ratedaccount + RatedSubjects []string // If provided, it will filter the ratedsubject + OrderIdStart int64 // Export from this order identifier + OrderIdEnd int64 // Export smaller than this order identifier + TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) + TimeEnd string // If provided, it will represent the end of the CDRs interval (<) + SkipErrors bool // Do not export errored CDRs + SkipRated bool // Do not export rated CDRs } type AttrRemCdrs struct { @@ -395,11 +400,27 @@ type AttrRemCdrs struct { } type AttrRateCdrs struct { - TimeStart string // Cdrs time start - TimeEnd string // Cdrs time end - RerateErrors bool // Rerate previous CDRs with errors (makes sense for reqtype rated and pseudoprepaid - RerateRated bool // Rerate CDRs which were previously rated (makes sense for reqtype rated and pseudoprepaid) - SendToStats bool // Set to true if the CDRs should be sent to stats server + CgrIds []string // If provided, it will filter based on the cgrids present in list + MediationRunIds []string // If provided, it will filter on mediation runid + TORs []string // If provided, filter on TypeOfRecord + CdrHosts []string // If provided, it will filter cdrhost + CdrSources []string // If provided, it will filter cdrsource + ReqTypes []string // If provided, it will fiter reqtype + Directions []string // If provided, it will fiter direction + Tenants []string // If provided, it will filter tenant + Categories []string // If provided, it will filter çategory + Accounts []string // If provided, it will filter account + Subjects []string // If provided, it will filter the rating subject + DestinationPrefixes []string // If provided, it will filter on destination prefix + RatedAccounts []string // If provided, it will filter ratedaccount + RatedSubjects []string // If provided, it will filter the ratedsubject + OrderIdStart int64 // Export from this order identifier + OrderIdEnd int64 // Export smaller than this order identifier + TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) + TimeEnd string // If provided, it will represent the end of the CDRs interval (<) + RerateErrors bool // Rerate previous CDRs with errors (makes sense for reqtype rated and pseudoprepaid + RerateRated bool // Rerate CDRs which were previously rated (makes sense for reqtype rated and pseudoprepaid) + SendToStats bool // Set to true if the CDRs should be sent to stats server } type AttrLoadTpFromFolder struct { diff --git a/utils/consts.go b/utils/consts.go index d5a6424df..7e385f7b2 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1,134 +1,161 @@ package utils const ( - VERSION = "0.9.1rc5" - POSTGRES = "postgres" - MYSQL = "mysql" - MONGO = "mongo" - REDIS = "redis" - LOCALHOST = "127.0.0.1" - FSCDR_FILE_CSV = "freeswitch_file_csv" - FSCDR_HTTP_JSON = "freeswitch_http_json" - NOT_IMPLEMENTED = "not implemented" - PREPAID = "prepaid" - POSTPAID = "postpaid" - PSEUDOPREPAID = "pseudoprepaid" - RATED = "rated" - ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" - ERR_SERVER_ERROR = "SERVER_ERROR" - ERR_NOT_FOUND = "NOT_FOUND" - ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING" - ERR_EXISTS = "EXISTS" - ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE" - ERR_PARSER_ERROR = "PARSER_ERROR" - TBL_TP_TIMINGS = "tp_timings" - TBL_TP_DESTINATIONS = "tp_destinations" - TBL_TP_RATES = "tp_rates" - TBL_TP_DESTINATION_RATES = "tp_destination_rates" - TBL_TP_RATING_PLANS = "tp_rating_plans" - TBL_TP_RATE_PROFILES = "tp_rating_profiles" - TBL_TP_SHARED_GROUPS = "tp_shared_groups" - TBL_TP_LCRS = "tp_lcr_rules" - TBL_TP_ACTIONS = "tp_actions" - TBL_TP_ACTION_PLANS = "tp_action_plans" - TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" - TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions" - TBL_CDRS_PRIMARY = "cdrs_primary" - TBL_CDRS_EXTRA = "cdrs_extra" - TBL_COST_DETAILS = "cost_details" - TBL_RATED_CDRS = "rated_cdrs" - TIMINGS_CSV = "Timings.csv" - DESTINATIONS_CSV = "Destinations.csv" - RATES_CSV = "Rates.csv" - DESTINATION_RATES_CSV = "DestinationRates.csv" - RATING_PLANS_CSV = "RatingPlans.csv" - RATING_PROFILES_CSV = "RatingProfiles.csv" - SHARED_GROUPS_CSV = "SharedGroups.csv" - LCRS_CSV = "LCRRules.csv" - ACTIONS_CSV = "Actions.csv" - ACTION_PLANS_CSV = "ActionPlans.csv" - ACTION_TRIGGERS_CSV = "ActionTriggers.csv" - ACCOUNT_ACTIONS_CSV = "AccountActions.csv" - DERIVED_CHARGERS_CSV = "DerivedChargers.csv" - CDR_STATS_CSV = "CdrStats.csv" - TIMINGS_NRCOLS = 6 - DESTINATIONS_NRCOLS = 2 - RATES_NRCOLS = 6 - DESTINATION_RATES_NRCOLS = 5 - DESTRATE_TIMINGS_NRCOLS = 4 - RATE_PROFILES_NRCOLS = 7 - SHARED_GROUPS_NRCOLS = 4 - LCRS_NRCOLS = 9 - ACTIONS_NRCOLS = 12 - ACTION_PLANS_NRCOLS = 4 - ACTION_TRIGGERS_NRCOLS = 15 - ACCOUNT_ACTIONS_NRCOLS = 5 - DERIVED_CHARGERS_NRCOLS = 17 - CDR_STATS_NRCOLS = 19 - ROUNDING_UP = "*up" - ROUNDING_MIDDLE = "*middle" - ROUNDING_DOWN = "*down" - ANY = "*any" - COMMENT_CHAR = '#' - CSV_SEP = ',' - FALLBACK_SEP = ';' - INFIELD_SEP = ";" - FIELDS_SEP = "," - REGEXP_PREFIX = "~" - JSON = "json" - GOB = "gob" - MSGPACK = "msgpack" - CSV_LOAD = "CSVLOAD" - CGRID = "cgrid" - ORDERID = "orderid" - ACCID = "accid" - CDRHOST = "cdrhost" - CDRSOURCE = "cdrsource" - REQTYPE = "reqtype" - DIRECTION = "direction" - TENANT = "tenant" - CATEGORY = "category" - ACCOUNT = "account" - SUBJECT = "subject" - DESTINATION = "destination" - SETUP_TIME = "setup_time" - ANSWER_TIME = "answer_time" - USAGE = "usage" - MEDI_RUNID = "mediation_runid" - COST = "cost" - DEFAULT_RUNID = "default" - STATIC_VALUE_PREFIX = "^" - CSV = "csv" - CDRE_DRYRUN = "dry_run" - INTERNAL = "internal" - ZERO_RATING_SUBJECT_PREFIX = "*zero" - OK = "OK" - CDRE_FIXED_WIDTH = "fwv" - XML_PROFILE_PREFIX = "*xml:" - CDRE = "cdre" - CDRC = "cdrc" - MASK_CHAR = "*" - CONCATENATED_KEY_SEP = ":" - META_DEFAULT = "*default" - FORKED_CDR = "forked_cdr" - UNIT_TEST = "UNIT_TEST" - HDR_VAL_SEP = "/" - MONETARY = "*monetary" - SMS = "*sms" - DATA = "*data" - VOICE = "*voice" - TOR = "tor" - HOURS = "hours" - MINUTES = "minutes" - NANOSECONDS = "nanoseconds" - SECONDS = "seconds" - OUT = "*out" - CDR_IMPORT = "cdr_import" - CDR_EXPORT = "cdr_export" - CDRFIELD = "cdrfield" - ASR = "ASR" - ACD = "ACD" - FILTER_REGEXP_TPL = "$1$2$3$4$5" + VERSION = "0.9.1rc5" + POSTGRES = "postgres" + MYSQL = "mysql" + MONGO = "mongo" + REDIS = "redis" + LOCALHOST = "127.0.0.1" + FSCDR_FILE_CSV = "freeswitch_file_csv" + FSCDR_HTTP_JSON = "freeswitch_http_json" + NOT_IMPLEMENTED = "not implemented" + PREPAID = "prepaid" + POSTPAID = "postpaid" + PSEUDOPREPAID = "pseudoprepaid" + RATED = "rated" + ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" + ERR_SERVER_ERROR = "SERVER_ERROR" + ERR_NOT_FOUND = "NOT_FOUND" + ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING" + ERR_EXISTS = "EXISTS" + ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE" + ERR_PARSER_ERROR = "PARSER_ERROR" + TBL_TP_TIMINGS = "tp_timings" + TBL_TP_DESTINATIONS = "tp_destinations" + TBL_TP_RATES = "tp_rates" + TBL_TP_DESTINATION_RATES = "tp_destination_rates" + TBL_TP_RATING_PLANS = "tp_rating_plans" + TBL_TP_RATE_PROFILES = "tp_rating_profiles" + TBL_TP_SHARED_GROUPS = "tp_shared_groups" + TBL_TP_LCRS = "tp_lcr_rules" + TBL_TP_ACTIONS = "tp_actions" + TBL_TP_ACTION_PLANS = "tp_action_plans" + TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" + TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions" + TBL_CDRS_PRIMARY = "cdrs_primary" + TBL_CDRS_EXTRA = "cdrs_extra" + TBL_COST_DETAILS = "cost_details" + TBL_RATED_CDRS = "rated_cdrs" + TIMINGS_CSV = "Timings.csv" + DESTINATIONS_CSV = "Destinations.csv" + RATES_CSV = "Rates.csv" + DESTINATION_RATES_CSV = "DestinationRates.csv" + RATING_PLANS_CSV = "RatingPlans.csv" + RATING_PROFILES_CSV = "RatingProfiles.csv" + SHARED_GROUPS_CSV = "SharedGroups.csv" + LCRS_CSV = "LCRRules.csv" + ACTIONS_CSV = "Actions.csv" + ACTION_PLANS_CSV = "ActionPlans.csv" + ACTION_TRIGGERS_CSV = "ActionTriggers.csv" + ACCOUNT_ACTIONS_CSV = "AccountActions.csv" + DERIVED_CHARGERS_CSV = "DerivedChargers.csv" + CDR_STATS_CSV = "CdrStats.csv" + TIMINGS_NRCOLS = 6 + DESTINATIONS_NRCOLS = 2 + RATES_NRCOLS = 6 + DESTINATION_RATES_NRCOLS = 5 + DESTRATE_TIMINGS_NRCOLS = 4 + RATE_PROFILES_NRCOLS = 7 + SHARED_GROUPS_NRCOLS = 4 + LCRS_NRCOLS = 9 + ACTIONS_NRCOLS = 12 + ACTION_PLANS_NRCOLS = 4 + ACTION_TRIGGERS_NRCOLS = 15 + ACCOUNT_ACTIONS_NRCOLS = 5 + DERIVED_CHARGERS_NRCOLS = 17 + CDR_STATS_NRCOLS = 19 + ROUNDING_UP = "*up" + ROUNDING_MIDDLE = "*middle" + ROUNDING_DOWN = "*down" + ANY = "*any" + COMMENT_CHAR = '#' + CSV_SEP = ',' + FALLBACK_SEP = ';' + INFIELD_SEP = ";" + FIELDS_SEP = "," + REGEXP_PREFIX = "~" + JSON = "json" + GOB = "gob" + MSGPACK = "msgpack" + CSV_LOAD = "CSVLOAD" + CGRID = "cgrid" + ORDERID = "orderid" + ACCID = "accid" + CDRHOST = "cdrhost" + CDRSOURCE = "cdrsource" + REQTYPE = "reqtype" + DIRECTION = "direction" + TENANT = "tenant" + CATEGORY = "category" + ACCOUNT = "account" + SUBJECT = "subject" + DESTINATION = "destination" + SETUP_TIME = "setup_time" + ANSWER_TIME = "answer_time" + USAGE = "usage" + MEDI_RUNID = "mediation_runid" + RATED_ACCOUNT = "rated_account" + RATED_SUBJECT = "rated_subject" + COST = "cost" + DEFAULT_RUNID = "default" + STATIC_VALUE_PREFIX = "^" + CSV = "csv" + CDRE_DRYRUN = "dry_run" + INTERNAL = "internal" + ZERO_RATING_SUBJECT_PREFIX = "*zero" + OK = "OK" + CDRE_FIXED_WIDTH = "fwv" + XML_PROFILE_PREFIX = "*xml:" + CDRE = "cdre" + CDRC = "cdrc" + MASK_CHAR = "*" + CONCATENATED_KEY_SEP = ":" + META_DEFAULT = "*default" + FORKED_CDR = "forked_cdr" + UNIT_TEST = "UNIT_TEST" + HDR_VAL_SEP = "/" + MONETARY = "*monetary" + SMS = "*sms" + DATA = "*data" + VOICE = "*voice" + TOR = "tor" + HOURS = "hours" + MINUTES = "minutes" + NANOSECONDS = "nanoseconds" + SECONDS = "seconds" + OUT = "*out" + CDR_IMPORT = "cdr_import" + CDR_EXPORT = "cdr_export" + CDRFIELD = "cdrfield" + ASR = "ASR" + ACD = "ACD" + FILTER_REGEXP_TPL = "$1$2$3$4$5" + ACTION_TIMING_PREFIX = "apl_" + RATING_PLAN_PREFIX = "rpl_" + RATING_PROFILE_PREFIX = "rpf_" + RP_ALIAS_PREFIX = "ral_" + ACC_ALIAS_PREFIX = "aal_" + ACTION_PREFIX = "act_" + SHARED_GROUP_PREFIX = "shg_" + ACCOUNT_PREFIX = "ubl_" + DESTINATION_PREFIX = "dst_" + LCR_PREFIX = "lcr_" + DERIVEDCHARGERS_PREFIX = "dcs_" + TEMP_DESTINATION_PREFIX = "tmp_" + LOG_CALL_COST_PREFIX = "cco_" + LOG_ACTION_TIMMING_PREFIX = "ltm_" + LOG_ACTION_TRIGGER_PREFIX = "ltr_" + LOG_ERR = "ler_" + LOG_CDR = "cdr_" + LOG_MEDIATED_CDR = "mcd_" + SESSION_MANAGER_SOURCE = "SMR" + MEDIATOR_SOURCE = "MED" + SCHED_SOURCE = "SCH" + RATER_SOURCE = "RAT" + CREATE_CDRS_TABLES_SQL = "create_cdrs_tables.sql" + CREATE_TARIFFPLAN_TABLES_SQL = "create_tariffplan_tables.sql" + TEST_SQL = "TEST_SQL" ) var ( diff --git a/utils/storedcdr.go b/utils/storedcdr.go index 518a2a124..74e43dc68 100644 --- a/utils/storedcdr.go +++ b/utils/storedcdr.go @@ -47,6 +47,8 @@ type StoredCdr struct { Usage time.Duration ExtraFields map[string]string MediationRunId string + RatedAccount string // Populated out of rating data + RatedSubject string Cost float64 } @@ -126,6 +128,10 @@ func (storedCdr *StoredCdr) FieldAsString(rsrFld *RSRField) string { return rsrFld.ParseValue(strconv.FormatInt(storedCdr.Usage.Nanoseconds(), 10)) case MEDI_RUNID: return rsrFld.ParseValue(storedCdr.MediationRunId) + case RATED_ACCOUNT: + return rsrFld.ParseValue(storedCdr.RatedAccount) + case RATED_SUBJECT: + return rsrFld.ParseValue(storedCdr.RatedSubject) case COST: return rsrFld.ParseValue(strconv.FormatFloat(storedCdr.Cost, 'f', -1, 64)) // Recommended to use FormatCost default: @@ -157,7 +163,7 @@ func (storedCdr *StoredCdr) AsStoredCdr() *StoredCdr { return storedCdr } -// Ability to send the CgrCdr remotely to another CDR server +// Ability to send the CgrCdr remotely to another CDR server, we do not include rating variables for now func (storedCdr *StoredCdr) AsHttpForm() url.Values { v := url.Values{} for fld, val := range storedCdr.ExtraFields { @@ -324,6 +330,8 @@ func (storedCdr *StoredCdr) AsCgrCdrOut() *CgrCdrOut { Usage: storedCdr.Usage.Seconds(), ExtraFields: storedCdr.ExtraFields, MediationRunId: storedCdr.MediationRunId, + RatedAccount: storedCdr.RatedAccount, + RatedSubject: storedCdr.RatedSubject, Cost: storedCdr.Cost, } } @@ -347,5 +355,7 @@ type CgrCdrOut struct { Usage float64 ExtraFields map[string]string MediationRunId string + RatedAccount string + RatedSubject string Cost float64 } diff --git a/utils/storedcdr_test.go b/utils/storedcdr_test.go index 62a3bf97c..8e33a591e 100644 --- a/utils/storedcdr_test.go +++ b/utils/storedcdr_test.go @@ -32,7 +32,7 @@ func TestStoredCdrInterfaces(t *testing.T) { func TestFieldAsString(t *testing.T) { cdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "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: DEFAULT_RUNID, - Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, + Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", } if cdr.FieldAsString(&RSRField{Id: CGRID}) != cdr.CgrId || cdr.FieldAsString(&RSRField{Id: ORDERID}) != "123" || @@ -51,6 +51,8 @@ func TestFieldAsString(t *testing.T) { cdr.FieldAsString(&RSRField{Id: USAGE}) != "10000000000" || cdr.FieldAsString(&RSRField{Id: MEDI_RUNID}) != cdr.MediationRunId || cdr.FieldAsString(&RSRField{Id: COST}) != "1.01" || + cdr.FieldAsString(&RSRField{Id: RATED_ACCOUNT}) != "dan" || + cdr.FieldAsString(&RSRField{Id: RATED_SUBJECT}) != "dans" || cdr.FieldAsString(&RSRField{Id: "field_extr1"}) != cdr.ExtraFields["field_extr1"] || cdr.FieldAsString(&RSRField{Id: "fieldextr2"}) != cdr.ExtraFields["fieldextr2"] || cdr.FieldAsString(&RSRField{Id: "dummy_field"}) != "" { @@ -71,6 +73,8 @@ func TestFieldAsString(t *testing.T) { cdr.FieldAsString(&RSRField{Id: ANSWER_TIME}) != cdr.AnswerTime.String(), cdr.FieldAsString(&RSRField{Id: USAGE}) != "10000000000", cdr.FieldAsString(&RSRField{Id: MEDI_RUNID}) != cdr.MediationRunId, + cdr.FieldAsString(&RSRField{Id: RATED_ACCOUNT}) != "dan", + cdr.FieldAsString(&RSRField{Id: RATED_SUBJECT}) != "dans", cdr.FieldAsString(&RSRField{Id: COST}) != "1.01", cdr.FieldAsString(&RSRField{Id: "field_extr1"}) != cdr.ExtraFields["field_extr1"], cdr.FieldAsString(&RSRField{Id: "fieldextr2"}) != cdr.ExtraFields["fieldextr2"], @@ -234,7 +238,7 @@ func TestStoredCdrAsHttpForm(t *testing.T) { storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID, - Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, + Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, RatedSubject: "dans", Cost: 1.01, } cdrForm := storCdr.AsHttpForm() if cdrForm.Get(TOR) != VOICE { @@ -291,7 +295,7 @@ func TestStoredCdrForkCdr(t *testing.T) { storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID, - Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "field_extr2": "valextr2"}, Cost: 1.01, + Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "field_extr2": "valextr2"}, Cost: 1.01, RatedSubject: "dans", } rtSampleCdrOut, err := storCdr.ForkCdr("sample_run1", &RSRField{Id: REQTYPE}, &RSRField{Id: DIRECTION}, &RSRField{Id: TENANT}, &RSRField{Id: CATEGORY}, &RSRField{Id: ACCOUNT}, &RSRField{Id: SUBJECT}, &RSRField{Id: DESTINATION}, &RSRField{Id: SETUP_TIME}, &RSRField{Id: ANSWER_TIME}, &RSRField{Id: USAGE}, @@ -379,12 +383,12 @@ func TestStoredCdrAsCgrCdrOut(t *testing.T) { storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID, - Usage: time.Duration(10), ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, + Usage: time.Duration(10), ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", } expectOutCdr := &CgrCdrOut{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID, - Usage: 0.00000001, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, + Usage: 0.00000001, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", } if cdrOut := storCdr.AsCgrCdrOut(); !reflect.DeepEqual(expectOutCdr, cdrOut) { t.Errorf("Expected: %+v, received: %+v", expectOutCdr, cdrOut)